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.

526 lines
13KB

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