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.

524 lines
13KB

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