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.

612 lines
16KB

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