Audio plugin host https://kx.studio/carla
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.

586 lines
14KB

  1. /*
  2. * DISTRHO Plugin Toolkit (DPT)
  3. * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Lesser General Public License for more details.
  13. *
  14. * For a full copy of the license see the LGPL.txt file
  15. */
  16. #include "DistrhoDefines.h"
  17. #if defined(DISTRHO_PLUGIN_TARGET_DSSI) && DISTRHO_PLUGIN_HAS_UI
  18. #include "DistrhoUIInternal.h"
  19. #include <QtCore/QSettings>
  20. #include <QtGui/QApplication>
  21. #include <QtGui/QMainWindow>
  22. #include <lo/lo.h>
  23. // -------------------------------------------------
  24. START_NAMESPACE_DISTRHO
  25. struct OscData {
  26. lo_address addr;
  27. const char* path;
  28. lo_server server;
  29. };
  30. struct StringData {
  31. d_string key;
  32. d_string value;
  33. };
  34. #if DISTRHO_PLUGIN_WANT_STATE
  35. void osc_send_configure(const OscData* oscData, const char* const key, const char* const value)
  36. {
  37. char targetPath[strlen(oscData->path)+11];
  38. strcpy(targetPath, oscData->path);
  39. strcat(targetPath, "/configure");
  40. lo_send(oscData->addr, targetPath, "ss", key, value);
  41. }
  42. #endif
  43. void osc_send_control(const OscData* oscData, const int32_t index, const float value)
  44. {
  45. char targetPath[strlen(oscData->path)+9];
  46. strcpy(targetPath, oscData->path);
  47. strcat(targetPath, "/control");
  48. lo_send(oscData->addr, targetPath, "if", index, value);
  49. }
  50. #if DISTRHO_PLUGIN_IS_SYNTH
  51. void osc_send_midi(const OscData* oscData, unsigned char data[4])
  52. {
  53. char targetPath[strlen(oscData->path)+6];
  54. strcpy(targetPath, oscData->path);
  55. strcat(targetPath, "/midi");
  56. lo_send(oscData->addr, targetPath, "m", data);
  57. }
  58. #endif
  59. void osc_send_update(const OscData* oscData, const char* const url)
  60. {
  61. char targetPath[strlen(oscData->path)+8];
  62. strcpy(targetPath, oscData->path);
  63. strcat(targetPath, "/update");
  64. lo_send(oscData->addr, targetPath, "s", url);
  65. }
  66. void osc_send_exiting(const OscData* oscData)
  67. {
  68. char targetPath[strlen(oscData->path)+9];
  69. strcpy(targetPath, oscData->path);
  70. strcat(targetPath, "/exiting");
  71. lo_send(oscData->addr, targetPath, "");
  72. }
  73. class UIDssi : public QMainWindow
  74. {
  75. public:
  76. UIDssi(const OscData* oscData_, const char* title)
  77. : QMainWindow(nullptr),
  78. oscData(oscData_),
  79. widget(this),
  80. ui(this, (intptr_t)widget.winId(), setParameterCallback, setStateCallback, nullptr, uiSendNoteCallback, uiResizeCallback)
  81. {
  82. setCentralWidget(&widget);
  83. setFixedSize(ui.getWidth(), ui.getHeight());
  84. setWindowTitle(title);
  85. // idle timer
  86. uiTimer = startTimer(30);
  87. // load settings
  88. QSettings settings("DISTRHO", DISTRHO_PLUGIN_NAME);
  89. restoreGeometry(settings.value("Global/Geometry", QByteArray()).toByteArray());
  90. }
  91. ~UIDssi()
  92. {
  93. // save settings
  94. QSettings settings("DISTRHO", DISTRHO_PLUGIN_NAME);
  95. settings.setValue("Global/Geometry", saveGeometry());
  96. if (uiTimer != 0)
  97. {
  98. killTimer(uiTimer);
  99. osc_send_exiting(oscData);
  100. }
  101. }
  102. // ---------------------------------------------
  103. #if DISTRHO_PLUGIN_WANT_STATE
  104. void dssiui_configure(const char* key, const char* value)
  105. {
  106. ui.stateChanged(key, value);
  107. }
  108. #endif
  109. void dssiui_control(unsigned long index, float value)
  110. {
  111. ui.parameterChanged(index, value);
  112. }
  113. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  114. void dssiui_program(unsigned long bank, unsigned long program)
  115. {
  116. unsigned long realProgram = bank * 128 + program;
  117. ui.programChanged(realProgram);
  118. }
  119. #endif
  120. #if DISTRHO_PLUGIN_IS_SYNTH
  121. void dssiui_midi(uint8_t data[4])
  122. {
  123. uint8_t status = data[1] & 0xF0;
  124. uint8_t channel = data[1] & 0x0F;
  125. // fix bad note-off
  126. if (status == 0x90 && data[3] == 0)
  127. status -= 0x10;
  128. if (status == 0x80)
  129. {
  130. uint8_t note = data[2];
  131. ui.noteReceived(false, channel, note, 0);
  132. }
  133. else if (status == 0x90)
  134. {
  135. uint8_t note = data[2];
  136. uint8_t velo = data[3];
  137. ui.noteReceived(true, channel, note, velo);
  138. }
  139. }
  140. #endif
  141. void dssiui_show()
  142. {
  143. show();
  144. }
  145. void dssiui_hide()
  146. {
  147. hide();
  148. }
  149. void dssiui_quit()
  150. {
  151. if (uiTimer != 0)
  152. {
  153. killTimer(uiTimer);
  154. uiTimer = 0;
  155. }
  156. close();
  157. qApp->quit();
  158. }
  159. // ---------------------------------------------
  160. protected:
  161. void setParameterValue(uint32_t rindex, float value)
  162. {
  163. osc_send_control(oscData, rindex, value);
  164. }
  165. #if DISTRHO_PLUGIN_WANT_STATE
  166. void setState(const char* key, const char* value)
  167. {
  168. osc_send_configure(oscData, key, value);
  169. }
  170. #endif
  171. #if DISTRHO_PLUGIN_IS_SYNTH
  172. void uiSendNote(bool onOff, uint8_t channel, uint8_t note, uint8_t velocity)
  173. {
  174. uint8_t mdata[4] = { 0, channel, note, velocity };
  175. mdata[1] += onOff ? 0x90 : 0x80;
  176. osc_send_midi(oscData, mdata);
  177. }
  178. #endif
  179. void uiResize(unsigned int width, unsigned int height)
  180. {
  181. widget.setFixedSize(width, height);
  182. setFixedSize(width, height);
  183. }
  184. void timerEvent(QTimerEvent* event)
  185. {
  186. if (event->timerId() == uiTimer)
  187. {
  188. ui.idle();
  189. while (lo_server_recv_noblock(oscData->server, 0) != 0) {}
  190. }
  191. QMainWindow::timerEvent(event);
  192. }
  193. private:
  194. // OSC Stuff
  195. const OscData* const oscData;
  196. // Qt4 stuff
  197. int uiTimer;
  198. QWidget widget;
  199. // Plugin UI
  200. UIInternal ui;
  201. // ---------------------------------------------
  202. // Callbacks
  203. static void setParameterCallback(void* ptr, uint32_t rindex, float value)
  204. {
  205. if (UIDssi* _this_ = (UIDssi*)ptr)
  206. _this_->setParameterValue(rindex, value);
  207. }
  208. static void setStateCallback(void* ptr, const char* key, const char* value)
  209. {
  210. #if DISTRHO_PLUGIN_WANT_STATE
  211. if (UIDssi* _this_ = (UIDssi*)ptr)
  212. _this_->setState(key, value);
  213. #else
  214. return;
  215. // unused
  216. Q_UNUSED(ptr);
  217. Q_UNUSED(key);
  218. Q_UNUSED(value);
  219. #endif
  220. }
  221. static void uiSendNoteCallback(void* ptr, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity)
  222. {
  223. #if DISTRHO_PLUGIN_IS_SYNTH
  224. if (UIDssi* _this_ = (UIDssi*)ptr)
  225. _this_->uiSendNote(onOff, channel, note, velocity);
  226. #else
  227. return;
  228. // unused
  229. Q_UNUSED(ptr);
  230. Q_UNUSED(onOff);
  231. Q_UNUSED(channel);
  232. Q_UNUSED(note);
  233. Q_UNUSED(velocity);
  234. #endif
  235. }
  236. static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height)
  237. {
  238. if (UIDssi* _this_ = (UIDssi*)ptr)
  239. _this_->uiResize(width, height);
  240. }
  241. };
  242. // -------------------------------------------------
  243. static UIDssi* globalUI = nullptr;
  244. void osc_error_handler(int num, const char* msg, const char* path)
  245. {
  246. qCritical("osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path);
  247. }
  248. #if DISTRHO_PLUGIN_WANT_STATE
  249. int osc_configure_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  250. {
  251. const char* const key = &argv[0]->s;
  252. const char* const value = &argv[1]->s;
  253. qDebug("osc_configure_handler(\"%s\", \"%s\")", key, value);
  254. if (globalUI)
  255. {
  256. globalUI->dssiui_configure(key, value);
  257. }
  258. else
  259. {
  260. StringData data = { key, value };
  261. globalConfigures.push_back(data);
  262. }
  263. return 0;
  264. }
  265. #endif
  266. int osc_control_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  267. {
  268. const int32_t rindex = argv[0]->i;
  269. const float value = argv[1]->f;
  270. qDebug("osc_control_handler(%i, %f)", rindex, value);
  271. int32_t index = rindex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS;
  272. // latency
  273. #if DISTRHO_PLUGIN_WANT_LATENCY
  274. index -= 1;
  275. #endif
  276. // sample-rate
  277. index -= 1;
  278. if (index == -1)
  279. {
  280. setLastUiSampleRate(value);
  281. return 0;
  282. }
  283. if (index < 0)
  284. return 0;
  285. if (globalUI)
  286. {
  287. globalUI->dssiui_control(index, value);
  288. }
  289. else
  290. {
  291. if (index >= (int32_t)globalControls.size())
  292. {
  293. for (int32_t i=globalControls.size(); i < index; i++)
  294. globalControls.push_back(0.0f);
  295. }
  296. globalControls[index] = value;
  297. }
  298. return 0;
  299. }
  300. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  301. int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  302. {
  303. const int32_t bank = argv[0]->i;
  304. const int32_t program = argv[1]->f;
  305. qDebug("osc_program_handler(%i, %i)", bank, program);
  306. if (globalUI)
  307. {
  308. globalUI->dssiui_program(bank, program);
  309. }
  310. else
  311. {
  312. globalProgram[0] = bank;
  313. globalProgram[1] = program;
  314. }
  315. return 0;
  316. }
  317. #endif
  318. #if DISTRHO_PLUGIN_IS_SYNTH
  319. int osc_midi_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  320. {
  321. qDebug("osc_midi_handler()");
  322. if (globalUI)
  323. globalUI->dssiui_midi(argv[0]->m);
  324. return 0;
  325. }
  326. #endif
  327. int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
  328. {
  329. const int32_t sampleRate = argv[0]->i;
  330. qDebug("osc_sample_rate_handler(%i)", sampleRate);
  331. setLastUiSampleRate(sampleRate);
  332. return 0;
  333. }
  334. int osc_show_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  335. {
  336. qDebug("osc_show_handler()");
  337. if (globalUI)
  338. globalUI->show();
  339. return 0;
  340. }
  341. int osc_hide_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  342. {
  343. qDebug("osc_hide_handler()");
  344. if (globalUI)
  345. globalUI->hide();
  346. return 0;
  347. }
  348. int osc_quit_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
  349. {
  350. qDebug("osc_quit_handler()");
  351. if (globalUI)
  352. globalUI->dssiui_quit();
  353. return 0;
  354. }
  355. int osc_debug_handler(const char* path, const char*, lo_arg**, int, lo_message, void*)
  356. {
  357. qDebug("osc_debug_handler(\"%s\")", path);
  358. return 0;
  359. }
  360. END_NAMESPACE_DISTRHO
  361. // -------------------------------------------------
  362. int main(int argc, char* argv[])
  363. {
  364. USE_NAMESPACE_DISTRHO
  365. if (argc != 5)
  366. {
  367. qWarning("Usage: %s <osc-url> <so-file> <plugin-label> <instance-name>", argv[0]);
  368. return 1;
  369. }
  370. QApplication app(argc, argv, true);
  371. const char* const oscUrl = argv[1];
  372. const char* const uiTitle = argv[4];
  373. char* const oscHost = lo_url_get_hostname(oscUrl);
  374. char* const oscPort = lo_url_get_port(oscUrl);
  375. char* const oscPath = lo_url_get_path(oscUrl);
  376. size_t oscPathSize = strlen(oscPath);
  377. lo_address oscAddr = lo_address_new(oscHost, oscPort);
  378. lo_server oscServer = lo_server_new(nullptr, osc_error_handler);
  379. OscData oscData = { oscAddr, oscPath, oscServer };
  380. #if DISTRHO_PLUGIN_WANT_STATE
  381. char oscPathConfigure[oscPathSize+11];
  382. strcpy(oscPathConfigure, oscPath);
  383. strcat(oscPathConfigure, "/configure");
  384. lo_server_add_method(oscServer, oscPathConfigure, "ss", osc_configure_handler, nullptr);
  385. #endif
  386. char oscPathControl[oscPathSize+9];
  387. strcpy(oscPathControl, oscPath);
  388. strcat(oscPathControl, "/control");
  389. lo_server_add_method(oscServer, oscPathControl, "if", osc_control_handler, nullptr);
  390. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  391. char oscPathProgram[oscPathSize+9];
  392. strcpy(oscPathProgram, oscPath);
  393. strcat(oscPathProgram, "/program");
  394. lo_server_add_method(oscServer, oscPathProgram, "ii", osc_program_handler, nullptr);
  395. #endif
  396. #if DISTRHO_PLUGIN_IS_SYNTH
  397. char oscPathMidi[oscPathSize+6];
  398. strcpy(oscPathMidi, oscPath);
  399. strcat(oscPathMidi, "/midi");
  400. lo_server_add_method(oscServer, oscPathMidi, "m", osc_midi_handler, nullptr);
  401. #endif
  402. char oscPathSampleRate[oscPathSize+13];
  403. strcpy(oscPathSampleRate, oscPath);
  404. strcat(oscPathSampleRate, "/sample-rate");
  405. lo_server_add_method(oscServer, oscPathSampleRate, "i", osc_sample_rate_handler, nullptr);
  406. char oscPathShow[oscPathSize+6];
  407. strcpy(oscPathShow, oscPath);
  408. strcat(oscPathShow, "/show");
  409. lo_server_add_method(oscServer, oscPathShow, "", osc_show_handler, nullptr);
  410. char oscPathHide[oscPathSize+6];
  411. strcpy(oscPathHide, oscPath);
  412. strcat(oscPathHide, "/hide");
  413. lo_server_add_method(oscServer, oscPathHide, "", osc_hide_handler, nullptr);
  414. char oscPathQuit[oscPathSize+6];
  415. strcpy(oscPathQuit, oscPath);
  416. strcat(oscPathQuit, "/quit");
  417. lo_server_add_method(oscServer, oscPathQuit, "", osc_quit_handler, nullptr);
  418. lo_server_add_method(oscServer, nullptr, nullptr, osc_debug_handler, nullptr);
  419. char* const serverPath = lo_server_get_url(oscServer);
  420. char* const pluginPath = strdup(QString("%1%2").arg(serverPath).arg(oscPathSize > 1 ? oscPath + 1 : oscPath).toUtf8().constData());
  421. free(serverPath);
  422. // send update msg and wait for sample-rate
  423. osc_send_update(&oscData, pluginPath);
  424. // wait for sample-rate
  425. for (int i=0; i < 1000; i++)
  426. {
  427. if (d_lastUiSampleRate != 0.0)
  428. break;
  429. lo_server_recv(oscServer);
  430. d_msleep(50);
  431. }
  432. int ret;
  433. if (d_lastUiSampleRate != 0.0)
  434. {
  435. globalUI = new UIDssi(&oscData, uiTitle);
  436. ret = app.exec();
  437. delete globalUI;
  438. globalUI = nullptr;
  439. }
  440. else
  441. {
  442. ret = 1;
  443. }
  444. #if DISTRHO_PLUGIN_WANT_STATE
  445. lo_server_del_method(oscServer, oscPathConfigure, "ss");
  446. #endif
  447. lo_server_del_method(oscServer, oscPathControl, "if");
  448. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  449. lo_server_del_method(oscServer, oscPathProgram, "ii");
  450. #endif
  451. #if DISTRHO_PLUGIN_IS_SYNTH
  452. lo_server_del_method(oscServer, oscPathMidi, "m");
  453. #endif
  454. lo_server_del_method(oscServer, oscPathSampleRate, "i");
  455. lo_server_del_method(oscServer, oscPathShow, "");
  456. lo_server_del_method(oscServer, oscPathHide, "");
  457. lo_server_del_method(oscServer, oscPathQuit, "");
  458. lo_server_del_method(oscServer, nullptr, nullptr);
  459. free(oscHost);
  460. free(oscPort);
  461. free(oscPath);
  462. free(pluginPath);
  463. lo_address_free(oscAddr);
  464. lo_server_free(oscServer);
  465. return ret;
  466. }
  467. // -------------------------------------------------
  468. #endif // DISTRHO_PLUGIN_TARGET_DSSI && DISTRHO_PLUGIN_HAS_UI