From e219a6cd1e786dd8a8daa6e6da28953ae3705fcb Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 26 Mar 2020 19:00:11 +0100 Subject: Source-tree consistency (headless tools at top-level) This move ardour-lua session tool to top-level, next to headless/hardev. --- luasession/luasession.cc | 604 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 604 insertions(+) create mode 100644 luasession/luasession.cc (limited to 'luasession/luasession.cc') diff --git a/luasession/luasession.cc b/luasession/luasession.cc new file mode 100644 index 0000000000..6f11ab675e --- /dev/null +++ b/luasession/luasession.cc @@ -0,0 +1,604 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef PLATFORM_WINDOWS +# include +# include +#else +# include +#endif + +#include + +#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 "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; + +/* 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::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 MyEventLoop* event_loop = NULL; + +/* ****************************************************************************/ +/* internal helper fn and callbacks */ + +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", 4096); + + static LuaReceiver lua_receiver; + + lua_receiver.listen_to (error); + lua_receiver.listen_to (info); + lua_receiver.listen_to (fatal); + lua_receiver.listen_to (warning); +} + +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 dir, string state) // throws +{ + if (prepare_engine ()) { + return 0; + } + + 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 +my_lua_print (std::string s) +{ + std::cout << s << "\n"; +} + +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); + 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 (); +} + +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; + 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 ]\n\n"); + printf ("Options:\n\ + -h, --help display this help and exit\n\ + -i, --interactive enter interactive mode after executing 'script'\n\ + -V, --version print version information and exit\n\ +\n"); + printf ("\n\ +Ardour at your finger tips...\n\ +\n"); + printf ("Report bugs to \n" + "Website: \n"); + ::exit (EXIT_SUCCESS); +} + +int +main (int argc, char** argv) +{ + const char* optstring = "hiV"; + + const struct option longopts[] = { + { "help", 0, 0, 'h' }, + { "interactive", 0, 0, 'i' }, + { "version", 0, 0, 'V' }, + }; + + bool interactive = false; + + 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"); + exit (EXIT_SUCCESS); + break; + + default: + cerr << "Error: unrecognized option. See --help for usage information.\n"; + ::exit (EXIT_FAILURE); + break; + } + } + + init (); + setup_lua (); + + if (argc > optind) { + lua->do_file (argv[optind]); + } else { + interactive = true; + } + + if (!interactive || !keep_running) { + /* continue to exit */ + } else if (is_tty ()) { + interactive_interpreter (); + } else { + luaL_dofile (lua->getState (), NULL); + } + + if (session) { + close_session (); + } + + engine_connections.drop_connections (); + + delete lua; + lua = NULL; + + AudioEngine::instance ()->stop (); + AudioEngine::destroy (); + + ARDOUR::cleanup (); + delete event_loop; + pthread_cancel_all (); + return 0; +} -- cgit v1.2.3