From f4553880f6a68b7cd8cb4cc5dcfc24612b00fc54 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 23 Feb 2016 15:41:21 +0100 Subject: [PATCH] Implement Lua session-scripts --- libs/ardour/ardour/session.h | 30 ++++ libs/ardour/luabindings.cc | 1 + libs/ardour/session.cc | 242 ++++++++++++++++++++++++++++++++- libs/ardour/session_process.cc | 3 + libs/ardour/session_state.cc | 37 +++++ 5 files changed, 312 insertions(+), 1 deletion(-) diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 15b7fe2fb5..d25f856b1f 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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 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 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 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 = diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index 6d640b8115..0fa52c4020 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -438,6 +438,7 @@ LuaBindings::common (lua_State* L) luabridge::getGlobalNamespace (L) .beginNamespace ("ARDOUR") .beginClass ("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) diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index c9fd336314..3509ee19a5 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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 @@ -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 +Session::registered_lua_functions () +{ + Glib::Threads::Mutex::Lock lm (lua_lock); + std::vector 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 ()); + } + } 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 (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 () { diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index 6f4ae531bb..b054d65787 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -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); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index fcd16788d8..e252563dcd 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -122,6 +122,8 @@ #include "control_protocol/control_protocol.h" +#include "LuaBridge/LuaBridge.h" + #include "i18n.h" #include @@ -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(); + } + 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 ... */