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.

zynaddsubfx.cpp 20KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. /*
  2. * Carla Native Plugins
  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 GPL.txt file
  16. */
  17. // for UINT32_MAX
  18. #define __STDC_LIMIT_MACROS
  19. #include <cstdint>
  20. #include "CarlaNative.hpp"
  21. #include "CarlaMIDI.h"
  22. #include "CarlaString.hpp"
  23. #include "RtList.hpp"
  24. #include <QtCore/QThread>
  25. #include "zynaddsubfx/Misc/Master.h"
  26. #include "zynaddsubfx/Misc/Part.h"
  27. #include "zynaddsubfx/Misc/Util.h"
  28. #ifdef WANT_ZYNADDSUBFX_UI
  29. // FIXME
  30. # ifdef override
  31. # define override_hack
  32. # undef override
  33. # endif
  34. # include "zynaddsubfx/UI/common.H"
  35. # include "zynaddsubfx/UI/MasterUI.h"
  36. # include <FL/Fl_Shared_Image.H>
  37. # include <FL/Fl_Tiled_Image.H>
  38. # include <FL/Fl_Dial.H>
  39. # include <FL/Fl_Theme.H>
  40. # ifdef override_hack
  41. # define override
  42. # undef override_hack
  43. # endif
  44. #endif
  45. #include <ctime>
  46. #include <set>
  47. #include <string>
  48. // Dummy variables and functions for linking purposes
  49. const char* instance_name = nullptr;
  50. class WavFile;
  51. namespace Nio {
  52. bool start(void){return 1;}
  53. void stop(void){}
  54. bool setSource(std::string){return true;}
  55. bool setSink(std::string){return true;}
  56. std::set<std::string> getSources(void){return std::set<std::string>();}
  57. std::set<std::string> getSinks(void){return std::set<std::string>();}
  58. std::string getSource(void){return "";}
  59. std::string getSink(void){return "";}
  60. void waveNew(WavFile*){}
  61. void waveStart(void){}
  62. void waveStop(void){}
  63. void waveEnd(void){}
  64. }
  65. SYNTH_T* synth = nullptr;
  66. #ifdef WANT_ZYNADDSUBFX_UI
  67. #define PIXMAP_PATH "/resources/zynaddsubfx/"
  68. static Fl_Tiled_Image* gModuleBackdrop = nullptr;
  69. static CarlaString gPixmapPath;
  70. extern CarlaString gUiPixmapPath;
  71. void set_module_parameters(Fl_Widget* o)
  72. {
  73. CARLA_ASSERT(gModuleBackdrop != nullptr);
  74. o->box(FL_DOWN_FRAME);
  75. o->align(o->align() | FL_ALIGN_IMAGE_BACKDROP);
  76. o->color(FL_BLACK);
  77. o->labeltype(FL_SHADOW_LABEL);
  78. if (gModuleBackdrop != nullptr)
  79. o->image(gModuleBackdrop);
  80. }
  81. #endif
  82. class ZynAddSubFxPlugin : public PluginDescriptorClass
  83. {
  84. public:
  85. enum Parameters {
  86. PARAMETER_COUNT = 0
  87. };
  88. ZynAddSubFxPlugin(const HostDescriptor* const host)
  89. : PluginDescriptorClass(host),
  90. kMaster(new Master()),
  91. kSampleRate(getSampleRate()),
  92. fIsActive(false),
  93. fThread(kMaster, host)
  94. {
  95. fThread.start();
  96. maybeInitPrograms(kMaster);
  97. for (int i = 0; i < NUM_MIDI_PARTS; ++i)
  98. kMaster->partonoff(i, 1);
  99. }
  100. ~ZynAddSubFxPlugin() override
  101. {
  102. //ensure that everything has stopped
  103. pthread_mutex_lock(&kMaster->mutex);
  104. pthread_mutex_unlock(&kMaster->mutex);
  105. fThread.stop();
  106. fThread.wait();
  107. delete kMaster;
  108. }
  109. protected:
  110. // -------------------------------------------------------------------
  111. // Plugin parameter calls
  112. uint32_t getParameterCount() override
  113. {
  114. return PARAMETER_COUNT;
  115. }
  116. const Parameter* getParameterInfo(const uint32_t index) override
  117. {
  118. CARLA_ASSERT(index < getParameterCount());
  119. //if (index >= PARAMETER_COUNT)
  120. return nullptr;
  121. static Parameter param;
  122. param.ranges.step = PARAMETER_RANGES_DEFAULT_STEP;
  123. param.ranges.stepSmall = PARAMETER_RANGES_DEFAULT_STEP_SMALL;
  124. param.ranges.stepLarge = PARAMETER_RANGES_DEFAULT_STEP_LARGE;
  125. param.scalePointCount = 0;
  126. param.scalePoints = nullptr;
  127. switch (index)
  128. {
  129. #if 0
  130. case PARAMETER_MASTER:
  131. param.hints = PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE;
  132. param.name = "Master Volume";
  133. param.unit = nullptr;
  134. param.ranges.min = 0.0f;
  135. param.ranges.max = 100.0f;
  136. param.ranges.def = 100.0f;
  137. break;
  138. #endif
  139. }
  140. return &param;
  141. }
  142. float getParameterValue(const uint32_t index) override
  143. {
  144. CARLA_ASSERT(index < getParameterCount());
  145. switch (index)
  146. {
  147. #if 0
  148. case PARAMETER_MASTER:
  149. return kMaster->Pvolume;
  150. #endif
  151. default:
  152. return 0.0f;
  153. }
  154. }
  155. // -------------------------------------------------------------------
  156. // Plugin midi-program calls
  157. uint32_t getMidiProgramCount() override
  158. {
  159. return sPrograms.count();
  160. }
  161. const MidiProgram* getMidiProgramInfo(const uint32_t index) override
  162. {
  163. CARLA_ASSERT(index < getMidiProgramCount());
  164. if (index >= sPrograms.count())
  165. return nullptr;
  166. const ProgramInfo* const pInfo(sPrograms.getAt(index));
  167. static MidiProgram midiProgram;
  168. midiProgram.bank = pInfo->bank;
  169. midiProgram.program = pInfo->prog;
  170. midiProgram.name = pInfo->name;
  171. return &midiProgram;
  172. }
  173. // -------------------------------------------------------------------
  174. // Plugin state calls
  175. void setParameterValue(const uint32_t index, const float value) override
  176. {
  177. CARLA_ASSERT(index < getParameterCount());
  178. switch (index)
  179. {
  180. }
  181. return;
  182. // unused, TODO
  183. (void)value;
  184. }
  185. void setMidiProgram(const uint8_t channel, const uint32_t bank, const uint32_t program) override
  186. {
  187. if (bank >= kMaster->bank.banks.size())
  188. return;
  189. if (program >= BANK_SIZE)
  190. return;
  191. bool isOffline = false;
  192. if (isOffline || ! fIsActive)
  193. loadProgram(kMaster, channel, bank, program);
  194. else
  195. fThread.loadLater(channel, bank, program);
  196. }
  197. void setCustomData(const char* const key, const char* const value) override
  198. {
  199. CARLA_ASSERT(key != nullptr);
  200. CARLA_ASSERT(value != nullptr);
  201. if (std::strcmp(key, "CarlaAlternateFile1") == 0) // xmz
  202. kMaster->loadXML(value);
  203. if (std::strcmp(key, "CarlaAlternateFile2") == 0) // xiz
  204. kMaster->part[0]->loadXMLinstrument(value);
  205. }
  206. // -------------------------------------------------------------------
  207. // Plugin process calls
  208. void activate() override
  209. {
  210. // broken
  211. //for (int i=0; i < NUM_MIDI_PARTS; ++i)
  212. // kMaster->setController(0, MIDI_CONTROL_ALL_SOUND_OFF, 0);
  213. fIsActive = true;
  214. }
  215. void deactivate() override
  216. {
  217. fIsActive = false;
  218. }
  219. void process(float**, float** const outBuffer, const uint32_t frames, const uint32_t midiEventCount, const MidiEvent* const midiEvents) override
  220. {
  221. if (pthread_mutex_trylock(&kMaster->mutex) != 0)
  222. {
  223. carla_zeroFloat(outBuffer[0], frames);
  224. carla_zeroFloat(outBuffer[1], frames);
  225. return;
  226. }
  227. for (uint32_t i=0; i < midiEventCount; ++i)
  228. {
  229. const MidiEvent* const midiEvent = &midiEvents[i];
  230. const uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent->data);
  231. const uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent->data);
  232. if (MIDI_IS_STATUS_NOTE_OFF(status))
  233. {
  234. const uint8_t note = midiEvent->data[1];
  235. kMaster->noteOff(channel, note);
  236. }
  237. else if (MIDI_IS_STATUS_NOTE_ON(status))
  238. {
  239. const uint8_t note = midiEvent->data[1];
  240. const uint8_t velo = midiEvent->data[2];
  241. kMaster->noteOn(channel, note, velo);
  242. }
  243. else if (MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status))
  244. {
  245. const uint8_t note = midiEvent->data[1];
  246. const uint8_t pressure = midiEvent->data[2];
  247. kMaster->polyphonicAftertouch(channel, note, pressure);
  248. }
  249. }
  250. kMaster->GetAudioOutSamples(frames, kSampleRate, outBuffer[0], outBuffer[1]);
  251. pthread_mutex_unlock(&kMaster->mutex);
  252. }
  253. #ifdef WANT_ZYNADDSUBFX_UI
  254. // -------------------------------------------------------------------
  255. // Plugin UI calls
  256. void uiShow(const bool show) override
  257. {
  258. if (show)
  259. fThread.uiShow();
  260. else
  261. fThread.uiHide();
  262. }
  263. #endif
  264. // -------------------------------------------------------------------
  265. // Plugin state calls
  266. char* getState() override
  267. {
  268. config.save();
  269. char* data = nullptr;
  270. kMaster->getalldata(&data);
  271. return data;
  272. }
  273. void setState(const char* const data) override
  274. {
  275. fThread.stopLoadLater();
  276. kMaster->putalldata((char*)data, 0);
  277. kMaster->applyparameters(true);
  278. }
  279. // -------------------------------------------------------------------
  280. private:
  281. struct ProgramInfo {
  282. uint32_t bank;
  283. uint32_t prog;
  284. const char* name;
  285. ProgramInfo(uint32_t bank_, uint32_t prog_, const char* name_)
  286. : bank(bank_),
  287. prog(prog_),
  288. name(carla_strdup(name_)) {}
  289. ~ProgramInfo()
  290. {
  291. if (name != nullptr)
  292. {
  293. delete[] name;
  294. name = nullptr;
  295. }
  296. }
  297. ProgramInfo() = delete;
  298. ProgramInfo(ProgramInfo&) = delete;
  299. ProgramInfo(const ProgramInfo&) = delete;
  300. };
  301. class ZynThread : public QThread
  302. {
  303. public:
  304. ZynThread(Master* const master, const HostDescriptor* const host)
  305. : kMaster(master),
  306. kHost(host),
  307. #ifdef WANT_ZYNADDSUBFX_UI
  308. fUi(nullptr),
  309. fUiClosed(0),
  310. fNextUiAction(-1),
  311. #endif
  312. fQuit(false),
  313. fChangeProgram(false),
  314. fNextChannel(0),
  315. fNextBank(0),
  316. fNextProgram(0)
  317. {
  318. }
  319. ~ZynThread()
  320. {
  321. // must be closed by now
  322. #ifdef WANT_ZYNADDSUBFX_UI
  323. CARLA_ASSERT(fUi == nullptr);
  324. #endif
  325. CARLA_ASSERT(fQuit);
  326. }
  327. void loadLater(const uint8_t channel, const uint32_t bank, const uint32_t program)
  328. {
  329. fNextChannel = channel;
  330. fNextBank = bank;
  331. fNextProgram = program;
  332. fChangeProgram = true;
  333. }
  334. void stopLoadLater()
  335. {
  336. fChangeProgram = false;
  337. fNextChannel = 0;
  338. fNextBank = 0;
  339. fNextProgram = 0;
  340. }
  341. void stop()
  342. {
  343. fQuit = true;
  344. quit();
  345. }
  346. #ifdef WANT_ZYNADDSUBFX_UI
  347. void uiShow()
  348. {
  349. fNextUiAction = 1;
  350. }
  351. void uiHide()
  352. {
  353. fNextUiAction = 0;
  354. }
  355. #endif
  356. protected:
  357. void run() override
  358. {
  359. while (! fQuit)
  360. {
  361. #ifdef WANT_ZYNADDSUBFX_UI
  362. Fl::lock();
  363. if (fNextUiAction == 1)
  364. {
  365. static bool initialized = false;
  366. if (! initialized)
  367. {
  368. initialized = true;
  369. fl_register_images();
  370. Fl_Dial::default_style(Fl_Dial::PIXMAP_DIAL);
  371. if (Fl_Shared_Image* const img = Fl_Shared_Image::get(gPixmapPath + "knob.png"))
  372. Fl_Dial::default_image(img);
  373. if (Fl_Shared_Image* const img = Fl_Shared_Image::get(gPixmapPath + "window_backdrop.png"))
  374. Fl::scheme_bg(new Fl_Tiled_Image(img));
  375. if(Fl_Shared_Image* const img = Fl_Shared_Image::get(gPixmapPath + "module_backdrop.png"))
  376. gModuleBackdrop = new Fl_Tiled_Image(img);
  377. Fl::background(50, 50, 50);
  378. Fl::background2(70, 70, 70);
  379. Fl::foreground(255, 255, 255);
  380. Fl_Theme::set("Cairo");
  381. }
  382. CARLA_ASSERT(fUi == nullptr);
  383. if (fUi == nullptr)
  384. {
  385. fUiClosed = 0;
  386. fUi = new MasterUI(kMaster, &fUiClosed);
  387. //fUi->npartcounter->callback(_npartcounterCallback, this);
  388. fUi->showUI();
  389. }
  390. }
  391. else if (fNextUiAction == 0)
  392. {
  393. CARLA_ASSERT(fUi != nullptr);
  394. if (fUi != nullptr)
  395. {
  396. delete fUi;
  397. fUi = nullptr;
  398. }
  399. }
  400. fNextUiAction = -1;
  401. if (fUiClosed != 0)
  402. {
  403. fUiClosed = 0;
  404. fNextUiAction = 0;
  405. kHost->ui_closed(kHost->handle);
  406. }
  407. Fl::check();
  408. Fl::unlock();
  409. #endif
  410. if (fChangeProgram)
  411. {
  412. fChangeProgram = false;
  413. loadProgram(kMaster, fNextChannel, fNextBank, fNextProgram);
  414. fNextChannel = 0;
  415. fNextBank = 0;
  416. fNextProgram = 0;
  417. carla_msleep(15);
  418. }
  419. else
  420. {
  421. carla_msleep(30);
  422. }
  423. }
  424. #ifdef WANT_ZYNADDSUBFX_UI
  425. if (fQuit && fUi != nullptr)
  426. {
  427. Fl::lock();
  428. delete fUi;
  429. fUi = nullptr;
  430. Fl::check();
  431. Fl::unlock();
  432. }
  433. #endif
  434. }
  435. #ifdef WANT_ZYNADDSUBFX_UI
  436. void handlePartCounterCallback(Fl_Widget* widget)
  437. {
  438. carla_stdout("handlePartCounterCallback(%p)", widget);
  439. }
  440. static void _npartcounterCallback(Fl_Widget* widget, void* ptr)
  441. {
  442. ((ZynThread*)ptr)->handlePartCounterCallback(widget);
  443. }
  444. #endif
  445. private:
  446. Master* const kMaster;
  447. const HostDescriptor* const kHost;
  448. #ifdef WANT_ZYNADDSUBFX_UI
  449. MasterUI* fUi;
  450. int fUiClosed;
  451. int fNextUiAction;
  452. #endif
  453. bool fQuit;
  454. bool fChangeProgram;
  455. uint8_t fNextChannel;
  456. uint32_t fNextBank;
  457. uint32_t fNextProgram;
  458. };
  459. Master* const kMaster;
  460. const unsigned kSampleRate;
  461. bool fIsActive;
  462. ZynThread fThread;
  463. static int sInstanceCount;
  464. static NonRtList<ProgramInfo*> sPrograms;
  465. static void maybeInitPrograms(Master* const master)
  466. {
  467. static bool doSearch = true;
  468. if (! doSearch)
  469. return;
  470. doSearch = false;
  471. sPrograms.append(new ProgramInfo(0, 0, "default"));
  472. pthread_mutex_lock(&master->mutex);
  473. // refresh banks
  474. master->bank.rescanforbanks();
  475. for (uint32_t i=0, size = master->bank.banks.size(); i < size; ++i)
  476. {
  477. if (master->bank.banks[i].dir.empty())
  478. continue;
  479. master->bank.loadbank(master->bank.banks[i].dir);
  480. for (unsigned int instrument = 0; instrument < BANK_SIZE; ++instrument)
  481. {
  482. const std::string insName(master->bank.getname(instrument));
  483. if (insName.empty() || insName[0] == '\0' || insName[0] == ' ')
  484. continue;
  485. sPrograms.append(new ProgramInfo(i+1, instrument, insName.c_str()));
  486. }
  487. }
  488. pthread_mutex_unlock(&master->mutex);
  489. }
  490. static void loadProgram(Master* const master, const uint8_t channel, const uint32_t bank, const uint32_t program)
  491. {
  492. if (bank == 0)
  493. {
  494. pthread_mutex_lock(&master->mutex);
  495. master->part[channel]->defaults();
  496. master->part[channel]->applyparameters(false);
  497. master->partonoff(channel, 1);
  498. pthread_mutex_unlock(&master->mutex);
  499. return;
  500. }
  501. const std::string& bankdir(master->bank.banks[bank-1].dir);
  502. if (! bankdir.empty())
  503. {
  504. pthread_mutex_lock(&master->mutex);
  505. master->partonoff(channel, 1);
  506. master->bank.loadbank(bankdir);
  507. master->bank.loadfromslot(program, master->part[channel]);
  508. master->part[channel]->applyparameters(false);
  509. pthread_mutex_unlock(&master->mutex);
  510. }
  511. }
  512. public:
  513. static PluginHandle _instantiate(const PluginDescriptor*, HostDescriptor* host)
  514. {
  515. if (sInstanceCount++ == 0)
  516. {
  517. CARLA_ASSERT(synth == nullptr);
  518. CARLA_ASSERT(denormalkillbuf == nullptr);
  519. synth = new SYNTH_T();
  520. synth->buffersize = host->get_buffer_size(host->handle);
  521. synth->samplerate = host->get_sample_rate(host->handle);
  522. synth->alias();
  523. config.init();
  524. config.cfg.SoundBufferSize = synth->buffersize;
  525. config.cfg.SampleRate = synth->samplerate;
  526. config.cfg.GzipCompression = 0;
  527. sprng(std::time(nullptr));
  528. denormalkillbuf = new float[synth->buffersize];
  529. for (int i=0; i < synth->buffersize; ++i)
  530. denormalkillbuf[i] = (RND - 0.5f) * 1e-16;
  531. Master::getInstance();
  532. #ifdef WANT_ZYNADDSUBFX_UI
  533. if (gPixmapPath.isEmpty())
  534. {
  535. gPixmapPath = host->resource_dir;
  536. gPixmapPath += PIXMAP_PATH;
  537. gUiPixmapPath = gPixmapPath;
  538. }
  539. #endif
  540. }
  541. return new ZynAddSubFxPlugin(host);
  542. }
  543. static void _cleanup(PluginHandle handle)
  544. {
  545. delete (ZynAddSubFxPlugin*)handle;
  546. if (--sInstanceCount == 0)
  547. {
  548. CARLA_ASSERT(synth != nullptr);
  549. CARLA_ASSERT(denormalkillbuf != nullptr);
  550. Master::deleteInstance();
  551. delete[] denormalkillbuf;
  552. denormalkillbuf = nullptr;
  553. delete synth;
  554. synth = nullptr;
  555. }
  556. }
  557. static void _clearPrograms()
  558. {
  559. for (auto it = sPrograms.begin(); it.valid(); it.next())
  560. {
  561. ProgramInfo* const programInfo(*it);
  562. delete programInfo;
  563. }
  564. sPrograms.clear();
  565. }
  566. private:
  567. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ZynAddSubFxPlugin)
  568. };
  569. int ZynAddSubFxPlugin::sInstanceCount = 0;
  570. NonRtList<ZynAddSubFxPlugin::ProgramInfo*> ZynAddSubFxPlugin::sPrograms;
  571. static const struct ProgramsDestructor {
  572. ProgramsDestructor() {}
  573. ~ProgramsDestructor() {
  574. ZynAddSubFxPlugin::_clearPrograms();
  575. }
  576. } _programsDestructor;
  577. // -----------------------------------------------------------------------
  578. static const PluginDescriptor zynaddsubfxDesc = {
  579. /* category */ PLUGIN_CATEGORY_SYNTH,
  580. #ifdef WANT_ZYNADDSUBFX_UI
  581. /* hints */ static_cast<PluginHints>(PLUGIN_IS_SYNTH|PLUGIN_HAS_GUI|PLUGIN_USES_STATE),
  582. #else
  583. /* hints */ static_cast<PluginHints>(PLUGIN_IS_SYNTH|PLUGIN_USES_STATE),
  584. #endif
  585. /* audioIns */ 2,
  586. /* audioOuts */ 2,
  587. /* midiIns */ 1,
  588. /* midiOuts */ 0,
  589. /* paramIns */ ZynAddSubFxPlugin::PARAMETER_COUNT,
  590. /* paramOuts */ 0,
  591. /* name */ "ZynAddSubFX",
  592. /* label */ "zynaddsubfx",
  593. /* maker */ "falkTX",
  594. /* copyright */ "GNU GPL v2+",
  595. PluginDescriptorFILL(ZynAddSubFxPlugin)
  596. };
  597. // -----------------------------------------------------------------------
  598. void carla_register_native_plugin_zynaddsubfx()
  599. {
  600. carla_register_native_plugin(&zynaddsubfxDesc);
  601. }
  602. // -----------------------------------------------------------------------