libardour lua-script-manager

This commit is contained in:
Robin Gareus 2016-02-12 13:24:41 +01:00
parent f0b6c8e111
commit d8008b2db1
3 changed files with 515 additions and 0 deletions

View File

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

381
libs/ardour/luascripting.cc Normal file
View File

@ -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 "";
}

View 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',