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.

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