diff --git a/gtk2_ardour/arlua b/gtk2_ardour/arlua new file mode 100755 index 0000000000..d8cec780ec --- /dev/null +++ b/gtk2_ardour/arlua @@ -0,0 +1,21 @@ +#!/bin/sh +TOP=`dirname "$0"`/.. +. $TOP/build/gtk2_ardour/ardev_common_waf.sh +export UBUNTU_MENUPROXY="" + +if [ $# -gt 0 ] ; then + case $1 in + -g|--gdb) GDB=gdb; shift ;; + esac +fi + +if test -z "$GDB"; then + exec $TOP/build/tools/luadevel/luasession "$@" +fi + +if test -n "`which gdb`"; then + exec gdb --args $TOP/build/tools/luadevel/luasession "$@" +fi +if test -n "`which lldb`"; then + exec lldb -- $TOP/build/tools/luadevel/luasession "$@" +fi diff --git a/tools/luadevel/ardour-lua.sh.in b/tools/luadevel/ardour-lua.sh.in new file mode 100644 index 0000000000..50d6e6e0c2 --- /dev/null +++ b/tools/luadevel/ardour-lua.sh.in @@ -0,0 +1,9 @@ +#!/bin/sh + +export LD_LIBRARY_PATH=@LIBDIR@${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} +export ARDOUR_DATA_PATH=@DATADIR@ +export ARDOUR_CONFIG_PATH=@CONFDIR@ +export ARDOUR_DLL_PATH=@LIBDIR@ +export VAMP_PATH=@LIBDIR@/vamp + +exec @LIBDIR@/luasession "$@" diff --git a/tools/luadevel/devel.cc b/tools/luadevel/devel.cc new file mode 100644 index 0000000000..3caa0513f3 --- /dev/null +++ b/tools/luadevel/devel.cc @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include +#include + +#define LIBPBD_API +#include "../../libs/pbd/pbd/reallocpool.h" +#include "../../libs/pbd/reallocpool.cc" + +#include +#include + +#include "lua/luastate.h" +#include "LuaBridge/LuaBridge.h" + +static void my_lua_print (std::string s) { + std::cout << s << "\n"; +} + + +class A { + public: + A() { printf ("CTOR\n"); _int = 4; for (int i = 0; i < 256; ++i) {arr[i] = i; ar2[i] = i/256.0; ar3[i] = i;} } + ~A() { printf ("DTOR\n"); } + + void set_int (int a) { _int = a; } + int get_int () const { return _int; } + + int& get_ref () { return _int; } + int get_arg (int &a) { printf ("a = %d\n", a); a = _int; printf ("a = %d\n", a); return 1; } + void get_arg2 (int &a, int& b) { a = _int; b = 100; } + void get_args (std::string &a) { a = "hello"; } + void set_ref (int const &a) { _int = a; } + + float * get_arr () { return arr; } + float * get_ar2 () { return ar2; } + int * get_ar3 () { return ar3; } + + void set_list (std::list sl) { _sl = sl; } + std::list& get_list () { return _sl; } + + uint32_t minone() { return -1; } + + enum EN { + RV1 = 1, RV2, RV3 + }; + + enum EN ret_enum () { return _en;} + void set_enum (enum EN en) { _en = en; } + + private: + std::list _sl; + int _int; + enum EN _en; + float arr[256]; + float ar2[256]; + int ar3[256]; +}; + +int main (int argc, char **argv) +{ +#if 0 + LuaState lua; +#else + PBD::ReallocPool _mempool ("Devel", 1048576); + LuaState lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool)); +#endif + lua.Print.connect (&my_lua_print); + lua_State* L = lua.getState(); + + +#if 1 + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .beginStdList ("StringList") + .endClass () + .endNamespace (); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .beginStdVector ("StringVector") + .endClass () + .endNamespace (); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .beginStdMap ("StringStringMap") + .endClass () + .endNamespace (); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .beginStdSet ("StringSet") + .endClass () + .endNamespace (); + + + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .registerArray ("FloatArray") + .registerArray ("IntArray") + .beginClass ("A") + .addConstructor () + .addFunction ("set_int", &A::set_int) + .addFunction ("get_int", &A::get_int) + .addRefFunction ("get_arg", &A::get_arg) + .addRefFunction ("get_arg2", &A::get_arg2) + .addRefFunction ("get_args", &A::get_args) + .addFunction ("set_ref", &A::set_ref) + .addFunction ("get_list", &A::get_list) + .addFunction ("set_list", &A::set_list) + .addFunction ("ret_enum", &A::ret_enum) + .addFunction ("set_enum", &A::set_enum) + .addFunction ("get_arr", &A::get_arr) + .addFunction ("get_ar2", &A::get_ar2) + .addFunction ("get_ar3", &A::get_ar3) + .endClass () + .endNamespace (); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("Test") + .beginClass ("A") + .addFunction ("minone", &A::minone) + .addConst ("cologne", 4711) + .endClass () + .addConst ("koln", 4711) + .endNamespace (); +#endif +#if 0 // session script test + lua.do_command ( + "function ArdourSession ()" + " local self = { scripts = {}, instances = {} }" + "" + " local foreach = function (fn)" + " for n, s in pairs (self.scripts) do" + " fn (n, s)" + " end" + " 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) end" + " end" + " collectgarbage()" + " end" + "" + " local add = 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 = { print = print, Session = Session, tostring = tostring, assert = assert, ipairs = ipairs, error = error, string = string, type = type, tonumber = tonumber, collectgarbage = collectgarbage, pairs = pairs, math = math, table = table, pcall = pcall }" + " self.instances[n] = load (string.dump(f), nil, nil, env)(a)" + " end" + "" + " local remove = function (n)" + " self.scripts[n] = nil" + " end" + "" + " local list = function ()" + " local rv = {}" + " foreach (function (n) 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 .. ' = '" + " 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) .. ' '" + " end" + " return rv;" + " elseif type(value) == \"function\" then" + " return rv .. string.format(\"%q\", string.dump(value))" + " 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)()" + " print (scripts)" + " for n, s in pairs (scripts) do" + " add (n, load(s['f']), s['a'])" + " end" + " end" + "" + " return { run = run, add = add, remove = remove," + " list = list, foreach = foreach," + " restore = restore, save = save}" + " end" + " " + " sess = ArdourSession ()" + " ArdourSession = nil" + ); + + luabridge::LuaRef *lua_run; + luabridge::LuaRef *lua_add; + luabridge::LuaRef *lua_del; + luabridge::LuaRef *lua_save; + luabridge::LuaRef *lua_load; + { + 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_save = new luabridge::LuaRef(lua_sess["save"]); + lua_load = new luabridge::LuaRef(lua_sess["restore"]); + } + lua.do_command ("collectgarbage()"); + +#if 1 + lua.do_command ("function factory (t) return function () local p = t or { } local a = t[1] or 'Nibor' print ('Hello ' .. a) end end"); + luabridge::LuaRef lua_fact = luabridge::getGlobal (L, "factory"); + luabridge::LuaRef tbl_arg (luabridge::newTable(L)); + //tbl_arg[1] = "Robin"; + (*lua_add)("t2", lua_fact, tbl_arg); +#else + lua.do_command ("function factory (t) return function () print ('Boo') end end"); + luabridge::LuaRef lua_fact = luabridge::getGlobal (L, "factory"); + (*lua_add)("t2", lua_fact()); +#endif + + lua.do_command ("function factory (t) return function () print ('Ahoy') end end"); + luabridge::LuaRef lua_fact2 = luabridge::getGlobal (L, "factory"); + (*lua_add)("t1", lua_fact2); + + luabridge::LuaRef savedstate ((*lua_save)()); + std::string saved = savedstate.cast(); + + (*lua_del)("t2"); + + try { + (*lua_run)(); + } catch (luabridge::LuaException const& e) { printf ("LuaException: %s\n", e.what ()); } + + (*lua_load)(saved); + + for (int i = 0; i < 2; ++i) { + lua.do_command ("collectgarbage()"); + lua.collect_garbage (); + try { + (*lua_run)(); + } catch (luabridge::LuaException const& e) { printf ("LuaException: %s\n", e.what ()); } + } + +#endif + + add_history("a = Test:A() b = 2 c = 3 d = 'a'"); + add_history("x = a:get_arg(b) y = a:get_arg2(b, c) z = a:get_args(d) "); + add_history("for i,n in ipairs(y) do print (i, n); end"); + + ///////////////////////////////////////////////////////////////////////////// + char *line; + while ((line = readline ("> "))) { + if (!strcmp (line, "quit")) { + break; + } + if (strlen(line) == 0) { + //lua.do_command("collectgarbage();"); + continue; + } + if (!lua.do_command (line)) { + add_history(line); // OK + } else { + add_history(line); // :) + } + } + printf("\n"); + return 0; +} diff --git a/tools/luadevel/luasession.cc b/tools/luadevel/luasession.cc new file mode 100644 index 0000000000..5ddea9015e --- /dev/null +++ b/tools/luadevel/luasession.cc @@ -0,0 +1,345 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "pbd/debug.h" +#include "pbd/event_loop.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/pthread_utils.h" +#include "pbd/reallocpool.h" +#include "pbd/receiver.h" +#include "pbd/transmitter.h" + +#include "ardour/ardour.h" +#include "ardour/audioengine.h" +#include "ardour/filename_extensions.h" +#include "ardour/filesystem_paths.h" +#include "ardour/luabindings.h" +#include "ardour/session.h" +#include "ardour/types.h" +#include "ardour/vst_types.h" + +#include +#include + +#include "lua/luastate.h" +#include "LuaBridge/LuaBridge.h" + +using namespace std; +using namespace ARDOUR; +using namespace PBD; + +static const char* localedir = LOCALEDIR; +static PBD::ScopedConnectionList engine_connections; +static PBD::ScopedConnectionList session_connections; +static Session *_session = NULL; +static LuaState *lua; + +class LuaReceiver : public Receiver +{ + protected: + void receive (Transmitter::Channel chn, const char * str) + { + const char *prefix = ""; + + switch (chn) { + case Transmitter::Error: + prefix = "[ERROR]: "; + break; + case Transmitter::Info: + /* ignore */ + return; + case Transmitter::Warning: + prefix = "[WARNING]: "; + break; + case Transmitter::Fatal: + prefix = "[FATAL]: "; + break; + case Transmitter::Throw: + /* this isn't supposed to happen */ + abort (); + } + + /* note: iostreams are already thread-safe: no external + lock required. + */ + + std::cout << prefix << str << std::endl; + + if (chn == Transmitter::Fatal) { + ::exit (9); + } + } +}; + +class MyEventLoop : public sigc::trackable, public EventLoop +{ + public: + MyEventLoop (std::string const& name) : EventLoop (name) { + run_loop_thread = Glib::Threads::Thread::self (); + } + + void call_slot (InvalidationRecord* ir, const boost::function& f) { + if (Glib::Threads::Thread::self () == run_loop_thread) { + //cout << string_compose ("%1/%2 direct dispatch of call slot via functor @ %3, invalidation %4\n", event_loop_name(), pthread_name(), &f, ir); + f (); + } else { + //cout << string_compose ("%1/%2 queue call-slot using functor @ %3, invalidation %4\n", event_loop_name(), pthread_name(), &f, ir); + assert (!ir); + f (); // XXX TODO, queue and process during run () + } + } + + void run () { + ; // TODO process Events, if any + } + + Glib::Threads::Mutex& slot_invalidation_mutex () { return request_buffer_map_lock; } + + private: + Glib::Threads::Thread* run_loop_thread; + Glib::Threads::Mutex request_buffer_map_lock; +}; + +static int do_audio_midi_setup (uint32_t desired_sample_rate) +{ + return AudioEngine::instance ()->start (); +} + +static MyEventLoop *event_loop = NULL; + +static void init () +{ + if (!ARDOUR::init (false, true, localedir)) { + cerr << "Ardour failed to initialize\n" << endl; + ::exit (EXIT_FAILURE); + } + + assert (!event_loop); + event_loop = new MyEventLoop ("lua"); + EventLoop::set_event_loop_for_thread (event_loop); + SessionEvent::create_per_thread_pool ("lua", 512); + + static LuaReceiver lua_receiver; + + lua_receiver.listen_to (error); + lua_receiver.listen_to (info); + lua_receiver.listen_to (fatal); + lua_receiver.listen_to (warning); + + ARDOUR::Session::AudioEngineSetupRequired.connect_same_thread (engine_connections, &do_audio_midi_setup); +} + +static void set_session (ARDOUR::Session *s) +{ + _session = s; + assert (lua); + lua_State* L = lua->getState (); + LuaBindings::set_session (L, _session); + lua->collect_garbage (); // drop references +} + +static void unset_session () +{ + session_connections.drop_connections (); + set_session (NULL); +} + +static Session * _load_session (string dir, string state) +{ + AudioEngine* engine = AudioEngine::instance (); + + if (!engine->current_backend ()) { + if (!engine->set_backend ("None (Dummy)", "Unit-Test", "")) { + std::cerr << "Cannot create Audio/MIDI engine\n"; + return 0; + } + } + + if (!engine->current_backend ()) { + std::cerr << "Cannot create Audio/MIDI engine\n"; + return 0; + } + + if (engine->running ()) { + engine->stop (); + } + + float sr; + SampleFormat sf; + + std::string s = Glib::build_filename (dir, state + statefile_suffix); + if (!Glib::file_test (dir, Glib::FILE_TEST_EXISTS)) { + std::cerr << "Cannot find session: " << s << "\n"; + return 0; + } + + if (Session::get_info_from_path (s, sr, sf) == 0) { + if (engine->set_sample_rate (sr)) { + std::cerr << "Cannot set session's samplerate.\n"; + return 0; + } + } else { + std::cerr << "Cannot get samplerate from session.\n"; + return 0; + } + + init_post_engine (); + + if (engine->start () != 0) { + std::cerr << "Cannot start Audio/MIDI engine\n"; + return 0; + } + + Session* session = new Session (*engine, dir, state); + return session; +} + +static Session* load_session (string dir, string state) +{ + Session* s = 0; + if (_session) { + cerr << "Session already open" << "\n"; + return 0; + } + try { + s = _load_session (dir, state); + } catch (failed_constructor& e) { + cerr << "failed_constructor: " << e.what () << "\n"; + return 0; + } catch (AudioEngine::PortRegistrationFailure& e) { + cerr << "PortRegistrationFailure: " << e.what () << "\n"; + return 0; + } catch (exception& e) { + cerr << "exception: " << e.what () << "\n"; + return 0; + } catch (...) { + cerr << "unknown exception.\n"; + return 0; + } + Glib::usleep (1000000); // allo signal propagation, callback/thread-pool setup + assert (s); + set_session (s); + s->DropReferences.connect_same_thread (session_connections, &unset_session); + return s; +} + +static void close_session () +{ + delete _session; + assert (!_session); +} + +static int close_session_lua (lua_State *L) +{ + if (!_session) { + cerr << "No open session" << "\n"; + return 0; + } + close_session (); + return 0; +} + +/* extern VST functions */ +int vstfx_init (void*) { return 0; } +void vstfx_exit () {} +void vstfx_destroy_editor (VSTState*) {} + +static void my_lua_print (std::string s) { + std::cout << s << "\n"; +} + +static void delay (float d) { + if (d > 0) { + Glib::usleep (d * 1000000); + } +} + +static void setup_lua () +{ + assert (!lua); + + lua = new LuaState (); + lua->Print.connect (&my_lua_print); + lua_State* L = lua->getState (); + + LuaBindings::stddef (L); + LuaBindings::common (L); + LuaBindings::session (L); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("_G") + .addFunction ("load_session", &load_session) + .addFunction ("close_session", &close_session) + .addFunction ("sleep", &delay) + .endNamespace (); + + luabridge::getGlobalNamespace (L) + .beginNamespace ("ARDOUR") + .beginClass ("Session") + .addExtCFunction ("close", &close_session_lua) + .endClass () + .endNamespace (); + + // push instance to global namespace (C++ lifetime) + luabridge::push (L, AudioEngine::create ()); + lua_setglobal (L, "AudioEngine"); +} + +int main (int argc, char **argv) +{ + init (); + setup_lua (); + + using_history (); + std::string histfile = Glib::build_filename (user_config_directory(), "/luahist"); + + read_history (histfile.c_str()); + + char *line; + while ((line = readline ("> "))) { + event_loop->run(); + if (!strcmp (line, "quit")) { + break; + } + if (strlen (line) == 0) { + continue; + } + if (!lua->do_command (line)) { + add_history (line); // OK + } else { + add_history (line); // :) + } + event_loop->run(); + free (line); + } + printf ("\n"); + + if (_session) { + close_session (); + } + + engine_connections.drop_connections (); + + delete lua; + lua = NULL; + + write_history (histfile.c_str()); + + AudioEngine::instance ()->stop (); + AudioEngine::destroy (); + + // cleanup + ARDOUR::cleanup (); + delete event_loop; + pthread_cancel_all (); + return 0; +} diff --git a/tools/luadevel/wscript b/tools/luadevel/wscript new file mode 100755 index 0000000000..730f08a7fd --- /dev/null +++ b/tools/luadevel/wscript @@ -0,0 +1,92 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +from waflib import Options, TaskGen +import waflib.Logs as Logs, waflib.Utils as Utils +import os +import shutil +import sys +import re +import time +from waflib.Task import Task + +top = '.' +out = 'build' + +def options(opt): + autowaf.set_options(opt) + +def configure(conf): + conf.load('misc') + conf.load('compiler_cxx') + conf.check_cc(function_name='readline', + header_name='stdio.h readline/readline.h', + lib='readline', + uselib_store='READLINE', + mandatory=False) + autowaf.configure(conf) + +def build(bld): + VERSION = "%s.%s" % (bld.env['MAJOR'], bld.env['MINOR']) + if not bld.is_defined('HAVE_READLINE'): + return; + # no wine + if bld.is_defined('WINDOWS_VST_SUPPORT') and bld.env['build_target'] != 'mingw': + return + + # TEST/DEVEL TOOL ####################### + obj = bld (features = 'cxx c cxxprogram') + obj.source = 'devel.cc' + obj.target = 'devel' + obj.uselib = ['SIGCPP', 'READLINE'] + obj.use = ['liblua'] + obj.install_path = None + ######################################### + + # commandline luasession wrapper + obj = bld(features = 'subst') + obj.source = 'ardour-lua.sh.in' + obj.target = 'ardour' + str (bld.env['MAJOR']) + '-lua' + obj.chmod = Utils.O755 + obj.install_path = bld.env['BINDIR'] + obj.LIBDIR = os.path.normpath(bld.env['DLLDIR']) + obj.DATADIR = os.path.normpath(bld.env['DATADIR']) + obj.CONFDIR = os.path.normpath(bld.env['CONFDIR']) + + # commandline luasession + obj = bld (features = 'cxx c cxxprogram') + obj.source = 'luasession.cc' + obj.target = 'luasession' + obj.includes = ['../libs'] + obj.use = ['liblua' + 'libpbd', + 'libardour', + 'libardour_cp', + 'libtimecode', + 'libmidipp', + ] + obj.defines = [ + 'VERSIONSTRING="' + str(bld.env['VERSION']) + '"', + 'DATA_DIR="' + os.path.normpath(bld.env['DATADIR']) + '"', + 'CONFIG_DIR="' + os.path.normpath(bld.env['SYSCONFDIR']) + '"', + 'LOCALEDIR="' + os.path.join(os.path.normpath(bld.env['DATADIR']), 'locale') + '"', + 'PACKAGE="' + "ARDOURUTILS" + '"', + ] + + obj.uselib = 'UUID FLAC FONTCONFIG GLIBMM GTHREAD OGG CURL DL' + obj.uselib += ' FFTW3F' + obj.uselib += ' AUDIOUNITS OSX LO ' + obj.uselib += ' TAGLIB ' + obj.uselib += ' READLINE ' + + if sys.platform == 'darwin': + obj.uselib += ' AUDIOUNITS OSX' + obj.use += ' libappleutility' + + #if bld.env['build_target'] == 'mingw': + # if bld.env['DEBUG'] == False: + # obj.linkflags = ['-mwindows'] + + if bld.is_defined('NEED_INTL'): + obj.linkflags = ' -lintl' + + obj.install_path = bld.env['DLLDIR'] diff --git a/wscript b/wscript index 69ea9b3220..30a710afe6 100644 --- a/wscript +++ b/wscript @@ -230,6 +230,7 @@ children = [ 'libs/vfork', 'libs/ardouralsautil', 'cfgtool', + 'tools/luadevel', ] i18n_children = [