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.

860 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 "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. const char* ildaeilConfigDir()
  277. {
  278. static water::String configDir;
  279. if (configDir.isEmpty())
  280. {
  281. #if defined(CARLA_OS_WASM)
  282. configDir = "/userfiles";
  283. #elif defined(CARLA_OS_MAC)
  284. configDir = getHomePath() + "/Documents/Ildaeil";
  285. #elif defined(CARLA_OS_WIN)
  286. configDir = water::File::getSpecialLocation(water::File::winMyDocuments).getFullPathName() + "\\Ildaeil";
  287. #else
  288. if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
  289. configDir = xdgEnv;
  290. else
  291. configDir = getHomePath() + "/.config";
  292. configDir += "/Ildaeil";
  293. #endif
  294. }
  295. return configDir.toRawUTF8();
  296. }
  297. // --------------------------------------------------------------------------------------------------------------------
  298. class IldaeilPlugin : public IldaeilBasePlugin
  299. {
  300. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  301. float* fDummyBuffer;
  302. float* fDummyBuffers[2];
  303. #endif
  304. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  305. static constexpr const uint kMaxMidiEventCount = 512;
  306. NativeMidiEvent* fMidiEvents;
  307. #endif
  308. mutable NativeTimeInfo fCarlaTimeInfo;
  309. mutable water::MemoryOutputStream fLastProjectState;
  310. uint32_t fLastLatencyValue;
  311. public:
  312. IldaeilPlugin()
  313. : IldaeilBasePlugin(),
  314. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  315. fDummyBuffer(nullptr),
  316. #endif
  317. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  318. fMidiEvents(nullptr),
  319. #endif
  320. fLastLatencyValue(0)
  321. {
  322. fCarlaPluginDescriptor = carla_get_native_rack_plugin();
  323. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  324. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  325. fCarlaHostDescriptor.handle = this;
  326. fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
  327. fCarlaHostDescriptor.uiName = "Ildaeil";
  328. fCarlaHostDescriptor.uiParentId = 0;
  329. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  330. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  331. fCarlaHostDescriptor.is_offline = host_is_offline;
  332. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  333. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  334. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  335. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  336. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  337. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  338. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  339. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  340. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  341. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  342. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  343. fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
  344. DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
  345. const char* const bundlePath = getBundlePath();
  346. #ifdef CARLA_OS_WIN
  347. #define EXT ".exe"
  348. #else
  349. #define EXT ""
  350. #endif
  351. if (bundlePath != nullptr
  352. && water::File(bundlePath + String(DISTRHO_OS_SEP_STR "carla-bridge-native" EXT)).existsAsFile())
  353. {
  354. fBinaryPath = bundlePath;
  355. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, bundlePath);
  356. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, getResourcePath(bundlePath));
  357. }
  358. #ifdef CARLA_OS_MAC
  359. else if (bundlePath != nullptr
  360. && water::File(bundlePath + String("/Contents/MacOS/carla-bridge-native" EXT)).existsAsFile())
  361. {
  362. fBinaryPath = bundlePath;
  363. fBinaryPath += "/Contents/MacOS";
  364. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, fBinaryPath);
  365. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, getResourcePath(bundlePath));
  366. }
  367. #endif
  368. else
  369. {
  370. #ifdef CARLA_OS_MAC
  371. fBinaryPath = "/Applications/Carla.app/Contents/MacOS";
  372. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
  373. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
  374. #else
  375. fBinaryPath = "/usr/lib/carla";
  376. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
  377. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
  378. #endif
  379. }
  380. if (fBinaryPath.isNotEmpty())
  381. carla_stdout("Using binary path for discovery tools: %s", fBinaryPath.buffer());
  382. #undef EXT
  383. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, getPluginPath(PLUGIN_LADSPA));
  384. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, getPluginPath(PLUGIN_DSSI));
  385. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, getPluginPath(PLUGIN_LV2));
  386. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST2, getPluginPath(PLUGIN_VST2));
  387. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, getPluginPath(PLUGIN_VST3));
  388. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_CLAP, getPluginPath(PLUGIN_CLAP));
  389. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, getPluginPath(PLUGIN_JSFX));
  390. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
  391. 0, 0, nullptr, 0.0f);
  392. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  393. fMidiEvents = new NativeMidiEvent[kMaxMidiEventCount];
  394. #endif
  395. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  396. // create dummy buffers
  397. bufferSizeChanged(getBufferSize());
  398. #endif
  399. }
  400. ~IldaeilPlugin() override
  401. {
  402. if (fCarlaHostHandle != nullptr)
  403. {
  404. carla_host_handle_free(fCarlaHostHandle);
  405. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  406. delete[] fDummyBuffer;
  407. #endif
  408. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  409. delete[] fMidiEvents;
  410. #endif
  411. }
  412. if (fCarlaPluginHandle != nullptr)
  413. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  414. }
  415. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  416. {
  417. const TimePosition& timePos(getTimePosition());
  418. fCarlaTimeInfo.playing = timePos.playing;
  419. fCarlaTimeInfo.frame = timePos.frame;
  420. fCarlaTimeInfo.bbt.valid = timePos.bbt.valid;
  421. fCarlaTimeInfo.bbt.bar = timePos.bbt.bar;
  422. fCarlaTimeInfo.bbt.beat = timePos.bbt.beat;
  423. fCarlaTimeInfo.bbt.tick = timePos.bbt.tick;
  424. fCarlaTimeInfo.bbt.barStartTick = timePos.bbt.barStartTick;
  425. fCarlaTimeInfo.bbt.beatsPerBar = timePos.bbt.beatsPerBar;
  426. fCarlaTimeInfo.bbt.beatType = timePos.bbt.beatType;
  427. fCarlaTimeInfo.bbt.ticksPerBeat = timePos.bbt.ticksPerBeat;
  428. fCarlaTimeInfo.bbt.beatsPerMinute = timePos.bbt.beatsPerMinute;
  429. return &fCarlaTimeInfo;
  430. }
  431. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  432. bool hostWriteMidiEvent(const NativeMidiEvent* const event)
  433. {
  434. MidiEvent midiEvent;
  435. midiEvent.frame = event->time;
  436. midiEvent.size = event->size;
  437. midiEvent.dataExt = nullptr;
  438. uint32_t i = 0;
  439. for (; i < event->size; ++i)
  440. midiEvent.data[i] = event->data[i];
  441. for (; i < MidiEvent::kDataSize; ++i)
  442. midiEvent.data[i] = 0;
  443. return writeMidiEvent(midiEvent);
  444. }
  445. #endif
  446. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  447. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  448. {
  449. switch (opcode)
  450. {
  451. // cannnot be supported
  452. case NATIVE_HOST_OPCODE_HOST_IDLE:
  453. break;
  454. // other stuff
  455. case NATIVE_HOST_OPCODE_NULL:
  456. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  457. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  458. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  459. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  460. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  461. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  462. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  463. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  464. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  465. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  466. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  467. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  468. // TESTING
  469. d_stdout("dispatcher %i:%s, %i, %li, %p, %f",
  470. opcode, NativeHostDispatcherOpcode2Str(opcode), index, value, ptr, opt);
  471. break;
  472. case NATIVE_HOST_OPCODE_UI_RESIZE:
  473. ildaeilResizeUI(fUI, index, value);
  474. break;
  475. }
  476. return 0;
  477. }
  478. protected:
  479. /* --------------------------------------------------------------------------------------------------------
  480. * Information */
  481. const char* getLabel() const override
  482. {
  483. #if ILDAEIL_STANDALONE
  484. return "Ildaeil";
  485. #elif DISTRHO_PLUGIN_IS_SYNTH
  486. return "IldaeilSynth";
  487. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  488. return "IldaeilMIDI";
  489. #else
  490. return "IldaeilFX";
  491. #endif
  492. }
  493. const char* getDescription() const override
  494. {
  495. return "Ildaeil is a mini-plugin host working as a plugin, allowing one-to-one plugin format reusage.";
  496. }
  497. const char* getMaker() const override
  498. {
  499. return "DISTRHO";
  500. }
  501. const char* getHomePage() const override
  502. {
  503. return "https://github.com/DISTRHO/Ildaeil";
  504. }
  505. const char* getLicense() const override
  506. {
  507. return "GPLv2+";
  508. }
  509. uint32_t getVersion() const override
  510. {
  511. return d_version(1, 3, 0);
  512. }
  513. int64_t getUniqueId() const override
  514. {
  515. #if ILDAEIL_STANDALONE
  516. return d_cconst('d', 'I', 'l', 'd');
  517. #elif DISTRHO_PLUGIN_IS_SYNTH
  518. return d_cconst('d', 'I', 'l', 'S');
  519. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  520. return d_cconst('d', 'I', 'l', 'M');
  521. #else
  522. return d_cconst('d', 'I', 'l', 'F');
  523. #endif
  524. }
  525. /* --------------------------------------------------------------------------------------------------------
  526. * Init */
  527. void initAudioPort(bool input, uint32_t index, AudioPort& port) override
  528. {
  529. port.groupId = kPortGroupStereo;
  530. Plugin::initAudioPort(input, index, port);
  531. }
  532. void initState(const uint32_t index, State& state) override
  533. {
  534. DISTRHO_SAFE_ASSERT_RETURN(index == 0,);
  535. state.hints = kStateIsOnlyForDSP;
  536. state.key = "project";
  537. state.defaultValue = ""
  538. "<?xml version='1.0' encoding='UTF-8'?>\n"
  539. "<!DOCTYPE CARLA-PROJECT>\n"
  540. "<CARLA-PROJECT VERSION='" CARLA_VERSION_STRMIN "'>\n"
  541. "</CARLA-PROJECT>\n";
  542. }
  543. /* --------------------------------------------------------------------------------------------------------
  544. * Internal data */
  545. String getState(const char* const key) const override
  546. {
  547. if (std::strcmp(key, "project") == 0)
  548. {
  549. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  550. fLastProjectState.reset();
  551. engine->saveProjectInternal(fLastProjectState);
  552. return String(static_cast<char*>(fLastProjectState.getDataAndRelease()), false);
  553. }
  554. return String();
  555. }
  556. void setState(const char* const key, const char* const value) override
  557. {
  558. if (std::strcmp(key, "project") == 0)
  559. {
  560. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  561. const water::String wvalue(value);
  562. water::XmlDocument xml(wvalue);
  563. {
  564. const MutexLocker cml(sPluginInfoLoadMutex);
  565. engine->loadProjectInternal(xml, true);
  566. }
  567. if (fUI != nullptr)
  568. ildaeilProjectLoadedFromDSP(fUI);
  569. }
  570. }
  571. /* --------------------------------------------------------------------------------------------------------
  572. * Process */
  573. void checkLatencyChanged()
  574. {
  575. if (fCarlaHostHandle == nullptr)
  576. return;
  577. uint32_t latency = 0;
  578. for (uint32_t i=0; i < carla_get_current_plugin_count(fCarlaHostHandle); ++i)
  579. latency += carla_get_plugin_latency(fCarlaHostHandle, i);
  580. if (fLastLatencyValue != latency)
  581. {
  582. fLastLatencyValue = latency;
  583. setLatency(latency);
  584. }
  585. }
  586. void activate() override
  587. {
  588. if (fCarlaPluginHandle != nullptr)
  589. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  590. checkLatencyChanged();
  591. }
  592. void deactivate() override
  593. {
  594. checkLatencyChanged();
  595. if (fCarlaPluginHandle != nullptr)
  596. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  597. }
  598. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  599. void run(const float** inputs, float** outputs, uint32_t frames,
  600. const MidiEvent* dpfMidiEvents, uint32_t dpfMidiEventCount) override
  601. #else
  602. void run(const float** inputs, float** outputs, uint32_t frames) override
  603. #endif
  604. {
  605. if (fCarlaPluginHandle != nullptr)
  606. {
  607. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  608. uint32_t midiEventCount = 0;
  609. for (uint32_t i=0; i < dpfMidiEventCount; ++i)
  610. {
  611. const MidiEvent& dpfMidiEvent(dpfMidiEvents[i]);
  612. if (dpfMidiEvent.size > 4)
  613. continue;
  614. NativeMidiEvent& midiEvent(fMidiEvents[midiEventCount]);
  615. midiEvent.time = dpfMidiEvent.frame;
  616. midiEvent.port = 0;
  617. midiEvent.size = dpfMidiEvent.size;
  618. std::memcpy(midiEvent.data, dpfMidiEvent.data, midiEvent.size);
  619. if (++midiEventCount == kMaxMidiEventCount)
  620. break;
  621. }
  622. #else
  623. static constexpr const NativeMidiEvent* fMidiEvents = nullptr;
  624. static constexpr const uint32_t midiEventCount = 0;
  625. #endif
  626. #if DISTRHO_PLUGIN_NUM_INPUTS == 0
  627. inputs = (const float**)fDummyBuffers;
  628. #endif
  629. #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  630. outputs = fDummyBuffers;
  631. #endif
  632. fCarlaPluginDescriptor->process(fCarlaPluginHandle, (float**)inputs, outputs, frames,
  633. fMidiEvents, midiEventCount);
  634. checkLatencyChanged();
  635. }
  636. else
  637. {
  638. std::memset(outputs[0], 0, sizeof(float)*frames);
  639. std::memset(outputs[1], 0, sizeof(float)*frames);
  640. }
  641. }
  642. void bufferSizeChanged(const uint32_t newBufferSize) override
  643. {
  644. #if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
  645. delete[] fDummyBuffer;
  646. fDummyBuffer = new float[newBufferSize];
  647. fDummyBuffers[0] = fDummyBuffer;
  648. fDummyBuffers[1] = fDummyBuffer;
  649. std::memset(fDummyBuffer, 0, sizeof(float)*newBufferSize);
  650. #endif
  651. if (fCarlaPluginHandle != nullptr)
  652. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_BUFFER_SIZE_CHANGED,
  653. 0, newBufferSize, nullptr, 0.0f);
  654. }
  655. void sampleRateChanged(const double newSampleRate) override
  656. {
  657. if (fCarlaPluginHandle != nullptr)
  658. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  659. 0, 0, nullptr, newSampleRate);
  660. }
  661. // -------------------------------------------------------------------------------------------------------
  662. /**
  663. Set our plugin class as non-copyable and add a leak detector just in case.
  664. */
  665. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilPlugin)
  666. };
  667. // -----------------------------------------------------------------------------------------------------------
  668. static uint32_t host_get_buffer_size(const NativeHostHandle handle)
  669. {
  670. return static_cast<IldaeilPlugin*>(handle)->getBufferSize();
  671. }
  672. static double host_get_sample_rate(const NativeHostHandle handle)
  673. {
  674. return static_cast<IldaeilPlugin*>(handle)->getSampleRate();
  675. }
  676. static bool host_is_offline(NativeHostHandle)
  677. {
  678. return false;
  679. }
  680. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  681. {
  682. return static_cast<IldaeilPlugin*>(handle)->hostGetTimeInfo();
  683. }
  684. static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
  685. {
  686. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  687. return static_cast<IldaeilPlugin*>(handle)->hostWriteMidiEvent(event);
  688. #else
  689. return handle != nullptr && event != nullptr && false;
  690. #endif
  691. }
  692. static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
  693. {
  694. ildaeilParameterChangeForUI(static_cast<IldaeilPlugin*>(handle)->fUI, index, value);
  695. }
  696. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
  697. {
  698. d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
  699. }
  700. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
  701. {
  702. d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
  703. }
  704. static void host_ui_closed(NativeHostHandle handle)
  705. {
  706. ildaeilCloseUI(static_cast<IldaeilPlugin*>(handle));
  707. }
  708. static const char* host_ui_open_file(const NativeHostHandle handle, const bool isDir, const char* const title, const char* const filter)
  709. {
  710. return ildaeilOpenFileForUI(static_cast<IldaeilPlugin*>(handle)->fUI, isDir, title, filter);
  711. }
  712. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
  713. {
  714. return nullptr;
  715. }
  716. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  717. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  718. {
  719. return static_cast<IldaeilPlugin*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  720. }
  721. /* --------------------------------------------------------------------------------------------------------------------
  722. * Plugin entry point, called by DPF to create a new plugin instance. */
  723. Plugin* createPlugin()
  724. {
  725. return new IldaeilPlugin();
  726. }
  727. // --------------------------------------------------------------------------------------------------------------------
  728. END_NAMESPACE_DISTRHO