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.

574 lines
21KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021-2022 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 LICENSE file.
  16. */
  17. #include "IldaeilBasePlugin.hpp"
  18. #include "CarlaEngine.hpp"
  19. #include "water/files/File.h"
  20. #include "water/streams/MemoryOutputStream.h"
  21. #include "water/xml/XmlDocument.h"
  22. START_NAMESPACE_DISTRHO
  23. using namespace CARLA_BACKEND_NAMESPACE;
  24. // --------------------------------------------------------------------------------------------------------------------
  25. static uint32_t host_get_buffer_size(NativeHostHandle);
  26. static double host_get_sample_rate(NativeHostHandle);
  27. static bool host_is_offline(NativeHostHandle);
  28. static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
  29. static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
  30. static void host_ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value);
  31. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
  32. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
  33. static void host_ui_closed(NativeHostHandle handle);
  34. static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  35. static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  36. static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt);
  37. // --------------------------------------------------------------------------------------------------------------------
  38. Mutex IldaeilBasePlugin::sPluginInfoLoadMutex;
  39. // --------------------------------------------------------------------------------------------------------------------
  40. const char* IldaeilBasePlugin::getPathForJSFX()
  41. {
  42. static water::String path;
  43. if (path.isEmpty())
  44. {
  45. #if defined(CARLA_OS_MAC)
  46. path = water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName()
  47. + "/Library/Application Support/REAPER/Effects";
  48. if (! water::File(path).isDirectory())
  49. path = "/Applications/REAPER.app/Contents/InstallFiles/Effects";
  50. #elif defined(CARLA_OS_WASM)
  51. path = "/jsfx";
  52. #elif defined(CARLA_OS_WIN)
  53. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\REAPER\\Effects";
  54. if (! water::File(path).isDirectory())
  55. path = water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName()
  56. + "\\REAPER\\InstallData\\Effects";
  57. #else
  58. if (const char* const configHome = std::getenv("XDG_CONFIG_HOME"))
  59. path = configHome;
  60. else
  61. path = water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName() + "/.config";
  62. path += "/REAPER/Effects";
  63. #endif
  64. }
  65. return path.toRawUTF8();
  66. }
  67. // --------------------------------------------------------------------------------------------------------------------
  68. class IldaeilPlugin : public IldaeilBasePlugin
  69. {
  70. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  71. float* fDummyBuffer;
  72. float* fDummyBuffers[2];
  73. #endif
  74. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  75. static constexpr const uint kMaxMidiEventCount = 512;
  76. NativeMidiEvent* fMidiEvents;
  77. #endif
  78. mutable NativeTimeInfo fCarlaTimeInfo;
  79. mutable water::MemoryOutputStream fLastProjectState;
  80. uint32_t fLastLatencyValue;
  81. public:
  82. IldaeilPlugin()
  83. : IldaeilBasePlugin(),
  84. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  85. fDummyBuffer(nullptr),
  86. #endif
  87. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  88. fMidiEvents(nullptr),
  89. #endif
  90. fLastLatencyValue(0)
  91. {
  92. fCarlaPluginDescriptor = carla_get_native_rack_plugin();
  93. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  94. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  95. fCarlaHostDescriptor.handle = this;
  96. fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
  97. fCarlaHostDescriptor.uiName = "Ildaeil";
  98. fCarlaHostDescriptor.uiParentId = 0;
  99. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  100. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  101. fCarlaHostDescriptor.is_offline = host_is_offline;
  102. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  103. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  104. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  105. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  106. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  107. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  108. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  109. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  110. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  111. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  112. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  113. fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
  114. DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
  115. const char* const bundlePath = getBundlePath();
  116. #ifdef CARLA_OS_WIN
  117. #define EXT ".exe"
  118. #else
  119. #define EXT ""
  120. #endif
  121. if (bundlePath != nullptr
  122. && water::File(bundlePath + water::String(DISTRHO_OS_SEP_STR "carla-bridge-native" EXT)).existsAsFile())
  123. {
  124. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, bundlePath);
  125. // carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "");
  126. }
  127. else
  128. {
  129. #ifdef CARLA_OS_MAC
  130. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
  131. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
  132. #else
  133. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
  134. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
  135. #endif
  136. }
  137. #undef EXT
  138. if (const char* const path = std::getenv("LV2_PATH"))
  139. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, path);
  140. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, getPathForJSFX());
  141. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
  142. 0, 0, nullptr, 0.0f);
  143. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  144. fMidiEvents = new NativeMidiEvent[kMaxMidiEventCount];
  145. #endif
  146. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  147. // create dummy buffers
  148. bufferSizeChanged(getBufferSize());
  149. #endif
  150. }
  151. ~IldaeilPlugin() override
  152. {
  153. if (fCarlaHostHandle != nullptr)
  154. {
  155. carla_host_handle_free(fCarlaHostHandle);
  156. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  157. delete[] fDummyBuffer;
  158. #endif
  159. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  160. delete[] fMidiEvents;
  161. #endif
  162. }
  163. if (fCarlaPluginHandle != nullptr)
  164. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  165. }
  166. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  167. {
  168. const TimePosition& timePos(getTimePosition());
  169. fCarlaTimeInfo.playing = timePos.playing;
  170. fCarlaTimeInfo.frame = timePos.frame;
  171. fCarlaTimeInfo.bbt.valid = timePos.bbt.valid;
  172. fCarlaTimeInfo.bbt.bar = timePos.bbt.bar;
  173. fCarlaTimeInfo.bbt.beat = timePos.bbt.beat;
  174. fCarlaTimeInfo.bbt.tick = timePos.bbt.tick;
  175. fCarlaTimeInfo.bbt.barStartTick = timePos.bbt.barStartTick;
  176. fCarlaTimeInfo.bbt.beatsPerBar = timePos.bbt.beatsPerBar;
  177. fCarlaTimeInfo.bbt.beatType = timePos.bbt.beatType;
  178. fCarlaTimeInfo.bbt.ticksPerBeat = timePos.bbt.ticksPerBeat;
  179. fCarlaTimeInfo.bbt.beatsPerMinute = timePos.bbt.beatsPerMinute;
  180. return &fCarlaTimeInfo;
  181. }
  182. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  183. bool hostWriteMidiEvent(const NativeMidiEvent* const event)
  184. {
  185. MidiEvent midiEvent;
  186. midiEvent.frame = event->time;
  187. midiEvent.size = event->size;
  188. midiEvent.dataExt = nullptr;
  189. uint32_t i = 0;
  190. for (; i < event->size; ++i)
  191. midiEvent.data[i] = event->data[i];
  192. for (; i < MidiEvent::kDataSize; ++i)
  193. midiEvent.data[i] = 0;
  194. return writeMidiEvent(midiEvent);
  195. }
  196. #endif
  197. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  198. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  199. {
  200. switch (opcode)
  201. {
  202. // cannnot be supported
  203. case NATIVE_HOST_OPCODE_HOST_IDLE:
  204. break;
  205. // other stuff
  206. case NATIVE_HOST_OPCODE_NULL:
  207. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  208. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  209. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  210. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  211. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  212. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  213. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  214. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  215. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  216. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  217. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  218. case NATIVE_HOST_OPCODE_UI_RESIZE:
  219. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  220. // TESTING
  221. d_stdout("dispatcher %i, %i, %li, %p, %f", opcode, index, value, ptr, opt);
  222. break;
  223. }
  224. return 0;
  225. }
  226. protected:
  227. /* --------------------------------------------------------------------------------------------------------
  228. * Information */
  229. const char* getLabel() const override
  230. {
  231. #if ILDAEIL_STANDALONE
  232. return "Ildaeil";
  233. #elif DISTRHO_PLUGIN_IS_SYNTH
  234. return "IldaeilSynth";
  235. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  236. return "IldaeilMIDI";
  237. #else
  238. return "IldaeilFX";
  239. #endif
  240. }
  241. const char* getDescription() const override
  242. {
  243. return "Ildaeil is a mini-plugin host working as a plugin, allowing one-to-one plugin format reusage.";
  244. }
  245. const char* getMaker() const override
  246. {
  247. return "DISTRHO";
  248. }
  249. const char* getHomePage() const override
  250. {
  251. return "https://github.com/DISTRHO/Ildaeil";
  252. }
  253. const char* getLicense() const override
  254. {
  255. return "GPLv2+";
  256. }
  257. uint32_t getVersion() const override
  258. {
  259. return d_version(1, 2, 0);
  260. }
  261. int64_t getUniqueId() const override
  262. {
  263. #if ILDAEIL_STANDALONE
  264. return d_cconst('d', 'I', 'l', 'd');
  265. #elif DISTRHO_PLUGIN_IS_SYNTH
  266. return d_cconst('d', 'I', 'l', 'S');
  267. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  268. return d_cconst('d', 'I', 'l', 'M');
  269. #else
  270. return d_cconst('d', 'I', 'l', 'F');
  271. #endif
  272. }
  273. /* --------------------------------------------------------------------------------------------------------
  274. * Init */
  275. void initAudioPort(bool input, uint32_t index, AudioPort& port) override
  276. {
  277. port.groupId = kPortGroupStereo;
  278. Plugin::initAudioPort(input, index, port);
  279. }
  280. void initState(const uint32_t index, State& state) override
  281. {
  282. DISTRHO_SAFE_ASSERT_RETURN(index == 0,);
  283. state.hints = kStateIsOnlyForDSP;
  284. state.key = "project";
  285. state.defaultValue = ""
  286. "<?xml version='1.0' encoding='UTF-8'?>\n"
  287. "<!DOCTYPE CARLA-PROJECT>\n"
  288. "<CARLA-PROJECT VERSION='" CARLA_VERSION_STRMIN "'>\n"
  289. "</CARLA-PROJECT>\n";
  290. }
  291. /* --------------------------------------------------------------------------------------------------------
  292. * Internal data */
  293. String getState(const char* const key) const override
  294. {
  295. if (std::strcmp(key, "project") == 0)
  296. {
  297. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  298. fLastProjectState.reset();
  299. engine->saveProjectInternal(fLastProjectState);
  300. return String(static_cast<char*>(fLastProjectState.getDataAndRelease()), false);
  301. }
  302. return String();
  303. }
  304. void setState(const char* const key, const char* const value) override
  305. {
  306. if (std::strcmp(key, "project") == 0)
  307. {
  308. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  309. water::XmlDocument xml(value);
  310. {
  311. const MutexLocker cml(sPluginInfoLoadMutex);
  312. engine->loadProjectInternal(xml, true);
  313. }
  314. if (fUI != nullptr)
  315. ildaeilProjectLoadedFromDSP(fUI);
  316. }
  317. }
  318. /* --------------------------------------------------------------------------------------------------------
  319. * Process */
  320. void checkLatencyChanged()
  321. {
  322. if (fCarlaHostHandle == nullptr)
  323. return;
  324. uint32_t latency = 0;
  325. for (uint32_t i=0; i < carla_get_current_plugin_count(fCarlaHostHandle); ++i)
  326. latency += carla_get_plugin_latency(fCarlaHostHandle, i);
  327. if (fLastLatencyValue != latency)
  328. {
  329. fLastLatencyValue = latency;
  330. setLatency(latency);
  331. }
  332. }
  333. void activate() override
  334. {
  335. if (fCarlaPluginHandle != nullptr)
  336. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  337. checkLatencyChanged();
  338. }
  339. void deactivate() override
  340. {
  341. checkLatencyChanged();
  342. if (fCarlaPluginHandle != nullptr)
  343. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  344. }
  345. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  346. void run(const float** inputs, float** outputs, uint32_t frames,
  347. const MidiEvent* dpfMidiEvents, uint32_t dpfMidiEventCount) override
  348. #else
  349. void run(const float** inputs, float** outputs, uint32_t frames) override
  350. #endif
  351. {
  352. if (fCarlaPluginHandle != nullptr)
  353. {
  354. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  355. uint32_t midiEventCount = 0;
  356. for (uint32_t i=0; i < dpfMidiEventCount; ++i)
  357. {
  358. const MidiEvent& dpfMidiEvent(dpfMidiEvents[i]);
  359. if (dpfMidiEvent.size > 4)
  360. continue;
  361. NativeMidiEvent& midiEvent(fMidiEvents[midiEventCount]);
  362. midiEvent.time = dpfMidiEvent.frame;
  363. midiEvent.port = 0;
  364. midiEvent.size = dpfMidiEvent.size;
  365. std::memcpy(midiEvent.data, dpfMidiEvent.data, midiEvent.size);
  366. if (++midiEventCount == kMaxMidiEventCount)
  367. break;
  368. }
  369. #else
  370. static constexpr const NativeMidiEvent* fMidiEvents = nullptr;
  371. static constexpr const uint32_t midiEventCount = 0;
  372. #endif
  373. #if DISTRHO_PLUGIN_NUM_INPUTS == 0
  374. inputs = (const float**)fDummyBuffers;
  375. #endif
  376. #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  377. outputs = fDummyBuffers;
  378. #endif
  379. fCarlaPluginDescriptor->process(fCarlaPluginHandle, (float**)inputs, outputs, frames,
  380. fMidiEvents, midiEventCount);
  381. checkLatencyChanged();
  382. }
  383. else
  384. {
  385. std::memset(outputs[0], 0, sizeof(float)*frames);
  386. std::memset(outputs[1], 0, sizeof(float)*frames);
  387. }
  388. }
  389. void bufferSizeChanged(const uint32_t newBufferSize) override
  390. {
  391. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  392. delete[] fDummyBuffer;
  393. fDummyBuffer = new float[newBufferSize];
  394. fDummyBuffers[0] = fDummyBuffer;
  395. fDummyBuffers[1] = fDummyBuffer;
  396. std::memset(fDummyBuffer, 0, sizeof(float)*newBufferSize);
  397. #endif
  398. if (fCarlaPluginHandle != nullptr)
  399. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_BUFFER_SIZE_CHANGED,
  400. 0, newBufferSize, nullptr, 0.0f);
  401. }
  402. void sampleRateChanged(const double newSampleRate) override
  403. {
  404. if (fCarlaPluginHandle != nullptr)
  405. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  406. 0, 0, nullptr, newSampleRate);
  407. }
  408. // -------------------------------------------------------------------------------------------------------
  409. /**
  410. Set our plugin class as non-copyable and add a leak detector just in case.
  411. */
  412. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilPlugin)
  413. };
  414. // -----------------------------------------------------------------------------------------------------------
  415. static uint32_t host_get_buffer_size(const NativeHostHandle handle)
  416. {
  417. return static_cast<IldaeilPlugin*>(handle)->getBufferSize();
  418. }
  419. static double host_get_sample_rate(const NativeHostHandle handle)
  420. {
  421. return static_cast<IldaeilPlugin*>(handle)->getSampleRate();
  422. }
  423. static bool host_is_offline(NativeHostHandle)
  424. {
  425. return false;
  426. }
  427. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  428. {
  429. return static_cast<IldaeilPlugin*>(handle)->hostGetTimeInfo();
  430. }
  431. static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
  432. {
  433. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  434. return static_cast<IldaeilPlugin*>(handle)->hostWriteMidiEvent(event);
  435. #else
  436. return handle != nullptr && event != nullptr && false;
  437. #endif
  438. }
  439. static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
  440. {
  441. ildaeilParameterChangeForUI(static_cast<IldaeilPlugin*>(handle)->fUI, index, value);
  442. }
  443. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
  444. {
  445. d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
  446. }
  447. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
  448. {
  449. d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
  450. }
  451. static void host_ui_closed(NativeHostHandle handle)
  452. {
  453. ildaeilCloseUI(static_cast<IldaeilPlugin*>(handle));
  454. }
  455. static const char* host_ui_open_file(const NativeHostHandle handle, const bool isDir, const char* const title, const char* const filter)
  456. {
  457. return ildaeilOpenFileForUI(static_cast<IldaeilPlugin*>(handle)->fUI, isDir, title, filter);
  458. }
  459. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
  460. {
  461. return nullptr;
  462. }
  463. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  464. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  465. {
  466. return static_cast<IldaeilPlugin*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  467. }
  468. /* --------------------------------------------------------------------------------------------------------------------
  469. * Plugin entry point, called by DPF to create a new plugin instance. */
  470. Plugin* createPlugin()
  471. {
  472. return new IldaeilPlugin();
  473. }
  474. // --------------------------------------------------------------------------------------------------------------------
  475. END_NAMESPACE_DISTRHO