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.

849 lines
31KB

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