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.

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