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.

800 lines
29KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021-2023 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 "DistrhoPluginUtils.hpp"
  19. #include "CarlaEngine.hpp"
  20. #include "water/files/File.h"
  21. #include "water/streams/MemoryOutputStream.h"
  22. #include "water/xml/XmlDocument.h"
  23. START_NAMESPACE_DISTRHO
  24. using namespace CARLA_BACKEND_NAMESPACE;
  25. // --------------------------------------------------------------------------------------------------------------------
  26. static uint32_t host_get_buffer_size(NativeHostHandle);
  27. static double host_get_sample_rate(NativeHostHandle);
  28. static bool host_is_offline(NativeHostHandle);
  29. static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
  30. static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
  31. static void host_ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value);
  32. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
  33. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
  34. static void host_ui_closed(NativeHostHandle handle);
  35. static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  36. static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  37. static intptr_t host_dispatcher(NativeHostHandle h, NativeHostDispatcherOpcode op, int32_t, intptr_t, void*, float);
  38. // --------------------------------------------------------------------------------------------------------------------
  39. Mutex IldaeilBasePlugin::sPluginInfoLoadMutex;
  40. // --------------------------------------------------------------------------------------------------------------------
  41. static water::String getHomePath()
  42. {
  43. static water::String path(water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName());
  44. return path;
  45. }
  46. static const char* getPathForLADSPA()
  47. {
  48. static water::String path;
  49. if (path.isEmpty())
  50. {
  51. #if defined(CARLA_OS_HAIKU)
  52. path = getHomePath() + "/.ladspa:/system/add-ons/media/ladspaplugins:/system/lib/ladspa";
  53. #elif defined(CARLA_OS_MAC)
  54. path = getHomePath() + "/Library/Audio/Plug-Ins/LADSPA:/Library/Audio/Plug-Ins/LADSPA";
  55. #elif defined(CARLA_OS_WASM)
  56. path = "/ladspa";
  57. #elif defined(CARLA_OS_WIN)
  58. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\LADSPA;";
  59. path += water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName() + "\\LADSPA";
  60. #else
  61. path = getHomePath() + "/.ladspa:/usr/lib/ladspa:/usr/local/lib/ladspa";
  62. #endif
  63. }
  64. return path.toRawUTF8();
  65. }
  66. static const char* getPathForDSSI()
  67. {
  68. static water::String path;
  69. if (path.isEmpty())
  70. {
  71. #if defined(CARLA_OS_HAIKU)
  72. path = getHomePath() + "/.dssi:/system/add-ons/media/dssiplugins:/system/lib/dssi";
  73. #elif defined(CARLA_OS_MAC)
  74. path = getHomePath() + "/Library/Audio/Plug-Ins/DSSI:/Library/Audio/Plug-Ins/DSSI";
  75. #elif defined(CARLA_OS_WASM)
  76. path = "/dssi";
  77. #elif defined(CARLA_OS_WIN)
  78. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\DSSI;";
  79. path += water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName() + "\\DSSI";
  80. #else
  81. path = getHomePath() + "/.dssi:/usr/lib/dssi:/usr/local/lib/dssi";
  82. #endif
  83. }
  84. return path.toRawUTF8();
  85. }
  86. static const char* getPathForLV2()
  87. {
  88. static water::String path;
  89. if (path.isEmpty())
  90. {
  91. #if defined(CARLA_OS_HAIKU)
  92. path = getHomePath() + "/.lv2:/system/add-ons/media/lv2plugins";
  93. #elif defined(CARLA_OS_MAC)
  94. path = getHomePath() + "/Library/Audio/Plug-Ins/LV2:/Library/Audio/Plug-Ins/LV2";
  95. #elif defined(CARLA_OS_WASM)
  96. path = "/lv2";
  97. #elif defined(CARLA_OS_WIN)
  98. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\LV2;";
  99. path += water::File::getSpecialLocation(water::File::winCommonProgramFiles).getFullPathName() + "\\LV2";
  100. #else
  101. path = getHomePath() + "/.lv2:/usr/lib/lv2:/usr/local/lib/lv2";
  102. #endif
  103. }
  104. return path.toRawUTF8();
  105. }
  106. static const char* getPathForVST2()
  107. {
  108. static water::String path;
  109. if (path.isEmpty())
  110. {
  111. #if defined(CARLA_OS_HAIKU)
  112. path = getHomePath() + "/.vst:/system/add-ons/media/vstplugins";
  113. #elif defined(CARLA_OS_MAC)
  114. path = getHomePath() + "/Library/Audio/Plug-Ins/VST:/Library/Audio/Plug-Ins/VST";
  115. #elif defined(CARLA_OS_WASM)
  116. path = "/vst";
  117. #elif defined(CARLA_OS_WIN)
  118. path = water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName() + "\\VstPlugins;";
  119. path += water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName() + "\\Steinberg\\VstPlugins;";
  120. path += water::File::getSpecialLocation(water::File::winCommonProgramFiles).getFullPathName() + "\\VST2";
  121. #else
  122. path = getHomePath() + "/.vst:/usr/lib/vst:/usr/local/lib/vst";
  123. #endif
  124. }
  125. return path.toRawUTF8();
  126. }
  127. static const char* getPathForVST3()
  128. {
  129. static water::String path;
  130. if (path.isEmpty())
  131. {
  132. #if defined(CARLA_OS_HAIKU)
  133. path = getHomePath() + "/.vst3:/system/add-ons/media/dssiplugins";
  134. #elif defined(CARLA_OS_MAC)
  135. path = getHomePath() + "/Library/Audio/Plug-Ins/VST3:/Library/Audio/Plug-Ins/VST3";
  136. #elif defined(CARLA_OS_WASM)
  137. path = "/vst3";
  138. #elif defined(CARLA_OS_WIN)
  139. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\VST3;";
  140. path += water::File::getSpecialLocation(water::File::winCommonProgramFiles).getFullPathName() + "\\VST3";
  141. #else
  142. path = getHomePath() + "/.vst3:/usr/lib/vst3:/usr/local/lib/vst3";
  143. #endif
  144. }
  145. return path.toRawUTF8();
  146. }
  147. static const char* getPathForCLAP()
  148. {
  149. static water::String path;
  150. if (path.isEmpty())
  151. {
  152. #if defined(CARLA_OS_HAIKU)
  153. path = getHomePath() + "/.clap:/system/add-ons/media/clapplugins";
  154. #elif defined(CARLA_OS_MAC)
  155. path = getHomePath() + "/Library/Audio/Plug-Ins/CLAP:/Library/Audio/Plug-Ins/CLAP";
  156. #elif defined(CARLA_OS_WASM)
  157. path = "/clap";
  158. #elif defined(CARLA_OS_WIN)
  159. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\CLAP;";
  160. path += water::File::getSpecialLocation(water::File::winCommonProgramFiles).getFullPathName() + "\\CLAP";
  161. #else
  162. path = getHomePath() + "/.clap:/usr/lib/clap:/usr/local/lib/clap";
  163. #endif
  164. }
  165. return path.toRawUTF8();
  166. }
  167. static const char* getPathForJSFX()
  168. {
  169. static water::String path;
  170. if (path.isEmpty())
  171. {
  172. #if defined(CARLA_OS_MAC)
  173. path = getHomePath()
  174. + "/Library/Application Support/REAPER/Effects";
  175. if (! water::File(path).isDirectory())
  176. path = "/Applications/REAPER.app/Contents/InstallFiles/Effects";
  177. #elif defined(CARLA_OS_WASM)
  178. path = "/jsfx";
  179. #elif defined(CARLA_OS_WIN)
  180. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\REAPER\\Effects";
  181. if (! water::File(path).isDirectory())
  182. path = water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName()
  183. + "\\REAPER\\InstallData\\Effects";
  184. #else
  185. if (const char* const configHome = std::getenv("XDG_CONFIG_HOME"))
  186. path = configHome;
  187. else
  188. path = getHomePath() + "/.config";
  189. path += "/REAPER/Effects";
  190. #endif
  191. }
  192. return path.toRawUTF8();
  193. }
  194. const char* IldaeilBasePlugin::getPluginPath(const PluginType ptype)
  195. {
  196. switch (ptype)
  197. {
  198. case PLUGIN_LADSPA:
  199. if (const char* const path = std::getenv("LADSPA_PATH"))
  200. return path;
  201. return getPathForLADSPA();
  202. case PLUGIN_DSSI:
  203. if (const char* const path = std::getenv("DSSI_PATH"))
  204. return path;
  205. return getPathForDSSI();
  206. case PLUGIN_LV2:
  207. if (const char* const path = std::getenv("LV2_PATH"))
  208. return path;
  209. return getPathForLV2();
  210. case PLUGIN_VST2:
  211. if (const char* const path = std::getenv("VST_PATH"))
  212. return path;
  213. return getPathForVST2();
  214. case PLUGIN_VST3:
  215. if (const char* const path = std::getenv("VST3_PATH"))
  216. return path;
  217. return getPathForVST3();
  218. case PLUGIN_CLAP:
  219. if (const char* const path = std::getenv("CLAP_PATH"))
  220. return path;
  221. return getPathForCLAP();
  222. case PLUGIN_JSFX:
  223. return getPathForJSFX();
  224. default:
  225. return nullptr;
  226. }
  227. }
  228. // --------------------------------------------------------------------------------------------------------------------
  229. const char* ildaeilConfigDir()
  230. {
  231. static water::String configDir;
  232. if (configDir.isEmpty())
  233. {
  234. #if defined(CARLA_OS_WASM)
  235. configDir = "/userfiles";
  236. #elif defined(CARLA_OS_MAC)
  237. configDir = getHomePath() + "/Documents/Ildaeil";
  238. #elif defined(CARLA_OS_WIN)
  239. configDir = water::File::getSpecialLocation(water::File::winMyDocuments).getFullPathName() + "\\Ildaeil";
  240. #else
  241. if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
  242. configDir = xdgEnv;
  243. else
  244. configDir = getHomePath() + "/.config";
  245. configDir += "/Ildaeil";
  246. #endif
  247. }
  248. return configDir.toRawUTF8();
  249. }
  250. // --------------------------------------------------------------------------------------------------------------------
  251. class IldaeilPlugin : public IldaeilBasePlugin
  252. {
  253. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  254. float* fDummyBuffer;
  255. float* fDummyBuffers[2];
  256. #endif
  257. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  258. static constexpr const uint kMaxMidiEventCount = 512;
  259. NativeMidiEvent* fMidiEvents;
  260. #endif
  261. mutable NativeTimeInfo fCarlaTimeInfo;
  262. mutable water::MemoryOutputStream fLastProjectState;
  263. uint32_t fLastLatencyValue;
  264. public:
  265. IldaeilPlugin()
  266. : IldaeilBasePlugin(),
  267. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  268. fDummyBuffer(nullptr),
  269. #endif
  270. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  271. fMidiEvents(nullptr),
  272. #endif
  273. fLastLatencyValue(0)
  274. {
  275. fCarlaPluginDescriptor = carla_get_native_rack_plugin();
  276. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  277. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  278. fCarlaHostDescriptor.handle = this;
  279. fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
  280. fCarlaHostDescriptor.uiName = "Ildaeil";
  281. fCarlaHostDescriptor.uiParentId = 0;
  282. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  283. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  284. fCarlaHostDescriptor.is_offline = host_is_offline;
  285. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  286. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  287. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  288. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  289. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  290. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  291. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  292. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  293. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  294. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  295. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  296. fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
  297. DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
  298. const char* const bundlePath = getBundlePath();
  299. #ifdef CARLA_OS_WIN
  300. #define EXT ".exe"
  301. #else
  302. #define EXT ""
  303. #endif
  304. if (bundlePath != nullptr
  305. && water::File(bundlePath + water::String(DISTRHO_OS_SEP_STR "carla-bridge-native" EXT)).existsAsFile())
  306. {
  307. fDiscoveryTool = bundlePath;
  308. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, bundlePath);
  309. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, getResourcePath(bundlePath));
  310. }
  311. #ifdef CARLA_OS_MAC
  312. else if (bundlePath != nullptr
  313. && water::File(bundlePath + water::String("/Contents/MacOS/carla-bridge-native" EXT)).existsAsFile())
  314. {
  315. fDiscoveryTool = bundlePath;
  316. fDiscoveryTool += "/Contents/MacOS";
  317. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, fDiscoveryTool);
  318. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, getResourcePath(bundlePath));
  319. }
  320. #endif
  321. else
  322. {
  323. #ifdef CARLA_OS_MAC
  324. fDiscoveryTool = "/Applications/Carla.app/Contents/MacOS";
  325. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
  326. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
  327. #else
  328. fDiscoveryTool = "/usr/lib/carla";
  329. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
  330. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
  331. #endif
  332. }
  333. carla_stdout("Using binary path: %s", fDiscoveryTool.buffer());
  334. fDiscoveryTool += DISTRHO_OS_SEP_STR "carla-discovery-native" EXT;
  335. #undef EXT
  336. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, getPluginPath(PLUGIN_LADSPA));
  337. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, getPluginPath(PLUGIN_DSSI));
  338. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, getPluginPath(PLUGIN_LV2));
  339. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST2, getPluginPath(PLUGIN_VST2));
  340. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, getPluginPath(PLUGIN_VST3));
  341. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_CLAP, getPluginPath(PLUGIN_CLAP));
  342. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, getPluginPath(PLUGIN_JSFX));
  343. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
  344. 0, 0, nullptr, 0.0f);
  345. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  346. fMidiEvents = new NativeMidiEvent[kMaxMidiEventCount];
  347. #endif
  348. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  349. // create dummy buffers
  350. bufferSizeChanged(getBufferSize());
  351. #endif
  352. }
  353. ~IldaeilPlugin() override
  354. {
  355. if (fCarlaHostHandle != nullptr)
  356. {
  357. carla_host_handle_free(fCarlaHostHandle);
  358. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  359. delete[] fDummyBuffer;
  360. #endif
  361. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  362. delete[] fMidiEvents;
  363. #endif
  364. }
  365. if (fCarlaPluginHandle != nullptr)
  366. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  367. }
  368. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  369. {
  370. const TimePosition& timePos(getTimePosition());
  371. fCarlaTimeInfo.playing = timePos.playing;
  372. fCarlaTimeInfo.frame = timePos.frame;
  373. fCarlaTimeInfo.bbt.valid = timePos.bbt.valid;
  374. fCarlaTimeInfo.bbt.bar = timePos.bbt.bar;
  375. fCarlaTimeInfo.bbt.beat = timePos.bbt.beat;
  376. fCarlaTimeInfo.bbt.tick = timePos.bbt.tick;
  377. fCarlaTimeInfo.bbt.barStartTick = timePos.bbt.barStartTick;
  378. fCarlaTimeInfo.bbt.beatsPerBar = timePos.bbt.beatsPerBar;
  379. fCarlaTimeInfo.bbt.beatType = timePos.bbt.beatType;
  380. fCarlaTimeInfo.bbt.ticksPerBeat = timePos.bbt.ticksPerBeat;
  381. fCarlaTimeInfo.bbt.beatsPerMinute = timePos.bbt.beatsPerMinute;
  382. return &fCarlaTimeInfo;
  383. }
  384. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  385. bool hostWriteMidiEvent(const NativeMidiEvent* const event)
  386. {
  387. MidiEvent midiEvent;
  388. midiEvent.frame = event->time;
  389. midiEvent.size = event->size;
  390. midiEvent.dataExt = nullptr;
  391. uint32_t i = 0;
  392. for (; i < event->size; ++i)
  393. midiEvent.data[i] = event->data[i];
  394. for (; i < MidiEvent::kDataSize; ++i)
  395. midiEvent.data[i] = 0;
  396. return writeMidiEvent(midiEvent);
  397. }
  398. #endif
  399. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  400. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  401. {
  402. switch (opcode)
  403. {
  404. // cannnot be supported
  405. case NATIVE_HOST_OPCODE_HOST_IDLE:
  406. break;
  407. // other stuff
  408. case NATIVE_HOST_OPCODE_NULL:
  409. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  410. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  411. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  412. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  413. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  414. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  415. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  416. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  417. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  418. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  419. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  420. case NATIVE_HOST_OPCODE_UI_RESIZE:
  421. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  422. // TESTING
  423. d_stdout("dispatcher %i, %i, %li, %p, %f", opcode, index, value, ptr, opt);
  424. break;
  425. }
  426. return 0;
  427. }
  428. protected:
  429. /* --------------------------------------------------------------------------------------------------------
  430. * Information */
  431. const char* getLabel() const override
  432. {
  433. #if ILDAEIL_STANDALONE
  434. return "Ildaeil";
  435. #elif DISTRHO_PLUGIN_IS_SYNTH
  436. return "IldaeilSynth";
  437. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  438. return "IldaeilMIDI";
  439. #else
  440. return "IldaeilFX";
  441. #endif
  442. }
  443. const char* getDescription() const override
  444. {
  445. return "Ildaeil is a mini-plugin host working as a plugin, allowing one-to-one plugin format reusage.";
  446. }
  447. const char* getMaker() const override
  448. {
  449. return "DISTRHO";
  450. }
  451. const char* getHomePage() const override
  452. {
  453. return "https://github.com/DISTRHO/Ildaeil";
  454. }
  455. const char* getLicense() const override
  456. {
  457. return "GPLv2+";
  458. }
  459. uint32_t getVersion() const override
  460. {
  461. return d_version(1, 2, 0);
  462. }
  463. int64_t getUniqueId() const override
  464. {
  465. #if ILDAEIL_STANDALONE
  466. return d_cconst('d', 'I', 'l', 'd');
  467. #elif DISTRHO_PLUGIN_IS_SYNTH
  468. return d_cconst('d', 'I', 'l', 'S');
  469. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  470. return d_cconst('d', 'I', 'l', 'M');
  471. #else
  472. return d_cconst('d', 'I', 'l', 'F');
  473. #endif
  474. }
  475. /* --------------------------------------------------------------------------------------------------------
  476. * Init */
  477. void initAudioPort(bool input, uint32_t index, AudioPort& port) override
  478. {
  479. port.groupId = kPortGroupStereo;
  480. Plugin::initAudioPort(input, index, port);
  481. }
  482. void initState(const uint32_t index, State& state) override
  483. {
  484. DISTRHO_SAFE_ASSERT_RETURN(index == 0,);
  485. state.hints = kStateIsOnlyForDSP;
  486. state.key = "project";
  487. state.defaultValue = ""
  488. "<?xml version='1.0' encoding='UTF-8'?>\n"
  489. "<!DOCTYPE CARLA-PROJECT>\n"
  490. "<CARLA-PROJECT VERSION='" CARLA_VERSION_STRMIN "'>\n"
  491. "</CARLA-PROJECT>\n";
  492. }
  493. /* --------------------------------------------------------------------------------------------------------
  494. * Internal data */
  495. String getState(const char* const key) const override
  496. {
  497. if (std::strcmp(key, "project") == 0)
  498. {
  499. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  500. fLastProjectState.reset();
  501. engine->saveProjectInternal(fLastProjectState);
  502. return String(static_cast<char*>(fLastProjectState.getDataAndRelease()), false);
  503. }
  504. return String();
  505. }
  506. void setState(const char* const key, const char* const value) override
  507. {
  508. if (std::strcmp(key, "project") == 0)
  509. {
  510. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  511. water::XmlDocument xml(value);
  512. {
  513. const MutexLocker cml(sPluginInfoLoadMutex);
  514. engine->loadProjectInternal(xml, true);
  515. }
  516. if (fUI != nullptr)
  517. ildaeilProjectLoadedFromDSP(fUI);
  518. }
  519. }
  520. /* --------------------------------------------------------------------------------------------------------
  521. * Process */
  522. void checkLatencyChanged()
  523. {
  524. if (fCarlaHostHandle == nullptr)
  525. return;
  526. uint32_t latency = 0;
  527. for (uint32_t i=0; i < carla_get_current_plugin_count(fCarlaHostHandle); ++i)
  528. latency += carla_get_plugin_latency(fCarlaHostHandle, i);
  529. if (fLastLatencyValue != latency)
  530. {
  531. fLastLatencyValue = latency;
  532. setLatency(latency);
  533. }
  534. }
  535. void activate() override
  536. {
  537. if (fCarlaPluginHandle != nullptr)
  538. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  539. checkLatencyChanged();
  540. }
  541. void deactivate() override
  542. {
  543. checkLatencyChanged();
  544. if (fCarlaPluginHandle != nullptr)
  545. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  546. }
  547. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  548. void run(const float** inputs, float** outputs, uint32_t frames,
  549. const MidiEvent* dpfMidiEvents, uint32_t dpfMidiEventCount) override
  550. #else
  551. void run(const float** inputs, float** outputs, uint32_t frames) override
  552. #endif
  553. {
  554. if (fCarlaPluginHandle != nullptr)
  555. {
  556. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  557. uint32_t midiEventCount = 0;
  558. for (uint32_t i=0; i < dpfMidiEventCount; ++i)
  559. {
  560. const MidiEvent& dpfMidiEvent(dpfMidiEvents[i]);
  561. if (dpfMidiEvent.size > 4)
  562. continue;
  563. NativeMidiEvent& midiEvent(fMidiEvents[midiEventCount]);
  564. midiEvent.time = dpfMidiEvent.frame;
  565. midiEvent.port = 0;
  566. midiEvent.size = dpfMidiEvent.size;
  567. std::memcpy(midiEvent.data, dpfMidiEvent.data, midiEvent.size);
  568. if (++midiEventCount == kMaxMidiEventCount)
  569. break;
  570. }
  571. #else
  572. static constexpr const NativeMidiEvent* fMidiEvents = nullptr;
  573. static constexpr const uint32_t midiEventCount = 0;
  574. #endif
  575. #if DISTRHO_PLUGIN_NUM_INPUTS == 0
  576. inputs = (const float**)fDummyBuffers;
  577. #endif
  578. #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  579. outputs = fDummyBuffers;
  580. #endif
  581. fCarlaPluginDescriptor->process(fCarlaPluginHandle, (float**)inputs, outputs, frames,
  582. fMidiEvents, midiEventCount);
  583. checkLatencyChanged();
  584. }
  585. else
  586. {
  587. std::memset(outputs[0], 0, sizeof(float)*frames);
  588. std::memset(outputs[1], 0, sizeof(float)*frames);
  589. }
  590. }
  591. void bufferSizeChanged(const uint32_t newBufferSize) override
  592. {
  593. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  594. delete[] fDummyBuffer;
  595. fDummyBuffer = new float[newBufferSize];
  596. fDummyBuffers[0] = fDummyBuffer;
  597. fDummyBuffers[1] = fDummyBuffer;
  598. std::memset(fDummyBuffer, 0, sizeof(float)*newBufferSize);
  599. #endif
  600. if (fCarlaPluginHandle != nullptr)
  601. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_BUFFER_SIZE_CHANGED,
  602. 0, newBufferSize, nullptr, 0.0f);
  603. }
  604. void sampleRateChanged(const double newSampleRate) override
  605. {
  606. if (fCarlaPluginHandle != nullptr)
  607. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  608. 0, 0, nullptr, newSampleRate);
  609. }
  610. // -------------------------------------------------------------------------------------------------------
  611. /**
  612. Set our plugin class as non-copyable and add a leak detector just in case.
  613. */
  614. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilPlugin)
  615. };
  616. // -----------------------------------------------------------------------------------------------------------
  617. static uint32_t host_get_buffer_size(const NativeHostHandle handle)
  618. {
  619. return static_cast<IldaeilPlugin*>(handle)->getBufferSize();
  620. }
  621. static double host_get_sample_rate(const NativeHostHandle handle)
  622. {
  623. return static_cast<IldaeilPlugin*>(handle)->getSampleRate();
  624. }
  625. static bool host_is_offline(NativeHostHandle)
  626. {
  627. return false;
  628. }
  629. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  630. {
  631. return static_cast<IldaeilPlugin*>(handle)->hostGetTimeInfo();
  632. }
  633. static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
  634. {
  635. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  636. return static_cast<IldaeilPlugin*>(handle)->hostWriteMidiEvent(event);
  637. #else
  638. return handle != nullptr && event != nullptr && false;
  639. #endif
  640. }
  641. static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
  642. {
  643. ildaeilParameterChangeForUI(static_cast<IldaeilPlugin*>(handle)->fUI, index, value);
  644. }
  645. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
  646. {
  647. d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
  648. }
  649. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
  650. {
  651. d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
  652. }
  653. static void host_ui_closed(NativeHostHandle handle)
  654. {
  655. ildaeilCloseUI(static_cast<IldaeilPlugin*>(handle));
  656. }
  657. static const char* host_ui_open_file(const NativeHostHandle handle, const bool isDir, const char* const title, const char* const filter)
  658. {
  659. return ildaeilOpenFileForUI(static_cast<IldaeilPlugin*>(handle)->fUI, isDir, title, filter);
  660. }
  661. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
  662. {
  663. return nullptr;
  664. }
  665. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  666. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  667. {
  668. return static_cast<IldaeilPlugin*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  669. }
  670. /* --------------------------------------------------------------------------------------------------------------------
  671. * Plugin entry point, called by DPF to create a new plugin instance. */
  672. Plugin* createPlugin()
  673. {
  674. return new IldaeilPlugin();
  675. }
  676. // --------------------------------------------------------------------------------------------------------------------
  677. END_NAMESPACE_DISTRHO