diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index 0ae3550..3579a29 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -33,6 +33,11 @@ # undef DEBUG #endif +#ifdef HAVE_LIBLO +# include +# include "extra/Thread.hpp" +#endif + #include #include "DistrhoPluginUtils.hpp" @@ -41,6 +46,8 @@ #include "extra/Base64.hpp" #include "extra/SharedResourcePointer.hpp" +#define REMOTE_HOST_PORT "2228" + namespace rack { namespace plugin { void initStaticPlugins(); @@ -57,7 +64,16 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- -struct Initializer { +struct Initializer +#ifdef HAVE_LIBLO +: public Thread +#endif +{ +#ifdef HAVE_LIBLO + lo_server oscServer = nullptr; + CardinalBasePlugin* oscPlugin = nullptr; +#endif + Initializer(const CardinalBasePlugin* const plugin) { using namespace rack; @@ -136,12 +152,33 @@ struct Initializer { INFO("Initializing plugins"); plugin::initStaticPlugins(); + +#ifdef HAVE_LIBLO + oscServer = lo_server_new_with_proto(REMOTE_HOST_PORT, LO_UDP, osc_error_handler); + DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,); + + lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this); + lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this); + lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr); + + startThread(); +#endif } ~Initializer() { using namespace rack; +#ifdef HAVE_LIBLO + if (oscServer != nullptr) + { + stopThread(5000); + lo_server_del_method(oscServer, nullptr, nullptr); + lo_server_free(oscServer); + oscServer = nullptr; + } +#endif + INFO("Destroying plugins"); plugin::destroyStaticPlugins(); @@ -154,6 +191,77 @@ struct Initializer { INFO("Destroying logger"); logger::destroy(); } + +#ifdef HAVE_LIBLO + void run() override + { + while (! shouldThreadExit()) + { + d_msleep(200); + while (lo_server_recv_noblock(oscServer, 0) != 0) {} + } + } + + 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); + } + + static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*) + { + d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types); + return 0; + } + + static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self) + { + d_stdout("osc_hello_handler()"); + const lo_address source = lo_message_get_source(m); + lo_send_from(source, static_cast(self)->oscServer, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok"); + return 0; + } + + static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self) + { + d_stdout("osc_load_handler()"); + DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0); + DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0); + + const int32_t size = argv[0]->blob.size; + DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0); + + const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data); + DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0); + + bool ok = false; + + if (CardinalBasePlugin* const plugin = static_cast(self)->oscPlugin) + { + CardinalPluginContext* const context = plugin->context; + std::vector data(size); + std::memcpy(data.data(), blob, size); + + const MutexLocker cml(context->mutex); + rack::contextSet(context); + rack::system::removeRecursively(context->patch->autosavePath); + rack::system::createDirectories(context->patch->autosavePath); + try { + rack::system::unarchiveToDirectory(data, context->patch->autosavePath); + context->patch->loadAutosave(); + ok = true; + } + catch (rack::Exception& e) { + WARN("%s", e.what()); + } + rack::contextSet(nullptr); + } + + const lo_address source = lo_message_get_source(m); + lo_send_from(source, static_cast(self)->oscServer, + LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail"); + return 0; + } +#endif }; // ----------------------------------------------------------------------------------------------------------- @@ -247,6 +355,10 @@ public: context->patch->loadTemplate(); context->engine->startFallbackThread(); +#ifdef HAVE_LIBLO + fInitializer->oscPlugin = this; +#endif + #if defined(__MOD_DEVICES__) && !defined(HEADLESS) context->window = new rack::window::Window; rack::window::WindowInit(context->window, this); @@ -260,6 +372,10 @@ public: ~CardinalPlugin() override { +#ifdef HAVE_LIBLO + fInitializer->oscPlugin = nullptr; +#endif + { const MutexLocker cml(context->mutex); rack::contextSet(context); diff --git a/src/Makefile b/src/Makefile index 64a3590..35f398b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -187,6 +187,14 @@ ifeq ($(WITH_LTO),true) LINK_FLAGS += -fno-strict-aliasing -flto -Werror=odr -Werror=lto-type-mismatch endif +# -------------------------------------------------------------- +# optional liblo + +ifeq ($(HAVE_LIBLO),true) +BASE_FLAGS += $(LIBLO_FLAGS) +LINK_FLAGS += $(LIBLO_LIBS) +endif + # -------------------------------------------------------------- # fallback path to resource files diff --git a/src/override/MenuBar.cpp b/src/override/MenuBar.cpp index 1e84695..596a75c 100644 --- a/src/override/MenuBar.cpp +++ b/src/override/MenuBar.cpp @@ -57,9 +57,16 @@ # undef DEBUG #endif +#ifdef HAVE_LIBLO +# include +#endif + #include #include "../PluginContext.hpp" +// #define REMOTE_HOST "localhost" +#define REMOTE_HOST "192.168.51.1" +#define REMOTE_HOST_PORT "2228" namespace rack { namespace app { @@ -92,6 +99,27 @@ struct FileButton : MenuButton { Window& window; const bool isStandalone; +#ifdef HAVE_LIBLO + bool oscConnected = false; + lo_server oscServer = nullptr; + + static int osc_handler(const char* const path, const char* const types, lo_arg** argv, const int argc, lo_message, void* const self) + { + d_stdout("osc_handler(\"%s\", \"%s\", %p, %i)", path, types, argv, argc); + + if (std::strcmp(path, "/resp") == 0 && argc == 2 && types[0] == 's' && types[1] == 's') { + d_stdout("osc_handler(\"%s\", ...) - got resp | '%s' '%s'", path, &argv[0]->s, &argv[1]->s); + if (std::strcmp(&argv[0]->s, "hello") == 0 && std::strcmp(&argv[1]->s, "ok") == 0) + static_cast(self)->oscConnected = true; + } + return 0; + } + + ~FileButton() { + lo_server_free(oscServer); + } +#endif + FileButton(Window& win, const bool standalone) : MenuButton(), window(win), isStandalone(standalone) {} @@ -127,6 +155,40 @@ struct FileButton : MenuButton { })); */ +#ifdef HAVE_LIBLO + if (oscServer == nullptr || !oscConnected) { + menu->addChild(createMenuItem("Connect to MOD", "", [this]() { + if (oscServer == nullptr) { + oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,); + lo_server_add_method(oscServer, "/resp", nullptr, osc_handler, this); + } + const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT); + DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); + lo_send(addr, "/hello", ""); + lo_address_free(addr); + })); + } else { + menu->addChild(createMenuItem("Deploy to MOD", "", []() { + const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT); + DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); + + APP->engine->prepareSave(); + APP->patch->saveAutosave(); + APP->patch->cleanAutosave(); + std::vector data(rack::system::archiveDirectory(APP->patch->autosavePath, 1)); + + if (const lo_blob blob = lo_blob_new(data.size(), data.data())) + { + lo_send(addr, "/load", "b", blob); + lo_blob_free(blob); + } + + lo_address_free(addr); + })); + } +#endif + menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { // APP->patch->revertDialog(); APP->patch->loadAction(APP->patch->path); @@ -140,6 +202,15 @@ struct FileButton : MenuButton { })); }; } + +#ifdef HAVE_LIBLO + void step() override { + MenuButton::step(); + if (oscServer != nullptr) { + while (lo_server_recv_noblock(oscServer, 0) != 0) {} + } + } +#endif };