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.

791 lines
20KB

  1. /*
  2. * Carla Plugin bridge code
  3. * Copyright (C) 2012 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * 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 GNU General Public License see the COPYING file
  16. */
  17. #ifdef BUILD_BRIDGE_PLUGIN
  18. #include "carla_bridge_client.h"
  19. #include "carla_plugin.h"
  20. #include <QtCore/QDir>
  21. #include <QtCore/QFile>
  22. #include <QtCore/QTextStream>
  23. #include <QtCore/QTimerEvent>
  24. #include <QtGui/QApplication>
  25. #include <QtGui/QDialog>
  26. #include <QtGui/QVBoxLayout>
  27. #include <QtGui/QtEvents>
  28. #ifdef Q_OS_UNIX
  29. #include <signal.h>
  30. #endif
  31. static int qargc = 0;
  32. static char** qargv = nullptr;
  33. static bool qCloseNow = false;
  34. #if defined(Q_OS_UNIX)
  35. void closeSignalHandler(int)
  36. {
  37. qCloseNow = true;
  38. }
  39. #elif defined(Q_OS_WIN)
  40. BOOL WINAPI closeSignalHandler(DWORD dwCtrlType)
  41. {
  42. if (dwCtrlType == CTRL_C_EVENT)
  43. {
  44. qCloseNow = true;
  45. return TRUE;
  46. }
  47. return FALSE;
  48. }
  49. #endif
  50. void initSignalHandler()
  51. {
  52. #if defined(Q_OS_UNIX)
  53. struct sigaction sint, sterm;
  54. sint.sa_handler = closeSignalHandler;
  55. sigemptyset(&sint.sa_mask);
  56. sint.sa_flags |= SA_RESTART;
  57. sigaction(SIGINT, &sint, 0);
  58. sterm.sa_handler = closeSignalHandler;
  59. sigemptyset(&sterm.sa_mask);
  60. sterm.sa_flags |= SA_RESTART;
  61. sigaction(SIGTERM, &sterm, 0);
  62. #elif defined(Q_OS_WIN)
  63. SetConsoleCtrlHandler(closeSignalHandler, TRUE);
  64. #endif
  65. }
  66. CARLA_BRIDGE_START_NAMESPACE
  67. // -------------------------------------------------------------------------
  68. class BridgePluginGUI : public QDialog
  69. {
  70. public:
  71. class Callback
  72. {
  73. public:
  74. virtual ~Callback() {}
  75. virtual void guiClosedCallback() = 0;
  76. };
  77. BridgePluginGUI(QWidget* const parent, Callback* const callback_, const char* const pluginName, const bool resizable)
  78. : QDialog(parent),
  79. callback(callback_)
  80. {
  81. qDebug("BridgePluginGUI::BridgePluginGUI(%p, %p, \"%s\", %s", parent, callback, pluginName, bool2str(resizable));
  82. Q_ASSERT(callback);
  83. Q_ASSERT(pluginName);
  84. m_firstShow = true;
  85. m_resizable = resizable;
  86. vbLayout = new QVBoxLayout(this);
  87. vbLayout->setContentsMargins(0, 0, 0, 0);
  88. setLayout(vbLayout);
  89. container = new GuiContainer(this);
  90. vbLayout->addWidget(container);
  91. setNewSize(50, 50);
  92. setWindowTitle(QString("%1 (GUI)").arg(pluginName));
  93. #ifdef Q_OS_WIN
  94. if (! resizable)
  95. setWindowFlags(windowFlags() | Qt::MSWindowsFixedSizeDialogHint);
  96. #endif
  97. }
  98. ~BridgePluginGUI()
  99. {
  100. qDebug("BridgePluginGUI::~BridgePluginGUI()");
  101. Q_ASSERT(container);
  102. Q_ASSERT(vbLayout);
  103. delete container;
  104. delete vbLayout;
  105. }
  106. GuiContainer* getContainer()
  107. {
  108. return container;
  109. }
  110. void setNewSize(int width, int height)
  111. {
  112. qDebug("BridgePluginGUI::setNewSize(%i, %i)", width, height);
  113. if (width < 30)
  114. width = 30;
  115. if (height < 30)
  116. height = 30;
  117. if (m_resizable)
  118. {
  119. resize(width, height);
  120. }
  121. else
  122. {
  123. setFixedSize(width, height);
  124. container->setFixedSize(width, height);
  125. }
  126. }
  127. void setVisible(const bool yesNo)
  128. {
  129. qDebug("BridgePluginGUI::setVisible(%s)", bool2str(yesNo));
  130. if (yesNo)
  131. {
  132. if (m_firstShow)
  133. {
  134. m_firstShow = false;
  135. restoreGeometry(QByteArray());
  136. }
  137. else if (! m_geometry.isNull())
  138. restoreGeometry(m_geometry);
  139. }
  140. else
  141. m_geometry = saveGeometry();
  142. QDialog::setVisible(yesNo);
  143. }
  144. protected:
  145. void hideEvent(QHideEvent* const event)
  146. {
  147. qDebug("BridgePluginGUI::hideEvent(%p)", event);
  148. event->accept();
  149. close();
  150. }
  151. void closeEvent(QCloseEvent* const event)
  152. {
  153. qDebug("BridgePluginGUI::closeEvent(%p)", event);
  154. if (event->spontaneous())
  155. callback->guiClosedCallback();
  156. QDialog::closeEvent(event);
  157. }
  158. void done(int r)
  159. {
  160. QDialog::done(r);
  161. close();
  162. }
  163. private:
  164. Callback* const callback;
  165. QVBoxLayout* vbLayout;
  166. GuiContainer* container;
  167. bool m_firstShow;
  168. bool m_resizable;
  169. QByteArray m_geometry;
  170. };
  171. // -------------------------------------------------------------------------
  172. class BridgePluginClient : public CarlaToolkit,
  173. public CarlaClient,
  174. public BridgePluginGUI::Callback,
  175. public QApplication
  176. {
  177. public:
  178. BridgePluginClient()
  179. : CarlaToolkit("carla-bridge-plugin"),
  180. CarlaClient(this),
  181. QApplication(qargc, qargv)
  182. {
  183. qDebug("BridgePluginClient::BridgePluginClient()");
  184. msgTimer = 0;
  185. nextWidth = 0;
  186. nextHeight = 0;
  187. engine = nullptr;
  188. plugin = nullptr;
  189. pluginGui = nullptr;
  190. m_client = this;
  191. }
  192. ~BridgePluginClient()
  193. {
  194. qDebug("BridgePluginClient::~BridgePluginClient()");
  195. Q_ASSERT(msgTimer == 0);
  196. Q_ASSERT(! pluginGui);
  197. }
  198. void setStuff(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin)
  199. {
  200. qDebug("BridgePluginClient::setStuff(%p, %p)", engine, plugin);
  201. Q_ASSERT(engine);
  202. Q_ASSERT(plugin);
  203. this->engine = engine;
  204. this->plugin = plugin;
  205. }
  206. // ---------------------------------------------------------------------
  207. // toolkit
  208. void init()
  209. {
  210. qDebug("BridgePluginClient::init()");
  211. }
  212. void exec(CarlaClient* const, const bool showGui)
  213. {
  214. qDebug("BridgePluginClient::exec()");
  215. if (showGui)
  216. {
  217. show();
  218. }
  219. else
  220. {
  221. CarlaClient::sendOscUpdate();
  222. CarlaClient::sendOscBridgeUpdate();
  223. QApplication::setQuitOnLastWindowClosed(false);
  224. }
  225. msgTimer = startTimer(50);
  226. QApplication::exec();
  227. }
  228. void quit()
  229. {
  230. qDebug("BridgePluginClient::quit()");
  231. if (msgTimer != 0)
  232. {
  233. QApplication::killTimer(msgTimer);
  234. msgTimer = 0;
  235. }
  236. if (pluginGui)
  237. {
  238. if (pluginGui->isVisible())
  239. hide();
  240. pluginGui->close();
  241. delete pluginGui;
  242. pluginGui = nullptr;
  243. }
  244. if (! QApplication::closingDown())
  245. QApplication::quit();
  246. }
  247. void show()
  248. {
  249. qDebug("BridgePluginClient::show()");
  250. Q_ASSERT(pluginGui);
  251. if (plugin)
  252. plugin->showGui(true);
  253. if (pluginGui)
  254. pluginGui->show();
  255. }
  256. void hide()
  257. {
  258. qDebug("BridgePluginClient::hide()");
  259. Q_ASSERT(pluginGui);
  260. if (pluginGui)
  261. pluginGui->hide();
  262. if (plugin)
  263. plugin->showGui(false);
  264. }
  265. void resize(int width, int height)
  266. {
  267. qDebug("BridgePluginClient::resize(%i, %i)", width, height);
  268. Q_ASSERT(pluginGui);
  269. if (pluginGui)
  270. pluginGui->setNewSize(width, height);
  271. }
  272. // ---------------------------------------------------------------------
  273. void createWindow(const bool resizable)
  274. {
  275. qDebug("BridgePluginClient::createWindow(%s)", bool2str(resizable));
  276. Q_ASSERT(plugin);
  277. pluginGui = new BridgePluginGUI(nullptr, this, plugin->name(), resizable);
  278. plugin->setGuiContainer(pluginGui->getContainer());
  279. }
  280. // ---------------------------------------------------------------------
  281. // processing
  282. void setParameter(const int32_t rindex, const double value)
  283. {
  284. qDebug("CarlaPluginClient::setParameter(%i, %g)", rindex, value);
  285. Q_ASSERT(plugin);
  286. if (! plugin)
  287. return;
  288. plugin->setParameterValueByRIndex(rindex, value, true, true, false);
  289. }
  290. void setProgram(const uint32_t index)
  291. {
  292. qDebug("CarlaPluginClient::setProgram(%i)", index);
  293. Q_ASSERT(engine);
  294. Q_ASSERT(plugin);
  295. Q_ASSERT(index < plugin->programCount());
  296. if (! (plugin && engine))
  297. return;
  298. if (index >= plugin->programCount())
  299. return;
  300. plugin->setProgram(index, true, true, false, true);
  301. double value;
  302. for (uint32_t i=0; i < plugin->parameterCount(); i++)
  303. {
  304. value = plugin->getParameterValue(i);
  305. engine->osc_send_bridge_set_parameter_value(i, value);
  306. engine->osc_send_bridge_set_default_value(i, value);
  307. }
  308. }
  309. void setMidiProgram(const uint32_t index)
  310. {
  311. qDebug("CarlaPluginClient::setMidiProgram(%i)", index);
  312. Q_ASSERT(engine);
  313. Q_ASSERT(plugin);
  314. if (! (plugin && engine))
  315. return;
  316. plugin->setMidiProgram(index, true, true, false, true);
  317. double value;
  318. for (uint32_t i=0; i < plugin->parameterCount(); i++)
  319. {
  320. value = plugin->getParameterValue(i);
  321. engine->osc_send_bridge_set_parameter_value(i, value);
  322. engine->osc_send_bridge_set_default_value(i, value);
  323. }
  324. }
  325. void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
  326. {
  327. qDebug("CarlaPluginClient::noteOn(%i, %i, %i)", channel, note, velo);
  328. Q_ASSERT(plugin);
  329. Q_ASSERT(velo > 0);
  330. if (! plugin)
  331. return;
  332. plugin->sendMidiSingleNote(channel, note, velo, true, true, false);
  333. }
  334. void noteOff(const uint8_t channel, const uint8_t note)
  335. {
  336. qDebug("CarlaPluginClient::noteOff(%i, %i)", channel, note);
  337. Q_ASSERT(plugin);
  338. if (! plugin)
  339. return;
  340. plugin->sendMidiSingleNote(channel, note, 0, true, true, false);
  341. }
  342. // ---------------------------------------------------------------------
  343. // plugin
  344. void saveNow()
  345. {
  346. qDebug("CarlaPluginClient::saveNow()");
  347. Q_ASSERT(plugin);
  348. Q_ASSERT(engine);
  349. if (! (plugin && engine))
  350. return;
  351. plugin->prepareForSave();
  352. for (uint32_t i=0; i < plugin->customDataCount(); i++)
  353. {
  354. const CarlaBackend::CustomData* const cdata = plugin->customData(i);
  355. engine->osc_send_bridge_set_custom_data(CarlaBackend::getCustomDataTypeString(cdata->type), cdata->key, cdata->value);
  356. }
  357. if (plugin->hints() & CarlaBackend::PLUGIN_USES_CHUNKS)
  358. {
  359. void* data = nullptr;
  360. int32_t dataSize = plugin->chunkData(&data);
  361. if (data && dataSize >= 4)
  362. {
  363. QString filePath;
  364. filePath = QDir::tempPath();
  365. #ifdef Q_OS_WIN
  366. filePath += "\\.CarlaChunk_";
  367. #else
  368. filePath += "/.CarlaChunk_";
  369. #endif
  370. filePath += plugin->name();
  371. QFile file(filePath);
  372. if (file.open(QIODevice::WriteOnly))
  373. {
  374. QByteArray chunk((const char*)data, dataSize);
  375. file.write(chunk);
  376. file.close();
  377. engine->osc_send_bridge_set_chunk_data(filePath.toUtf8().constData());
  378. }
  379. }
  380. }
  381. engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_SAVED, "");
  382. }
  383. void setCustomData(const char* const type, const char* const key, const char* const value)
  384. {
  385. qDebug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value);
  386. Q_ASSERT(plugin);
  387. if (! plugin)
  388. return;
  389. plugin->setCustomData(CarlaBackend::getCustomDataStringType(type), key, value, true);
  390. }
  391. void setChunkData(const char* const filePath)
  392. {
  393. qDebug("CarlaPluginClient::setChunkData(\"%s\")", filePath);
  394. Q_ASSERT(plugin);
  395. if (! plugin)
  396. return;
  397. nextChunkFilePath = QString(filePath);
  398. while (! nextChunkFilePath.isEmpty())
  399. carla_msleep(25);
  400. }
  401. // ---------------------------------------------------------------------
  402. // callback
  403. void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const double value3)
  404. {
  405. qDebug("CarlaPluginClient::handleCallback(%s, %i, %i, %g)", CarlaBackend::CallbackType2str(action), value1, value2, value3);
  406. if (! engine)
  407. return;
  408. switch (action)
  409. {
  410. case CarlaBackend::CALLBACK_PARAMETER_VALUE_CHANGED:
  411. engine->osc_send_bridge_set_parameter_value(value1, value3);
  412. break;
  413. case CarlaBackend::CALLBACK_PROGRAM_CHANGED:
  414. engine->osc_send_bridge_set_program(value1);
  415. break;
  416. case CarlaBackend::CALLBACK_MIDI_PROGRAM_CHANGED:
  417. engine->osc_send_bridge_set_midi_program(value1);
  418. break;
  419. case CarlaBackend::CALLBACK_NOTE_ON:
  420. {
  421. //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_ON, (uint8_t)value1, (uint8_t)value2 };
  422. //osc_send_midi(mdata);
  423. break;
  424. }
  425. case CarlaBackend::CALLBACK_NOTE_OFF:
  426. {
  427. //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_OFF, (uint8_t)value1, (uint8_t)value2 };
  428. //osc_send_midi(mdata);
  429. break;
  430. }
  431. case CarlaBackend::CALLBACK_SHOW_GUI:
  432. if (value1 == 0)
  433. engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, "");
  434. break;
  435. case CarlaBackend::CALLBACK_RESIZE_GUI:
  436. Q_ASSERT(value1 > 0 && value2 > 0);
  437. nextWidth = value1;
  438. nextHeight = value2;
  439. break;
  440. case CarlaBackend::CALLBACK_RELOAD_PARAMETERS:
  441. //if (CARLA_PLUGIN)
  442. //{
  443. // for (uint32_t i=0; i < CARLA_PLUGIN->parameterCount(); i++)
  444. // {
  445. // osc_send_control(i, CARLA_PLUGIN->getParameterValue(i));
  446. // }
  447. //}
  448. break;
  449. case CarlaBackend::CALLBACK_QUIT:
  450. //QApplication::quit();
  451. break;
  452. default:
  453. break;
  454. }
  455. Q_UNUSED(value3);
  456. }
  457. // ---------------------------------------------------------------------
  458. static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3)
  459. {
  460. Q_ASSERT(ptr);
  461. if (! ptr)
  462. return;
  463. BridgePluginClient* const _this_ = (BridgePluginClient*)ptr;
  464. _this_->handleCallback(action, value1, value2, value3);
  465. }
  466. protected:
  467. void guiClosedCallback()
  468. {
  469. }
  470. void timerEvent(QTimerEvent* const event)
  471. {
  472. if (qCloseNow)
  473. return quit();
  474. if (event->timerId() == msgTimer)
  475. {
  476. if (! nextChunkFilePath.isEmpty())
  477. {
  478. #ifdef Q_OS_WIN
  479. if (nextChunkFilePath.startsWith("/"))
  480. {
  481. // running under Wine, posix host
  482. nextChunkFilePath = nextChunkFilePath.replace(0, 1, "Z:/");
  483. nextChunkFilePath = QDir::toNativeSeparators(nextChunkFilePath);
  484. }
  485. #endif
  486. QFile chunkFile(nextChunkFilePath);
  487. if (plugin && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text))
  488. {
  489. QTextStream in(&chunkFile);
  490. QString stringData(in.readAll());
  491. chunkFile.close();
  492. chunkFile.remove();
  493. plugin->setChunkData(stringData.toUtf8().constData());
  494. }
  495. nextChunkFilePath.clear();
  496. }
  497. if (nextWidth > 0 && nextHeight > 0 && pluginGui)
  498. {
  499. pluginGui->setNewSize(nextWidth, nextHeight);
  500. nextWidth = 0;
  501. nextHeight = 0;
  502. }
  503. if (plugin)
  504. plugin->idleGui();
  505. if (! CarlaClient::runMessages())
  506. {
  507. Q_ASSERT(msgTimer == 0);
  508. msgTimer = 0;
  509. return;
  510. }
  511. }
  512. QApplication::timerEvent(event);
  513. }
  514. // ---------------------------------------------------------------------
  515. private:
  516. int msgTimer;
  517. int nextWidth, nextHeight;
  518. QString nextChunkFilePath;
  519. CarlaBackend::CarlaEngine* engine;
  520. CarlaBackend::CarlaPlugin* plugin;
  521. BridgePluginGUI* pluginGui;
  522. };
  523. // -------------------------------------------------------------------------
  524. CARLA_BRIDGE_END_NAMESPACE
  525. int main(int argc, char* argv[])
  526. {
  527. if (argc != 6)
  528. {
  529. qWarning("usage: %s <osc-url|\"null\"> <type> <filename> <name|\"(none)\"> <label>", argv[0]);
  530. return 1;
  531. }
  532. qargc = argc;
  533. qargv = argv;
  534. const char* const oscUrl = argv[1];
  535. const char* const stype = argv[2];
  536. const char* const filename = argv[3];
  537. const char* name = argv[4];
  538. const char* const label = argv[5];
  539. const bool useOsc = strcmp(oscUrl, "null");
  540. if (strcmp(name, "(none)") == 0)
  541. name = nullptr;
  542. CarlaBackend::PluginType itype;
  543. if (strcmp(stype, "LADSPA") == 0)
  544. itype = CarlaBackend::PLUGIN_LADSPA;
  545. else if (strcmp(stype, "DSSI") == 0)
  546. itype = CarlaBackend::PLUGIN_DSSI;
  547. else if (strcmp(stype, "LV2") == 0)
  548. itype = CarlaBackend::PLUGIN_LV2;
  549. else if (strcmp(stype, "VST") == 0)
  550. itype = CarlaBackend::PLUGIN_VST;
  551. else
  552. {
  553. itype = CarlaBackend::PLUGIN_NONE;
  554. qWarning("Invalid plugin type '%s'", stype);
  555. return 1;
  556. }
  557. // Init bridge client
  558. CarlaBridge::BridgePluginClient client;
  559. client.init();
  560. // Init OSC
  561. if (useOsc && ! client.oscInit(oscUrl))
  562. {
  563. client.quit();
  564. return -1;
  565. }
  566. // Init backend engine
  567. CarlaBackend::CarlaEngineJack engine;
  568. engine.setCallback(client.callback, &client);
  569. // bridge client <-> engine
  570. client.registerOscEngine(&engine);
  571. // Init engine
  572. QString engName = QString("%1 (master)").arg(name ? name : label);
  573. engName.truncate(engine.maxClientNameSize());
  574. if (! engine.init(engName.toUtf8().constData()))
  575. {
  576. const char* const lastError = CarlaBackend::getLastError();
  577. qWarning("Bridge engine failed to start, error was:\n%s", lastError);
  578. engine.close();
  579. client.sendOscBridgeError(lastError);
  580. client.quit();
  581. return 2;
  582. }
  583. /// Init plugin
  584. short id = engine.addPlugin(itype, filename, name, label);
  585. int ret;
  586. if (id >= 0 && id < CarlaBackend::MAX_PLUGINS)
  587. {
  588. CarlaBackend::CarlaPlugin* const plugin = engine.getPlugin(id);
  589. client.setStuff(&engine, plugin);
  590. // create window if needed
  591. bool guiResizable;
  592. CarlaBackend::GuiType guiType;
  593. plugin->getGuiInfo(&guiType, &guiResizable);
  594. if (guiType == CarlaBackend::GUI_INTERNAL_QT4 || guiType == CarlaBackend::GUI_INTERNAL_COCOA || guiType == CarlaBackend::GUI_INTERNAL_HWND || guiType == CarlaBackend::GUI_INTERNAL_X11)
  595. {
  596. client.createWindow(guiResizable);
  597. }
  598. if (! useOsc)
  599. plugin->setActive(true, false, false);
  600. ret = 0;
  601. }
  602. else
  603. {
  604. const char* const lastError = CarlaBackend::getLastError();
  605. qWarning("Plugin failed to load, error was:\n%s", lastError);
  606. client.sendOscBridgeError(lastError);
  607. ret = 1;
  608. }
  609. if (ret == 0)
  610. {
  611. initSignalHandler();
  612. client.exec(nullptr, !useOsc);
  613. }
  614. engine.removeAllPlugins();
  615. engine.close();
  616. if (useOsc)
  617. {
  618. // Close OSC
  619. client.sendOscExiting();
  620. client.oscClose();
  621. // bridge client can't be closed manually, only by host
  622. }
  623. else
  624. {
  625. // Close bridge client
  626. client.quit();
  627. }
  628. return ret;
  629. }
  630. #endif // BUILD_BRIDGE_PLUGIN