diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h index 86fad9e6e..c782a4997 100644 --- a/source/backend/CarlaBackend.h +++ b/source/backend/CarlaBackend.h @@ -914,29 +914,35 @@ typedef enum { */ ENGINE_CALLBACK_SAMPLE_RATE_CHANGED = 34, + /*! + * NSM callback. + * (Work in progress, values are not defined yet) + */ + ENGINE_CALLBACK_NSM = 35, + /*! * Idle frontend. * This is used by the engine during long operations that might block the frontend, * giving it the possibility to idle while the operation is still in place. */ - ENGINE_CALLBACK_IDLE = 35, + ENGINE_CALLBACK_IDLE = 36, /*! * Show a message as information. * @a valueStr The message */ - ENGINE_CALLBACK_INFO = 36, + ENGINE_CALLBACK_INFO = 37, /*! * Show a message as an error. * @a valueStr The message */ - ENGINE_CALLBACK_ERROR = 37, + ENGINE_CALLBACK_ERROR = 38, /*! * The engine has crashed or malfunctioned and will no longer work. */ - ENGINE_CALLBACK_QUIT = 38 + ENGINE_CALLBACK_QUIT = 39 } EngineCallbackOpcode; @@ -1030,42 +1036,37 @@ typedef enum { */ ENGINE_OPTION_AUDIO_DEVICE = 12, - /*! - * Set data needed for NSM support. - */ - ENGINE_OPTION_NSM_INIT = 13, - /*! * Set path used for a specific plugin type. * Uses value as the plugin format, valueStr as actual path. * @see PluginType */ - ENGINE_OPTION_PLUGIN_PATH = 14, + ENGINE_OPTION_PLUGIN_PATH = 13, /*! * Set path to the binary files. * Default unset. * @note Must be set for plugin and UI bridges to work */ - ENGINE_OPTION_PATH_BINARIES = 15, + ENGINE_OPTION_PATH_BINARIES = 14, /*! * Set path to the resource files. * Default unset. * @note Must be set for some internal plugins to work */ - ENGINE_OPTION_PATH_RESOURCES = 16, + ENGINE_OPTION_PATH_RESOURCES = 15, /*! * Prevent bad plugin and UI behaviour. * @note: Linux only */ - ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 17, + ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 16, /*! * Set frontend winId, used to define as parent window for plugin UIs. */ - ENGINE_OPTION_FRONTEND_WIN_ID = 18 + ENGINE_OPTION_FRONTEND_WIN_ID = 17 } EngineOption; diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h index 83f65486b..e6402cca7 100644 --- a/source/backend/CarlaHost.h +++ b/source/backend/CarlaHost.h @@ -899,6 +899,16 @@ CARLA_EXPORT const char* carla_get_library_filename(); */ CARLA_EXPORT const char* carla_get_library_folder(); +/*! + * Initialize NSM (announce). + */ +CARLA_EXPORT bool carla_nsm_init(int pid, const char* executableName); + +/*! + * Allow NSM callbacks. + */ +CARLA_EXPORT void carla_nsm_ready(); + /** @} */ #endif /* CARLA_HOST_H_INCLUDED */ diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index eb8b7abe3..2ac5b6822 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -89,257 +89,333 @@ static CarlaBackendStandalone gStandalone; #define NSM_API_VERSION_MAJOR 1 #define NSM_API_VERSION_MINOR 2 -//#define NSM_CLIENT_FEATURES ":switch:optional-gui:" #define NSM_CLIENT_FEATURES ":switch:" +//#define NSM_CLIENT_FEATURES ":switch:optional-gui:" class CarlaNSM { public: CarlaNSM() noexcept - : fOscServer(nullptr), - fClientId(), + : fOscAddress(nullptr), + fOscServer(nullptr), + fOscServerThread(nullptr), fProjectPath(), fHasBroadcast(false), - fHasShowHideUI(false) {} + fHasOptionalGui(false), + fHasServerControl(false), + fStarted() {} ~CarlaNSM() { - if (fOscServer != nullptr) + if (fOscServerThread != nullptr) { - lo_server_del_method(fOscServer, "/reply", "ssss"); - lo_server_del_method(fOscServer, "/nsm/client/open", "sss"); - lo_server_del_method(fOscServer, "/nsm/client/save", ""); - //lo_server_del_method(fOscServer, "/nsm/client/show_optional_gui", ""); - //lo_server_del_method(fOscServer, "/nsm/client/hide_optional_gui", ""); - lo_server_free(fOscServer); + lo_server_thread_stop(fOscServerThread); + lo_server_thread_free(fOscServerThread); + fOscServerThread = nullptr; fOscServer = nullptr; } + + if (fOscAddress != nullptr) + { + lo_address_free(fOscAddress); + fOscAddress = nullptr; + } } - void announce(const int pid, const char* const initName) + bool announce(const int pid, const char* const executableName) { + CARLA_SAFE_ASSERT_RETURN(pid != 0, false); + CARLA_SAFE_ASSERT_RETURN(executableName != nullptr && executableName[0] != '\0', false); + const char* const NSM_URL(std::getenv("NSM_URL")); if (NSM_URL == nullptr) - return; + return false; const lo_address addr = lo_address_new_from_url(NSM_URL); - CARLA_SAFE_ASSERT_RETURN(addr != nullptr,); + CARLA_SAFE_ASSERT_RETURN(addr != nullptr, false); const int proto = lo_address_get_protocol(addr); - if (fOscServer == nullptr) + if (fOscServerThread == nullptr) { // create new OSC server - fOscServer = lo_server_new_with_proto(nullptr, proto, _error_handler); - - // register message handlers and start OSC thread - lo_server_add_method(fOscServer, "/reply", "ssss", _reply_handler, this); - lo_server_add_method(fOscServer, "/nsm/client/open", "sss", _open_handler, this); - lo_server_add_method(fOscServer, "/nsm/client/save", "", _save_handler, this); - //lo_server_add_method(fOscServer, "/nsm/client/show_optional_gui", "", _show_gui_handler, this); - //lo_server_add_method(fOscServer, "/nsm/client/hide_optional_gui", "", _hide_gui_handler, this); - // /nsm/client/session_is_loaded + fOscServerThread = lo_server_thread_new_with_proto(nullptr, proto, _osc_error_handler); + CARLA_SAFE_ASSERT_RETURN(fOscServerThread != nullptr, false); + + // register message handlers + lo_server_thread_add_method(fOscServerThread, "/error", "sis", _error_handler, this); + lo_server_thread_add_method(fOscServerThread, "/reply", "ssss", _reply_handler, this); + lo_server_thread_add_method(fOscServerThread, "/nsm/client/open", "sss", _open_handler, this); + lo_server_thread_add_method(fOscServerThread, "/nsm/client/save", "", _save_handler, this); + lo_server_thread_add_method(fOscServerThread, "/nsm/client/session_is_loaded", "", _loaded_handler, this); + lo_server_thread_add_method(fOscServerThread, "/nsm/client/show_optional_gui", "", _show_gui_handler, this); + lo_server_thread_add_method(fOscServerThread, "/nsm/client/hide_optional_gui", "", _hide_gui_handler, this); + lo_server_thread_add_method(fOscServerThread, nullptr, nullptr, _broadcast_handler, this); + + fOscServer = lo_server_thread_get_server(fOscServerThread); } #ifndef BUILD_ANSI_TEST lo_send_from(addr, fOscServer, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii", - "Carla", NSM_CLIENT_FEATURES, initName, NSM_API_VERSION_MAJOR, NSM_API_VERSION_MINOR, pid); + "Carla", NSM_CLIENT_FEATURES, executableName, NSM_API_VERSION_MAJOR, NSM_API_VERSION_MINOR, pid); #endif lo_address_free(addr); + + return true; } - void idle() noexcept + void start() { - if (fOscServer == nullptr) - return; + CARLA_SAFE_ASSERT_RETURN(fOscServerThread != nullptr,); + CARLA_SAFE_ASSERT_RETURN(! fStarted,); - for (;;) - { - try { - if (lo_server_recv_noblock(fOscServer, 0) == 0) - break; - } CARLA_SAFE_EXCEPTION_CONTINUE("NSM OSC idle") - } + fStarted = true; + lo_server_thread_start(fOscServerThread); + } + + static CarlaNSM& getInstance() + { + static CarlaNSM sInstance; + return sInstance; } protected: - int handleReply(const char* const path, const char* const types, lo_arg** const argv, const int argc, const lo_message msg) + int handleError(const char* const method, const int code, const char* const message) { - carla_debug("CarlaNSM::handleReply(%s, %i, %p, %s, %p)", path, argc, argv, types, msg); + carla_stdout("CarlaNSM::handleError(\"%s\", %i, \"%s\")", method, code, message); - const char* const method = &argv[0]->s; - const char* const message = &argv[1]->s; - const char* const smName = &argv[2]->s; - const char* const features = &argv[3]->s; + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 0, code, 0.0f, message); - CARLA_SAFE_ASSERT_RETURN(std::strcmp(method, "/nsm/server/announce") == 0, 0); + return 1; - fHasBroadcast = std::strstr(features, ":broadcast:") != nullptr; - fHasShowHideUI = std::strstr(features, ":optional-gui:") != nullptr; + // may be unused + (void)method; + } - carla_stdout("'%s' started: %s", smName, message); + int handleReply(const char* const method, const char* const message, const char* const smName, const char* const features, + const lo_message msg) + { + CARLA_SAFE_ASSERT_RETURN(fOscServerThread != nullptr, 1); + carla_stdout("CarlaNSM::handleReply(\"%s\", \"%s\", \"%s\", \"%s\")", method, message, smName, features); - // TODO: send callback, disable open+save etc + if (std::strcmp(method, "/nsm/server/announce") == 0) + { + char* const addressURL(lo_address_get_url(lo_message_get_source(msg))); + CARLA_SAFE_ASSERT_RETURN(addressURL != nullptr, 0); - return 0; + if (fOscAddress != nullptr) + lo_address_free(fOscAddress); -#ifndef DEBUG - // unused - (void)path; (void)types; (void)argc; (void)msg; -#endif - } + fOscAddress = lo_address_new_from_url(addressURL); + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 0); - // FIXME - int handleOpen(const char* const path, const char* const types, lo_arg** const argv, const int argc, const lo_message msg) - { - CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 0); - carla_debug("CarlaNSM::handleOpen(\"%s\", \"%s\", %p, %i, %p)", path, types, argv, argc, msg); + fHasBroadcast = std::strstr(features, ":broadcast:") != nullptr; + fHasOptionalGui = std::strstr(features, ":optional-gui:") != nullptr; + fHasServerControl = std::strstr(features, ":server_control:") != nullptr; - const char* const projectPath = &argv[0]->s; - //const char* const displayName = &argv[1]->s; - const char* const clientId = &argv[2]->s; + carla_stdout("Carla started via '%s', message: %s", smName, message); - if (! carla_is_engine_running()) - { -#ifndef BUILD_BRIDGE - gStandalone.engineOptions.processMode = CB::ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS; - gStandalone.engineOptions.transportMode = CB::ENGINE_TRANSPORT_MODE_JACK; -#endif + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 1, 0, 0.0f, smName); - carla_engine_init("JACK", clientId); + // UI starts visible + lo_send_from(fOscAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", ""); } else { - CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr, 0); - - for (uint i=0, count=gStandalone.engine->getCurrentPluginCount(); i < count; ++i) - gStandalone.engine->removePlugin(i); + carla_stdout("Got unknown NSM reply method '%s'", method); } - fClientId = clientId; + return 0; + } + + int handleOpen(const char* const projectPath, const char* const displayName, const char* const clientNameId) + { + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + carla_stdout("CarlaNSM::handleOpen(\"%s\", \"%s\", \"%s\")", projectPath, displayName, clientNameId); + + if (carla_is_engine_running()) + carla_engine_close(); + + carla_engine_init("JACK", clientNameId); fProjectPath = projectPath; fProjectPath += ".carxp"; - CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr, 0); + using namespace juce; - if (juce::File(fProjectPath.buffer()).existsAsFile()) - gStandalone.engine->loadProject(fProjectPath); + const String jfilename = String(CharPointer_UTF8(fProjectPath)); -#ifndef BUILD_ANSI_TEST - lo_send_from(lo_message_get_source(msg), fOscServer, LO_TT_IMMEDIATE, "/reply", "ss", "/nsm/client/open", "OK"); -#endif + if (File(jfilename).existsAsFile()) + carla_load_project(fProjectPath); -#if 0 - if (fHasBroadcast) - { - char* const url = lo_server_get_url(fOscServer); + lo_send_from(fOscAddress, fOscServer, LO_TT_IMMEDIATE, "/reply", "ss", "/nsm/client/open", "OK"); - lo_send(lo_message_get_source(msg), "/nsm/server/broadcast", "sssss", - "/non/hello", url, "Carla", CARLA_VERSION_STRING, clientId); + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 2, 0, 0.0f, projectPath); - //lo_send_from(lo_message_get_source(msg), fOscServer, LO_TT_IMMEDIATE, "/nsm/server/broadcast", "sssss" - // "/non/hello", url, "Carla", CARLA_VERSION_STRING, clientId); + return 0; + } - lo_send(lo_message_get_source(msg), "/signal/created", "ssfff", "/path/somewhere", true ? "in" : "out", 0.0f, 1.0f, 0.5f); + int handleSave() + { + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fProjectPath.isNotEmpty(), 0); + carla_stdout("CarlaNSM::handleSave()"); - std::free(url); + carla_save_project(fProjectPath); - carla_stdout("Broadcast sent!"); - } - else - carla_stdout("Broadcast NOT NOT NOT sent!"); -#endif + lo_send_from(fOscAddress, fOscServer, LO_TT_IMMEDIATE, "/reply", "ss", "/nsm/client/save", "OK"); + + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 3, 0, 0.0f, nullptr); return 0; + } -#ifndef DEBUG - // unused - (void)path; (void)types; (void)argc; (void)msg; -#endif + int handleSessionIsLoaded() + { + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + carla_stdout("CarlaNSM::handleSessionIsLoaded()"); + + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 4, 0, 0.0f, nullptr); + + return 0; } - int handleSave(const char* const path, const char* const types, lo_arg** const argv, const int argc, const lo_message msg) + int handleShowOptionalGui() { - CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 0); - CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr, 0); - CARLA_SAFE_ASSERT_RETURN(fProjectPath.isNotEmpty(), 0); - carla_debug("CarlaNSM::handleSave(\"%s\", \"%s\", %p, %i, %p)", path, types, argv, argc, msg); + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + carla_stdout("CarlaNSM::handleShowOptionalGui()"); - gStandalone.engine->saveProject(fProjectPath); + lo_send_from(fOscAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", ""); -#ifndef BUILD_ANSI_TEST - lo_send_from(lo_message_get_source(msg), fOscServer, LO_TT_IMMEDIATE, "/reply", "ss", "/nsm/client/save", "OK"); -#endif + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 5, 0, 0.0f, nullptr); return 0; + } -#ifndef DEBUG - // unused - (void)path; (void)types; (void)argv; (void)argc; (void)msg; -#endif + int handleHideOptionalGui() + { + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + carla_stdout("CarlaNSM::handleHideOptionalGui()"); + + lo_send_from(fOscAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", ""); + + if (gStandalone.engineCallback != nullptr) + gStandalone.engineCallback(gStandalone.engineCallbackPtr, CB::ENGINE_CALLBACK_NSM, 0, 6, 0, 0.0f, nullptr); + + return 0; } -#if 0 - int handleShowHideGui(const lo_message msg, const bool show) + int handleBroadcast(const char* const types, lo_arg** const argv, const int argc) { - CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 0); - CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr, 0); - //CARLA_SAFE_ASSERT_RETURN(gStandalone.frontendWinId != 0, 0); - carla_debug("CarlaNSM::handleShowHideGui(%s)", bool2str(show)); + CARLA_SAFE_ASSERT_RETURN(fOscAddress != nullptr, 1); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr, 1); + carla_stdout("CarlaNSM::handleBroadcast(%s, %p, %i)", types, argv, argc); -#ifndef BUILD_ANSI_TEST - lo_send_from(lo_message_get_source(msg), fOscServer, LO_TT_IMMEDIATE, show ? "/nsm/client/gui_is_shown" : "/nsm/client/gui_is_hidden", ""); -#endif + // TODO return 0; } -#endif private: - lo_server fOscServer; - - CarlaString fClientId; - CarlaString fProjectPath; + lo_address fOscAddress; + lo_server fOscServer; + lo_server_thread fOscServerThread; + CarlaString fProjectPath; bool fHasBroadcast; - bool fHasShowHideUI; + bool fHasOptionalGui; + bool fHasServerControl; + bool fStarted; #define handlePtr ((CarlaNSM*)data) - static void _error_handler(int num, const char* msg, const char* path) + static void _osc_error_handler(int num, const char* msg, const char* path) { - carla_stderr2("CarlaNSM::_error_handler(%i, \"%s\", \"%s\")", num, msg, path); + carla_stderr2("CarlaNSM::_osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path); + } + + static int _error_handler(const char*, const char* types, lo_arg** argv, int argc, lo_message, void* data) + { + CARLA_SAFE_ASSERT_RETURN(argc == 3, 1); + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "sis") == 0, 1); + + const char* const method = &argv[0]->s; + const int code = argv[1]->i; + const char* const message = &argv[2]->s; + + return handlePtr->handleError(method, code, message); } - static int _reply_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) + static int _reply_handler(const char*, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) { - return handlePtr->handleReply(path, types, argv, argc, msg); + CARLA_SAFE_ASSERT_RETURN(argc == 4, 1); + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "ssss") == 0, 1); + + const char* const method = &argv[0]->s; + const char* const message = &argv[1]->s; + const char* const smName = &argv[2]->s; + const char* const features = &argv[3]->s; + + return handlePtr->handleReply(method, message, smName, features, msg); } - static int _open_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) + static int _open_handler(const char*, const char* types, lo_arg** argv, int argc, lo_message, void* data) { - return handlePtr->handleOpen(path, types, argv, argc, msg); + CARLA_SAFE_ASSERT_RETURN(argc == 3, 1); + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "sss") == 0, 1); + + const char* const projectPath = &argv[0]->s; + const char* const displayName = &argv[1]->s; + const char* const clientNameId = &argv[2]->s; + + return handlePtr->handleOpen(projectPath, displayName, clientNameId); } - static int _save_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) + static int _save_handler(const char*, const char*, lo_arg**, int argc, lo_message, void* data) { - return handlePtr->handleSave(path, types, argv, argc, msg); + CARLA_SAFE_ASSERT_RETURN(argc == 0, 1); + + return handlePtr->handleSave(); } -#if 0 - static int _show_gui_handler(const char*, const char*, lo_arg**, int, lo_message, void* data) + static int _loaded_handler(const char*, const char*, lo_arg**, int argc, lo_message, void* data) { - return handlePtr->handleShowHideGui(true); + CARLA_SAFE_ASSERT_RETURN(argc == 0, 1); + + return handlePtr->handleSessionIsLoaded(); } - static int _hide_gui_handler(const char*, const char*, lo_arg**, int, lo_message, void* data) + static int _show_gui_handler(const char*, const char*, lo_arg**, int argc, lo_message, void* data) { - return handlePtr->handleShowHideGui(false); + CARLA_SAFE_ASSERT_RETURN(argc == 0, 1); + + return handlePtr->handleShowOptionalGui(); + } + + static int _hide_gui_handler(const char*, const char*, lo_arg**, int argc, lo_message, void* data) + { + CARLA_SAFE_ASSERT_RETURN(argc == 0, 1); + + return handlePtr->handleHideOptionalGui(); + } + + static int _broadcast_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message, void* data) + { + return handlePtr->handleBroadcast(types, argv, argc); } -#endif #undef handlePtr @@ -347,8 +423,6 @@ private: CARLA_DECLARE_NON_COPY_CLASS(CarlaNSM) }; -static CarlaNSM gNSM; - #endif // HAVE_LIBLO // ------------------------------------------------------------------------------------------------------------------- @@ -685,9 +759,6 @@ void carla_engine_idle() { CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr,); -#ifdef HAVE_LIBLO - gNSM.idle(); -#endif gStandalone.engine->idle(); } @@ -793,14 +864,6 @@ void carla_set_engine_option(EngineOption option, int value, const char* valueSt gStandalone.engineOptions.audioDevice = carla_strdup_safe(valueStr); break; - case CB:: ENGINE_OPTION_NSM_INIT: - CARLA_SAFE_ASSERT_RETURN(value != 0,); - CARLA_SAFE_ASSERT_RETURN(valueStr != nullptr && valueStr[0] != '\0',); -#ifdef HAVE_LIBLO - gNSM.announce(value, valueStr); -#endif - break; - case CB::ENGINE_OPTION_PLUGIN_PATH: CARLA_SAFE_ASSERT_RETURN(value > CB::PLUGIN_NONE,); CARLA_SAFE_ASSERT_RETURN(value <= CB::PLUGIN_SFZ,); @@ -2195,6 +2258,27 @@ const char* carla_get_host_osc_url_udp() // ------------------------------------------------------------------------------------------------------------------- +bool carla_nsm_init(int pid, const char* executableName) +{ +#ifdef HAVE_LIBLO + return CarlaNSM::getInstance().announce(pid, executableName); +#else + return false; + + // unused + (void)pid; (void)executableName; +#endif +} + +void carla_nsm_ready() +{ +#ifdef HAVE_LIBLO + CarlaNSM::getInstance().start(); +#endif +} + +// ------------------------------------------------------------------------------------------------------------------- + #include "CarlaPluginUI.cpp" #include "CarlaDssiUtils.cpp" #include "CarlaPatchbayUtils.cpp" diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 552e439fd..cbb933b7a 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -1214,7 +1214,6 @@ void CarlaEngine::setOption(const EngineOption option, const int value, const ch switch (option) { case ENGINE_OPTION_DEBUG: - case ENGINE_OPTION_NSM_INIT: break; case ENGINE_OPTION_PROCESS_MODE: diff --git a/source/carla_backend.py b/source/carla_backend.py index 61ba657c3..3d0b33ec0 100644 --- a/source/carla_backend.py +++ b/source/carla_backend.py @@ -671,21 +671,25 @@ ENGINE_CALLBACK_BUFFER_SIZE_CHANGED = 33 # @a value3 New sample rate ENGINE_CALLBACK_SAMPLE_RATE_CHANGED = 34 +# NSM callback. +# (Work in progress, values are not defined yet) +ENGINE_CALLBACK_NSM = 35 + # Idle frontend. # This is used by the engine during long operations that might block the frontend, # giving it the possibility to idle while the operation is still in place. -ENGINE_CALLBACK_IDLE = 35 +ENGINE_CALLBACK_IDLE = 36 # Show a message as information. # @a valueStr The message -ENGINE_CALLBACK_INFO = 36 +ENGINE_CALLBACK_INFO = 37 # Show a message as an error. # @a valueStr The message -ENGINE_CALLBACK_ERROR = 37 +ENGINE_CALLBACK_ERROR = 38 # The engine has crashed or malfunctioned and will no longer work. -ENGINE_CALLBACK_QUIT = 38 +ENGINE_CALLBACK_QUIT = 39 # ------------------------------------------------------------------------------------------------------------ # Engine Option @@ -748,30 +752,27 @@ ENGINE_OPTION_AUDIO_SAMPLE_RATE = 11 # Default unset. ENGINE_OPTION_AUDIO_DEVICE = 12 -# Set data needed for NSM support. -ENGINE_OPTION_NSM_INIT = 13 - # Set path used for a specific plugin type. # Uses value as the plugin format, valueStr as actual path. # @see PluginType -ENGINE_OPTION_PLUGIN_PATH = 14 +ENGINE_OPTION_PLUGIN_PATH = 13 # Set path to the binary files. # Default unset. # @note Must be set for plugin and UI bridges to work -ENGINE_OPTION_PATH_BINARIES = 15 +ENGINE_OPTION_PATH_BINARIES = 14 # Set path to the resource files. # Default unset. # @note Must be set for some internal plugins to work -ENGINE_OPTION_PATH_RESOURCES = 16 +ENGINE_OPTION_PATH_RESOURCES = 15 # Prevent bad plugin and UI behaviour. # @note: Linux only -ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 17 +ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 16 # Set frontend winId, used to define as parent window for plugin UIs. -ENGINE_OPTION_FRONTEND_WIN_ID = 18 +ENGINE_OPTION_FRONTEND_WIN_ID = 17 # ------------------------------------------------------------------------------------------------------------ # Engine Process Mode @@ -1194,6 +1195,7 @@ class CarlaHostMeta(object): # info about this host object self.isControl = False self.isPlugin = False + self.nsmOK = False # settings self.processMode = ENGINE_PROCESS_MODE_CONTINUOUS_RACK @@ -2343,6 +2345,12 @@ class CarlaHostDLL(CarlaHostMeta): self.lib.carla_get_host_osc_url_udp.argtypes = None self.lib.carla_get_host_osc_url_udp.restype = c_char_p + self.lib.carla_nsm_init.argtypes = [c_int, c_char_p] + self.lib.carla_nsm_init.restype = c_bool + + self.lib.carla_nsm_ready.argtypes = None + self.lib.carla_nsm_ready.restype = None + # -------------------------------------------------------------------------------------------------------- def get_engine_driver_count(self): @@ -2605,6 +2613,12 @@ class CarlaHostDLL(CarlaHostMeta): def get_host_osc_url_udp(self): return charPtrToString(self.lib.carla_get_host_osc_url_udp()) + def nsm_init(self, pid, executableName): + return bool(self.lib.carla_nsm_init(pid, executableName.encode("utf-8"))) + + def nsm_ready(self): + self.lib.carla_nsm_ready() + # ------------------------------------------------------------------------------------------------------------ # Helper object for CarlaHostPlugin diff --git a/source/carla_backend_qt.py b/source/carla_backend_qt.py index a9a58b893..af0506a13 100644 --- a/source/carla_backend_qt.py +++ b/source/carla_backend_qt.py @@ -80,6 +80,7 @@ class CarlaHostSignals(QObject): TransportModeChangedCallback = pyqtSignal(int) BufferSizeChangedCallback = pyqtSignal(int) SampleRateChangedCallback = pyqtSignal(float) + NSMCallback = pyqtSignal(int, int, str) InfoCallback = pyqtSignal(str) ErrorCallback = pyqtSignal(str) QuitCallback = pyqtSignal() diff --git a/source/carla_host.py b/source/carla_host.py index b41167419..907f43015 100644 --- a/source/carla_host.py +++ b/source/carla_host.py @@ -121,9 +121,9 @@ class HostWindow(QMainWindow): elif LADISH_APP_NAME: self.fClientName = LADISH_APP_NAME self.fSessionManagerName = "LADISH" - elif NSM_URL: - self.fClientName = "Carla" # "Carla.tmp" - self.fSessionManagerName = "Non Session Manager" + elif NSM_URL and host.nsmOK: + self.fClientName = "Carla.tmp" + self.fSessionManagerName = "Non Session Manager TMP" else: self.fClientName = CARLA_CLIENT_NAME or "Carla" self.fSessionManagerName = "" @@ -387,6 +387,8 @@ class HostWindow(QMainWindow): host.PatchbayConnectionAddedCallback.connect(self.slot_handlePatchbayConnectionAddedCallback) host.PatchbayConnectionRemovedCallback.connect(self.slot_handlePatchbayConnectionRemovedCallback) + host.NSMCallback.connect(self.slot_handleNSMCallback) + host.DebugCallback.connect(self.slot_handleDebugCallback) host.InfoCallback.connect(self.slot_handleInfoCallback) host.ErrorCallback.connect(self.slot_handleErrorCallback) @@ -407,6 +409,13 @@ class HostWindow(QMainWindow): if self.host.isPlugin: self.startTimers() + # For NSM we wait for the open message + if NSM_URL and host.nsmOK: + self.fFirstEngineInit = False + self.setEngineSettings() + host.nsm_ready() + return + QTimer.singleShot(0, self.slot_engineStart) # -------------------------------------------------------------------------------------------------------- @@ -429,7 +438,7 @@ class HostWindow(QMainWindow): def setProperWindowTitle(self): title = self.fClientName - if self.fProjectFilename: + if self.fProjectFilename and not self.host.nsmOK: title += " - %s" % os.path.basename(self.fProjectFilename) if self.fSessionManagerName: title += " (%s)" % self.fSessionManagerName @@ -605,9 +614,11 @@ class HostWindow(QMainWindow): self.ui.act_engine_start.setEnabled(False) self.ui.act_engine_stop.setEnabled(True) + canSave = (self.fProjectFilename and os.path.exists(self.fProjectFilename)) or not self.fSessionManagerName + self.ui.act_file_save.setEnabled(canSave) + if not self.fSessionManagerName: self.ui.act_file_open.setEnabled(True) - self.ui.act_file_save.setEnabled(True) self.ui.act_file_save_as.setEnabled(True) self.ui.panelTime.setEnabled(True) @@ -1564,6 +1575,45 @@ class HostWindow(QMainWindow): # -------------------------------------------------------------------------------------------------------- + @pyqtSlot(int, int, str) + def slot_handleNSMCallback(self, value1, value2, valueStr): + print("--------------- NSM:", value1, value2, valueStr) + + # Error + if value1 == 0: + pass + + # Reply + elif value1 == 1: + self.fFirstEngineInit = False + self.fSessionManagerName = valueStr + self.setProperWindowTitle() + + # Open + elif value1 == 2: + self.fClientName = os.path.basename(valueStr) + self.fProjectFilename = QFileInfo(valueStr+".carxp").absoluteFilePath() + self.setProperWindowTitle() + self.ui.act_file_save.setEnabled(True) + + # Save + elif value1 == 3: + pass + + # Session is Loaded + elif value1 == 4: + pass + + # Show Optional Gui + elif value1 == 5: + self.show() + + # Hide Optional Gui + elif value1 == 6: + self.hide() + + # -------------------------------------------------------------------------------------------------------- + @pyqtSlot(int, int, int, float, str) def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valueStr): print("DEBUG:", pluginId, value1, value2, value3, valueStr) @@ -1951,6 +2001,8 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valueStr): host.BufferSizeChangedCallback.emit(value1) elif action == ENGINE_CALLBACK_SAMPLE_RATE_CHANGED: host.SampleRateChangedCallback.emit(value3) + elif action == ENGINE_CALLBACK_NSM: + host.NSMCallback.emit(value1, value2, valueStr) elif action == ENGINE_CALLBACK_IDLE: QApplication.instance().processEvents() elif action == ENGINE_CALLBACK_INFO: @@ -2048,7 +2100,7 @@ def initHost(initName, libPrefixOrPluginClass, isControl, isPlugin, failError): host.set_engine_option(ENGINE_OPTION_PATH_RESOURCES, 0, pathResources) if not isControl: - host.set_engine_option(ENGINE_OPTION_NSM_INIT, os.getpid(), initName) + host.nsmOK = host.nsm_init(os.getpid(), initName) # -------------------------------------------------------------------------------------------------------- # Init utils diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index 651539c92..b1927ccdc 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -270,6 +270,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept return "ENGINE_CALLBACK_BUFFER_SIZE_CHANGED"; case ENGINE_CALLBACK_SAMPLE_RATE_CHANGED: return "ENGINE_CALLBACK_SAMPLE_RATE_CHANGED"; + case ENGINE_CALLBACK_NSM: + return "ENGINE_CALLBACK_NSM"; case ENGINE_CALLBACK_IDLE: return "ENGINE_CALLBACK_IDLE"; case ENGINE_CALLBACK_INFO: @@ -315,8 +317,6 @@ const char* EngineOption2Str(const EngineOption option) noexcept return "ENGINE_OPTION_AUDIO_SAMPLE_RATE"; case ENGINE_OPTION_AUDIO_DEVICE: return "ENGINE_OPTION_AUDIO_DEVICE"; - case ENGINE_OPTION_NSM_INIT: - return "ENGINE_OPTION_NSM_INIT"; case ENGINE_OPTION_PLUGIN_PATH: return "ENGINE_OPTION_PLUGIN_PATH"; case ENGINE_OPTION_PATH_BINARIES: