DISTRHO Plugin Framework
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.

532 lines
13KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include "DistrhoUIInternal.hpp"
  17. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  18. # error DSSI UIs do not support direct access!
  19. #endif
  20. #include "../extra/Sleep.hpp"
  21. #include <lo/lo.h>
  22. START_NAMESPACE_DISTRHO
  23. // --------------------------------------------------------------------------------------------------------------------
  24. #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
  25. static constexpr const sendNoteFunc sendNoteCallback = nullptr;
  26. #endif
  27. // unused in DSSI, we only use external and standalone UIs
  28. static constexpr const setSizeFunc setSizeCallback = nullptr;
  29. // unsupported in DSSI
  30. static constexpr const fileRequestFunc fileRequestCallback = nullptr;
  31. // --------------------------------------------------------------------------------------------------------------------
  32. struct OscData {
  33. lo_address addr;
  34. const char* path;
  35. lo_server server;
  36. OscData()
  37. : addr(nullptr),
  38. path(nullptr),
  39. server(nullptr) {}
  40. void idle() const
  41. {
  42. if (server == nullptr)
  43. return;
  44. while (lo_server_recv_noblock(server, 0) != 0) {}
  45. }
  46. void send_configure(const char* const key, const char* const value) const
  47. {
  48. char targetPath[std::strlen(path)+11];
  49. std::strcpy(targetPath, path);
  50. std::strcat(targetPath, "/configure");
  51. lo_send(addr, targetPath, "ss", key, value);
  52. }
  53. void send_control(const int32_t index, const float value) const
  54. {
  55. char targetPath[std::strlen(path)+9];
  56. std::strcpy(targetPath, path);
  57. std::strcat(targetPath, "/control");
  58. lo_send(addr, targetPath, "if", index, value);
  59. }
  60. void send_midi(uchar data[4]) const
  61. {
  62. char targetPath[std::strlen(path)+6];
  63. std::strcpy(targetPath, path);
  64. std::strcat(targetPath, "/midi");
  65. lo_send(addr, targetPath, "m", data);
  66. }
  67. void send_update(const char* const url) const
  68. {
  69. char targetPath[std::strlen(path)+8];
  70. std::strcpy(targetPath, path);
  71. std::strcat(targetPath, "/update");
  72. lo_send(addr, targetPath, "s", url);
  73. }
  74. void send_exiting() const
  75. {
  76. char targetPath[std::strlen(path)+9];
  77. std::strcpy(targetPath, path);
  78. std::strcat(targetPath, "/exiting");
  79. lo_send(addr, targetPath, "");
  80. }
  81. };
  82. // -----------------------------------------------------------------------
  83. class UIDssi : public DGL_NAMESPACE::IdleCallback
  84. {
  85. public:
  86. UIDssi(const OscData& oscData, const char* const uiTitle, const double sampleRate)
  87. : fUI(this, 0, sampleRate, nullptr,
  88. setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, fileRequestCallback),
  89. fHostClosed(false),
  90. fOscData(oscData)
  91. {
  92. fUI.setWindowTitle(uiTitle);
  93. }
  94. ~UIDssi()
  95. {
  96. if (fOscData.server != nullptr && ! fHostClosed)
  97. fOscData.send_exiting();
  98. }
  99. void exec_start()
  100. {
  101. fUI.exec(this);
  102. }
  103. void idleCallback() override
  104. {
  105. fOscData.idle();
  106. if (fHostClosed)
  107. return;
  108. fUI.exec_idle();
  109. }
  110. // -------------------------------------------------------------------
  111. #if DISTRHO_PLUGIN_WANT_STATE
  112. void dssiui_configure(const char* key, const char* value)
  113. {
  114. fUI.stateChanged(key, value);
  115. }
  116. #endif
  117. void dssiui_control(ulong index, float value)
  118. {
  119. fUI.parameterChanged(index, value);
  120. }
  121. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  122. void dssiui_program(ulong bank, ulong program)
  123. {
  124. fUI.programLoaded(bank * 128 + program);
  125. }
  126. #endif
  127. void dssiui_samplerate(const double sampleRate)
  128. {
  129. fUI.setSampleRate(sampleRate, true);
  130. }
  131. void dssiui_show(const bool focus = false)
  132. {
  133. fUI.setWindowVisible(true);
  134. if (focus)
  135. fUI.focus();
  136. }
  137. void dssiui_hide()
  138. {
  139. fUI.setWindowVisible(false);
  140. }
  141. void dssiui_quit()
  142. {
  143. fHostClosed = true;
  144. fUI.quit();
  145. }
  146. // -------------------------------------------------------------------
  147. protected:
  148. void setParameterValue(const uint32_t rindex, const float value)
  149. {
  150. if (fOscData.server == nullptr)
  151. return;
  152. fOscData.send_control(rindex, value);
  153. }
  154. void setState(const char* const key, const char* const value)
  155. {
  156. if (fOscData.server == nullptr)
  157. return;
  158. fOscData.send_configure(key, value);
  159. }
  160. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  161. void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
  162. {
  163. if (fOscData.server == nullptr)
  164. return;
  165. if (channel > 0xF)
  166. return;
  167. uint8_t mdata[4] = {
  168. 0,
  169. static_cast<uint8_t>(channel + (velocity != 0 ? 0x90 : 0x80)),
  170. note,
  171. velocity
  172. };
  173. fOscData.send_midi(mdata);
  174. }
  175. #endif
  176. private:
  177. UIExporter fUI;
  178. bool fHostClosed;
  179. const OscData& fOscData;
  180. // -------------------------------------------------------------------
  181. // Callbacks
  182. #define uiPtr ((UIDssi*)ptr)
  183. static void setParameterCallback(void* ptr, uint32_t rindex, float value)
  184. {
  185. uiPtr->setParameterValue(rindex, value);
  186. }
  187. static void setStateCallback(void* ptr, const char* key, const char* value)
  188. {
  189. uiPtr->setState(key, value);
  190. }
  191. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  192. static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
  193. {
  194. uiPtr->sendNote(channel, note, velocity);
  195. }
  196. #endif
  197. #undef uiPtr
  198. };
  199. // -----------------------------------------------------------------------
  200. static OscData gOscData;
  201. static const char* gUiTitle = nullptr;
  202. static UIDssi* globalUI = nullptr;
  203. static double sampleRate = 0.0;
  204. static void initUiIfNeeded()
  205. {
  206. if (globalUI != nullptr)
  207. return;
  208. if (sampleRate == 0.0)
  209. sampleRate = 44100.0;
  210. globalUI = new UIDssi(gOscData, gUiTitle, sampleRate);
  211. }
  212. // -----------------------------------------------------------------------
  213. int osc_debug_handler(const char* path, const char*, lo_arg**, int, lo_message, void*)
  214. {
  215. d_debug("osc_debug_handler(\"%s\")", path);
  216. return 0;
  217. #ifndef DEBUG
  218. // unused
  219. (void)path;
  220. #endif
  221. }
  222. void osc_error_handler(int num, const char* msg, const char* path)
  223. {
  224. d_stderr("osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path);
  225. }
  226. #if DISTRHO_PLUGIN_WANT_STATE
  227. int osc_configure_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  228. {
  229. const char* const key = &argv[0]->s;
  230. const char* const value = &argv[1]->s;
  231. d_debug("osc_configure_handler(\"%s\", \"%s\")", key, value);
  232. initUiIfNeeded();
  233. globalUI->dssiui_configure(key, value);
  234. return 0;
  235. }
  236. #endif
  237. int osc_control_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  238. {
  239. const int32_t rindex = argv[0]->i;
  240. const float value = argv[1]->f;
  241. d_debug("osc_control_handler(%i, %f)", rindex, value);
  242. int32_t index = rindex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS;
  243. // latency
  244. #if DISTRHO_PLUGIN_WANT_LATENCY
  245. index -= 1;
  246. #endif
  247. if (index < 0)
  248. return 0;
  249. initUiIfNeeded();
  250. globalUI->dssiui_control(index, value);
  251. return 0;
  252. }
  253. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  254. int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  255. {
  256. const int32_t bank = argv[0]->i;
  257. const int32_t program = argv[1]->f;
  258. d_debug("osc_program_handler(%i, %i)", bank, program);
  259. initUiIfNeeded();
  260. globalUI->dssiui_program(bank, program);
  261. return 0;
  262. }
  263. #endif
  264. int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  265. {
  266. sampleRate = argv[0]->i;
  267. d_debug("osc_sample_rate_handler(%f)", sampleRate);
  268. if (globalUI != nullptr)
  269. globalUI->dssiui_samplerate(sampleRate);
  270. return 0;
  271. }
  272. int osc_show_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  273. {
  274. d_debug("osc_show_handler()");
  275. initUiIfNeeded();
  276. globalUI->dssiui_show();
  277. return 0;
  278. }
  279. int osc_hide_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  280. {
  281. d_debug("osc_hide_handler()");
  282. if (globalUI != nullptr)
  283. globalUI->dssiui_hide();
  284. return 0;
  285. }
  286. int osc_quit_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  287. {
  288. d_debug("osc_quit_handler()");
  289. if (globalUI != nullptr)
  290. globalUI->dssiui_quit();
  291. return 0;
  292. }
  293. END_NAMESPACE_DISTRHO
  294. // -----------------------------------------------------------------------
  295. int main(int argc, char* argv[])
  296. {
  297. USE_NAMESPACE_DISTRHO
  298. #if defined(DISTRHO_UI_LINUX_WEBVIEW_START)
  299. if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0)
  300. return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv);
  301. #endif
  302. // dummy test mode
  303. if (argc == 1)
  304. {
  305. gUiTitle = "DSSI UI Test";
  306. initUiIfNeeded();
  307. globalUI->dssiui_show(true);
  308. globalUI->exec_start();
  309. delete globalUI;
  310. globalUI = nullptr;
  311. return 0;
  312. }
  313. if (argc != 5)
  314. {
  315. d_stderr("Usage: %s <osc-url> <plugin-dll> <plugin-label> <instance-name>", argv[0]);
  316. return 1;
  317. }
  318. const char* oscUrl = argv[1];
  319. const char* uiTitle = argv[4];
  320. char* const oscHost = lo_url_get_hostname(oscUrl);
  321. char* const oscPort = lo_url_get_port(oscUrl);
  322. char* const oscPath = lo_url_get_path(oscUrl);
  323. size_t oscPathSize = strlen(oscPath);
  324. lo_address oscAddr = lo_address_new(oscHost, oscPort);
  325. lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, osc_error_handler);
  326. char* const oscServerPath = lo_server_get_url(oscServer);
  327. char pluginPath[strlen(oscServerPath)+oscPathSize];
  328. strcpy(pluginPath, oscServerPath);
  329. strcat(pluginPath, oscPath+1);
  330. #if DISTRHO_PLUGIN_WANT_STATE
  331. char oscPathConfigure[oscPathSize+11];
  332. strcpy(oscPathConfigure, oscPath);
  333. strcat(oscPathConfigure, "/configure");
  334. lo_server_add_method(oscServer, oscPathConfigure, "ss", osc_configure_handler, nullptr);
  335. #endif
  336. char oscPathControl[oscPathSize+9];
  337. strcpy(oscPathControl, oscPath);
  338. strcat(oscPathControl, "/control");
  339. lo_server_add_method(oscServer, oscPathControl, "if", osc_control_handler, nullptr);
  340. d_stdout("oscServerPath: \"%s\"", oscServerPath);
  341. d_stdout("pluginPath: \"%s\"", pluginPath);
  342. d_stdout("oscPathControl: \"%s\"", oscPathControl);
  343. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  344. char oscPathProgram[oscPathSize+9];
  345. strcpy(oscPathProgram, oscPath);
  346. strcat(oscPathProgram, "/program");
  347. lo_server_add_method(oscServer, oscPathProgram, "ii", osc_program_handler, nullptr);
  348. #endif
  349. char oscPathSampleRate[oscPathSize+13];
  350. strcpy(oscPathSampleRate, oscPath);
  351. strcat(oscPathSampleRate, "/sample-rate");
  352. lo_server_add_method(oscServer, oscPathSampleRate, "i", osc_sample_rate_handler, nullptr);
  353. char oscPathShow[oscPathSize+6];
  354. strcpy(oscPathShow, oscPath);
  355. strcat(oscPathShow, "/show");
  356. lo_server_add_method(oscServer, oscPathShow, "", osc_show_handler, nullptr);
  357. char oscPathHide[oscPathSize+6];
  358. strcpy(oscPathHide, oscPath);
  359. strcat(oscPathHide, "/hide");
  360. lo_server_add_method(oscServer, oscPathHide, "", osc_hide_handler, nullptr);
  361. char oscPathQuit[oscPathSize+6];
  362. strcpy(oscPathQuit, oscPath);
  363. strcat(oscPathQuit, "/quit");
  364. lo_server_add_method(oscServer, oscPathQuit, "", osc_quit_handler, nullptr);
  365. lo_server_add_method(oscServer, nullptr, nullptr, osc_debug_handler, nullptr);
  366. gUiTitle = uiTitle;
  367. gOscData.addr = oscAddr;
  368. gOscData.path = oscPath;
  369. gOscData.server = oscServer;
  370. gOscData.send_update(pluginPath);
  371. // wait for init
  372. for (int i=0; i < 100; ++i)
  373. {
  374. lo_server_recv(oscServer);
  375. if (sampleRate != 0.0 || globalUI != nullptr)
  376. break;
  377. d_msleep(50);
  378. }
  379. int ret = 1;
  380. if (sampleRate != 0.0 || globalUI != nullptr)
  381. {
  382. initUiIfNeeded();
  383. globalUI->exec_start();
  384. delete globalUI;
  385. globalUI = nullptr;
  386. ret = 0;
  387. }
  388. #if DISTRHO_PLUGIN_WANT_STATE
  389. lo_server_del_method(oscServer, oscPathConfigure, "ss");
  390. #endif
  391. lo_server_del_method(oscServer, oscPathControl, "if");
  392. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  393. lo_server_del_method(oscServer, oscPathProgram, "ii");
  394. #endif
  395. lo_server_del_method(oscServer, oscPathSampleRate, "i");
  396. lo_server_del_method(oscServer, oscPathShow, "");
  397. lo_server_del_method(oscServer, oscPathHide, "");
  398. lo_server_del_method(oscServer, oscPathQuit, "");
  399. lo_server_del_method(oscServer, nullptr, nullptr);
  400. std::free(oscServerPath);
  401. std::free(oscHost);
  402. std::free(oscPort);
  403. std::free(oscPath);
  404. lo_address_free(oscAddr);
  405. lo_server_free(oscServer);
  406. return ret;
  407. }