libardour lua-script-manager
authorRobin Gareus <robin@gareus.org>
Fri, 12 Feb 2016 12:24:41 +0000 (13:24 +0100)
committerRobin Gareus <robin@gareus.org>
Tue, 23 Feb 2016 14:41:06 +0000 (15:41 +0100)
libs/ardour/ardour/luascripting.h [new file with mode: 0644]
libs/ardour/luascripting.cc [new file with mode: 0644]
libs/ardour/wscript

diff --git a/libs/ardour/ardour/luascripting.h b/libs/ardour/ardour/luascripting.h
new file mode 100644 (file)
index 0000000..a5a066b
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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 <vector>
+
+#include <boost/shared_ptr.hpp>
+#include <glibmm/threads.h>
+
+#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<LuaScriptInfo> LuaScriptInfoPtr;
+typedef std::vector<LuaScriptInfoPtr> LuaScriptList;
+
+typedef boost::shared_ptr<LuaScriptParam> LuaScriptParamPtr;
+typedef std::vector<LuaScriptParamPtr> 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 (file)
index 0000000..d3768c1
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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 <cstring>
+
+#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<string> luascripts;
+       find_files_matching_pattern (luascripts, lua_search_path (), "*.lua");
+
+       for (vector<string>::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<std::string>();
+       LuaScriptInfo::ScriptType type = LuaScriptInfo::str2type (nfo["type"].cast<std::string>());
+
+       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> ();
+                               std::string title = i.value ()["title"].cast<std::string> ();
+                               std::string dflt;
+                               bool optional = false;
+
+                               if (i.value ()["default"].isString ()) {
+                                       dflt = i.value ()["default"].cast<std::string> ();
+                               }
+                               if (i.value ()["optional"].isBoolean ()) {
+                                       optional = i.value ()["optional"].cast<bool> ();
+                               }
+                               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<std::string> ();
+               }
+       } catch (luabridge::LuaException const& e) { }
+       return "";
+}
index 6af465003388d74f0c716461e5dc37a91fc1f5fc..b885da79d2549d9d45b0d47a5258bd9513e6af0c 100644 (file)
@@ -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',