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.

855 lines
31KB

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