You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

257 lines
7.4KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2024 Filipe Coelho <falktx@falktx.com>
  4. * SPDX-License-Identifier: GPL-3.0-or-later
  5. */
  6. #include <engine/Engine.hpp>
  7. #include <patch.hpp>
  8. #include <system.hpp>
  9. #ifdef NDEBUG
  10. # undef DEBUG
  11. #endif
  12. #include "CardinalRemote.hpp"
  13. #include "CardinalPluginContext.hpp"
  14. #include "extra/Base64.hpp"
  15. #include "extra/ScopedSafeLocale.hpp"
  16. #if defined(STATIC_BUILD) || ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  17. # undef HAVE_LIBLO
  18. #endif
  19. #if (defined(HAVE_LIBLO) || ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS) && !defined(HEADLESS)
  20. # define CARDINAL_REMOTE_ENABLED
  21. #endif
  22. #ifdef HAVE_LIBLO
  23. # include <lo/lo.h>
  24. #endif
  25. namespace rack {
  26. namespace engine {
  27. void Engine_setRemoteDetails(Engine*, remoteUtils::RemoteDetails*);
  28. }
  29. }
  30. // -----------------------------------------------------------------------------------------------------------
  31. namespace remoteUtils {
  32. #ifdef HAVE_LIBLO
  33. static int osc_handler(const char* const path, const char* const types, lo_arg** argv, const int argc, lo_message, void* const self)
  34. {
  35. d_stdout("osc_handler(\"%s\", \"%s\", %p, %i)", path, types, argv, argc);
  36. if (std::strcmp(path, "/resp") == 0 && argc == 2 && types[0] == 's' && types[1] == 's')
  37. {
  38. d_stdout("osc_handler(\"%s\", ...) - got resp | '%s' '%s'", path, &argv[0]->s, &argv[1]->s);
  39. if (std::strcmp(&argv[0]->s, "hello") == 0)
  40. {
  41. if (std::strcmp(&argv[1]->s, "ok") == 0)
  42. static_cast<RemoteDetails*>(self)->connected = true;
  43. }
  44. else if (std::strcmp(&argv[0]->s, "features") == 0)
  45. {
  46. static_cast<RemoteDetails*>(self)->screenshot = std::strstr(&argv[1]->s, ":screenshot:") != nullptr;
  47. }
  48. }
  49. return 0;
  50. }
  51. #endif
  52. RemoteDetails* getRemote()
  53. {
  54. #ifdef CARDINAL_REMOTE_ENABLED
  55. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  56. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, nullptr);
  57. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
  58. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  59. return ui->remoteDetails;
  60. #else
  61. return nullptr;
  62. #endif
  63. }
  64. bool connectToRemote(const char* const url)
  65. {
  66. #ifdef CARDINAL_REMOTE_ENABLED
  67. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  68. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, false);
  69. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
  70. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false);
  71. RemoteDetails* remoteDetails = ui->remoteDetails;
  72. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  73. if (remoteDetails == nullptr)
  74. {
  75. ui->remoteDetails = remoteDetails = new RemoteDetails;
  76. remoteDetails->handle = ui;
  77. remoteDetails->url = strdup(url);
  78. remoteDetails->autoDeploy = true;
  79. remoteDetails->connected = true;
  80. remoteDetails->first = false;
  81. remoteDetails->screenshot = false;
  82. }
  83. #elif defined(HAVE_LIBLO)
  84. const lo_address addr = lo_address_new_from_url(url);
  85. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr, false);
  86. if (remoteDetails == nullptr)
  87. {
  88. const lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr);
  89. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr, false);
  90. ui->remoteDetails = remoteDetails = new RemoteDetails;
  91. remoteDetails->handle = oscServer;
  92. remoteDetails->url = strdup(url);
  93. remoteDetails->autoDeploy = true;
  94. remoteDetails->first = true;
  95. remoteDetails->connected = false;
  96. remoteDetails->screenshot = false;
  97. lo_server_add_method(oscServer, "/resp", nullptr, osc_handler, remoteDetails);
  98. sendFullPatchToRemote(remoteDetails);
  99. Engine_setRemoteDetails(context->engine, remoteDetails);
  100. }
  101. else if (std::strcmp(remoteDetails->url, url) != 0)
  102. {
  103. ui->remoteDetails = nullptr;
  104. disconnectFromRemote(remoteDetails);
  105. return connectToRemote(url);
  106. }
  107. lo_send(addr, "/hello", "");
  108. lo_address_free(addr);
  109. #endif
  110. return remoteDetails != nullptr;
  111. #else
  112. return false;
  113. #endif
  114. }
  115. void disconnectFromRemote(RemoteDetails* const remote)
  116. {
  117. if (remote != nullptr)
  118. {
  119. #ifdef HAVE_LIBLO
  120. lo_server_free(static_cast<lo_server>(remote->handle));
  121. #endif
  122. std::free(const_cast<char*>(remote->url));
  123. delete remote;
  124. }
  125. }
  126. void idleRemote(RemoteDetails* const remote)
  127. {
  128. DISTRHO_SAFE_ASSERT_RETURN(remote != nullptr,);
  129. #ifdef HAVE_LIBLO
  130. while (lo_server_recv_noblock(static_cast<lo_server>(remote->handle), 0) != 0) {}
  131. #endif
  132. }
  133. void sendParamChangeToRemote(RemoteDetails* const remote, int64_t moduleId, int paramId, float value)
  134. {
  135. #ifdef CARDINAL_REMOTE_ENABLED
  136. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  137. char paramBuf[512] = {};
  138. {
  139. const ScopedSafeLocale cssl;
  140. std::snprintf(paramBuf, sizeof(paramBuf), "%lld:%d:%f", (long long)moduleId, paramId, value);
  141. }
  142. static_cast<CardinalBaseUI*>(remote->handle)->setState("param", paramBuf);
  143. #elif defined(HAVE_LIBLO)
  144. const lo_address addr = lo_address_new_from_url(remote->url);
  145. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,);
  146. lo_send(addr, "/param", "hif", moduleId, paramId, value);
  147. lo_address_free(addr);
  148. #endif
  149. #endif
  150. }
  151. void sendFullPatchToRemote(RemoteDetails* const remote)
  152. {
  153. #ifdef CARDINAL_REMOTE_ENABLED
  154. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  155. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr,);
  156. context->engine->prepareSave();
  157. context->patch->saveAutosave();
  158. context->patch->cleanAutosave();
  159. std::vector<uint8_t> data;
  160. using namespace rack::system;
  161. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  162. FILE* const f = std::fopen(join(context->patch->autosavePath, "patch.json").c_str(), "r");
  163. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  164. DEFER({
  165. std::fclose(f);
  166. });
  167. std::fseek(f, 0, SEEK_END);
  168. const long fileSize = std::ftell(f);
  169. DISTRHO_SAFE_ASSERT_RETURN(fileSize > 0,);
  170. std::fseek(f, 0, SEEK_SET);
  171. char* const fileContent = new char[fileSize+1];
  172. DISTRHO_SAFE_ASSERT_RETURN(std::fread(fileContent, fileSize, 1, f) == 1,);
  173. fileContent[fileSize] = '\0';
  174. static_cast<CardinalBaseUI*>(remote->handle)->setState("patch", fileContent);
  175. delete[] fileContent;
  176. #elif defined(HAVE_LIBLO)
  177. try {
  178. data = archiveDirectory(context->patch->autosavePath, 1);
  179. } DISTRHO_SAFE_EXCEPTION_RETURN("sendFullPatchToRemote",);
  180. DISTRHO_SAFE_ASSERT_RETURN(data.size() >= 4,);
  181. const lo_address addr = lo_address_new_from_url(remote->url);
  182. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,);
  183. if (const lo_blob blob = lo_blob_new(data.size(), data.data()))
  184. {
  185. lo_send(addr, "/load", "b", blob);
  186. lo_blob_free(blob);
  187. }
  188. lo_address_free(addr);
  189. #endif
  190. #endif
  191. }
  192. void sendScreenshotToRemote(RemoteDetails* const remote, const char* const screenshot)
  193. {
  194. #if defined(HAVE_LIBLO) && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  195. const lo_address addr = lo_address_new_from_url(remote->url);
  196. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,);
  197. std::vector<uint8_t> data(d_getChunkFromBase64String(screenshot));
  198. if (const lo_blob blob = lo_blob_new(data.size(), data.data()))
  199. {
  200. lo_send(addr, "/screenshot", "b", blob);
  201. lo_blob_free(blob);
  202. }
  203. lo_address_free(addr);
  204. #endif
  205. }
  206. }
  207. // -----------------------------------------------------------------------------------------------------------