Collection of tools useful for audio production
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.

613 lines
15KB

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