/* * DISTRHO Cardinal Plugin * Copyright (C) 2021-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 3 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the LICENSE file. */ #include #include #include #ifdef NDEBUG # undef DEBUG #endif #include "CardinalRemote.hpp" #include "PluginContext.hpp" #include "extra/Base64.hpp" #include "extra/ScopedSafeLocale.hpp" #if defined(STATIC_BUILD) || ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS # undef HAVE_LIBLO #endif #if (defined(HAVE_LIBLO) || ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS) && !defined(HEADLESS) # define CARDINAL_REMOTE_ENABLED #endif #ifdef HAVE_LIBLO # include // # define REMOTE_HOST "localhost" # define REMOTE_HOST "192.168.51.1" #endif // ----------------------------------------------------------------------------------------------------------- namespace remoteUtils { #ifdef HAVE_LIBLO 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)->connected = true; } return 0; } #endif RemoteDetails* getRemote() { #ifdef CARDINAL_REMOTE_ENABLED CardinalPluginContext* const context = static_cast(APP); DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, nullptr); CardinalBaseUI* const ui = static_cast(context->ui); DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr); return ui->remoteDetails; #else return nullptr; #endif } bool connectToRemote() { #ifdef CARDINAL_REMOTE_ENABLED CardinalPluginContext* const context = static_cast(APP); DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, false); CardinalBaseUI* const ui = static_cast(context->ui); DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); RemoteDetails* remoteDetails = ui->remoteDetails; #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS if (remoteDetails == nullptr) { ui->remoteDetails = remoteDetails = new RemoteDetails; remoteDetails->handle = ui; remoteDetails->connected = true; remoteDetails->autoDeploy = true; } #elif defined(HAVE_LIBLO) if (remoteDetails == nullptr) { const lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr); DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr, false); ui->remoteDetails = remoteDetails = new RemoteDetails; remoteDetails->handle = oscServer; remoteDetails->connected = false; remoteDetails->autoDeploy = false; lo_server_add_method(oscServer, "/resp", nullptr, osc_handler, remoteDetails); } const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, CARDINAL_DEFAULT_REMOTE_HOST_PORT); DISTRHO_SAFE_ASSERT(addr != nullptr); if (addr != nullptr) { lo_send(addr, "/hello", ""); lo_address_free(addr); } #endif return remoteDetails != nullptr; #else return false; #endif } void disconnectFromRemote(RemoteDetails* const remote) { if (remote != nullptr) { #ifdef HAVE_LIBLO lo_server_free(static_cast(remote->handle)); #endif delete remote; } } void idleRemote(RemoteDetails* const remote) { DISTRHO_SAFE_ASSERT_RETURN(remote != nullptr,); #ifdef HAVE_LIBLO while (lo_server_recv_noblock(static_cast(remote->handle), 0) != 0) {} #endif } void sendParamChangeToRemote(RemoteDetails* const remote, int64_t moduleId, int paramId, float value) { #ifdef CARDINAL_REMOTE_ENABLED #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS char paramBuf[512] = {}; { const ScopedSafeLocale cssl; std::snprintf(paramBuf, sizeof(paramBuf), "%lld:%d:%f", (long long)moduleId, paramId, value); } static_cast(remote->handle)->setState("param", paramBuf); #elif defined(HAVE_LIBLO) const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, CARDINAL_DEFAULT_REMOTE_HOST_PORT); DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); lo_send(addr, "/param", "hif", moduleId, paramId, value); lo_address_free(addr); #endif #endif } void sendFullPatchToRemote(RemoteDetails* const remote) { #ifdef CARDINAL_REMOTE_ENABLED CardinalPluginContext* const context = static_cast(APP); DISTRHO_SAFE_ASSERT_RETURN(context != nullptr,); context->engine->prepareSave(); context->patch->saveAutosave(); context->patch->cleanAutosave(); std::vector data; using namespace rack::system; #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS FILE* const f = std::fopen(join(context->patch->autosavePath, "patch.json").c_str(), "r"); DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,); DEFER({ std::fclose(f); }); std::fseek(f, 0, SEEK_END); const long fileSize = std::ftell(f); DISTRHO_SAFE_ASSERT_RETURN(fileSize > 0,); std::fseek(f, 0, SEEK_SET); char* const fileContent = new char[fileSize+1]; DISTRHO_SAFE_ASSERT_RETURN(std::fread(fileContent, fileSize, 1, f) == 1,); fileContent[fileSize] = '\0'; static_cast(remote->handle)->setState("patch", fileContent); delete[] fileContent; #elif defined(HAVE_LIBLO) try { data = archiveDirectory(context->patch->autosavePath, 1); } DISTRHO_SAFE_EXCEPTION_RETURN("sendFullPatchToRemote",); DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,); const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, CARDINAL_DEFAULT_REMOTE_HOST_PORT); DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); 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 #endif } void sendScreenshotToRemote(RemoteDetails*, const char* const screenshot) { #if defined(HAVE_LIBLO) && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, CARDINAL_DEFAULT_REMOTE_HOST_PORT); DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); std::vector data(d_getChunkFromBase64String(screenshot)); if (const lo_blob blob = lo_blob_new(data.size(), data.data())) { lo_send(addr, "/screenshot", "b", blob); lo_blob_free(blob); } lo_address_free(addr); #endif } } // -----------------------------------------------------------------------------------------------------------