Implement Lua session-scripts
This commit is contained in:
parent
51385ced3c
commit
f4553880f6
|
@ -42,10 +42,13 @@
|
|||
#include "pbd/error.h"
|
||||
#include "pbd/event_loop.h"
|
||||
#include "pbd/rcu.h"
|
||||
#include "pbd/reallocpool.h"
|
||||
#include "pbd/statefuldestructible.h"
|
||||
#include "pbd/signals.h"
|
||||
#include "pbd/undo.h"
|
||||
|
||||
#include "lua/luastate.h"
|
||||
|
||||
#include "evoral/types.hpp"
|
||||
|
||||
#include "midi++/types.h"
|
||||
|
@ -57,6 +60,7 @@
|
|||
#include "ardour/chan_count.h"
|
||||
#include "ardour/delivery.h"
|
||||
#include "ardour/interthread_info.h"
|
||||
#include "ardour/luascripting.h"
|
||||
#include "ardour/location.h"
|
||||
#include "ardour/monitor_processor.h"
|
||||
#include "ardour/rc_configuration.h"
|
||||
|
@ -83,6 +87,10 @@ class Controllable;
|
|||
class ControllableDescriptor;
|
||||
}
|
||||
|
||||
namespace luabridge {
|
||||
class LuaRef;
|
||||
}
|
||||
|
||||
namespace Evoral {
|
||||
class Curve;
|
||||
}
|
||||
|
@ -724,6 +732,13 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
|
|||
|
||||
PBD::Signal1<void,bool> AuditionActive;
|
||||
|
||||
/* session script */
|
||||
void register_lua_function (const std::string&, const std::string&, const LuaScriptParamList&);
|
||||
void unregister_lua_function (const std::string& name);
|
||||
std::vector<std::string> registered_lua_functions ();
|
||||
uint32_t registered_lua_function_count () const { return _n_lua_scripts; }
|
||||
void scripts_changed (); // called from lua, updates _n_lua_scripts
|
||||
|
||||
/* flattening stuff */
|
||||
|
||||
boost::shared_ptr<Region> write_one_track (Track&, framepos_t start, framepos_t end,
|
||||
|
@ -1274,6 +1289,21 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
|
|||
bool pending_abort;
|
||||
bool pending_auto_loop;
|
||||
|
||||
PBD::ReallocPool _mempool;
|
||||
LuaState lua;
|
||||
Glib::Threads::Mutex lua_lock;
|
||||
luabridge::LuaRef * _lua_run;
|
||||
luabridge::LuaRef * _lua_add;
|
||||
luabridge::LuaRef * _lua_del;
|
||||
luabridge::LuaRef * _lua_list;
|
||||
luabridge::LuaRef * _lua_load;
|
||||
luabridge::LuaRef * _lua_save;
|
||||
luabridge::LuaRef * _lua_cleanup;
|
||||
uint32_t _n_lua_scripts;
|
||||
|
||||
void setup_lua ();
|
||||
void try_run_lua (pframes_t);
|
||||
|
||||
Butler* _butler;
|
||||
|
||||
static const PostTransportWork ProcessCannotProceedMask =
|
||||
|
|
|
@ -438,6 +438,7 @@ LuaBindings::common (lua_State* L)
|
|||
luabridge::getGlobalNamespace (L)
|
||||
.beginNamespace ("ARDOUR")
|
||||
.beginClass <Session> ("Session")
|
||||
.addFunction ("scripts_changed", &Session::scripts_changed) // used internally
|
||||
.addFunction ("transport_rolling", &Session::transport_rolling)
|
||||
.addFunction ("request_transport_speed", &Session::request_transport_speed)
|
||||
.addFunction ("transport_frame", &Session::transport_frame)
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
#include "ardour/filename_extensions.h"
|
||||
#include "ardour/gain_control.h"
|
||||
#include "ardour/graph.h"
|
||||
#include "ardour/luabindings.h"
|
||||
#include "ardour/midiport_manager.h"
|
||||
#include "ardour/scene_changer.h"
|
||||
#include "ardour/midi_patch_manager.h"
|
||||
|
@ -106,6 +107,8 @@
|
|||
#include "midi++/port.h"
|
||||
#include "midi++/mmc.h"
|
||||
|
||||
#include "LuaBridge/LuaBridge.h"
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
#include <glibmm/checksum.h>
|
||||
|
@ -227,6 +230,8 @@ Session::Session (AudioEngine &eng,
|
|||
, pending_locate_flush (false)
|
||||
, pending_abort (false)
|
||||
, pending_auto_loop (false)
|
||||
, _mempool ("Session", 1048576)
|
||||
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
|
||||
, _butler (new Butler (*this))
|
||||
, _post_transport_work (0)
|
||||
, cumulative_rf_motion (0)
|
||||
|
@ -307,6 +312,8 @@ Session::Session (AudioEngine &eng,
|
|||
|
||||
pre_engine_init (fullpath);
|
||||
|
||||
setup_lua ();
|
||||
|
||||
if (_is_new) {
|
||||
|
||||
Stateful::loading_state_version = CURRENT_SESSION_FILE_VERSION;
|
||||
|
@ -590,8 +597,19 @@ Session::destroy ()
|
|||
delete state_tree;
|
||||
state_tree = 0;
|
||||
|
||||
/* reset dynamic state version back to default */
|
||||
// unregister all lua functions, drop held references (if any)
|
||||
(*_lua_cleanup)();
|
||||
lua.do_command ("Session = nil");
|
||||
delete _lua_run;
|
||||
delete _lua_add;
|
||||
delete _lua_del;
|
||||
delete _lua_list;
|
||||
delete _lua_save;
|
||||
delete _lua_load;
|
||||
delete _lua_cleanup;
|
||||
lua.collect_garbage ();
|
||||
|
||||
/* reset dynamic state version back to default */
|
||||
Stateful::loading_state_version = 0;
|
||||
|
||||
_butler->drop_references ();
|
||||
|
@ -4896,6 +4914,228 @@ Session::audition_playlist ()
|
|||
queue_event (ev);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Session::register_lua_function (
|
||||
const std::string& name,
|
||||
const std::string& script,
|
||||
const LuaScriptParamList& args
|
||||
)
|
||||
{
|
||||
Glib::Threads::Mutex::Lock lm (lua_lock);
|
||||
|
||||
lua_State* L = lua.getState();
|
||||
|
||||
const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
|
||||
luabridge::LuaRef tbl_arg (luabridge::newTable(L));
|
||||
for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) {
|
||||
if ((*i)->optional && !(*i)->is_set) { continue; }
|
||||
tbl_arg[(*i)->name] = (*i)->value;
|
||||
}
|
||||
(*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void
|
||||
Session::unregister_lua_function (const std::string& name)
|
||||
{
|
||||
Glib::Threads::Mutex::Lock lm (lua_lock);
|
||||
(*_lua_del)(name); // throws luabridge::LuaException
|
||||
lua.collect_garbage ();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
Session::registered_lua_functions ()
|
||||
{
|
||||
Glib::Threads::Mutex::Lock lm (lua_lock);
|
||||
std::vector<std::string> rv;
|
||||
|
||||
try {
|
||||
luabridge::LuaRef list ((*_lua_list)());
|
||||
for (luabridge::Iterator i (list); !i.isNil (); ++i) {
|
||||
if (!i.key ().isString ()) { assert(0); continue; }
|
||||
rv.push_back (i.key ().cast<std::string> ());
|
||||
}
|
||||
} catch (luabridge::LuaException const& e) { }
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void _lua_print (std::string s) {
|
||||
std::cout << "SessionLua: " << s << "\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
Session::try_run_lua (pframes_t nframes)
|
||||
{
|
||||
if (_n_lua_scripts == 0) return;
|
||||
Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK);
|
||||
if (tm.locked ()) {
|
||||
try { (*_lua_run)(nframes); } catch (luabridge::LuaException const& e) { }
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Session::setup_lua ()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
lua.Print.connect (&_lua_print);
|
||||
#endif
|
||||
lua.do_command (
|
||||
"function ArdourSession ()"
|
||||
" local self = { scripts = {}, instances = {} }"
|
||||
""
|
||||
" local remove = function (n)"
|
||||
" self.scripts[n] = nil"
|
||||
" self.instances[n] = nil"
|
||||
" Session:scripts_changed()" // call back
|
||||
" end"
|
||||
""
|
||||
" local addinternal = function (n, f, a)"
|
||||
" assert(type(n) == 'string', 'function-name must be string')"
|
||||
" assert(type(f) == 'function', 'Given script is a not a function')"
|
||||
" assert(type(a) == 'table' or type(a) == 'nil', 'Given argument is invalid')"
|
||||
" assert(self.scripts[n] == nil, 'Callback \"'.. n ..'\" already exists.')"
|
||||
" self.scripts[n] = { ['f'] = f, ['a'] = a }"
|
||||
" local env = _ENV; env.f = nil env.io = nil env.os = nil env.loadfile = nil env.require = nil env.dofile = nil env.package = nil env.debug = nil"
|
||||
" local env = { print = print, Session = Session, tostring = tostring, assert = assert, ipairs = ipairs, error = error, select = select, string = string, type = type, tonumber = tonumber, collectgarbage = collectgarbage, pairs = pairs, math = math, table = table, pcall = pcall }"
|
||||
" self.instances[n] = load (string.dump(f, true), nil, nil, env)(a)"
|
||||
" Session:scripts_changed()" // call back
|
||||
" end"
|
||||
""
|
||||
" local add = function (n, b, a)"
|
||||
" assert(type(b) == 'string', 'ByteCode must be string')"
|
||||
" load (b)()" // assigns f
|
||||
" assert(type(f) == 'string', 'Assigned ByteCode must be string')"
|
||||
" addinternal (n, load(f), a)"
|
||||
" end"
|
||||
""
|
||||
" local run = function (...)"
|
||||
" for n, s in pairs (self.instances) do"
|
||||
" local status, err = pcall (s, ...)"
|
||||
" if not status then"
|
||||
" print ('fn \"'.. n .. '\": ', err)"
|
||||
" remove (n)"
|
||||
" end"
|
||||
" end"
|
||||
" collectgarbage()"
|
||||
" end"
|
||||
""
|
||||
" local cleanup = function ()"
|
||||
" self.scripts = nil"
|
||||
" self.instances = nil"
|
||||
" end"
|
||||
""
|
||||
" local list = function ()"
|
||||
" local rv = {}"
|
||||
" for n, _ in pairs (self.scripts) do"
|
||||
" rv[n] = true"
|
||||
" end"
|
||||
" return rv"
|
||||
" end"
|
||||
""
|
||||
" local function basic_serialize (o)"
|
||||
" if type(o) == \"number\" then"
|
||||
" return tostring(o)"
|
||||
" else"
|
||||
" return string.format(\"%q\", o)"
|
||||
" end"
|
||||
" end"
|
||||
""
|
||||
" local function serialize (name, value)"
|
||||
" local rv = name .. ' = '"
|
||||
" collectgarbage()"
|
||||
" if type(value) == \"number\" or type(value) == \"string\" or type(value) == \"nil\" then"
|
||||
" return rv .. basic_serialize(value) .. ' '"
|
||||
" elseif type(value) == \"table\" then"
|
||||
" rv = rv .. '{} '"
|
||||
" for k,v in pairs(value) do"
|
||||
" local fieldname = string.format(\"%s[%s]\", name, basic_serialize(k))"
|
||||
" rv = rv .. serialize(fieldname, v) .. ' '"
|
||||
" collectgarbage()" // string concatenation allocates a new string :(
|
||||
" end"
|
||||
" return rv;"
|
||||
" elseif type(value) == \"function\" then"
|
||||
" return rv .. string.format(\"%q\", string.dump(value, true))"
|
||||
" else"
|
||||
" error('cannot save a ' .. type(value))"
|
||||
" end"
|
||||
" end"
|
||||
""
|
||||
""
|
||||
" local save = function ()"
|
||||
" return (serialize('scripts', self.scripts))"
|
||||
" end"
|
||||
""
|
||||
" local restore = function (state)"
|
||||
" self.scripts = {}"
|
||||
" load (state)()"
|
||||
" for n, s in pairs (scripts) do"
|
||||
" addinternal (n, load(s['f']), s['a'])"
|
||||
" end"
|
||||
" end"
|
||||
""
|
||||
" return { run = run, add = add, remove = remove,"
|
||||
" list = list, restore = restore, save = save, cleanup = cleanup}"
|
||||
" end"
|
||||
" "
|
||||
" sess = ArdourSession ()"
|
||||
" ArdourSession = nil"
|
||||
" "
|
||||
"function ardour () end"
|
||||
);
|
||||
|
||||
lua_State* L = lua.getState();
|
||||
|
||||
try {
|
||||
luabridge::LuaRef lua_sess = luabridge::getGlobal (L, "sess");
|
||||
lua.do_command ("sess = nil"); // hide it.
|
||||
lua.do_command ("collectgarbage()");
|
||||
|
||||
_lua_run = new luabridge::LuaRef(lua_sess["run"]);
|
||||
_lua_add = new luabridge::LuaRef(lua_sess["add"]);
|
||||
_lua_del = new luabridge::LuaRef(lua_sess["remove"]);
|
||||
_lua_list = new luabridge::LuaRef(lua_sess["list"]);
|
||||
_lua_save = new luabridge::LuaRef(lua_sess["save"]);
|
||||
_lua_load = new luabridge::LuaRef(lua_sess["restore"]);
|
||||
_lua_cleanup = new luabridge::LuaRef(lua_sess["cleanup"]);
|
||||
} catch (luabridge::LuaException const& e) {
|
||||
fatal << string_compose (_("programming error: %1"),
|
||||
X_("Failed to setup Lua interpreter"))
|
||||
<< endmsg;
|
||||
abort(); /*NOTREACHED*/
|
||||
}
|
||||
|
||||
LuaBindings::stddef (L);
|
||||
LuaBindings::common (L);
|
||||
LuaBindings::dsp (L);
|
||||
luabridge::push <Session *> (L, this);
|
||||
lua_setglobal (L, "Session");
|
||||
}
|
||||
|
||||
void
|
||||
Session::scripts_changed ()
|
||||
{
|
||||
assert (!lua_lock.trylock()); // must hold lua_lock
|
||||
|
||||
try {
|
||||
luabridge::LuaRef list ((*_lua_list)());
|
||||
int cnt = 0;
|
||||
for (luabridge::Iterator i (list); !i.isNil (); ++i) {
|
||||
if (!i.key ().isString ()) { assert(0); continue; }
|
||||
++cnt;
|
||||
}
|
||||
_n_lua_scripts = cnt;
|
||||
} catch (luabridge::LuaException const& e) {
|
||||
fatal << string_compose (_("programming error: %1"),
|
||||
X_("Indexing Lua Session Scripts failed."))
|
||||
<< endmsg;
|
||||
abort(); /*NOTREACHED*/
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Session::non_realtime_set_audition ()
|
||||
{
|
||||
|
|
|
@ -358,6 +358,7 @@ Session::process_with_events (pframes_t nframes)
|
|||
}
|
||||
|
||||
if (events.empty() || next_event == events.end()) {
|
||||
try_run_lua (nframes); // also during export ?? ->move to process_without_events()
|
||||
process_without_events (nframes);
|
||||
return;
|
||||
}
|
||||
|
@ -425,6 +426,8 @@ Session::process_with_events (pframes_t nframes)
|
|||
this_nframes = abs (floor(frames_moved / _transport_speed));
|
||||
}
|
||||
|
||||
try_run_lua (this_nframes);
|
||||
|
||||
if (this_nframes) {
|
||||
|
||||
click (_transport_frame, this_nframes);
|
||||
|
|
|
@ -122,6 +122,8 @@
|
|||
|
||||
#include "control_protocol/control_protocol.h"
|
||||
|
||||
#include "LuaBridge/LuaBridge.h"
|
||||
|
||||
#include "i18n.h"
|
||||
#include <locale.h>
|
||||
|
||||
|
@ -1249,6 +1251,26 @@ Session::state (bool full_state)
|
|||
node->add_child_copy (*_extra_xml);
|
||||
}
|
||||
|
||||
{
|
||||
Glib::Threads::Mutex::Lock lm (lua_lock);
|
||||
std::string saved;
|
||||
{
|
||||
luabridge::LuaRef savedstate ((*_lua_save)());
|
||||
saved = savedstate.cast<std::string>();
|
||||
}
|
||||
lua.collect_garbage ();
|
||||
lm.release ();
|
||||
|
||||
gchar* b64 = g_base64_encode ((const guchar*)saved.c_str (), saved.size ());
|
||||
std::string b64s (b64);
|
||||
g_free (b64);
|
||||
|
||||
XMLNode* script_node = new XMLNode (X_("Script"));
|
||||
script_node->add_property (X_("lua"), LUA_VERSION);
|
||||
script_node->add_content (b64s);
|
||||
node->add_child_nocopy (*script_node);
|
||||
}
|
||||
|
||||
return *node;
|
||||
}
|
||||
|
||||
|
@ -1459,6 +1481,21 @@ Session::set_state (const XMLNode& node, int version)
|
|||
ControlProtocolManager::instance().set_state (*child, version);
|
||||
}
|
||||
|
||||
if ((child = find_named_node (node, "Script"))) {
|
||||
for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
|
||||
if (!(*n)->is_content ()) { continue; }
|
||||
gsize size;
|
||||
guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size);
|
||||
try {
|
||||
Glib::Threads::Mutex::Lock lm (lua_lock);
|
||||
(*_lua_load)(std::string ((const char*)buf, size));
|
||||
} catch (luabridge::LuaException const& e) {
|
||||
cerr << "LuaException:" << e.what () << endl;
|
||||
}
|
||||
g_free (buf);
|
||||
}
|
||||
}
|
||||
|
||||
update_route_record_state ();
|
||||
|
||||
/* here beginneth the second phase ... */
|
||||
|
|
Loading…
Reference in New Issue
Block a user