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.

724 lines
21KB

  1. /*
  2. * Carla Native Plugins
  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. #include "CarlaNative.hpp"
  18. #include "juce_gui_basics.h"
  19. using namespace juce;
  20. #include "vex/VexArp.h"
  21. #include "vex/VexChorus.h"
  22. #include "vex/VexDelay.h"
  23. #include "vex/VexReverb.h"
  24. #include "vex/VexSyntModule.h"
  25. #include "vex/PeggyViewComponent.h"
  26. #include "vex/lookandfeel/MyLookAndFeel.h"
  27. #include "vex/resources/Resources.h"
  28. #include "vex/resources/Resources.cpp"
  29. // -----------------------------------------------------------------------
  30. class HelperWindow2 : public DocumentWindow
  31. {
  32. public:
  33. HelperWindow2()
  34. : DocumentWindow("PlugWindow", Colour(50, 50, 200), DocumentWindow::closeButton, false),
  35. fClosed(false)
  36. {
  37. setVisible(false);
  38. setAlwaysOnTop(true);
  39. setDropShadowEnabled(false);
  40. setOpaque(true);
  41. //setResizable(false, false);
  42. //setUsingNativeTitleBar(false);
  43. }
  44. void show(Component* const comp)
  45. {
  46. fClosed = false;
  47. const int width = comp->getWidth();
  48. const int height = comp->getHeight()+getTitleBarHeight();
  49. centreWithSize(width, height);
  50. setContentNonOwned(comp, false);
  51. setSize(width, height);
  52. if (! isOnDesktop())
  53. addToDesktop();
  54. setVisible(true);
  55. }
  56. void hide()
  57. {
  58. setVisible(false);
  59. if (isOnDesktop())
  60. removeFromDesktop();
  61. clearContentComponent();
  62. }
  63. bool wasClosedByUser() const
  64. {
  65. return fClosed;
  66. }
  67. protected:
  68. void closeButtonPressed() override
  69. {
  70. fClosed = true;
  71. }
  72. private:
  73. bool fClosed;
  74. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HelperWindow2)
  75. };
  76. // -----------------------------------------------------------------------
  77. class VexEditorComponent : public ComboBox::Listener,
  78. public Slider::Listener,
  79. public Button::Listener,
  80. public ChangeListener,
  81. public Component,
  82. public PeggyViewComponent::Callback
  83. {
  84. public:
  85. VexEditorComponent()
  86. {
  87. internalCachedImage1 = ImageCache::getFromMemory(Resources::vex3_png, Resources::vex3_pngSize);
  88. // Comboboxes, wave selection
  89. addAndMakeVisible(comboBox1 = new ComboBox (String::empty));
  90. comboBox1->setEditableText(false);
  91. comboBox1->setJustificationType(Justification::centredLeft);
  92. comboBox1->setTextWhenNothingSelected(String("silent"));
  93. comboBox1->setTextWhenNoChoicesAvailable(String("silent"));
  94. comboBox1->addListener(this);
  95. comboBox1->setColour(ComboBox::backgroundColourId, Colours::black);
  96. comboBox1->setColour(ComboBox::textColourId, Colours::lightgrey);
  97. comboBox1->setColour(ComboBox::outlineColourId, Colours::grey);
  98. comboBox1->setColour(ComboBox::buttonColourId, Colours::grey);
  99. comboBox1->setWantsKeyboardFocus(false);
  100. comboBox1->setLookAndFeel(&mlaf);
  101. addAndMakeVisible (comboBox2 = new ComboBox (String::empty));
  102. comboBox2->setEditableText (false);
  103. comboBox2->setJustificationType (Justification::centredLeft);
  104. comboBox2->setTextWhenNothingSelected (String("silent"));
  105. comboBox2->setTextWhenNoChoicesAvailable (String("silent"));
  106. comboBox2->addListener (this);
  107. comboBox2->setColour(ComboBox::backgroundColourId, Colours::black);
  108. comboBox2->setColour(ComboBox::textColourId, Colours::lightgrey);
  109. comboBox2->setColour(ComboBox::outlineColourId, Colours::grey);
  110. comboBox2->setColour(ComboBox::buttonColourId, Colours::grey);
  111. comboBox2->setWantsKeyboardFocus(false);
  112. comboBox2->setLookAndFeel(&mlaf);
  113. addAndMakeVisible (comboBox3 = new ComboBox (String::empty));
  114. comboBox3->setEditableText (false);
  115. comboBox3->setJustificationType (Justification::centredLeft);
  116. comboBox3->setTextWhenNothingSelected (String("silent"));
  117. comboBox3->setTextWhenNoChoicesAvailable (String("silent"));
  118. comboBox3->addListener (this);
  119. comboBox3->setColour(ComboBox::backgroundColourId, Colours::black);
  120. comboBox3->setColour(ComboBox::textColourId, Colours::lightgrey);
  121. comboBox3->setColour(ComboBox::outlineColourId, Colours::grey);
  122. comboBox3->setColour(ComboBox::buttonColourId, Colours::grey);
  123. comboBox3->setWantsKeyboardFocus(false);
  124. comboBox3->setLookAndFeel(&mlaf);
  125. for (int i = 0, tableSize = WaveRenderer::getWaveTableSize(); i < tableSize; ++i)
  126. {
  127. String tableName(WaveRenderer::getWaveTableName(i));
  128. comboBox1->addItem(tableName, i + 1);
  129. comboBox2->addItem(tableName, i + 1);
  130. comboBox3->addItem(tableName, i + 1);
  131. }
  132. addChildComponent(p1 = new PeggyViewComponent(1, _d, this));
  133. p1->setLookAndFeel(&mlaf);
  134. addChildComponent(p2 = new PeggyViewComponent(2, _d, this));
  135. p2->setLookAndFeel(&mlaf);
  136. addChildComponent(p3 = new PeggyViewComponent(3, _d, this));
  137. p3->setLookAndFeel(&mlaf);
  138. //ownerFilter->addChangeListener (this);
  139. setSize(800,500);
  140. }
  141. ~VexEditorComponent()
  142. {
  143. removeAllChildren();
  144. }
  145. protected:
  146. void paint(Graphics& g) override
  147. {
  148. g.drawImage(internalCachedImage1,
  149. 0, 0, 800, 500,
  150. 0, 0, internalCachedImage1.getWidth(), internalCachedImage1.getHeight());
  151. }
  152. void resized() override
  153. {
  154. comboBox1->setBounds(13, 38, 173, 23);
  155. comboBox2->setBounds(213, 38, 173, 23);
  156. comboBox3->setBounds(413, 38, 173, 23);
  157. p1->setBounds(10, 20, 207, 280);
  158. p2->setBounds(210, 20, 207, 280);
  159. p3->setBounds(410, 20, 207, 280);
  160. }
  161. void changeListenerCallback(ChangeBroadcaster* source) override
  162. {
  163. }
  164. void comboBoxChanged(ComboBox* comboBoxThatHasChanged) override
  165. {
  166. }
  167. void sliderValueChanged(Slider* sliderThatWasMoved) override
  168. {
  169. }
  170. void buttonClicked(Button* buttonThatWasClicked) override
  171. {
  172. }
  173. void somethingChanged(const uint32_t id) override
  174. {
  175. }
  176. private:
  177. Image internalCachedImage1;
  178. MyLookAndFeel mlaf;
  179. ScopedPointer<ComboBox> comboBox1;
  180. ScopedPointer<ComboBox> comboBox2;
  181. ScopedPointer<ComboBox> comboBox3;
  182. ScopedPointer<PeggyViewComponent> p1;
  183. ScopedPointer<PeggyViewComponent> p2;
  184. ScopedPointer<PeggyViewComponent> p3;
  185. VexArpSettings _d;
  186. };
  187. // -----------------------------------------------------------------------
  188. class VexSynthPlugin : public PluginClass
  189. {
  190. public:
  191. static const unsigned int kParamCount = 92;
  192. VexSynthPlugin(const HostDescriptor* const host)
  193. : PluginClass(host),
  194. obf(nullptr),
  195. abf(nullptr),
  196. dbf1(nullptr),
  197. dbf2(nullptr),
  198. dbf3(nullptr),
  199. fChorus(fParameters),
  200. fDelay(fParameters),
  201. fReverb(fParameters),
  202. fSynth(fParameters)
  203. {
  204. std::memset(fParameters, 0, sizeof(float)*kParamCount);
  205. fParameters[0] = 1.0f; // main volume
  206. for (int i = 0; i < 3; ++i)
  207. {
  208. const int offset = i * 24;
  209. fParameters[offset + 1] = 0.5f;
  210. fParameters[offset + 2] = 0.5f;
  211. fParameters[offset + 3] = 0.5f;
  212. fParameters[offset + 4] = 0.5f;
  213. fParameters[offset + 5] = 0.9f;
  214. fParameters[offset + 6] = 0.0f;
  215. fParameters[offset + 7] = 1.0f;
  216. fParameters[offset + 8] = 0.5f;
  217. fParameters[offset + 9] = 0.0f;
  218. fParameters[offset + 10] = 0.2f;
  219. fParameters[offset + 11] = 0.0f;
  220. fParameters[offset + 12] = 0.5f;
  221. fParameters[offset + 13] = 0.5f;
  222. fParameters[offset + 14] = 0.0f;
  223. fParameters[offset + 15] = 0.3f;
  224. fParameters[offset + 16] = 0.7f;
  225. fParameters[offset + 17] = 0.1f;
  226. fParameters[offset + 18] = 0.5f;
  227. fParameters[offset + 19] = 0.5f;
  228. fParameters[offset + 20] = 0.0f;
  229. fParameters[offset + 21] = 0.0f;
  230. fParameters[offset + 22] = 0.5f;
  231. fParameters[offset + 23] = 0.5f;
  232. fParameters[offset + 24] = 0.5f;
  233. }
  234. // ^1 - 72
  235. fParameters[73] = 0.5f; // Delay Time
  236. fParameters[74] = 0.4f; // Delay Feedback
  237. fParameters[75] = 0.0f; // Delay Volume
  238. fParameters[76] = 0.3f; // Chorus Rate
  239. fParameters[77] = 0.6f; // Chorus Depth
  240. fParameters[78] = 0.5f; // Chorus Volume
  241. fParameters[79] = 0.6f; // Reverb Size
  242. fParameters[80] = 0.7f; // Reverb Width
  243. fParameters[81] = 0.6f; // Reverb Damp
  244. fParameters[82] = 0.0f; // Reverb Volume
  245. fParameters[83] = 0.5f; // wave1 panning
  246. fParameters[84] = 0.5f; // wave2 panning
  247. fParameters[85] = 0.5f; // wave3 panning
  248. fParameters[86] = 0.5f; // wave1 volume
  249. fParameters[87] = 0.5f; // wave2 volume
  250. fParameters[88] = 0.5f; // wave3 volume
  251. fParameters[89] = 1.0f; // wave1 on/off
  252. fParameters[90] = 1.0f; // wave2 on/off
  253. fParameters[91] = 1.0f; // wave3 on/off
  254. bufferSizeChanged(getBufferSize());
  255. sampleRateChanged(getSampleRate());
  256. for (int i = 0; i < kParamCount; ++i)
  257. fSynth.update(i);
  258. }
  259. ~VexSynthPlugin()
  260. {
  261. delete obf;
  262. delete abf;
  263. delete dbf1;
  264. delete dbf2;
  265. delete dbf3;
  266. }
  267. protected:
  268. // -------------------------------------------------------------------
  269. // Plugin parameter calls
  270. uint32_t getParameterCount() const override
  271. {
  272. return kParamCount;
  273. }
  274. const Parameter* getParameterInfo(const uint32_t index) const override
  275. {
  276. static Parameter paramInfo;
  277. int hints = PARAMETER_IS_ENABLED|PARAMETER_IS_AUTOMABLE;
  278. paramInfo.name = nullptr;
  279. paramInfo.unit = nullptr;
  280. paramInfo.ranges.def = 0.0f;
  281. paramInfo.ranges.min = 0.0f;
  282. paramInfo.ranges.max = 1.0f;
  283. paramInfo.ranges.step = PARAMETER_RANGES_DEFAULT_STEP;
  284. paramInfo.ranges.stepSmall = PARAMETER_RANGES_DEFAULT_STEP_SMALL;
  285. paramInfo.ranges.stepLarge = PARAMETER_RANGES_DEFAULT_STEP_LARGE;
  286. paramInfo.scalePointCount = 0;
  287. paramInfo.scalePoints = nullptr;
  288. if (index >= 1 && index <= 72)
  289. {
  290. uint32_t ri = index % 24;
  291. switch (ri)
  292. {
  293. case 1:
  294. paramInfo.name = "oct";
  295. break;
  296. case 2:
  297. paramInfo.name = "cent";
  298. break;
  299. case 3:
  300. paramInfo.name = "phaseOffset";
  301. break;
  302. case 4:
  303. paramInfo.name = "phaseIncOffset";
  304. break;
  305. case 5:
  306. paramInfo.name = "filter";
  307. break;
  308. case 6:
  309. paramInfo.name = "filter";
  310. break;
  311. case 7:
  312. paramInfo.name = "filter";
  313. break;
  314. case 8:
  315. paramInfo.name = "filter";
  316. break;
  317. case 9:
  318. paramInfo.name = "F ADSR";
  319. break;
  320. case 10:
  321. paramInfo.name = "F ADSR";
  322. break;
  323. case 11:
  324. paramInfo.name = "F ADSR";
  325. break;
  326. case 12:
  327. paramInfo.name = "F ADSR";
  328. break;
  329. case 13:
  330. paramInfo.name = "F velocity";
  331. break;
  332. case 14:
  333. paramInfo.name = "A ADSR";
  334. break;
  335. case 15:
  336. paramInfo.name = "A ADSR";
  337. break;
  338. case 16:
  339. paramInfo.name = "A ADSR";
  340. break;
  341. case 17:
  342. paramInfo.name = "A ADSR";
  343. break;
  344. case 18:
  345. paramInfo.name = "A velocity";
  346. break;
  347. case 19:
  348. paramInfo.name = "lfoC";
  349. break;
  350. case 20:
  351. paramInfo.name = "lfoA";
  352. break;
  353. case 21:
  354. paramInfo.name = "lfoF";
  355. break;
  356. case 22:
  357. paramInfo.name = "fx vol D";
  358. break;
  359. case 23:
  360. paramInfo.name = "fx vol C";
  361. break;
  362. case 24:
  363. case 0:
  364. paramInfo.name = "fx vol R";
  365. break;
  366. default:
  367. paramInfo.name = "unknown2";
  368. break;
  369. }
  370. paramInfo.hints = static_cast<ParameterHints>(hints);
  371. return &paramInfo;
  372. }
  373. switch (index)
  374. {
  375. case 0:
  376. paramInfo.name = "Main volume";
  377. break;
  378. case 73:
  379. paramInfo.name = "Delay Time";
  380. break;
  381. case 74:
  382. paramInfo.name = "Delay Feedback";
  383. break;
  384. case 75:
  385. paramInfo.name = "Delay Volume";
  386. break;
  387. case 76:
  388. paramInfo.name = "Chorus Rate";
  389. break;
  390. case 77:
  391. paramInfo.name = "Chorus Depth";
  392. break;
  393. case 78:
  394. paramInfo.name = "Chorus Volume";
  395. break;
  396. case 79:
  397. paramInfo.name = "Reverb Size";
  398. break;
  399. case 80:
  400. paramInfo.name = "Reverb Width";
  401. break;
  402. case 81:
  403. paramInfo.name = "Reverb Damp";
  404. break;
  405. case 82:
  406. paramInfo.name = "Reverb Volume";
  407. break;
  408. case 83:
  409. paramInfo.name = "Wave1 Panning";
  410. break;
  411. case 84:
  412. paramInfo.name = "Wave2 Panning";
  413. break;
  414. case 85:
  415. paramInfo.name = "Wave3 Panning";
  416. break;
  417. case 86:
  418. paramInfo.name = "Wave1 Volume";
  419. break;
  420. case 87:
  421. paramInfo.name = "Wave2 Volume";
  422. break;
  423. case 88:
  424. paramInfo.name = "Wave3 Volume";
  425. break;
  426. case 89:
  427. paramInfo.name = "Wave1 on/off";
  428. break;
  429. case 90:
  430. paramInfo.name = "Wave2 on/off";
  431. break;
  432. case 91:
  433. paramInfo.name = "Wave3 on/off";
  434. break;
  435. default:
  436. paramInfo.name = "unknown";
  437. break;
  438. }
  439. paramInfo.hints = static_cast<ParameterHints>(hints);
  440. return &paramInfo;
  441. }
  442. float getParameterValue(const uint32_t index) const override
  443. {
  444. return (index < kParamCount) ? fParameters[index] : 0.0f;
  445. }
  446. // -------------------------------------------------------------------
  447. // Plugin state calls
  448. void setParameterValue(const uint32_t index, const float value) override
  449. {
  450. if (index < kParamCount)
  451. {
  452. fParameters[index] = value;
  453. fSynth.update(index);
  454. }
  455. }
  456. // -------------------------------------------------------------------
  457. // Plugin process calls
  458. void process(float**, float** outBuffer, const uint32_t frames, const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
  459. {
  460. const TimeInfo* const timeInfo(getTimeInfo());
  461. const double bpm((timeInfo != nullptr && timeInfo->bbt.valid) ? timeInfo->bbt.beatsPerMinute : 120.0);
  462. for (uint32_t i=0; i < midiEventCount; ++i)
  463. {
  464. const MidiEvent* const midiEvent(&midiEvents[i]);
  465. const uint8_t status(MIDI_GET_STATUS_FROM_DATA(midiEvent->data));
  466. if (status == MIDI_STATUS_NOTE_ON)
  467. {
  468. fSynth.playNote(midiEvent->data[1], midiEvent->data[2], 0, 1);
  469. }
  470. else if (status == MIDI_STATUS_NOTE_OFF)
  471. {
  472. fSynth.releaseNote(midiEvent->data[1], 0, 1);
  473. }
  474. else if (status == MIDI_STATUS_CONTROL_CHANGE)
  475. {
  476. const uint8_t control(midiEvent->data[1]);
  477. if (control == MIDI_CONTROL_ALL_SOUND_OFF)
  478. fSynth.kill();
  479. else if (control == MIDI_CONTROL_ALL_NOTES_OFF)
  480. fSynth.releaseAll(1);
  481. }
  482. }
  483. if (obf->getNumSamples() != (int)frames)
  484. {
  485. obf->setSize(2, frames, 0, 0, 1);
  486. abf->setSize(2, frames, 0, 0, 1);
  487. dbf1->setSize(2, frames, 0, 0, 1);
  488. dbf2->setSize(2, frames, 0, 0, 1);
  489. dbf3->setSize(2, frames, 0, 0, 1);
  490. }
  491. obf ->clear();
  492. dbf1->clear();
  493. dbf2->clear();
  494. dbf3->clear();
  495. fSynth.doProcess(*obf, *abf, *dbf1, *dbf2, *dbf3);
  496. if (fParameters[75] > 0.001f) fDelay.processBlock(dbf1, bpm);
  497. if (fParameters[78] > 0.001f) fChorus.processBlock(dbf2);
  498. if (fParameters[82] > 0.001f) fReverb.processBlock(dbf3);
  499. AudioSampleBuffer output(outBuffer, 2, 0, frames);
  500. output.clear();
  501. obf->addFrom(0, 0, *dbf1, 0, 0, frames, fParameters[75]);
  502. obf->addFrom(1, 0, *dbf1, 1, 0, frames, fParameters[75]);
  503. obf->addFrom(0, 0, *dbf2, 0, 0, frames, fParameters[78]);
  504. obf->addFrom(1, 0, *dbf2, 1, 0, frames, fParameters[78]);
  505. obf->addFrom(0, 0, *dbf3, 0, 0, frames, fParameters[82]);
  506. obf->addFrom(1, 0, *dbf3, 1, 0, frames, fParameters[82]);
  507. output.addFrom(0, 0, *obf, 0, 0, frames, fParameters[0]);
  508. output.addFrom(1, 0, *obf, 1, 0, frames, fParameters[0]);
  509. }
  510. // -------------------------------------------------------------------
  511. // Plugin UI calls
  512. void uiShow(const bool show) override
  513. {
  514. if (show)
  515. {
  516. if (fWindow == nullptr)
  517. {
  518. fWindow = new HelperWindow2();
  519. fWindow->setName(getUiName());
  520. }
  521. if (fView == nullptr)
  522. fView = new VexEditorComponent();
  523. fWindow->show(fView);
  524. }
  525. else if (fWindow != nullptr)
  526. {
  527. fWindow->hide();
  528. fView = nullptr;
  529. fWindow = nullptr;
  530. }
  531. }
  532. void uiIdle() override
  533. {
  534. if (fWindow == nullptr)
  535. return;
  536. if (fWindow->wasClosedByUser())
  537. {
  538. uiShow(false);
  539. uiClosed();
  540. }
  541. }
  542. void uiSetParameterValue(const uint32_t, const float) override
  543. {
  544. if (fView == nullptr)
  545. return;
  546. //fView->update();
  547. }
  548. // -------------------------------------------------------------------
  549. // Plugin dispatcher calls
  550. void bufferSizeChanged(const uint32_t bufferSize) override
  551. {
  552. delete obf;
  553. delete abf;
  554. delete dbf1;
  555. delete dbf2;
  556. delete dbf3;
  557. obf = new AudioSampleBuffer(2, bufferSize);
  558. abf = new AudioSampleBuffer(2, bufferSize);
  559. dbf1 = new AudioSampleBuffer(2, bufferSize);
  560. dbf2 = new AudioSampleBuffer(2, bufferSize);
  561. dbf3 = new AudioSampleBuffer(2, bufferSize);
  562. }
  563. void sampleRateChanged(const double sampleRate) override
  564. {
  565. fChorus.setSampleRate(sampleRate);
  566. fDelay.setSampleRate(sampleRate);
  567. fSynth.setSampleRate(sampleRate);
  568. }
  569. void uiNameChanged(const char* const uiName) override
  570. {
  571. if (fWindow == nullptr)
  572. return;
  573. fWindow->setName(uiName);
  574. }
  575. private:
  576. float fParameters[kParamCount];
  577. AudioSampleBuffer* obf;
  578. AudioSampleBuffer* abf;
  579. AudioSampleBuffer* dbf1; // delay
  580. AudioSampleBuffer* dbf2; // chorus
  581. AudioSampleBuffer* dbf3; // reverb
  582. VexChorus fChorus;
  583. VexDelay fDelay;
  584. VexReverb fReverb;
  585. VexSyntModule fSynth;
  586. ScopedPointer<VexEditorComponent> fView;
  587. ScopedPointer<HelperWindow2> fWindow;
  588. PluginClassEND(VexSynthPlugin)
  589. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VexSynthPlugin)
  590. };
  591. // -----------------------------------------------------------------------
  592. static const PluginDescriptor vexSynthDesc = {
  593. /* category */ PLUGIN_CATEGORY_SYNTH,
  594. /* hints */ static_cast<PluginHints>(PLUGIN_HAS_GUI|PLUGIN_NEEDS_SINGLE_THREAD|PLUGIN_USES_TIME),
  595. /* supports */ static_cast<PluginSupports>(0x0),
  596. /* audioIns */ 0,
  597. /* audioOuts */ 2,
  598. /* midiIns */ 1,
  599. /* midiOuts */ 0,
  600. /* paramIns */ VexSynthPlugin::kParamCount,
  601. /* paramOuts */ 0,
  602. /* name */ "VexSynth",
  603. /* label */ "vexSynth",
  604. /* maker */ "falkTX",
  605. /* copyright */ "GNU GPL v2+",
  606. PluginDescriptorFILL(VexSynthPlugin)
  607. };
  608. // -----------------------------------------------------------------------
  609. CARLA_EXPORT
  610. void carla_register_native_plugin_vex_synth()
  611. {
  612. carla_register_native_plugin(&vexSynthDesc);
  613. }
  614. // -----------------------------------------------------------------------