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.

709 lines
20KB

  1. /*
  2. * Carla Bridge Plugin
  3. * Copyright (C) 2012-2014 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 "CarlaEngine.hpp"
  19. #include "CarlaPlugin.hpp"
  20. #include "CarlaHost.h"
  21. #include "CarlaBackendUtils.hpp"
  22. #include "CarlaBridgeUtils.hpp"
  23. #include "CarlaMIDI.h"
  24. #ifdef CARLA_OS_UNIX
  25. # include <signal.h>
  26. #endif
  27. // TODO
  28. #if 0
  29. # include "juce_gui_basics.h"
  30. using juce::JUCEApplication;
  31. using juce::JUCEApplicationBase;
  32. using juce::String;
  33. using juce::Timer;
  34. #endif
  35. // -------------------------------------------------------------------------
  36. static bool gIsInitiated = false;
  37. static volatile bool gCloseNow = false;
  38. static volatile bool gSaveNow = false;
  39. #ifdef CARLA_OS_WIN
  40. static BOOL WINAPI winSignalHandler(DWORD dwCtrlType)
  41. {
  42. if (dwCtrlType == CTRL_C_EVENT)
  43. {
  44. gCloseNow = true;
  45. return TRUE;
  46. }
  47. return FALSE;
  48. }
  49. #elif defined(CARLA_OS_LINUX)
  50. static void closeSignalHandler(int)
  51. {
  52. gCloseNow = true;
  53. }
  54. static void saveSignalHandler(int)
  55. {
  56. gSaveNow = true;
  57. }
  58. #endif
  59. static void initSignalHandler()
  60. {
  61. #ifdef CARLA_OS_WIN
  62. SetConsoleCtrlHandler(winSignalHandler, TRUE);
  63. #elif defined(CARLA_OS_LINUX)
  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. #ifdef HACE_JUCE_UI
  86. static CarlaBridge::CarlaBridgeClient* gBridgeClient = nullptr;
  87. class CarlaJuceApp : public JUCEApplication,
  88. Timer
  89. {
  90. public:
  91. CarlaJuceApp() {}
  92. ~CarlaJuceApp() {}
  93. void initialise(const String&) override
  94. {
  95. startTimer(30);
  96. }
  97. void shutdown() override
  98. {
  99. gCloseNow = true;
  100. stopTimer();
  101. }
  102. const String getApplicationName() override
  103. {
  104. return "CarlaPlugin";
  105. }
  106. const String getApplicationVersion() override
  107. {
  108. return CARLA_VERSION_STRING;
  109. }
  110. void timerCallback() override
  111. {
  112. carla_engine_idle();
  113. if (gBridgeClient != nullptr)
  114. gBridgeClient->oscIdle();
  115. if (gCloseNow)
  116. {
  117. quit();
  118. gCloseNow = false;
  119. }
  120. }
  121. };
  122. static JUCEApplicationBase* juce_CreateApplication() { return new CarlaJuceApp(); }
  123. #endif
  124. // -------------------------------------------------------------------------
  125. CARLA_BRIDGE_START_NAMESPACE
  126. #if 0
  127. } // Fix editor indentation
  128. #endif
  129. // -------------------------------------------------------------------------
  130. class CarlaPluginClient : public CarlaBridgeClient
  131. {
  132. public:
  133. CarlaPluginClient(const bool useBridge, const char* const clientName, const char* const audioBaseName, const char* const controlBaseName, const char* const timeBaseName)
  134. : CarlaBridgeClient(nullptr),
  135. fPlugin(nullptr),
  136. fEngine(nullptr)
  137. {
  138. CARLA_ASSERT(clientName != nullptr && clientName[0] != '\0');
  139. carla_debug("CarlaPluginClient::CarlaPluginClient(%s, \"%s\", %s, %s, %s)", bool2str(useBridge), clientName, audioBaseName, controlBaseName, timeBaseName);
  140. carla_set_engine_callback(callback, this);
  141. if (useBridge)
  142. carla_engine_init_bridge(audioBaseName, controlBaseName, timeBaseName, clientName);
  143. else
  144. carla_engine_init("JACK", clientName);
  145. fEngine = carla_get_engine();
  146. }
  147. ~CarlaPluginClient() override
  148. {
  149. carla_debug("CarlaPluginClient::~CarlaPluginClient()");
  150. #ifdef HACE_JUCE_UI
  151. gBridgeClient = nullptr;
  152. #endif
  153. carla_engine_close();
  154. }
  155. bool isOk() const noexcept
  156. {
  157. return (fEngine != nullptr);
  158. }
  159. void oscInit(const char* const url)
  160. {
  161. CarlaBridgeClient::oscInit(url);
  162. fEngine->setOscBridgeData(&fOscData);
  163. }
  164. void ready(const bool doSaveLoad)
  165. {
  166. fPlugin = fEngine->getPlugin(0);
  167. if (doSaveLoad)
  168. {
  169. fProjFileName = fPlugin->getName();
  170. fProjFileName += ".carxs";
  171. //if (! File::isAbsolutePath((const char*)fProjFileName))
  172. // fProjFileName = File::getCurrentWorkingDirectory().getChildFile((const char*)fProjFileName).getFullPathName().toRawUTF8();
  173. //if (! fPlugin->loadStateFromFile(fProjFileName))
  174. // carla_stderr("Plugin preset load failed, error was:\n%s", fEngine->getLastError());
  175. }
  176. }
  177. #if 1
  178. void idle()
  179. {
  180. CARLA_SAFE_ASSERT_RETURN(fEngine != nullptr,);
  181. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  182. carla_engine_idle();
  183. CarlaBridgeClient::oscIdle();
  184. if (gSaveNow)
  185. {
  186. gSaveNow = false;
  187. if (fProjFileName.isNotEmpty())
  188. {
  189. if (! fPlugin->saveStateToFile(fProjFileName))
  190. carla_stderr("Plugin preset save failed, error was:\n%s", fEngine->getLastError());
  191. }
  192. }
  193. if (gCloseNow)
  194. {
  195. //gCloseNow = false;
  196. // close something?
  197. }
  198. }
  199. #endif
  200. void exec(int argc, char* argv[])
  201. {
  202. #if 0
  203. gBridgeClient = this;
  204. JUCEApplicationBase::createInstance = &juce_CreateApplication;
  205. JUCEApplicationBase::main(JUCE_MAIN_FUNCTION_ARGS);
  206. #else
  207. for (; ! gCloseNow;)
  208. {
  209. idle();
  210. carla_msleep(24);
  211. }
  212. #endif
  213. // may be unused
  214. return; (void)argc; (void)argv;
  215. }
  216. // ---------------------------------------------------------------------
  217. // plugin management
  218. void saveNow()
  219. {
  220. CARLA_SAFE_ASSERT_RETURN(fEngine != nullptr,);
  221. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  222. carla_debug("CarlaPluginClient::saveNow()");
  223. fPlugin->prepareForSave();
  224. for (uint32_t i=0; i < fPlugin->getCustomDataCount(); ++i)
  225. {
  226. const CarlaBackend::CustomData& cdata(fPlugin->getCustomData(i));
  227. fEngine->oscSend_bridge_set_custom_data(cdata.type, cdata.key, cdata.value);
  228. }
  229. if (fPlugin->getOptionsEnabled() & CarlaBackend::PLUGIN_OPTION_USE_CHUNKS)
  230. {
  231. void* data = nullptr;
  232. int32_t dataSize = fPlugin->getChunkData(&data);
  233. if (data && dataSize >= 4)
  234. {
  235. #if 0
  236. QString filePath;
  237. filePath = QDir::tempPath();
  238. #ifdef Q_OS_WIN
  239. filePath += "\\.CarlaChunk_";
  240. #else
  241. filePath += "/.CarlaChunk_";
  242. #endif
  243. filePath += fPlugin->getName();
  244. QFile file(filePath);
  245. if (file.open(QIODevice::WriteOnly))
  246. {
  247. QByteArray chunk((const char*)data, dataSize);
  248. file.write(chunk);
  249. file.close();
  250. fEngine->oscSend_bridge_set_chunk_data(filePath.toUtf8().constData());
  251. }
  252. #endif
  253. }
  254. }
  255. fEngine->oscSend_bridge_configure(CARLA_BRIDGE_MSG_SAVED, "");
  256. }
  257. void setParameterMidiChannel(const uint32_t index, const uint8_t channel)
  258. {
  259. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  260. carla_debug("CarlaPluginClient::setParameterMidiChannel(%i, %i)", index, channel);
  261. fPlugin->setParameterMidiChannel(index, channel, false, false);
  262. }
  263. void setParameterMidiCC(const uint32_t index, const int16_t cc)
  264. {
  265. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  266. carla_debug("CarlaPluginClient::setParameterMidiCC(%i, %i)", index, cc);
  267. fPlugin->setParameterMidiCC(index, cc, false, false);
  268. }
  269. void setCustomData(const char* const type, const char* const key, const char* const value)
  270. {
  271. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  272. carla_debug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value);
  273. fPlugin->setCustomData(type, key, value, true);
  274. }
  275. void setChunkData(const char* const filePath)
  276. {
  277. CARLA_SAFE_ASSERT_RETURN(filePath != nullptr && filePath[0] != '\0',);
  278. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  279. carla_debug("CarlaPluginClient::setChunkData(\"%s\")", filePath);
  280. #if 0
  281. QString chunkFilePath(filePath);
  282. #ifdef CARLA_OS_WIN
  283. if (chunkFilePath.startsWith("/"))
  284. {
  285. // running under Wine, posix host
  286. chunkFilePath = chunkFilePath.replace(0, 1, "Z:/");
  287. chunkFilePath = QDir::toNativeSeparators(chunkFilePath);
  288. }
  289. #endif
  290. QFile chunkFile(chunkFilePath);
  291. if (fPlugin != nullptr && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text))
  292. {
  293. QTextStream in(&chunkFile);
  294. QString stringData(in.readAll());
  295. chunkFile.close();
  296. chunkFile.remove();
  297. fPlugin->setChunkData(stringData.toUtf8().constData());
  298. }
  299. #endif
  300. }
  301. // ---------------------------------------------------------------------
  302. protected:
  303. void handleCallback(const CarlaBackend::EngineCallbackOpcode action, const int value1, const int value2, const float value3, const char* const valueStr)
  304. {
  305. CARLA_BACKEND_USE_NAMESPACE;
  306. // TODO
  307. switch (action)
  308. {
  309. case ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED:
  310. if (isOscControlRegistered())
  311. {
  312. CARLA_SAFE_ASSERT_RETURN(value1 >= 0,);
  313. fEngine->oscSend_bridge_parameter_value(static_cast<uint32_t>(value1), value3);
  314. }
  315. break;
  316. case ENGINE_CALLBACK_UI_STATE_CHANGED:
  317. if (! isOscControlRegistered())
  318. {
  319. if (value1 != 1 && gIsInitiated)
  320. gCloseNow = true;
  321. }
  322. else
  323. {
  324. // show-gui button
  325. fEngine->oscSend_bridge_configure(CARLA_BRIDGE_MSG_HIDE_GUI, "");
  326. }
  327. break;
  328. default:
  329. break;
  330. }
  331. return;
  332. (void)value2;
  333. (void)value3;
  334. (void)valueStr;
  335. }
  336. private:
  337. CarlaBackend::CarlaPlugin* fPlugin;
  338. const CarlaBackend::CarlaEngine* fEngine;
  339. CarlaString fProjFileName;
  340. static void callback(void* ptr, CarlaBackend::EngineCallbackOpcode action, unsigned int pluginId, int value1, int value2, float value3, const char* valueStr)
  341. {
  342. carla_debug("CarlaPluginClient::callback(%p, %i:%s, %i, %i, %i, %f, \"%s\")", ptr, action, CarlaBackend::EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr);
  343. CARLA_SAFE_ASSERT_RETURN(ptr != nullptr,);
  344. CARLA_SAFE_ASSERT_RETURN(pluginId == 0,);
  345. return ((CarlaPluginClient*)ptr)->handleCallback(action, value1, value2, value3, valueStr);
  346. }
  347. };
  348. // -------------------------------------------------------------------------
  349. int CarlaBridgeOsc::handleMsgShow()
  350. {
  351. carla_debug("CarlaBridgeOsc::handleMsgShow()");
  352. #ifdef HACE_JUCE_UI
  353. const juce::MessageManagerLock mmLock;
  354. #endif
  355. if (carla_get_plugin_info(0)->hints & CarlaBackend::PLUGIN_HAS_CUSTOM_UI)
  356. carla_show_custom_ui(0, true);
  357. return 0;
  358. }
  359. int CarlaBridgeOsc::handleMsgHide()
  360. {
  361. carla_debug("CarlaBridgeOsc::handleMsgHide()");
  362. #ifdef HACE_JUCE_UI
  363. const juce::MessageManagerLock mmLock;
  364. #endif
  365. if (carla_get_plugin_info(0)->hints & CarlaBackend::PLUGIN_HAS_CUSTOM_UI)
  366. carla_show_custom_ui(0, false);
  367. return 0;
  368. }
  369. int CarlaBridgeOsc::handleMsgQuit()
  370. {
  371. carla_debug("CarlaBridgeOsc::handleMsgQuit()");
  372. gCloseNow = true;
  373. return 0;
  374. }
  375. // -------------------------------------------------------------------------
  376. int CarlaBridgeOsc::handleMsgPluginSaveNow()
  377. {
  378. CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1);
  379. carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()");
  380. CarlaPluginClient* const plugClient((CarlaPluginClient*)fClient);
  381. plugClient->saveNow();
  382. return 0;
  383. }
  384. int CarlaBridgeOsc::handleMsgPluginSetParameterMidiChannel(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  385. {
  386. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(2, "ii");
  387. CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1);
  388. carla_debug("CarlaBridgeOsc::handleMsgPluginSetParameterMidiChannel()");
  389. const int32_t index = argv[0]->i;
  390. const int32_t channel = argv[1]->i;
  391. CARLA_SAFE_ASSERT_RETURN(index >= 0, 0);
  392. CARLA_SAFE_ASSERT_RETURN(channel >= 0 && channel < MAX_MIDI_CHANNELS, 0);
  393. CarlaPluginClient* const plugClient((CarlaPluginClient*)fClient);
  394. plugClient->setParameterMidiChannel(static_cast<uint32_t>(index), static_cast<uint8_t>(channel));
  395. return 0;
  396. }
  397. int CarlaBridgeOsc::handleMsgPluginSetParameterMidiCC(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  398. {
  399. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(2, "ii");
  400. CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1);
  401. carla_debug("CarlaBridgeOsc::handleMsgPluginSetParameterMidiCC()");
  402. const int32_t index = argv[0]->i;
  403. const int32_t cc = argv[1]->i;
  404. CARLA_SAFE_ASSERT_RETURN(index >= 0, 0);
  405. CARLA_SAFE_ASSERT_RETURN(cc >= 1 && cc < 0x5F, 0);
  406. CarlaPluginClient* const plugClient((CarlaPluginClient*)fClient);
  407. plugClient->setParameterMidiCC(static_cast<uint32_t>(index), static_cast<int16_t>(cc));
  408. return 0;
  409. }
  410. int CarlaBridgeOsc::handleMsgPluginSetChunk(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  411. {
  412. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(1, "s");
  413. CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1);
  414. carla_debug("CarlaBridgeOsc::handleMsgPluginSetChunk()");
  415. const char* const chunkFile = (const char*)&argv[0]->s;
  416. CarlaPluginClient* const plugClient((CarlaPluginClient*)fClient);
  417. plugClient->setChunkData(chunkFile);
  418. return 0;
  419. }
  420. int CarlaBridgeOsc::handleMsgPluginSetCustomData(CARLA_BRIDGE_OSC_HANDLE_ARGS)
  421. {
  422. CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "sss");
  423. CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1);
  424. carla_debug("CarlaBridgeOsc::handleMsgPluginSetCustomData()");
  425. const char* const type = (const char*)&argv[0]->s;
  426. const char* const key = (const char*)&argv[1]->s;
  427. const char* const value = (const char*)&argv[2]->s;
  428. CarlaPluginClient* const plugClient((CarlaPluginClient*)fClient);
  429. plugClient->setCustomData(type, key, value);
  430. return 0;
  431. }
  432. CARLA_BRIDGE_END_NAMESPACE
  433. // -------------------------------------------------------------------------
  434. int main(int argc, char* argv[])
  435. {
  436. CARLA_BRIDGE_USE_NAMESPACE;
  437. // ---------------------------------------------------------------------
  438. // Check argument count
  439. if (argc != 7)
  440. {
  441. carla_stdout("usage: %s <osc-url|\"null\"> <type> <filename> <name|\"(none)\"> <label> <uniqueId>", argv[0]);
  442. return 1;
  443. }
  444. // ---------------------------------------------------------------------
  445. // Get args
  446. const char* const oscUrl = argv[1];
  447. const char* const stype = argv[2];
  448. const char* const filename = argv[3];
  449. const char* name = argv[4];
  450. const char* label = argv[5];
  451. const int64_t uniqueId = static_cast<int64_t>(std::atol(argv[6]));
  452. // ---------------------------------------------------------------------
  453. // Setup args
  454. const char* const shmIds(std::getenv("ENGINE_BRIDGE_SHM_IDS"));
  455. const bool useBridge = (shmIds != nullptr);
  456. const bool useOsc = (std::strcmp(oscUrl, "null") != 0 && std::strcmp(oscUrl, "(null)") != 0 && std::strcmp(oscUrl, "NULL") != 0);
  457. if (std::strcmp(name, "(none)") == 0)
  458. name = nullptr;
  459. //if (std::strlen(label) == 0)
  460. // label = nullptr;
  461. char bridgeBaseAudioName[6+1];
  462. char bridgeBaseControlName[6+1];
  463. char bridgeBaseTimeName[6+1];
  464. if (useBridge)
  465. {
  466. CARLA_SAFE_ASSERT_RETURN(std::strlen(shmIds) == 6*3, 1);
  467. std::strncpy(bridgeBaseAudioName, shmIds, 6);
  468. std::strncpy(bridgeBaseControlName, shmIds+6, 6);
  469. std::strncpy(bridgeBaseTimeName, shmIds+12, 6);
  470. bridgeBaseAudioName[6] = '\0';
  471. bridgeBaseControlName[6] = '\0';
  472. bridgeBaseTimeName[6] = '\0';
  473. }
  474. else
  475. {
  476. bridgeBaseAudioName[0] = '\0';
  477. bridgeBaseControlName[0] = '\0';
  478. bridgeBaseTimeName[0] = '\0';
  479. }
  480. // ---------------------------------------------------------------------
  481. // Check plugin type
  482. CarlaBackend::PluginType itype(CarlaBackend::getPluginTypeFromString(stype));
  483. if (itype == CarlaBackend::PLUGIN_NONE)
  484. {
  485. carla_stderr("Invalid plugin type '%s'", stype);
  486. return 1;
  487. }
  488. // ---------------------------------------------------------------------
  489. // Set client name
  490. CarlaString clientName((name != nullptr) ? name : label);
  491. //if (clientName.isEmpty())
  492. // clientName = File(filename).getFileNameWithoutExtension().toRawUTF8();
  493. // ---------------------------------------------------------------------
  494. // Set extraStuff
  495. const void* extraStuff = nullptr;
  496. if (itype == CarlaBackend::PLUGIN_GIG || itype == CarlaBackend::PLUGIN_SF2)
  497. {
  498. if (label == nullptr)
  499. label = clientName;
  500. if (std::strstr(label, " (16 outs)") == 0)
  501. extraStuff = "true";
  502. }
  503. // ---------------------------------------------------------------------
  504. // Init plugin client
  505. CarlaPluginClient client(useBridge, clientName, bridgeBaseAudioName, bridgeBaseControlName, bridgeBaseTimeName);
  506. if (! client.isOk())
  507. {
  508. carla_stderr("Failed to init engine, error was:\n%s", carla_get_last_error());
  509. return 1;
  510. }
  511. // ---------------------------------------------------------------------
  512. // Init OSC
  513. if (useOsc)
  514. client.oscInit(oscUrl);
  515. // ---------------------------------------------------------------------
  516. // Listen for ctrl+c or sigint/sigterm events
  517. initSignalHandler();
  518. // ---------------------------------------------------------------------
  519. // Init plugin
  520. int ret;
  521. if (carla_add_plugin(CarlaBackend::BINARY_NATIVE, itype, filename, name, label, uniqueId, extraStuff))
  522. {
  523. if (useOsc)
  524. {
  525. client.sendOscUpdate();
  526. client.sendOscBridgeUpdate();
  527. }
  528. else
  529. {
  530. carla_set_active(0, true);
  531. if (const CarlaPluginInfo* const pluginInfo = carla_get_plugin_info(0))
  532. {
  533. if (pluginInfo->hints & CarlaBackend::PLUGIN_HAS_CUSTOM_UI)
  534. carla_show_custom_ui(0, true);
  535. }
  536. }
  537. client.ready(!useOsc);
  538. gIsInitiated = true;
  539. client.exec(argc, argv);
  540. carla_set_engine_about_to_close();
  541. carla_remove_plugin(0);
  542. ret = 0;
  543. }
  544. else
  545. {
  546. const char* const lastError(carla_get_last_error());
  547. carla_stderr("Plugin failed to load, error was:\n%s", lastError);
  548. if (useOsc)
  549. client.sendOscBridgeError(lastError);
  550. ret = 1;
  551. }
  552. // ---------------------------------------------------------------------
  553. // Close OSC
  554. if (useOsc)
  555. client.oscClose();
  556. return ret;
  557. }