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.

825 lines
30KB

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