#include #include #include #include #include #include #include #include #ifdef PLATFORM_WINDOWS # include # include #else # include #endif #include #include "pbd/basename.h" #include "pbd/debug.h" #include "pbd/error.h" #include "pbd/event_loop.h" #include "pbd/failed_constructor.h" #include "pbd/pthread_utils.h" #include "pbd/reallocpool.h" #include "pbd/receiver.h" #include "pbd/transmitter.h" #include "pbd/win_console.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 "LuaBridge/LuaBridge.h" #include "lua/luastate.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; static bool keep_running = true; static bool terminate_when_halted = false; /* extern VST functions */ int vstfx_init (void*) { return 0; } void vstfx_exit () { } void vstfx_destroy_editor (VSTState*) { } class LuaReceiver : public Receiver { protected: void receive (Transmitter::Channel chn, const char* str) { const char* prefix = ""; switch (chn) { case Transmitter::Debug: /* ignore */ return; case Transmitter::Info: /* ignore */ return; case Transmitter::Warning: prefix = "[WARNING]: "; break; case Transmitter::Error: prefix = "[ERROR]: "; 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) { console_madness_end (); ::exit (9); } } }; class MyEventLoop : public sigc::trackable, public EventLoop { public: MyEventLoop (std::string const& name) : EventLoop (name) { run_loop_thread = Glib::Threads::Thread::self (); } bool call_slot (InvalidationRecord* ir, const std::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 () } return true; } void run () { ; // TODO process Events, if any } Glib::Threads::RWLock& slot_invalidation_rwlock () { return request_buffer_map_lock; } private: Glib::Threads::Thread* run_loop_thread; Glib::Threads::RWLock request_buffer_map_lock; }; static MyEventLoop* event_loop = NULL; /* ****************************************************************************/ /* internal helper fn and callbacks */ static void init () { if (!ARDOUR::init (true, localedir)) { cerr << "Ardour failed to initialize\n" << endl; console_madness_end (); ::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", 4096); static LuaReceiver lua_receiver; lua_receiver.listen_to (warning); lua_receiver.listen_to (error); lua_receiver.listen_to (fatal); } 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 int prepare_engine () { 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 -1; } } if (!engine->current_backend ()) { std::cerr << "Cannot create Audio/MIDI engine\n"; return -1; } if (engine->running ()) { engine->stop (); } return 0; } static int start_engine (uint32_t rate) { AudioEngine* engine = AudioEngine::instance (); if (engine->set_sample_rate (rate)) { std::cerr << "Cannot set session's samplerate.\n"; return -1; } if (engine->start () != 0) { std::cerr << "Cannot start Audio/MIDI engine\n"; return -1; } return 0; } static Session* _create_session (string dir, string state, uint32_t rate) // throws { if (prepare_engine ()) { return 0; } std::string s = Glib::build_filename (dir, state + statefile_suffix); if (Glib::file_test (dir, Glib::FILE_TEST_EXISTS)) { std::cerr << "Session already exists: " << s << "\n"; return 0; } if (start_engine (rate)) { return 0; } // TODO add option/bindings for this BusProfile bus_profile; bus_profile.master_out_channels = 2; AudioEngine* engine = AudioEngine::instance (); Session* session = new Session (*engine, dir, state, &bus_profile); return session; } static Session* _load_session (string const& dir, string state) // throws { if (prepare_engine ()) { return 0; } if (state.empty ()) { state = Session::get_snapshot_from_instant (dir); } if (state.empty ()) { state = PBD::basename_nosuffix (dir); } float sr; SampleFormat sf; std::string v; 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, v) != 0) { std::cerr << "Cannot get samplerate from session.\n"; return 0; } if (start_engine (sr)) { return 0; } AudioEngine* engine = AudioEngine::instance (); Session* session = new Session (*engine, dir, state); return session; } /* ****************************************************************************/ /* lua bound functions */ static Session* create_session (string dir, string state, uint32_t rate) { Session* s = 0; if (session) { cerr << "Session already open" << "\n"; return 0; } try { s = _create_session (dir, state, rate); } 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); // allow signal propagation, callback/thread-pool setup if (!s) { return 0; } set_session (s); s->DropReferences.connect_same_thread (session_connections, &unset_session); return s; } 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); // allow signal propagation, callback/thread-pool setup if (!s) { return 0; } set_session (s); s->DropReferences.connect_same_thread (session_connections, &unset_session); return s; } static int set_debug_options (const char* opts) { return PBD::parse_debug_options (opts); } 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; } static void delay (float d) { if (d > 0) { Glib::usleep (d * 1000000); } } static int do_quit (lua_State* L) { keep_running = false; return 0; } static void engine_halted (const char* err) { if (terminate_when_halted) { cerr << "Engine halted: " << err << "\n"; console_madness_end (); ::exit (EXIT_FAILURE); } } /* ****************************************************************************/ static void my_lua_print (std::string s) { std::cout << s << "\n"; } static void setup_lua () { assert (!lua); lua = new LuaState (false, false); lua->Print.connect (&my_lua_print); lua_State* L = lua->getState (); LuaBindings::stddef (L); LuaBindings::common (L); LuaBindings::session (L); LuaBindings::osc (L); luabridge::getGlobalNamespace (L) .beginNamespace ("_G") .addFunction ("create_session", &create_session) .addFunction ("load_session", &load_session) .addFunction ("close_session", &close_session) .addFunction ("sleep", &delay) .addFunction ("quit", &do_quit) .addFunction ("set_debug_options", &set_debug_options) .endNamespace (); // add a Session::close() method 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"); AudioEngine::instance ()->stop (); AudioEngine::instance ()->Halted.connect_same_thread (engine_connections, std::bind (&engine_halted, _1)); } static int incomplete (lua_State* L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; const char* msg = lua_tolstring (L, -1, &lmsg); if (lmsg >= 5 && strcmp (msg + lmsg - 5, "") == 0) { lua_pop (L, 1); return 1; } } return 0; } static void interactive_interpreter () { using_history (); std::string histfile = Glib::build_filename (user_config_directory (), "/luahist"); rl_bind_key ('\t', rl_insert); // disable completion read_history (histfile.c_str ()); char* line = NULL; while (keep_running && (line = readline ("> "))) { event_loop->run (); if (!strcmp (line, "quit")) { free (line); line = NULL; break; } if (strlen (line) == 0) { free (line); line = NULL; continue; } do { LuaState lt (false, false); lua_State* L = lt.getState (); int status = luaL_loadbuffer (L, line, strlen (line), "=stdin"); if (!incomplete (L, status)) { break; } char* l2 = readline (">> "); if (!l2) { break; } if (strlen (l2) == 0) { continue; } line = (char*)realloc ((void*)line, (strlen (line) + strlen (l2) + 2) * sizeof (char)); strcat (line, "\n"); strcat (line, l2); free (l2); } while (1); if (lua->do_command (line)) { /* error */ free (line); line = NULL; continue; } add_history (line); event_loop->run (); free (line); line = NULL; } free (line); printf ("\n"); write_history (histfile.c_str ()); } static bool is_tty () { #ifdef PLATFORM_WINDOWS return _isatty (_fileno (stdin)); #else return isatty (0); #endif } static void usage () { printf ("ardour-lua - interactive Ardour Lua interpreter.\n\n"); printf ("Usage: ardour-lua [ OPTIONS ] [ file [args] ]\n\n"); /* clang-format off */ /* 1 2 3 4 5 6 7 8 *2345678901234567890123456789012345678901234567890123456789012345678901234567890*/ printf ("Options:\n\ -h, --help display this help and exit\n\ -i, --interactive enter interactive mode after executing 'script',\n\ force the interpreter to run interactively\n\ -X, --exit-when-halted terminate when the audio-engine halts\n\ unexpectedly (disconnect, or too many xruns)\n\ -V, --version print version information and exit\n\ \n"); printf ("\n\ Ardour at your finger tips...\n\ \n"); /* clang-format on */ printf ("Report bugs to \n" "Website: \n"); console_madness_end (); ::exit (EXIT_SUCCESS); } int main (int argc, char** argv) { const char* optstring = "hiVX"; /* clang-format off */ const struct option longopts[] = { { "help", 0, 0, 'h' }, { "interactive", 0, 0, 'i' }, { "version", 0, 0, 'V' }, { "exit-when-halted", 0, 0, 'X' }, }; /* clang-format on */ bool interactive = false; console_madness_begin (); int c = 0; while (EOF != (c = getopt_long (argc, argv, optstring, longopts, (int*)0))) { switch (c) { case 'h': usage (); break; case 'i': interactive = true; break; case 'V': printf ("ardour-lua version %s\n\n", VERSIONSTRING); printf ("Copyright (C) GPL 2015-2020 Robin Gareus \n"); console_madness_end (); exit (EXIT_SUCCESS); break; case 'X': terminate_when_halted = true; break; default: cerr << "Error: unrecognized option. See --help for usage information.\n"; console_madness_end (); ::exit (EXIT_FAILURE); break; } } init (); setup_lua (); { /* push arguments to script, use scoped LuaRef */ lua_State* L = lua->getState (); luabridge::LuaRef arg (luabridge::newTable (L)); for (int i = 1; i < argc - optind; ++i) { arg[i] = std::string (argv[i + optind]); } luabridge::push (L, arg); lua_setglobal (L, "arg"); } int res = 0; if (argc > optind && 0 != strcmp (argv[optind], "-")) { res = lua->do_file (argv[optind]); if (!interactive) { keep_running = false; } } if (!keep_running) { /* continue to exit */ } else if (is_tty () || interactive) { interactive_interpreter (); } else { res = luaL_dofile (lua->getState (), NULL); } if (session) { close_session (); } engine_connections.drop_connections (); delete lua; lua = NULL; AudioEngine::instance ()->stop (); ARDOUR::cleanup (); delete event_loop; pthread_cancel_all (); console_madness_end (); return res; }