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.

567 lines
14KB

  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. #elif defined(CARLA_OS_LINUX) || defined(CARLA_OS_HAIKU)
  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. CARLA_BACKEND_START_NAMESPACE
  88. extern const char* findDSSIGUI(const char* const filename, const char* const label);
  89. CARLA_BACKEND_END_NAMESPACE
  90. CARLA_BRIDGE_START_NAMESPACE
  91. #if 0
  92. } // Fix editor indentation
  93. #endif
  94. // -------------------------------------------------------------------------
  95. class CarlaPluginClient : public CarlaBridgeClient,
  96. public QObject
  97. {
  98. public:
  99. CarlaPluginClient(const char* const name)
  100. : CarlaBridgeClient(nullptr),
  101. QObject(nullptr),
  102. fEngine(nullptr),
  103. fPlugin(nullptr),
  104. fClosed(false),
  105. fTimerId(0)
  106. {
  107. carla_debug("CarlaPluginClient::CarlaPluginClient()");
  108. carla_engine_init("JACK", name);
  109. carla_set_engine_callback(callback, this);
  110. }
  111. ~CarlaPluginClient()
  112. {
  113. carla_debug("CarlaPluginClient::~CarlaPluginClient()");
  114. CARLA_ASSERT(fTimerId == 0);
  115. carla_set_engine_about_to_close();
  116. carla_engine_close();
  117. }
  118. void ready()
  119. {
  120. CARLA_ASSERT(fTimerId == 0);
  121. fEngine = carla_get_standalone_engine();
  122. fPlugin = fEngine->getPlugin(0);
  123. fTimerId = startTimer(50);
  124. }
  125. void idle()
  126. {
  127. if (fEngine != nullptr)
  128. fEngine->idle();
  129. CarlaBridgeClient::oscIdle();
  130. if (gSaveNow)
  131. {
  132. // TODO
  133. gSaveNow = false;
  134. }
  135. if (gCloseNow)
  136. {
  137. //close();
  138. if (fTimerId != 0)
  139. {
  140. killTimer(fTimerId);
  141. fTimerId = 0;
  142. }
  143. if (QApplication* const app = qApp)
  144. {
  145. if (! app->closingDown())
  146. app->quit();
  147. }
  148. }
  149. }
  150. // ---------------------------------------------------------------------
  151. // plugin management
  152. void saveNow()
  153. {
  154. qDebug("CarlaPluginClient::saveNow()");
  155. CARLA_ASSERT(fEngine != nullptr);
  156. CARLA_ASSERT(fPlugin != nullptr);
  157. if (fPlugin == nullptr || fEngine == nullptr)
  158. return;
  159. fPlugin->prepareForSave();
  160. for (uint32_t i=0; i < fPlugin->customDataCount(); i++)
  161. {
  162. const CarlaBackend::CustomData& cdata = fPlugin->customData(i);
  163. fEngine->osc_send_bridge_set_custom_data(cdata.type, cdata.key, cdata.value);
  164. }
  165. if (fPlugin->options() & CarlaBackend::PLUGIN_OPTION_USE_CHUNKS)
  166. {
  167. void* data = nullptr;
  168. int32_t dataSize = fPlugin->chunkData(&data);
  169. if (data && dataSize >= 4)
  170. {
  171. QString filePath;
  172. filePath = QDir::tempPath();
  173. #ifdef Q_OS_WIN
  174. filePath += "\\.CarlaChunk_";
  175. #else
  176. filePath += "/.CarlaChunk_";
  177. #endif
  178. filePath += fPlugin->name();
  179. QFile file(filePath);
  180. if (file.open(QIODevice::WriteOnly))
  181. {
  182. QByteArray chunk((const char*)data, dataSize);
  183. file.write(chunk);
  184. file.close();
  185. fEngine->osc_send_bridge_set_chunk_data(filePath.toUtf8().constData());
  186. }
  187. }
  188. }
  189. //engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_SAVED, "");
  190. }
  191. void setCustomData(const char* const type, const char* const key, const char* const value)
  192. {
  193. carla_debug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value);
  194. CARLA_ASSERT(fPlugin != nullptr);
  195. if (fPlugin != nullptr)
  196. fPlugin->setCustomData(type, key, value, true);
  197. }
  198. void setChunkData(const char* const filePath)
  199. {
  200. carla_debug("CarlaPluginClient::setChunkData(\"%s\")", filePath);
  201. CARLA_ASSERT(fPlugin != nullptr);
  202. if (fPlugin == nullptr)
  203. return;
  204. QString chunkFilePath(filePath);
  205. #ifdef CARLA_OS_WIN
  206. if (chunkFilePath.startsWith("/"))
  207. {
  208. // running under Wine, posix host
  209. chunkFilePath = chunkFilePath.replace(0, 1, "Z:/");
  210. chunkFilePath = QDir::toNativeSeparators(chunkFilePath);
  211. }
  212. #endif
  213. QFile chunkFile(chunkFilePath);
  214. if (fPlugin != nullptr && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text))
  215. {
  216. QTextStream in(&chunkFile);
  217. QString stringData(in.readAll());
  218. chunkFile.close();
  219. chunkFile.remove();
  220. fPlugin->setChunkData(stringData.toUtf8().constData());
  221. }
  222. }
  223. // ---------------------------------------------------------------------
  224. // processing
  225. void setParameter(const int32_t rindex, const float value)
  226. {
  227. carla_debug("CarlaPluginClient::setParameter(%i, %f)", rindex, value);
  228. CARLA_ASSERT(fPlugin != nullptr);
  229. if (fPlugin != nullptr)
  230. fPlugin->setParameterValueByRIndex(rindex, value, true, true, false);
  231. }
  232. void setProgram(const uint32_t index)
  233. {
  234. carla_debug("CarlaPluginClient::setProgram(%i)", index);
  235. CARLA_ASSERT(fPlugin != nullptr);
  236. if (fPlugin != nullptr)
  237. fPlugin->setProgram(index, true, true, false);
  238. }
  239. void setMidiProgram(const uint32_t index)
  240. {
  241. carla_debug("CarlaPluginClient::setMidiProgram(%i)", index);
  242. CARLA_ASSERT(fPlugin != nullptr);
  243. if (fPlugin != nullptr)
  244. fPlugin->setMidiProgram(index, true, true, false);
  245. }
  246. void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
  247. {
  248. carla_debug("CarlaPluginClient::noteOn(%i, %i, %i)", channel, note, velo);
  249. CARLA_ASSERT(fPlugin != nullptr);
  250. CARLA_ASSERT(velo > 0);
  251. if (fPlugin != nullptr)
  252. fPlugin->sendMidiSingleNote(channel, note, velo, true, true, false);
  253. }
  254. void noteOff(const uint8_t channel, const uint8_t note)
  255. {
  256. carla_debug("CarlaPluginClient::noteOff(%i, %i)", channel, note);
  257. CARLA_ASSERT(fPlugin != nullptr);
  258. if (fPlugin != nullptr)
  259. fPlugin->sendMidiSingleNote(channel, note, 0, true, true, false);
  260. }
  261. protected:
  262. void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const float value3, const char* const valueStr)
  263. {
  264. CARLA_BACKEND_USE_NAMESPACE;
  265. switch (action)
  266. {
  267. case CALLBACK_SHOW_GUI:
  268. if (value1 != 1 && ! isOscControlRegistered())
  269. gCloseNow = true;
  270. break;
  271. default: // TODO
  272. (void)value2;
  273. (void)value3;
  274. (void)valueStr;
  275. break;
  276. }
  277. }
  278. private:
  279. CarlaBackend::CarlaEngine* fEngine;
  280. CarlaBackend::CarlaPlugin* fPlugin;
  281. bool fClosed;
  282. int fTimerId;
  283. void timerEvent(QTimerEvent* const event)
  284. {
  285. if (event->timerId() == fTimerId)
  286. {
  287. idle();
  288. }
  289. QObject::timerEvent(event);
  290. }
  291. static void callback(void* ptr, CarlaBackend::CallbackType action, unsigned int pluginId, int value1, int value2, float value3, const char* valueStr)
  292. {
  293. return ((CarlaPluginClient*)ptr)->handleCallback(action, value1, value2, value3, valueStr);
  294. // unused
  295. (void)pluginId;
  296. }
  297. };
  298. // -------------------------------------------------------------------------
  299. int CarlaBridgeOsc::handleMsgShow()
  300. {
  301. carla_debug("CarlaBridgeOsc::handleMsgShow()");
  302. CARLA_ASSERT(kClient != nullptr);
  303. if (kClient == nullptr)
  304. return 1;
  305. carla_show_gui(0, true);
  306. return 0;
  307. }
  308. int CarlaBridgeOsc::handleMsgHide()
  309. {
  310. carla_debug("CarlaBridgeOsc::handleMsgHide()");
  311. CARLA_ASSERT(kClient != nullptr);
  312. if (kClient == nullptr)
  313. return 1;
  314. carla_show_gui(0, false);
  315. return 0;
  316. }
  317. int CarlaBridgeOsc::handleMsgQuit()
  318. {
  319. carla_debug("CarlaBridgeOsc::handleMsgQuit()");
  320. CARLA_ASSERT(kClient != nullptr);
  321. if (kClient == nullptr)
  322. return 1;
  323. gCloseNow = true;
  324. return 0;
  325. }
  326. // -------------------------------------------------------------------------
  327. int CarlaBridgeOsc::handleMsgPluginSaveNow()
  328. {
  329. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  330. CARLA_ASSERT(kClient != nullptr);
  331. if (kClient == nullptr)
  332. return 1;
  333. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  334. plugClient->saveNow();
  335. return 0;
  336. }
  337. int CarlaBridgeOsc::handleMsgPluginSetChunk(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  338. {
  339. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  340. CARLA_ASSERT(kClient != nullptr);
  341. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(1, "s");
  342. if (kClient == nullptr)
  343. return 1;
  344. const char* const chunkFile = (const char*)&argv[0]->s;
  345. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  346. plugClient->setChunkData(chunkFile);
  347. return 0;
  348. }
  349. int CarlaBridgeOsc::handleMsgPluginSetCustomData(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  350. {
  351. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  352. CARLA_ASSERT(kClient != nullptr);
  353. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "sss");
  354. if (kClient == nullptr)
  355. return 1;
  356. const char* const type = (const char*)&argv[0]->s;
  357. const char* const key = (const char*)&argv[1]->s;
  358. const char* const value = (const char*)&argv[2]->s;
  359. CarlaPluginClient* const plugClient = (CarlaPluginClient*)kClient;
  360. plugClient->setCustomData(type, key, value);
  361. return 0;
  362. }
  363. // -------------------------------------------------------------------------
  364. CARLA_BRIDGE_END_NAMESPACE
  365. int main(int argc, char* argv[])
  366. {
  367. CARLA_BRIDGE_USE_NAMESPACE
  368. if (argc != 6)
  369. {
  370. carla_stdout("usage: %s <osc-url|\"null\"> <type> <filename> <name|\"(none)\"> <label>", argv[0]);
  371. return 1;
  372. }
  373. const char* const oscUrl = argv[1];
  374. const char* const stype = argv[2];
  375. const char* const filename = argv[3];
  376. const char* name = argv[4];
  377. const char* const label = argv[5];
  378. const bool useOsc = std::strcmp(oscUrl, "null");
  379. if (std::strcmp(name, "(none)") == 0)
  380. name = nullptr;
  381. CarlaBackend::PluginType itype;
  382. if (std::strcmp(stype, "LADSPA") == 0)
  383. itype = CarlaBackend::PLUGIN_LADSPA;
  384. else if (std::strcmp(stype, "DSSI") == 0)
  385. itype = CarlaBackend::PLUGIN_DSSI;
  386. else if (std::strcmp(stype, "LV2") == 0)
  387. itype = CarlaBackend::PLUGIN_LV2;
  388. else if (std::strcmp(stype, "VST") == 0)
  389. itype = CarlaBackend::PLUGIN_VST;
  390. else if (std::strcmp(stype, "VST3") == 0)
  391. itype = CarlaBackend::PLUGIN_VST3;
  392. else
  393. {
  394. carla_stderr("Invalid plugin type '%s'", stype);
  395. return 1;
  396. }
  397. QApplication app(argc, argv, true);
  398. app.setQuitOnLastWindowClosed(false);
  399. // Init Plugin client
  400. CarlaPluginClient client(name ? name : label);
  401. // Init OSC
  402. if (useOsc)
  403. client.oscInit(oscUrl);
  404. // Listen for ctrl+c or sigint/sigterm events
  405. initSignalHandler();
  406. const void* extraStuff = nullptr;
  407. if (itype == CarlaBackend::PLUGIN_DSSI)
  408. extraStuff = CarlaBackend::findDSSIGUI(filename, label);
  409. // Init plugin
  410. int ret;
  411. if (carla_add_plugin(CarlaBackend::BINARY_NATIVE, itype, filename, name, label, extraStuff))
  412. {
  413. if (useOsc)
  414. {
  415. client.sendOscUpdate();
  416. client.sendOscBridgeUpdate();
  417. }
  418. else
  419. {
  420. carla_set_active(0, true);
  421. carla_show_gui(0, true);
  422. }
  423. client.ready();
  424. ret = app.exec();
  425. carla_remove_plugin(0);
  426. }
  427. else
  428. {
  429. const char* const lastError = carla_get_last_error();
  430. carla_stderr("Plugin failed to load, error was:\n%s", lastError);
  431. if (useOsc)
  432. client.sendOscBridgeError(lastError);
  433. ret = 1;
  434. }
  435. if (extraStuff != nullptr && itype == CarlaBackend::PLUGIN_DSSI)
  436. delete[] (const char*)extraStuff;
  437. // Close OSC
  438. if (useOsc)
  439. client.oscClose();
  440. return ret;
  441. }
  442. #endif // BRIDGE_PLUGIN