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.

766 lines
22KB

  1. /*
  2. * Carla Plugin Engine (Native)
  3. * Copyright (C) 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. #ifdef BUILD_BRIDGE
  18. # error This file should not be compiled if building bridge
  19. #endif
  20. #include "CarlaEngineInternal.hpp"
  21. #include "CarlaStateUtils.hpp"
  22. #include "CarlaNative.hpp"
  23. CARLA_BACKEND_START_NAMESPACE
  24. #if 0
  25. // -----------------------------------------------------------------------
  26. class CarlaEngineNativeThread : public QThread
  27. {
  28. public:
  29. enum UiState {
  30. UiNone = 0,
  31. UiHide,
  32. UiShow,
  33. UiCrashed
  34. };
  35. CarlaEngineNativeThread(CarlaEngine* const engine)
  36. : kEngine(engine),
  37. fBinary("carla-control"),
  38. fProcess(nullptr),
  39. fUiState(UiNone)
  40. {
  41. carla_debug("CarlaEngineNativeThread::CarlaEngineNativeThread(engine:\"%s\")", engine->getName());
  42. }
  43. ~CarlaEngineNativeThread()
  44. {
  45. CARLA_ASSERT_INT(fUiState == UiNone, fUiState);
  46. carla_debug("CarlaEngineNativeThread::~CarlaEngineNativeThread()");
  47. if (fProcess != nullptr)
  48. {
  49. delete fProcess;
  50. fProcess = nullptr;
  51. }
  52. }
  53. void setOscData(const char* const binary)
  54. {
  55. fBinary = binary;
  56. }
  57. UiState getUiState()
  58. {
  59. const UiState state(fUiState);
  60. fUiState = UiNone;
  61. return state;
  62. }
  63. void stop()
  64. {
  65. if (fProcess == nullptr)
  66. return;
  67. fUiState = UiNone;
  68. fProcess->kill();
  69. //fProcess->close();
  70. }
  71. protected:
  72. void run()
  73. {
  74. carla_debug("CarlaEngineNativeThread::run() - binary:\"%s\"", (const char*)fBinary);
  75. if (fProcess == nullptr)
  76. {
  77. fProcess = new QProcess(nullptr);
  78. fProcess->setProcessChannelMode(QProcess::ForwardedChannels);
  79. }
  80. else if (fProcess->state() == QProcess::Running)
  81. {
  82. carla_stderr("CarlaEngineNativeThread::run() - already running, giving up...");
  83. fUiState = UiCrashed;
  84. fProcess->terminate();
  85. //kEngine->callback(CarlaBackend::CALLBACK_SHOW_GUI, kPlugin->id(), -1, 0, 0.0f, nullptr);
  86. // TODO: tell master to hide UI
  87. return;
  88. }
  89. QStringList arguments;
  90. arguments << kEngine->getOscServerPathTCP();
  91. fProcess->start((const char*)fBinary, arguments);
  92. fProcess->waitForStarted();
  93. fUiState = UiShow;
  94. fProcess->waitForFinished(-1);
  95. if (fProcess->exitCode() == 0)
  96. {
  97. // Hide
  98. fUiState = UiHide;
  99. carla_stdout("CarlaEngineNativeThread::run() - GUI closed");
  100. }
  101. else
  102. {
  103. // Kill
  104. fUiState = UiCrashed;
  105. carla_stderr("CarlaEngineNativeThread::run() - GUI crashed while running");
  106. }
  107. }
  108. private:
  109. CarlaEngine* const kEngine;
  110. CarlaString fBinary;
  111. QProcess* fProcess;
  112. UiState fUiState;
  113. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineNativeThread)
  114. };
  115. // -----------------------------------------------------------------------
  116. class CarlaEngineNative : public PluginClass,
  117. public CarlaEngine
  118. {
  119. public:
  120. CarlaEngineNative(const HostDescriptor* const host)
  121. : PluginClass(host),
  122. CarlaEngine(),
  123. fIsRunning(true),
  124. fThread(this)
  125. {
  126. carla_debug("CarlaEngineNative::CarlaEngineNative()");
  127. // set-up engine
  128. fOptions.processMode = PROCESS_MODE_CONTINUOUS_RACK;
  129. fOptions.transportMode = TRANSPORT_MODE_PLUGIN;
  130. fOptions.forceStereo = true;
  131. fOptions.preferPluginBridges = false;
  132. fOptions.preferUiBridges = false;
  133. init("Carla-Plugin");
  134. // set control thread binary
  135. CarlaString threadBinary(getResourceDir());
  136. threadBinary += "/../";
  137. threadBinary += "carla_control.py";
  138. fThread.setOscData(threadBinary);
  139. // TESTING
  140. // if (! addPlugin(PLUGIN_INTERNAL, nullptr, "MIDI Transpose", "midiTranspose"))
  141. // carla_stdout("TESTING PLUG1 ERROR:\n%s", getLastError());
  142. // if (! addPlugin(PLUGIN_INTERNAL, nullptr, "ZynAddSubFX", "zynaddsubfx"))
  143. // carla_stdout("TESTING PLUG2 ERROR:\n%s", getLastError());
  144. // if (! addPlugin(PLUGIN_INTERNAL, nullptr, "Ping Pong Pan", "PingPongPan"))
  145. // carla_stdout("TESTING PLUG3 ERROR:\n%s", getLastError());
  146. }
  147. ~CarlaEngineNative() override
  148. {
  149. carla_debug("CarlaEngineNative::~CarlaEngineNative()");
  150. fIsRunning = false;
  151. setAboutToClose();
  152. removeAllPlugins();
  153. close();
  154. }
  155. protected:
  156. // -------------------------------------
  157. // CarlaEngine virtual calls
  158. bool init(const char* const clientName) override
  159. {
  160. carla_debug("CarlaEngineNative::init(\"%s\")", clientName);
  161. fBufferSize = PluginClass::getBufferSize();
  162. fSampleRate = PluginClass::getSampleRate();
  163. CarlaEngine::init(clientName);
  164. return true;
  165. }
  166. bool close() override
  167. {
  168. carla_debug("CarlaEngineNative::close()");
  169. runPendingRtEvents();
  170. return CarlaEngine::close();
  171. }
  172. bool isRunning() const noexcept override
  173. {
  174. return fIsRunning;
  175. }
  176. bool isOffline() const noexcept override
  177. {
  178. return false;
  179. }
  180. EngineType getType() const noexcept override
  181. {
  182. return kEngineTypePlugin;
  183. }
  184. // -------------------------------------------------------------------
  185. // Plugin parameter calls
  186. uint32_t getParameterCount() const override
  187. {
  188. if (pData->curPluginCount == 0 || pData->plugins == nullptr)
  189. return 0;
  190. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  191. if (plugin == nullptr || ! plugin->isEnabled())
  192. return 0;
  193. return pData->plugins[0].plugin->getParameterCount();
  194. }
  195. const Parameter* getParameterInfo(const uint32_t index) const override
  196. {
  197. if (index >= getParameterCount())
  198. return nullptr;
  199. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  200. if (plugin == nullptr || ! plugin->isEnabled())
  201. return nullptr;
  202. static ::Parameter param;
  203. static char strBufName[STR_MAX+1];
  204. static char strBufUnit[STR_MAX+1];
  205. const ParameterData& paramData(plugin->getParameterData(index));
  206. const ParameterRanges& paramRanges(plugin->getParameterRanges(index));
  207. plugin->getParameterName(index, strBufName);
  208. plugin->getParameterUnit(index, strBufUnit);
  209. unsigned int hints = 0x0;
  210. if (paramData.hints & PARAMETER_IS_BOOLEAN)
  211. hints |= ::PARAMETER_IS_BOOLEAN;
  212. if (paramData.hints & PARAMETER_IS_INTEGER)
  213. hints |= ::PARAMETER_IS_INTEGER;
  214. if (paramData.hints & PARAMETER_IS_LOGARITHMIC)
  215. hints |= ::PARAMETER_IS_LOGARITHMIC;
  216. if (paramData.hints & PARAMETER_IS_AUTOMABLE)
  217. hints |= ::PARAMETER_IS_AUTOMABLE;
  218. if (paramData.hints & PARAMETER_USES_SAMPLERATE)
  219. hints |= ::PARAMETER_USES_SAMPLE_RATE;
  220. if (paramData.hints & PARAMETER_USES_SCALEPOINTS)
  221. hints |= ::PARAMETER_USES_SCALEPOINTS;
  222. if (paramData.hints & PARAMETER_USES_CUSTOM_TEXT)
  223. hints |= ::PARAMETER_USES_CUSTOM_TEXT;
  224. if (paramData.type == PARAMETER_INPUT || paramData.type == PARAMETER_OUTPUT)
  225. {
  226. if (paramData.hints & PARAMETER_IS_ENABLED)
  227. hints |= ::PARAMETER_IS_ENABLED;
  228. if (paramData.type == PARAMETER_OUTPUT)
  229. hints |= ::PARAMETER_IS_OUTPUT;
  230. }
  231. param.hints = static_cast< ::ParameterHints>(hints);
  232. param.name = strBufName;
  233. param.unit = strBufUnit;
  234. param.ranges.def = paramRanges.def;
  235. param.ranges.min = paramRanges.min;
  236. param.ranges.max = paramRanges.max;
  237. param.ranges.step = paramRanges.step;
  238. param.ranges.stepSmall = paramRanges.stepSmall;
  239. param.ranges.stepLarge = paramRanges.stepLarge;
  240. param.scalePointCount = 0; // TODO
  241. param.scalePoints = nullptr;
  242. return &param;
  243. }
  244. float getParameterValue(const uint32_t index) const override
  245. {
  246. if (index >= getParameterCount())
  247. return 0.0f;
  248. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  249. if (plugin == nullptr || ! plugin->isEnabled())
  250. return 0.0f;
  251. return plugin->getParameterValue(index);
  252. }
  253. const char* getParameterText(const uint32_t index, const float value) const override // FIXME - use value
  254. {
  255. if (index >= getParameterCount())
  256. return nullptr;
  257. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  258. if (plugin == nullptr || ! plugin->isEnabled())
  259. return nullptr;
  260. static char strBuf[STR_MAX+1];
  261. plugin->getParameterText(index, strBuf);
  262. return strBuf;
  263. }
  264. // -------------------------------------------------------------------
  265. // Plugin midi-program calls
  266. uint32_t getMidiProgramCount() const override
  267. {
  268. if (pData->curPluginCount == 0 || pData->plugins == nullptr)
  269. return 0;
  270. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  271. if (plugin == nullptr || ! plugin->isEnabled())
  272. return 0.0f;
  273. return plugin->getMidiProgramCount();
  274. }
  275. const MidiProgram* getMidiProgramInfo(const uint32_t index) const override
  276. {
  277. if (index >= getMidiProgramCount())
  278. return nullptr;
  279. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  280. if (plugin == nullptr || ! plugin->isEnabled())
  281. return nullptr;
  282. static ::MidiProgram midiProg;
  283. {
  284. const MidiProgramData& midiProgData(plugin->getMidiProgramData(index));
  285. midiProg.bank = midiProgData.bank;
  286. midiProg.program = midiProgData.program;
  287. midiProg.name = midiProgData.name;
  288. }
  289. return &midiProg;
  290. }
  291. // -------------------------------------------------------------------
  292. // Plugin state calls
  293. void setParameterValue(const uint32_t index, const float value) override
  294. {
  295. if (index >= getParameterCount())
  296. return;
  297. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  298. if (plugin == nullptr || ! plugin->isEnabled())
  299. return;
  300. plugin->setParameterValue(index, value, false, false, false);
  301. }
  302. void setMidiProgram(const uint8_t, const uint32_t bank, const uint32_t program) override
  303. {
  304. if (pData->curPluginCount == 0 || pData->plugins == nullptr)
  305. return;
  306. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  307. if (plugin == nullptr || ! plugin->isEnabled())
  308. return;
  309. plugin->setMidiProgramById(bank, program, false, false, false);
  310. }
  311. void setCustomData(const char* const key, const char* const value) override
  312. {
  313. CARLA_ASSERT(key != nullptr);
  314. CARLA_ASSERT(value != nullptr);
  315. return;
  316. // TODO
  317. // unused
  318. (void)key;
  319. (void)value;
  320. }
  321. // -------------------------------------------------------------------
  322. // Plugin process calls
  323. void activate() override
  324. {
  325. for (uint32_t i=0; i < pData->curPluginCount; ++i)
  326. {
  327. CarlaPlugin* const plugin(pData->plugins[i].plugin);
  328. if (plugin == nullptr || ! plugin->isEnabled())
  329. continue;
  330. plugin->setActive(true, true, false);
  331. }
  332. }
  333. void deactivate() override
  334. {
  335. for (uint32_t i=0; i < pData->curPluginCount; ++i)
  336. {
  337. CarlaPlugin* const plugin(pData->plugins[i].plugin);
  338. if (plugin == nullptr || ! plugin->isEnabled())
  339. continue;
  340. plugin->setActive(false, true, false);
  341. }
  342. // just in case
  343. runPendingRtEvents();
  344. }
  345. void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const ::MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  346. {
  347. if (pData->curPluginCount == 0)
  348. {
  349. FloatVectorOperations::clear(outBuffer[0], frames);
  350. FloatVectorOperations::clear(outBuffer[1], frames);
  351. return runPendingRtEvents();;
  352. }
  353. // ---------------------------------------------------------------
  354. // Time Info
  355. const ::TimeInfo* timeInfo(PluginClass::getTimeInfo());
  356. fTimeInfo.playing = timeInfo->playing;
  357. fTimeInfo.frame = timeInfo->frame;
  358. fTimeInfo.usecs = timeInfo->usecs;
  359. fTimeInfo.valid = 0x0;
  360. if (timeInfo->bbt.valid)
  361. {
  362. fTimeInfo.valid |= EngineTimeInfo::ValidBBT;
  363. fTimeInfo.bbt.bar = timeInfo->bbt.bar;
  364. fTimeInfo.bbt.beat = timeInfo->bbt.beat;
  365. fTimeInfo.bbt.tick = timeInfo->bbt.tick;
  366. fTimeInfo.bbt.barStartTick = timeInfo->bbt.barStartTick;
  367. fTimeInfo.bbt.beatsPerBar = timeInfo->bbt.beatsPerBar;
  368. fTimeInfo.bbt.beatType = timeInfo->bbt.beatType;
  369. fTimeInfo.bbt.ticksPerBeat = timeInfo->bbt.ticksPerBeat;
  370. fTimeInfo.bbt.beatsPerMinute = timeInfo->bbt.beatsPerMinute;
  371. }
  372. // ---------------------------------------------------------------
  373. // initialize input events
  374. carla_zeroStruct<EngineEvent>(pData->bufEvents.in, INTERNAL_EVENT_COUNT);
  375. {
  376. uint32_t engineEventIndex = 0;
  377. for (uint32_t i=0; i < midiEventCount && engineEventIndex < INTERNAL_EVENT_COUNT; ++i)
  378. {
  379. const ::MidiEvent& midiEvent(midiEvents[i]);
  380. if (midiEvent.size > 4)
  381. continue;
  382. const uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent.data);
  383. const uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent.data);
  384. // we don't want some events
  385. if (status == MIDI_STATUS_PROGRAM_CHANGE)
  386. continue;
  387. // handle note/sound off properly
  388. if (status == MIDI_STATUS_CONTROL_CHANGE)
  389. {
  390. const uint8_t control = midiEvent.data[1];
  391. if (MIDI_IS_CONTROL_BANK_SELECT(control))
  392. continue;
  393. if (control == MIDI_CONTROL_ALL_SOUND_OFF || control == MIDI_CONTROL_ALL_NOTES_OFF)
  394. {
  395. EngineEvent& engineEvent(pData->bufEvents.in[engineEventIndex++]);
  396. engineEvent.clear();
  397. engineEvent.type = kEngineEventTypeControl;
  398. engineEvent.time = midiEvent.time;
  399. engineEvent.channel = channel;
  400. engineEvent.ctrl.type = (control == MIDI_CONTROL_ALL_SOUND_OFF) ? kEngineControlEventTypeAllSoundOff : kEngineControlEventTypeAllNotesOff;
  401. engineEvent.ctrl.param = 0;
  402. engineEvent.ctrl.value = 0.0f;
  403. continue;
  404. }
  405. }
  406. EngineEvent& engineEvent(pData->bufEvents.in[engineEventIndex++]);
  407. engineEvent.clear();
  408. engineEvent.type = kEngineEventTypeMidi;
  409. engineEvent.time = midiEvent.time;
  410. engineEvent.channel = channel;
  411. engineEvent.midi.data[0] = MIDI_GET_STATUS_FROM_DATA(midiEvent.data);
  412. engineEvent.midi.data[1] = midiEvent.data[1];
  413. engineEvent.midi.data[2] = midiEvent.data[2];
  414. engineEvent.midi.data[3] = midiEvent.data[3];
  415. engineEvent.midi.size = midiEvent.size;
  416. }
  417. }
  418. // ---------------------------------------------------------------
  419. // create audio buffers
  420. float* inBuf[2] = { inBuffer[0], inBuffer[1] };
  421. float* outBuf[2] = { outBuffer[0], outBuffer[1] };
  422. // ---------------------------------------------------------------
  423. // process
  424. processRack(inBuf, outBuf, frames);
  425. runPendingRtEvents();
  426. }
  427. // -------------------------------------------------------------------
  428. // Plugin UI calls
  429. void uiShow(const bool show) override
  430. {
  431. if (show)
  432. {
  433. fThread.start();
  434. }
  435. else
  436. {
  437. #if 0
  438. for (uint32_t i=0; i < pData->curPluginCount; ++i)
  439. {
  440. CarlaPlugin* const plugin(pData->plugins[i].plugin);
  441. if (plugin == nullptr || ! plugin->enabled())
  442. continue;
  443. plugin->showGui(false);
  444. }
  445. #endif
  446. fThread.stop();
  447. }
  448. }
  449. void uiIdle() override
  450. {
  451. CarlaEngine::idle();
  452. switch(fThread.getUiState())
  453. {
  454. case CarlaEngineNativeThread::UiNone:
  455. case CarlaEngineNativeThread::UiShow:
  456. break;
  457. case CarlaEngineNativeThread::UiCrashed:
  458. hostUiUnavailable();
  459. break;
  460. case CarlaEngineNativeThread::UiHide:
  461. uiClosed();
  462. break;
  463. }
  464. }
  465. void uiSetParameterValue(const uint32_t index, const float value) override
  466. {
  467. if (index >= getParameterCount())
  468. return;
  469. CarlaPlugin* const plugin(pData->plugins[0].plugin);
  470. if (plugin == nullptr || ! plugin->isEnabled())
  471. return;
  472. plugin->uiParameterChange(index, value);
  473. }
  474. void uiSetMidiProgram(const uint8_t channel, const uint32_t bank, const uint32_t program) override
  475. {
  476. return;
  477. // TODO
  478. // unused
  479. (void)channel;
  480. (void)bank;
  481. (void)program;
  482. }
  483. void uiSetCustomData(const char* const key, const char* const value) override
  484. {
  485. CARLA_ASSERT(key != nullptr);
  486. CARLA_ASSERT(value != nullptr);
  487. return;
  488. // TODO
  489. // unused
  490. (void)key;
  491. (void)value;
  492. }
  493. // -------------------------------------------------------------------
  494. // Plugin state calls
  495. char* getState() const override
  496. {
  497. QString string;
  498. QTextStream out(&string);
  499. out << "<?xml version='1.0' encoding='UTF-8'?>\n";
  500. out << "<!DOCTYPE CARLA-PROJECT>\n";
  501. out << "<CARLA-PROJECT VERSION='1.0'>\n";
  502. bool firstPlugin = true;
  503. char strBuf[STR_MAX+1];
  504. for (unsigned int i=0; i < pData->curPluginCount; ++i)
  505. {
  506. CarlaPlugin* const plugin(pData->plugins[i].plugin);
  507. if (plugin != nullptr && plugin->isEnabled())
  508. {
  509. if (! firstPlugin)
  510. out << "\n";
  511. plugin->getRealName(strBuf);
  512. if (*strBuf != 0)
  513. out << QString(" <!-- %1 -->\n").arg(xmlSafeString(strBuf, true));
  514. QString content;
  515. fillXmlStringFromSaveState(content, plugin->getSaveState());
  516. out << " <Plugin>\n";
  517. out << content;
  518. out << " </Plugin>\n";
  519. firstPlugin = false;
  520. }
  521. }
  522. out << "</CARLA-PROJECT>\n";
  523. return strdup(string.toUtf8().constData());
  524. }
  525. void setState(const char* const data) override
  526. {
  527. QDomDocument xml;
  528. xml.setContent(QString(data));
  529. QDomNode xmlNode(xml.documentElement());
  530. if (xmlNode.toElement().tagName() != "CARLA-PROJECT")
  531. {
  532. carla_stderr2("Not a valid Carla project");
  533. return;
  534. }
  535. QDomNode node(xmlNode.firstChild());
  536. while (! node.isNull())
  537. {
  538. if (node.toElement().tagName() == "Plugin")
  539. {
  540. SaveState saveState;
  541. fillSaveStateFromXmlNode(saveState, node);
  542. CARLA_SAFE_ASSERT_CONTINUE(saveState.type != nullptr)
  543. const void* extraStuff = nullptr;
  544. // FIXME
  545. //if (std::strcmp(saveState.type, "DSSI") == 0)
  546. // extraStuff = findDSSIGUI(saveState.binary, saveState.label);
  547. // TODO - proper find&load plugins
  548. if (addPlugin(getPluginTypeFromString(saveState.type), saveState.binary, saveState.name, saveState.label, extraStuff))
  549. {
  550. if (CarlaPlugin* plugin = getPlugin(pData->curPluginCount-1))
  551. plugin->loadSaveState(saveState);
  552. }
  553. }
  554. node = node.nextSibling();
  555. }
  556. }
  557. // -------------------------------------------------------------------
  558. private:
  559. bool fIsRunning;
  560. CarlaEngineNativeThread fThread;
  561. PluginClassEND(CarlaEngineNative)
  562. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineNative)
  563. };
  564. // -----------------------------------------------------------------------
  565. static const PluginDescriptor carlaDesc = {
  566. /* category */ ::PLUGIN_CATEGORY_OTHER,
  567. /* hints */ static_cast< ::PluginHints>(::PLUGIN_IS_SYNTH|::PLUGIN_HAS_GUI|::PLUGIN_USES_SINGLE_THREAD|::PLUGIN_USES_STATE),
  568. /* supports */ static_cast<PluginSupports>(PLUGIN_SUPPORTS_EVERYTHING),
  569. /* audioIns */ 2,
  570. /* audioOuts */ 2,
  571. /* midiIns */ 1,
  572. /* midiOuts */ 1,
  573. /* paramIns */ 0,
  574. /* paramOuts */ 0,
  575. /* name */ "Carla-Plugin (TESTING)",
  576. /* label */ "carla",
  577. /* maker */ "falkTX",
  578. /* copyright */ "GNU GPL v2+",
  579. PluginDescriptorFILL(CarlaEngineNative)
  580. };
  581. #endif
  582. CARLA_EXPORT
  583. void carla_register_native_plugin_carla()
  584. {
  585. //carla_register_native_plugin(&carlaDesc);
  586. }
  587. CARLA_BACKEND_END_NAMESPACE
  588. // -----------------------------------------------------------------------