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.

587 lines
15KB

  1. /*
  2. * Carla Bridge Plugin
  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 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 GNU General Public License see the GPL.txt file
  16. */
  17. #ifdef BRIDGE_PLUGIN
  18. #include "CarlaBridgeClient.hpp"
  19. #include "CarlaBridgeToolkit.hpp"
  20. #include "CarlaStandalone.hpp"
  21. #include "CarlaEngine.hpp"
  22. #include "CarlaPlugin.hpp"
  23. //#include <set>
  24. #include <QtCore/QDir>
  25. #include <QtCore/QFile>
  26. #include <QtCore/QTextStream>
  27. //#include <QtXml/QDomDocument>
  28. #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
  29. # include <QtWidgets/QApplication>
  30. #else
  31. # include <QtGui/QApplication>
  32. #endif
  33. #ifdef CARLA_OS_UNIX
  34. # include <signal.h>
  35. #endif
  36. // -------------------------------------------------------------------------
  37. static bool gCloseNow = false;
  38. static bool gSaveNow = false;
  39. #ifdef CARLA_OS_WIN
  40. BOOL WINAPI closeSignalHandler(DWORD dwCtrlType)
  41. {
  42. if (dwCtrlType == CTRL_C_EVENT)
  43. {
  44. gCloseNow = true;
  45. return TRUE;
  46. }
  47. return FALSE;
  48. }
  49. #else
  50. void closeSignalHandler(int)
  51. {
  52. gCloseNow = true;
  53. }
  54. void saveSignalHandler(int)
  55. {
  56. gSaveNow = true;
  57. }
  58. #endif
  59. void initSignalHandler()
  60. {
  61. #ifdef CARLA_OS_WIN
  62. SetConsoleCtrlHandler(closeSignalHandler, TRUE);
  63. #else
  64. struct sigaction sint;
  65. struct sigaction sterm;
  66. struct sigaction susr1;
  67. sint.sa_handler = closeSignalHandler;
  68. sint.sa_flags = SA_RESTART;
  69. sint.sa_restorer = nullptr;
  70. sigemptyset(&sint.sa_mask);
  71. sigaction(SIGINT, &sint, nullptr);
  72. sterm.sa_handler = closeSignalHandler;
  73. sterm.sa_flags = SA_RESTART;
  74. sterm.sa_restorer = nullptr;
  75. sigemptyset(&sterm.sa_mask);
  76. sigaction(SIGTERM, &sterm, nullptr);
  77. susr1.sa_handler = saveSignalHandler;
  78. susr1.sa_flags = SA_RESTART;
  79. susr1.sa_restorer = nullptr;
  80. sigemptyset(&susr1.sa_mask);
  81. sigaction(SIGUSR1, &susr1, nullptr);
  82. #endif
  83. }
  84. // -------------------------------------------------------------------------
  85. // Helpers
  86. extern CarlaBackend::CarlaEngine* carla_get_standalone_engine();
  87. const char* findDSSIGUI(const char* const filename, const char* const label)
  88. {
  89. QString guiFilename;
  90. guiFilename.clear();
  91. QString pluginDir(filename);
  92. pluginDir.resize(pluginDir.lastIndexOf("."));
  93. QString shortName = QFileInfo(pluginDir).baseName();
  94. QString checkLabel = QString(label);
  95. QString checkSName = shortName;
  96. if (! checkLabel.endsWith("_")) checkLabel += "_";
  97. if (! checkSName.endsWith("_")) checkSName += "_";
  98. QStringList guiFiles = QDir(pluginDir).entryList();
  99. foreach (const QString& gui, guiFiles)
  100. {
  101. if (gui.startsWith(checkLabel) || gui.startsWith(checkSName))
  102. {
  103. QFileInfo finalname(pluginDir + QDir::separator() + gui);
  104. guiFilename = finalname.absoluteFilePath();
  105. break;
  106. }
  107. }
  108. if (guiFilename.isEmpty())
  109. return nullptr;
  110. return carla_strdup(guiFilename.toUtf8().constData());
  111. }
  112. CARLA_BRIDGE_START_NAMESPACE
  113. #if 0
  114. } // Fix editor indentation
  115. #endif
  116. // -------------------------------------------------------------------------
  117. class CarlaPluginClient : public CarlaBridgeClient,
  118. public QObject
  119. {
  120. public:
  121. CarlaPluginClient(const char* const name)
  122. : CarlaBridgeClient(nullptr),
  123. QObject(nullptr),
  124. fEngine(nullptr),
  125. fPlugin(nullptr),
  126. fClosed(false),
  127. fTimerId(0)
  128. {
  129. carla_debug("CarlaPluginClient::CarlaPluginClient()");
  130. carla_engine_init("JACK", name);
  131. carla_set_engine_callback(callback, this);
  132. }
  133. ~CarlaPluginClient()
  134. {
  135. carla_debug("CarlaPluginClient::~CarlaPluginClient()");
  136. CARLA_ASSERT(fTimerId == 0);
  137. carla_set_engine_about_to_close();
  138. carla_engine_close();
  139. }
  140. void ready()
  141. {
  142. CARLA_ASSERT(fTimerId == 0);
  143. fEngine = carla_get_standalone_engine();
  144. fPlugin = fEngine->getPlugin(0);
  145. fTimerId = startTimer(50);
  146. }
  147. void idle()
  148. {
  149. if (fEngine != nullptr)
  150. fEngine->idle();
  151. CarlaBridgeClient::oscIdle();
  152. if (gSaveNow)
  153. {
  154. // TODO
  155. gSaveNow = false;
  156. }
  157. if (gCloseNow)
  158. {
  159. //close();
  160. if (fTimerId != 0)
  161. {
  162. killTimer(fTimerId);
  163. fTimerId = 0;
  164. }
  165. if (QApplication* const app = qApp)
  166. {
  167. if (! app->closingDown())
  168. app->quit();
  169. }
  170. }
  171. }
  172. // ---------------------------------------------------------------------
  173. // plugin management
  174. void saveNow()
  175. {
  176. qDebug("CarlaPluginClient::saveNow()");
  177. CARLA_ASSERT(fEngine != nullptr);
  178. CARLA_ASSERT(fPlugin != nullptr);
  179. if (fPlugin == nullptr || fEngine == nullptr)
  180. return;
  181. fPlugin->prepareForSave();
  182. for (uint32_t i=0; i < fPlugin->customDataCount(); i++)
  183. {
  184. const CarlaBackend::CustomData& cdata = fPlugin->customData(i);
  185. fEngine->osc_send_bridge_set_custom_data(cdata.type, cdata.key, cdata.value);
  186. }
  187. if (fPlugin->options() & CarlaBackend::PLUGIN_OPTION_USE_CHUNKS)
  188. {
  189. void* data = nullptr;
  190. int32_t dataSize = fPlugin->chunkData(&data);
  191. if (data && dataSize >= 4)
  192. {
  193. QString filePath;
  194. filePath = QDir::tempPath();
  195. #ifdef Q_OS_WIN
  196. filePath += "\\.CarlaChunk_";
  197. #else
  198. filePath += "/.CarlaChunk_";
  199. #endif
  200. filePath += fPlugin->name();
  201. QFile file(filePath);
  202. if (file.open(QIODevice::WriteOnly))
  203. {
  204. QByteArray chunk((const char*)data, dataSize);
  205. file.write(chunk);
  206. file.close();
  207. fEngine->osc_send_bridge_set_chunk_data(filePath.toUtf8().constData());
  208. }
  209. }
  210. }
  211. //engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_SAVED, "");
  212. }
  213. void setCustomData(const char* const type, const char* const key, const char* const value)
  214. {
  215. carla_debug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value);
  216. CARLA_ASSERT(fPlugin != nullptr);
  217. if (fPlugin != nullptr)
  218. fPlugin->setCustomData(type, key, value, true);
  219. }
  220. void setChunkData(const char* const filePath)
  221. {
  222. carla_debug("CarlaPluginClient::setChunkData(\"%s\")", filePath);
  223. CARLA_ASSERT(fPlugin != nullptr);
  224. if (fPlugin == nullptr)
  225. return;
  226. QString chunkFilePath(filePath);
  227. #ifdef CARLA_OS_WIN
  228. if (chunkFilePath.startsWith("/"))
  229. {
  230. // running under Wine, posix host
  231. chunkFilePath = chunkFilePath.replace(0, 1, "Z:/");
  232. chunkFilePath = QDir::toNativeSeparators(chunkFilePath);
  233. }
  234. #endif
  235. QFile chunkFile(chunkFilePath);
  236. if (fPlugin != nullptr && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text))
  237. {
  238. QTextStream in(&chunkFile);
  239. QString stringData(in.readAll());
  240. chunkFile.close();
  241. chunkFile.remove();
  242. fPlugin->setChunkData(stringData.toUtf8().constData());
  243. }
  244. }
  245. // ---------------------------------------------------------------------
  246. // processing
  247. void setParameter(const int32_t rindex, const float value)
  248. {
  249. carla_debug("CarlaPluginClient::setParameter(%i, %f)", rindex, value);
  250. CARLA_ASSERT(fPlugin != nullptr);
  251. if (fPlugin != nullptr)
  252. fPlugin->setParameterValueByRIndex(rindex, value, true, true, false);
  253. }
  254. void setProgram(const uint32_t index)
  255. {
  256. carla_debug("CarlaPluginClient::setProgram(%i)", index);
  257. CARLA_ASSERT(fPlugin != nullptr);
  258. if (fPlugin != nullptr)
  259. fPlugin->setProgram(index, true, true, false);
  260. }
  261. void setMidiProgram(const uint32_t index)
  262. {
  263. carla_debug("CarlaPluginClient::setMidiProgram(%i)", index);
  264. CARLA_ASSERT(fPlugin != nullptr);
  265. if (fPlugin != nullptr)
  266. fPlugin->setMidiProgram(index, true, true, false);
  267. }
  268. void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
  269. {
  270. carla_debug("CarlaPluginClient::noteOn(%i, %i, %i)", channel, note, velo);
  271. CARLA_ASSERT(fPlugin != nullptr);
  272. CARLA_ASSERT(velo > 0);
  273. if (fPlugin != nullptr)
  274. fPlugin->sendMidiSingleNote(channel, note, velo, true, true, false);
  275. }
  276. void noteOff(const uint8_t channel, const uint8_t note)
  277. {
  278. carla_debug("CarlaPluginClient::noteOff(%i, %i)", channel, note);
  279. CARLA_ASSERT(fPlugin != nullptr);
  280. if (fPlugin != nullptr)
  281. fPlugin->sendMidiSingleNote(channel, note, 0, true, true, false);
  282. }
  283. protected:
  284. void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const float value3, const char* const valueStr)
  285. {
  286. CARLA_BACKEND_USE_NAMESPACE;
  287. switch (action)
  288. {
  289. case CALLBACK_SHOW_GUI:
  290. if (value1 != 1 && ! isOscControlRegistered())
  291. gCloseNow = true;
  292. break;
  293. }
  294. }
  295. private:
  296. CarlaBackend::CarlaEngine* fEngine;
  297. CarlaBackend::CarlaPlugin* fPlugin;
  298. bool fClosed;
  299. int fTimerId;
  300. void timerEvent(QTimerEvent* const event)
  301. {
  302. if (event->timerId() == fTimerId)
  303. {
  304. idle();
  305. }
  306. QObject::timerEvent(event);
  307. }
  308. static void callback(void* ptr, CarlaBackend::CallbackType action, unsigned int pluginId, int value1, int value2, float value3, const char* valueStr)
  309. {
  310. return ((CarlaPluginClient*)ptr)->handleCallback(action, value1, value2, value3, valueStr);
  311. }
  312. };
  313. // -------------------------------------------------------------------------
  314. int CarlaBridgeOsc::handleMsgShow()
  315. {
  316. carla_debug("CarlaBridgeOsc::handleMsgShow()");
  317. CARLA_ASSERT(kClient != nullptr);
  318. if (kClient == nullptr)
  319. return 1;
  320. carla_show_gui(0, true);
  321. return 0;
  322. }
  323. int CarlaBridgeOsc::handleMsgHide()
  324. {
  325. carla_debug("CarlaBridgeOsc::handleMsgHide()");
  326. CARLA_ASSERT(kClient != nullptr);
  327. if (kClient == nullptr)
  328. return 1;
  329. carla_show_gui(0, false);
  330. return 0;
  331. }
  332. int CarlaBridgeOsc::handleMsgQuit()
  333. {
  334. carla_debug("CarlaBridgeOsc::handleMsgQuit()");
  335. CARLA_ASSERT(kClient != nullptr);
  336. if (kClient == nullptr)
  337. return 1;
  338. gCloseNow = true;
  339. return 0;
  340. }
  341. // -------------------------------------------------------------------------
  342. int CarlaBridgeOsc::handleMsgPluginSaveNow()
  343. {
  344. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  345. CARLA_ASSERT(kClient != nullptr);
  346. if (kClient == nullptr)
  347. return 1;
  348. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  349. plugClient->saveNow();
  350. return 0;
  351. }
  352. int CarlaBridgeOsc::handleMsgPluginSetChunk(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  353. {
  354. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  355. CARLA_ASSERT(kClient != nullptr);
  356. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(1, "s");
  357. if (kClient == nullptr)
  358. return 1;
  359. const char* const chunkFile = (const char*)&argv[0]->s;
  360. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  361. plugClient->setChunkData(chunkFile);
  362. return 0;
  363. }
  364. int CarlaBridgeOsc::handleMsgPluginSetCustomData(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  365. {
  366. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  367. CARLA_ASSERT(kClient != nullptr);
  368. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "sss");
  369. if (kClient == nullptr)
  370. return 1;
  371. const char* const type = (const char*)&argv[0]->s;
  372. const char* const key = (const char*)&argv[1]->s;
  373. const char* const value = (const char*)&argv[2]->s;
  374. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  375. plugClient->setCustomData(type, key, value);
  376. return 0;
  377. }
  378. // -------------------------------------------------------------------------
  379. CARLA_BRIDGE_END_NAMESPACE
  380. int main(int argc, char* argv[])
  381. {
  382. CARLA_BRIDGE_USE_NAMESPACE
  383. if (argc != 6)
  384. {
  385. carla_stdout("usage: %s <osc-url|\"null\"> <type> <filename> <name|\"(none)\"> <label>", argv[0]);
  386. return 1;
  387. }
  388. const char* const oscUrl = argv[1];
  389. const char* const stype = argv[2];
  390. const char* const filename = argv[3];
  391. const char* name = argv[4];
  392. const char* const label = argv[5];
  393. const bool useOsc = std::strcmp(oscUrl, "null");
  394. if (std::strcmp(name, "(none)") == 0)
  395. name = nullptr;
  396. CarlaBackend::PluginType itype;
  397. if (std::strcmp(stype, "LADSPA") == 0)
  398. itype = CarlaBackend::PLUGIN_LADSPA;
  399. else if (std::strcmp(stype, "DSSI") == 0)
  400. itype = CarlaBackend::PLUGIN_DSSI;
  401. else if (std::strcmp(stype, "LV2") == 0)
  402. itype = CarlaBackend::PLUGIN_LV2;
  403. else if (std::strcmp(stype, "VST") == 0)
  404. itype = CarlaBackend::PLUGIN_VST;
  405. else
  406. {
  407. carla_stderr("Invalid plugin type '%s'", stype);
  408. return 1;
  409. }
  410. QApplication app(argc, argv, true);
  411. // Init Plugin client
  412. CarlaPluginClient client(name ? name : label);
  413. // Init OSC
  414. if (useOsc)
  415. client.oscInit(oscUrl);
  416. // Listen for ctrl+c or sigint/sigterm events
  417. initSignalHandler();
  418. const void* extraStuff = nullptr;
  419. if (itype == CarlaBackend::PLUGIN_DSSI)
  420. extraStuff = findDSSIGUI(filename, label);
  421. // Init plugin
  422. int ret;
  423. if (carla_add_plugin(CarlaBackend::BINARY_NATIVE, itype, filename, name, label, extraStuff))
  424. {
  425. if (useOsc)
  426. {
  427. app.setQuitOnLastWindowClosed(false);
  428. client.sendOscUpdate();
  429. client.sendOscBridgeUpdate();
  430. }
  431. else
  432. {
  433. carla_set_active(0, true);
  434. carla_show_gui(0, true);
  435. }
  436. client.ready();
  437. ret = app.exec();
  438. carla_remove_plugin(0);
  439. }
  440. else
  441. {
  442. const char* const lastError = carla_get_last_error();
  443. carla_stderr("Plugin failed to load, error was:\n%s", lastError);
  444. if (useOsc)
  445. client.sendOscBridgeError(lastError);
  446. ret = 1;
  447. }
  448. if (extraStuff != nullptr && itype == CarlaBackend::PLUGIN_DSSI)
  449. delete[] (const char*)extraStuff;
  450. // Close OSC
  451. if (useOsc)
  452. client.oscClose();
  453. return ret;
  454. }
  455. #endif // BRIDGE_PLUGIN