From 3d44fb9d798523ef7879766aa07a07388f758a67 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 27 Jul 2023 14:01:02 +0200 Subject: [PATCH] Proper OSC remote control implementation, allowed on standalones Signed-off-by: falkTX --- src/CardinalCommon.cpp | 124 ++++++++++++++++++++++++++++++--------- src/CardinalCommon.hpp | 17 ++++-- src/CardinalPlugin.cpp | 34 ++++++++++- src/PluginContext.hpp | 6 ++ src/override/MenuBar.cpp | 45 ++++++++++++++ 5 files changed, 192 insertions(+), 34 deletions(-) diff --git a/src/CardinalCommon.cpp b/src/CardinalCommon.cpp index 4546779..22e7b2b 100644 --- a/src/CardinalCommon.cpp +++ b/src/CardinalCommon.cpp @@ -63,7 +63,7 @@ # include #endif -#ifdef CARDINAL_INIT_OSC_THREAD +#ifdef HAVE_LIBLO # include #endif @@ -231,7 +231,7 @@ void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, // ----------------------------------------------------------------------------------------------------------- -#ifdef CARDINAL_INIT_OSC_THREAD +#ifdef HAVE_LIBLO static void osc_error_handler(int num, const char* msg, const char* path) { d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path); @@ -245,9 +245,9 @@ static int osc_fallback_handler(const char* const path, const char* const types, static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self) { - d_debug("osc_hello_handler()"); + d_stdout("Hello received from OSC, saying hello back to them o/"); const lo_address source = lo_message_get_source(m); - const lo_server server = lo_server_thread_get_server(static_cast(self)->oscServerThread); + const lo_server server = static_cast(self)->oscServer; lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok"); return 0; } @@ -287,7 +287,7 @@ static int osc_load_handler(const char*, const char* types, lo_arg** argv, int a } const lo_address source = lo_message_get_source(m); - const lo_server server = lo_server_thread_get_server(static_cast(self)->oscServerThread); + const lo_server server = static_cast(self)->oscServer; lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail"); return 0; } @@ -344,6 +344,7 @@ static int osc_host_param_handler(const char*, const char* types, lo_arg** argv, return 0; } +# ifdef CARDINAL_INIT_OSC_THREAD static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self) { d_debug("osc_screenshot_handler()"); @@ -368,10 +369,11 @@ static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, } const lo_address source = lo_message_get_source(m); - const lo_server server = lo_server_thread_get_server(static_cast(self)->oscServerThread); + const lo_server server = static_cast(self)->oscServer; lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "screenshot", ok ? "ok" : "fail"); return 0; } +# endif #endif // ----------------------------------------------------------------------------------------------------------- @@ -575,41 +577,32 @@ Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalB loadSettings(isRealInstance); -#ifdef CARDINAL_INIT_OSC_THREAD + #if defined(CARDINAL_INIT_OSC_THREAD) INFO("Initializing OSC Remote control"); const char* port; if (const char* const portEnv = std::getenv("CARDINAL_REMOTE_HOST_PORT")) port = portEnv; else port = CARDINAL_DEFAULT_REMOTE_PORT; - oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler); - DISTRHO_SAFE_ASSERT_RETURN(oscServerThread != nullptr,); - - lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this); - lo_server_thread_add_method(oscServerThread, "/host-param", "if", osc_host_param_handler, this); - lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this); - lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this); - lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this); - lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr); - lo_server_thread_start(oscServerThread); -#else + startRemoteServer(port); + #elif defined(HAVE_LIBLO) + if (isStandalone()) { + INFO("OSC Remote control is available on request"); + } else { + INFO("OSC Remote control is not available on plugin variants"); + } + #else INFO("OSC Remote control is not enabled in this build"); -#endif + #endif } Initializer::~Initializer() { using namespace rack; -#ifdef CARDINAL_INIT_OSC_THREAD - if (oscServerThread != nullptr) - { - lo_server_thread_stop(oscServerThread); - lo_server_thread_del_method(oscServerThread, nullptr, nullptr); - lo_server_thread_free(oscServerThread); - oscServerThread = nullptr; - } -#endif + #ifdef HAVE_LIBLO + stopRemoteServer(); + #endif if (shouldSaveSettings) { @@ -670,6 +663,81 @@ void Initializer::loadSettings(const bool isRealInstance) switchDarkMode(settings::uiTheme == "dark"); } +#ifdef HAVE_LIBLO +bool Initializer::startRemoteServer(const char* const port) +{ + #ifdef CARDINAL_INIT_OSC_THREAD + if (oscServerThread != nullptr) + return true; + + if ((oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr) + return false; + + oscServer = lo_server_thread_get_server(oscServerThread); + + lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this); + lo_server_thread_add_method(oscServerThread, "/host-param", "if", osc_host_param_handler, this); + lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this); + lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this); + lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this); + lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr); + lo_server_thread_start(oscServerThread); + #else + if (oscServer != nullptr) + return true; + + if ((oscServer = lo_server_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr) + return false; + + lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this); + lo_server_add_method(oscServer, "/host-param", "if", osc_host_param_handler, this); + lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this); + lo_server_add_method(oscServer, "/param", "hif", osc_param_handler, this); + lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr); + #endif + + return true; +} + +void Initializer::stopRemoteServer() +{ + DISTRHO_SAFE_ASSERT(remotePluginInstance == nullptr); + + #ifdef CARDINAL_INIT_OSC_THREAD + if (oscServerThread != nullptr) + { + lo_server_thread_stop(oscServerThread); + lo_server_thread_del_method(oscServerThread, nullptr, nullptr); + lo_server_thread_free(oscServerThread); + oscServerThread = oscServer = nullptr; + } + #else + if (oscServer != nullptr) + { + lo_server_del_method(oscServer, nullptr, nullptr); + lo_server_free(oscServer); + oscServer = nullptr; + } + #endif +} + +void Initializer::stepRemoteServer() +{ + DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(remotePluginInstance != nullptr,); + + #ifndef CARDINAL_INIT_OSC_THREAD + for (;;) + { + try { + if (lo_server_recv_noblock(oscServer, 0) == 0) + break; + } DISTRHO_SAFE_EXCEPTION_CONTINUE("stepRemoteServer") + } + #endif +} +#endif // HAVE_LIBLO + // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO diff --git a/src/CardinalCommon.hpp b/src/CardinalCommon.hpp index a1693a4..756f02d 100644 --- a/src/CardinalCommon.hpp +++ b/src/CardinalCommon.hpp @@ -87,6 +87,7 @@ void openBrowser(const std::string& url); # define CARDINAL_INIT_OSC_THREAD #endif +typedef void* lo_server; typedef void* lo_server_thread; START_NAMESPACE_DISTRHO @@ -97,10 +98,6 @@ struct CardinalPluginContext; struct Initializer { -#ifdef CARDINAL_INIT_OSC_THREAD - lo_server_thread oscServerThread = nullptr; - CardinalBasePlugin* remotePluginInstance = nullptr; -#endif std::string templatePath; std::string factoryTemplatePath; bool shouldSaveSettings = false; @@ -108,6 +105,18 @@ struct Initializer Initializer(const CardinalBasePlugin* plugin, const CardinalBaseUI* ui); ~Initializer(); void loadSettings(bool isRealInstance); + + #ifdef HAVE_LIBLO + lo_server oscServer = nullptr; + #ifdef CARDINAL_INIT_OSC_THREAD + lo_server_thread oscServerThread = nullptr; + #endif + CardinalBasePlugin* remotePluginInstance = nullptr; + + bool startRemoteServer(const char* port); + void stopRemoteServer(); + void stepRemoteServer(); + #endif }; #ifndef HEADLESS diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index c79fd29..9da494a 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -167,7 +167,6 @@ struct ScopedContext { } }; - // ----------------------------------------------------------------------------------------------------------- class CardinalPlugin : public CardinalBasePlugin @@ -333,7 +332,7 @@ public: ~CardinalPlugin() override { - #ifdef CARDINAL_INIT_OSC_THREAD + #ifdef HAVE_LIBLO if (fInitializer->remotePluginInstance == this) fInitializer->remotePluginInstance = nullptr; #endif @@ -359,6 +358,37 @@ public: return context; } + #ifdef HAVE_LIBLO + bool startRemoteServer(const char* const port) override + { + if (fInitializer->remotePluginInstance != nullptr) + return false; + + if (fInitializer->startRemoteServer(port)) + { + fInitializer->remotePluginInstance = this; + return true; + } + + return false; + } + + void stopRemoteServer() override + { + DISTRHO_SAFE_ASSERT_RETURN(fInitializer->remotePluginInstance == this,); + + fInitializer->remotePluginInstance = nullptr; + fInitializer->stopRemoteServer(); + } + + void stepRemoteServer() override + { + DISTRHO_SAFE_ASSERT_RETURN(fInitializer->remotePluginInstance == this,); + + fInitializer->stepRemoteServer(); + } + #endif + protected: /* -------------------------------------------------------------------------------------------------------- * Information */ diff --git a/src/PluginContext.hpp b/src/PluginContext.hpp index 86d3f53..e80670a 100644 --- a/src/PluginContext.hpp +++ b/src/PluginContext.hpp @@ -218,6 +218,12 @@ public: context(new CardinalPluginContext(this)) {} ~CardinalBasePlugin() override {} + #ifdef HAVE_LIBLO + virtual bool startRemoteServer(const char* port) = 0; + virtual void stopRemoteServer() = 0; + virtual void stepRemoteServer() = 0; + #endif + #ifndef HEADLESS friend class CardinalUI; #endif diff --git a/src/override/MenuBar.cpp b/src/override/MenuBar.cpp index 9928ac0..c4464be 100644 --- a/src/override/MenuBar.cpp +++ b/src/override/MenuBar.cpp @@ -53,6 +53,7 @@ #include "../CardinalCommon.hpp" #include "../CardinalRemote.hpp" +#include "../PluginContext.hpp" #include "DistrhoPlugin.hpp" #include "DistrhoStandaloneUtils.hpp" @@ -729,6 +730,10 @@ struct ViewButton : MenuButton { struct EngineButton : MenuButton { +#ifdef HAVE_LIBLO + bool remoteServerStarted = false; +#endif + void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); menu->cornerFlags = BND_CORNER_TOP; @@ -741,6 +746,34 @@ struct EngineButton : MenuButton { settings::cpuMeter ^= true; })); +#ifdef HAVE_LIBLO + if (isStandalone()) { + CardinalPluginContext* const context = static_cast(APP); + CardinalBasePlugin* const plugin = static_cast(context->plugin); + + // const bool remoteServerStarted = this->remoteServerStarted; + const std::string remoteControlText = remoteServerStarted ? " " CHECKMARK_STRING : ""; + + menu->addChild(createMenuItem("Enable OSC remote control", remoteControlText, [=]() { + if (remoteServerStarted) { + remoteServerStarted = false; + plugin->stopRemoteServer(); + return; + } + + async_dialog_text_input("OSC network port", CARDINAL_DEFAULT_REMOTE_PORT, [=](char* const port) { + if (port == nullptr) + return; + + if (plugin->startRemoteServer(port)) + remoteServerStarted = true; + + std::free(port); + }); + })); + } +#endif + if (isUsingNativeAudio()) { if (supportsAudioInput()) { const bool enabled = isAudioInputEnabled(); @@ -782,6 +815,18 @@ struct EngineButton : MenuButton { } } } + +#ifdef HAVE_LIBLO + void step() override { + MenuButton::step(); + + if (remoteServerStarted) { + CardinalPluginContext* const context = static_cast(APP); + CardinalBasePlugin* const plugin = static_cast(context->plugin); + plugin->stepRemoteServer(); + } + } +#endif };