From d8008b2db16a37ae3776446597a797df34fad08b Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Fri, 12 Feb 2016 13:24:41 +0100 Subject: libardour lua-script-manager --- libs/ardour/ardour/luascripting.h | 133 +++++++++++++ libs/ardour/luascripting.cc | 381 ++++++++++++++++++++++++++++++++++++++ libs/ardour/wscript | 1 + 3 files changed, 515 insertions(+) create mode 100644 libs/ardour/ardour/luascripting.h create mode 100644 libs/ardour/luascripting.cc diff --git a/libs/ardour/ardour/luascripting.h b/libs/ardour/ardour/luascripting.h new file mode 100644 index 0000000000..a5a066b2cd --- /dev/null +++ b/libs/ardour/ardour/luascripting.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef _ardour_luascripting_h_ +#define _ardour_luascripting_h_ +#include + +#include +#include + +#include "ardour/libardour_visibility.h" + +namespace ARDOUR { + +class LIBARDOUR_API LuaScriptInfo { + public: + + enum ScriptType { + Invalid, + DSP, + Session, + EditorHook, + EditorAction, + }; + + static std::string type2str (const ScriptType t); + static ScriptType str2type (const std::string& str); + + LuaScriptInfo (ScriptType t, const std::string &n, const std::string &p) + : type (t) + , name (n) + , path (p) + { } + + virtual ~LuaScriptInfo () { } + + ScriptType type; + std::string name; + std::string path; + + std::string author; + std::string license; + std::string category; + std::string description; +}; + +struct LIBARDOUR_API LuaScriptParam { + public: + LuaScriptParam ( + const std::string& n, + const std::string& t, + const std::string& d, + bool o) + : name (n) + , title (t) + , dflt (d) + , optional (o) + , is_set (false) + , value (d) + {} + + std::string name; + std::string title; + std::string dflt; + bool optional; + bool is_set; + std::string value; +}; + + +typedef boost::shared_ptr LuaScriptInfoPtr; +typedef std::vector LuaScriptList; + +typedef boost::shared_ptr LuaScriptParamPtr; +typedef std::vector LuaScriptParamList; + + +class LIBARDOUR_API LuaScripting { + +public: + static LuaScripting& instance(); + + ~LuaScripting (); + + LuaScriptList &scripts (LuaScriptInfo::ScriptType); + + void refresh (); + static LuaScriptInfoPtr script_info (const std::string &script ) { return scan_script ("", script); } + + static LuaScriptParamList script_params (LuaScriptInfoPtr, const std::string &); + static LuaScriptParamList session_script_params (LuaScriptInfoPtr lsi) { + return script_params (lsi, "sess_params"); + } + + static bool try_compile (const std::string&, const LuaScriptParamList&); + static std::string get_factory_bytecode (const std::string&); + +private: + static LuaScripting* _instance; // singleton + LuaScripting (); + + void scan (); + void check_scan (); + static LuaScriptInfoPtr scan_script (const std::string &, const std::string & sc = ""); + static void lua_print (std::string s); + + LuaScriptList *_sl_dsp; + LuaScriptList *_sl_session; + LuaScriptList *_sl_hook; + LuaScriptList *_sl_action; + LuaScriptList _empty_script_info; + + Glib::Threads::Mutex _lock; +}; + +} // namespace ARDOUR + +#endif // _ardour_luascripting_h_ diff --git a/libs/ardour/luascripting.cc b/libs/ardour/luascripting.cc new file mode 100644 index 0000000000..d3768c17d5 --- /dev/null +++ b/libs/ardour/luascripting.cc @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2016 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include + +#include "pbd/error.h" +#include "pbd/file_utils.h" +#include "pbd/compose.h" + +#include "ardour/luascripting.h" +#include "ardour/search_paths.h" + +#include "lua/luastate.h" +#include "LuaBridge/LuaBridge.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using namespace std; + +LuaScripting* LuaScripting::_instance = 0; + +LuaScripting& +LuaScripting::instance () +{ + if (!_instance) { + _instance = new LuaScripting; + } + return *_instance; +} + +LuaScripting::LuaScripting () + : _sl_dsp (0) + , _sl_session (0) + , _sl_hook (0) + , _sl_action (0) +{ + ; +} + +LuaScripting::~LuaScripting () +{ + if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) { + // don't bother, just exit quickly. + delete _sl_dsp; + delete _sl_session; + delete _sl_hook; + delete _sl_action; + } +} + +void +LuaScripting::check_scan () +{ + if (!_sl_dsp || !_sl_session || !_sl_hook || !_sl_action) { + scan (); + } +} + +void +LuaScripting::refresh () +{ + Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK); + + if (!lm.locked()) { + return; + } + + delete _sl_dsp; + delete _sl_session; + delete _sl_hook; + delete _sl_action; + + _sl_dsp = 0; + _sl_session = 0; + _sl_hook = 0; + _sl_action = 0; +} + +struct ScriptSorter { + bool operator () (LuaScriptInfoPtr a, LuaScriptInfoPtr b) { + return a->name < b->name; + } +}; + +void +LuaScripting::scan () +{ + Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK); + + if (!lm.locked()) { + return; + } + +#define CLEAR_OR_NEW(LIST) \ + if (LIST) { LIST->clear (); } else { LIST = new LuaScriptList (); } + + CLEAR_OR_NEW (_sl_dsp) + CLEAR_OR_NEW (_sl_session) + CLEAR_OR_NEW (_sl_hook) + CLEAR_OR_NEW (_sl_action) + +#undef CLEAR_OR_NEW + + vector luascripts; + find_files_matching_pattern (luascripts, lua_search_path (), "*.lua"); + + for (vector::iterator i = luascripts.begin(); i != luascripts.end (); ++i) { + LuaScriptInfoPtr lsi = scan_script (*i); + if (!lsi) { + PBD::info << string_compose (_("Script '%1' has no valid descriptor."), *i) << endmsg; + continue; + } + switch (lsi->type) { + case LuaScriptInfo::DSP: + _sl_dsp->push_back(lsi); + break; + case LuaScriptInfo::Session: + _sl_session->push_back(lsi); + break; + case LuaScriptInfo::EditorHook: + _sl_hook->push_back(lsi); + break; + case LuaScriptInfo::EditorAction: + _sl_action->push_back(lsi); + break; + default: + break; + } + } + + std::sort (_sl_dsp->begin(), _sl_dsp->end(), ScriptSorter()); + std::sort (_sl_session->begin(), _sl_session->end(), ScriptSorter()); + std::sort (_sl_hook->begin(), _sl_hook->end(), ScriptSorter()); + std::sort (_sl_action->begin(), _sl_action->end(), ScriptSorter()); + +} + +void +LuaScripting::lua_print (std::string s) { + PBD::info << "Lua: " << s << "\n"; +} + +LuaScriptInfoPtr +LuaScripting::scan_script (const std::string &fn, const std::string &sc) +{ + LuaState lua; + if (!(fn.empty() ^ sc.empty())){ + // give either file OR script + assert (0); + return LuaScriptInfoPtr(); + } + + lua_State* L = lua.getState(); + lua.Print.connect (&LuaScripting::lua_print); + + lua.do_command ("io = nil;"); + + lua.do_command ( + "ardourluainfo = {}" + "function ardour (entry)" + " ardourluainfo['type'] = assert(entry['type'])" + " ardourluainfo['name'] = assert(entry['name'])" + " ardourluainfo['author'] = entry['author'] or 'Unknown'" + " ardourluainfo['license'] = entry['license'] or ''" + " ardourluainfo['description'] = entry['description'] or ''" + " end" + ); + + try { + int err; + if (fn.empty()) { + err = lua.do_command (sc); + } else { + err = lua.do_file (fn); + } + if (err) { + return LuaScriptInfoPtr(); + } + } catch (...) { // luabridge::LuaException + return LuaScriptInfoPtr(); + } + luabridge::LuaRef nfo = luabridge::getGlobal (L, "ardourluainfo"); + if (nfo.type() != LUA_TTABLE) { + return LuaScriptInfoPtr(); + } + + if (nfo["name"].type() != LUA_TSTRING || nfo["type"].type() != LUA_TSTRING) { + return LuaScriptInfoPtr(); + } + + std::string name = nfo["name"].cast(); + LuaScriptInfo::ScriptType type = LuaScriptInfo::str2type (nfo["type"].cast()); + + if (name.empty() || type == LuaScriptInfo::Invalid) { + return LuaScriptInfoPtr(); + } + + LuaScriptInfoPtr lsi (new LuaScriptInfo (type, name, fn)); + + for (luabridge::Iterator i(nfo); !i.isNil (); ++i) { + if (!i.key().isString() || !i.value().isString()) { + return LuaScriptInfoPtr(); + } + std::string key = i.key().tostring(); + std::string val = i.value().tostring(); + + if (key == "author") { lsi->author = val; } + if (key == "license") { lsi->license = val; } + if (key == "description") { lsi->description = val; } + } + + return lsi; +} + +LuaScriptList & +LuaScripting::scripts (LuaScriptInfo::ScriptType type) { + check_scan(); + + switch (type) { + case LuaScriptInfo::DSP: + return *_sl_dsp; + break; + case LuaScriptInfo::Session: + return *_sl_session; + break; + case LuaScriptInfo::EditorHook: + return *_sl_hook; + break; + case LuaScriptInfo::EditorAction: + return *_sl_action; + break; + default: + return _empty_script_info; + break; + } +} + + +std::string +LuaScriptInfo::type2str (const ScriptType t) { + switch (t) { + case LuaScriptInfo::DSP: return "DSP"; + case LuaScriptInfo::Session: return "Session"; + case LuaScriptInfo::EditorHook: return "EditorHook"; + case LuaScriptInfo::EditorAction: return "EditorAction"; + default: return "Invalid"; + } +} + +LuaScriptInfo::ScriptType +LuaScriptInfo::str2type (const std::string& str) { + const char* type = str.c_str(); + if (!strcasecmp (type, "DSP")) {return LuaScriptInfo::DSP;} + if (!strcasecmp (type, "Session")) {return LuaScriptInfo::Session;} + if (!strcasecmp (type, "EditorHook")) {return LuaScriptInfo::EditorHook;} + if (!strcasecmp (type, "EditorAction")) {return LuaScriptInfo::EditorAction;} + return LuaScriptInfo::Invalid; +} + +LuaScriptParamList +LuaScripting::script_params (LuaScriptInfoPtr lsi, const std::string &fn) +{ + LuaScriptParamList rv; + assert (lsi); + + LuaState lua; + lua_State* L = lua.getState(); + lua.Print.connect (&LuaScripting::lua_print); + lua.do_command ("io = nil;"); + lua.do_command ("function ardour () end"); + + try { + lua.do_file (lsi->path); + } catch (luabridge::LuaException const& e) { + return rv; + } + + luabridge::LuaRef lua_params = luabridge::getGlobal (L, fn.c_str()); + if (lua_params.isFunction ()) { + luabridge::LuaRef params = lua_params (); + if (params.isTable ()) { + for (luabridge::Iterator i (params); !i.isNil (); ++i) { + if (!i.key ().isString ()) { continue; } + if (!i.value ().isTable ()) { continue; } + if (!i.value ()["title"].isString ()) { continue; } + + std::string name = i.key ().cast (); + std::string title = i.value ()["title"].cast (); + std::string dflt; + bool optional = false; + + if (i.value ()["default"].isString ()) { + dflt = i.value ()["default"].cast (); + } + if (i.value ()["optional"].isBoolean ()) { + optional = i.value ()["optional"].cast (); + } + LuaScriptParamPtr lsspp (new LuaScriptParam(name, title, dflt, optional)); + rv.push_back (lsspp); + } + } + } + return rv; +} + +bool +LuaScripting::try_compile (const std::string& script, const LuaScriptParamList& args) +{ + const std::string& bytecode = get_factory_bytecode (script); + if (bytecode.empty()) { + return false; + } + LuaState l; + lua_State* L = l.getState(); + + l.do_command ("" + " function checkfactory (b, a)" + " assert(type(b) == 'string', 'ByteCode must be string')" + " load(b)()" + " assert(type(f) == 'string', 'Assigned ByteCode must be string')" + " local env = _ENV; env.f = nil env.debug = nil os.exit = nil" + " load (string.dump(f, true), nil, nil, env)(a)" + " end" + ); + + try { + luabridge::LuaRef lua_test = luabridge::getGlobal (L, "checkfactory"); + l.do_command ("checkfactory = nil"); // hide it. + l.do_command ("collectgarbage()"); + lua_test (bytecode); + return true; // OK + } catch (luabridge::LuaException const& e) { } + + return false; +} + +std::string +LuaScripting::get_factory_bytecode (const std::string& script) +{ + LuaState l; + l.Print.connect (&LuaScripting::lua_print); + lua_State* L = l.getState(); + + l.do_command ( + " function ardour () end" + "" + " function dump_function (f)" + " assert(type(f) == 'function', 'Factory is a not a function')" + " return string.format(\"f = %q\", string.dump(f, true))" + " end" + ); + + try { + luabridge::LuaRef lua_dump = luabridge::getGlobal (L, "dump_function"); + l.do_command ("dump_function = nil"); // hide it + l.do_command (script); // register "factory" + luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory"); + + if (lua_factory.isFunction()) { + return (lua_dump(lua_factory)).cast (); + } + } catch (luabridge::LuaException const& e) { } + return ""; +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 6af4650033..b885da79d2 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -112,6 +112,7 @@ libardour_sources = [ 'ltc_file_reader.cc', 'ltc_slave.cc', 'luabindings.cc', + 'luascripting.cc', 'meter.cc', 'midi_automation_list_binder.cc', 'midi_buffer.cc', -- cgit v1.2.3