Implement Lua session-scripts

This commit is contained in:
Robin Gareus 2016-02-23 15:41:21 +01:00
parent 51385ced3c
commit f4553880f6
5 changed files with 312 additions and 1 deletions

View File

@ -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 =

View File

@ -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)

View File

@ -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 ()
{

View File

@ -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);

View File

@ -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 ... */