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.

565 lines
20KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021-2022 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 "CarlaEngine.hpp"
  19. #include "water/files/File.h"
  20. #include "water/streams/MemoryOutputStream.h"
  21. #include "water/xml/XmlDocument.h"
  22. START_NAMESPACE_DISTRHO
  23. using namespace CARLA_BACKEND_NAMESPACE;
  24. // --------------------------------------------------------------------------------------------------------------------
  25. static uint32_t host_get_buffer_size(NativeHostHandle);
  26. static double host_get_sample_rate(NativeHostHandle);
  27. static bool host_is_offline(NativeHostHandle);
  28. static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
  29. static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
  30. static void host_ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value);
  31. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
  32. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
  33. static void host_ui_closed(NativeHostHandle handle);
  34. static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  35. static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  36. static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt);
  37. // --------------------------------------------------------------------------------------------------------------------
  38. Mutex IldaeilBasePlugin::sPluginInfoLoadMutex;
  39. // --------------------------------------------------------------------------------------------------------------------
  40. const char* IldaeilBasePlugin::getPathForJSFX()
  41. {
  42. static water::String path;
  43. if (path.isEmpty())
  44. {
  45. #if defined(CARLA_OS_MAC)
  46. path = water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName()
  47. + "/Library/Application Support/REAPER/Effects";
  48. #elif defined(CARLA_OS_WASM)
  49. path = "/jsfx";
  50. #elif defined(CARLA_OS_WIN)
  51. path = water::File::getSpecialLocation(water::File::winAppData).getFullPathName() + "\\REAPER\\Effects";
  52. if (! water::File(path).isDirectory())
  53. path = water::File::getSpecialLocation(water::File::winProgramFiles).getFullPathName()
  54. + "\\REAPER\\InstallData\\Effects";
  55. #else
  56. if (const char* const configHome = std::getenv("XDG_CONFIG_HOME"))
  57. path = configHome;
  58. else
  59. path = water::File::getSpecialLocation(water::File::userHomeDirectory).getFullPathName() + "/.config";
  60. path += "/REAPER/Effects";
  61. #endif
  62. }
  63. return path.toRawUTF8();
  64. }
  65. // --------------------------------------------------------------------------------------------------------------------
  66. class IldaeilPlugin : public IldaeilBasePlugin
  67. {
  68. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  69. static constexpr const uint kMaxMidiEventCount = 512;
  70. NativeMidiEvent* fMidiEvents;
  71. uint32_t fMidiEventCount;
  72. float* fDummyBuffer;
  73. float* fDummyBuffers[2];
  74. #endif
  75. mutable NativeTimeInfo fCarlaTimeInfo;
  76. mutable water::MemoryOutputStream fLastProjectState;
  77. uint32_t fLastLatencyValue;
  78. public:
  79. IldaeilPlugin()
  80. : IldaeilBasePlugin(),
  81. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  82. fMidiEvents(nullptr),
  83. fMidiEventCount(0),
  84. fDummyBuffer(nullptr),
  85. #endif
  86. fLastLatencyValue(0)
  87. {
  88. fCarlaPluginDescriptor = carla_get_native_rack_plugin();
  89. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  90. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  91. fCarlaHostDescriptor.handle = this;
  92. fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
  93. fCarlaHostDescriptor.uiName = "Ildaeil";
  94. fCarlaHostDescriptor.uiParentId = 0;
  95. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  96. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  97. fCarlaHostDescriptor.is_offline = host_is_offline;
  98. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  99. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  100. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  101. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  102. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  103. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  104. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  105. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  106. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  107. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  108. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  109. fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
  110. DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
  111. if (const char* const bundlePath = getBundlePath())
  112. {
  113. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, bundlePath);
  114. // carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "");
  115. }
  116. else
  117. {
  118. #ifdef CARLA_OS_MAC
  119. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
  120. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
  121. #else
  122. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
  123. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
  124. #endif
  125. }
  126. if (const char* const path = std::getenv("LV2_PATH"))
  127. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, path);
  128. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, getPathForJSFX());
  129. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
  130. 0, 0, nullptr, 0.0f);
  131. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  132. fMidiEvents = new NativeMidiEvent[kMaxMidiEventCount];
  133. // create dummy buffers
  134. bufferSizeChanged(getBufferSize());
  135. #endif
  136. }
  137. ~IldaeilPlugin() override
  138. {
  139. if (fCarlaHostHandle != nullptr)
  140. {
  141. carla_host_handle_free(fCarlaHostHandle);
  142. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  143. delete[] fMidiEvents;
  144. delete[] fDummyBuffer;
  145. #endif
  146. }
  147. if (fCarlaPluginHandle != nullptr)
  148. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  149. }
  150. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  151. {
  152. const TimePosition& timePos(getTimePosition());
  153. fCarlaTimeInfo.playing = timePos.playing;
  154. fCarlaTimeInfo.frame = timePos.frame;
  155. fCarlaTimeInfo.bbt.valid = timePos.bbt.valid;
  156. fCarlaTimeInfo.bbt.bar = timePos.bbt.bar;
  157. fCarlaTimeInfo.bbt.beat = timePos.bbt.beat;
  158. fCarlaTimeInfo.bbt.tick = timePos.bbt.tick;
  159. fCarlaTimeInfo.bbt.barStartTick = timePos.bbt.barStartTick;
  160. fCarlaTimeInfo.bbt.beatsPerBar = timePos.bbt.beatsPerBar;
  161. fCarlaTimeInfo.bbt.beatType = timePos.bbt.beatType;
  162. fCarlaTimeInfo.bbt.ticksPerBeat = timePos.bbt.ticksPerBeat;
  163. fCarlaTimeInfo.bbt.beatsPerMinute = timePos.bbt.beatsPerMinute;
  164. return &fCarlaTimeInfo;
  165. }
  166. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  167. bool hostWriteMidiEvent(const NativeMidiEvent* const event)
  168. {
  169. MidiEvent midiEvent;
  170. midiEvent.frame = event->time;
  171. midiEvent.size = event->size;
  172. midiEvent.dataExt = nullptr;
  173. uint32_t i = 0;
  174. for (; i < event->size; ++i)
  175. midiEvent.data[i] = event->data[i];
  176. for (; i < MidiEvent::kDataSize; ++i)
  177. midiEvent.data[i] = 0;
  178. return writeMidiEvent(midiEvent);
  179. }
  180. #endif
  181. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  182. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  183. {
  184. switch (opcode)
  185. {
  186. // cannnot be supported
  187. case NATIVE_HOST_OPCODE_HOST_IDLE:
  188. break;
  189. // other stuff
  190. case NATIVE_HOST_OPCODE_NULL:
  191. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  192. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  193. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  194. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  195. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  196. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  197. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  198. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  199. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  200. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  201. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  202. case NATIVE_HOST_OPCODE_UI_RESIZE:
  203. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  204. // TESTING
  205. d_stdout("dispatcher %i, %i, %li, %p, %f", opcode, index, value, ptr, opt);
  206. break;
  207. }
  208. return 0;
  209. }
  210. protected:
  211. /* --------------------------------------------------------------------------------------------------------
  212. * Information */
  213. /**
  214. Get the plugin label.
  215. A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers.
  216. */
  217. const char* getLabel() const override
  218. {
  219. #if DISTRHO_PLUGIN_IS_SYNTH
  220. return "IldaeilSynth";
  221. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  222. return "IldaeilMIDI";
  223. #else
  224. return "IldaeilFX";
  225. #endif
  226. }
  227. /**
  228. Get an extensive comment/description about the plugin.
  229. */
  230. const char* getDescription() const override
  231. {
  232. return "Ildaeil is a mini-plugin host working as a plugin, allowing one-to-one plugin format reusage.";
  233. }
  234. /**
  235. Get the plugin author/maker.
  236. */
  237. const char* getMaker() const override
  238. {
  239. return "DISTRHO";
  240. }
  241. /**
  242. Get the plugin homepage.
  243. */
  244. const char* getHomePage() const override
  245. {
  246. return "https://github.com/DISTRHO/Ildaeil";
  247. }
  248. /**
  249. Get the plugin license name (a single line of text).
  250. For commercial plugins this should return some short copyright information.
  251. */
  252. const char* getLicense() const override
  253. {
  254. return "GPLv2+";
  255. }
  256. /**
  257. Get the plugin version, in hexadecimal.
  258. */
  259. uint32_t getVersion() const override
  260. {
  261. return d_version(1, 0, 0);
  262. }
  263. /**
  264. Get the plugin unique Id.
  265. This value is used by LADSPA, DSSI and VST plugin formats.
  266. */
  267. int64_t getUniqueId() const override
  268. {
  269. #if DISTRHO_PLUGIN_IS_SYNTH
  270. return d_cconst('d', 'I', 'l', 'S');
  271. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  272. return d_cconst('d', 'I', 'l', 'M');
  273. #else
  274. return d_cconst('d', 'I', 'l', 'F');
  275. #endif
  276. }
  277. /* --------------------------------------------------------------------------------------------------------
  278. * Init */
  279. void initState(const uint32_t index, String& stateKey, String& defaultStateValue) override
  280. {
  281. DISTRHO_SAFE_ASSERT_RETURN(index == 0,);
  282. stateKey = "project";
  283. defaultStateValue = ""
  284. "<?xml version='1.0' encoding='UTF-8'?>\n"
  285. "<!DOCTYPE CARLA-PROJECT>\n"
  286. "<CARLA-PROJECT VERSION='" CARLA_VERSION_STRMIN "'>\n"
  287. "</CARLA-PROJECT>\n";
  288. }
  289. /* --------------------------------------------------------------------------------------------------------
  290. * Internal data */
  291. String getState(const char* const key) const override
  292. {
  293. if (std::strcmp(key, "project") == 0)
  294. {
  295. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  296. fLastProjectState.reset();
  297. engine->saveProjectInternal(fLastProjectState);
  298. return String(static_cast<char*>(fLastProjectState.getDataAndRelease()), false);
  299. }
  300. return String();
  301. }
  302. void setState(const char* const key, const char* const value) override
  303. {
  304. if (std::strcmp(key, "project") == 0)
  305. {
  306. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  307. water::XmlDocument xml(value);
  308. {
  309. const MutexLocker cml(sPluginInfoLoadMutex);
  310. engine->loadProjectInternal(xml, true);
  311. }
  312. if (fUI != nullptr)
  313. ildaeilProjectLoadedFromDSP(fUI);
  314. }
  315. }
  316. /* --------------------------------------------------------------------------------------------------------
  317. * Process */
  318. void checkLatencyChanged()
  319. {
  320. if (fCarlaHostHandle == nullptr)
  321. return;
  322. uint32_t latency = 0;
  323. for (uint32_t i=0; i < carla_get_current_plugin_count(fCarlaHostHandle); ++i)
  324. latency += carla_get_plugin_latency(fCarlaHostHandle, i);
  325. if (fLastLatencyValue != latency)
  326. {
  327. fLastLatencyValue = latency;
  328. setLatency(latency);
  329. }
  330. }
  331. void activate() override
  332. {
  333. if (fCarlaPluginHandle != nullptr)
  334. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  335. checkLatencyChanged();
  336. }
  337. void deactivate() override
  338. {
  339. if (fCarlaPluginHandle != nullptr)
  340. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  341. }
  342. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  343. void run(const float** inputs, float** outputs, uint32_t frames,
  344. const MidiEvent* dpfMidiEvents, uint32_t dpfMidiEventCount) override
  345. #else
  346. void run(const float** inputs, float** outputs, uint32_t frames) override
  347. #endif
  348. {
  349. if (fCarlaPluginHandle != nullptr)
  350. {
  351. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  352. uint32_t midiEventCount = 0;
  353. for (uint32_t i=0; i < dpfMidiEventCount; ++i)
  354. {
  355. const MidiEvent& dpfMidiEvent(dpfMidiEvents[i]);
  356. if (dpfMidiEvent.size > 4)
  357. continue;
  358. NativeMidiEvent& midiEvent(fMidiEvents[midiEventCount]);
  359. midiEvent.time = dpfMidiEvent.frame;
  360. midiEvent.port = 0;
  361. midiEvent.size = dpfMidiEvent.size;
  362. std::memcpy(midiEvent.data, dpfMidiEvent.data, midiEvent.size);
  363. if (++midiEventCount == kMaxMidiEventCount)
  364. break;
  365. }
  366. # if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  367. fCarlaPluginDescriptor->process(fCarlaPluginHandle, fDummyBuffers, fDummyBuffers, frames,
  368. fMidiEvents, midiEventCount);
  369. // unused
  370. (void)outputs;
  371. # else
  372. fCarlaPluginDescriptor->process(fCarlaPluginHandle, fDummyBuffers, outputs, frames,
  373. fMidiEvents, midiEventCount);
  374. # endif
  375. // unused
  376. (void)inputs;
  377. #else
  378. fCarlaPluginDescriptor->process(fCarlaPluginHandle, (float**)inputs, outputs, frames, nullptr, 0);
  379. #endif
  380. checkLatencyChanged();
  381. }
  382. else
  383. {
  384. std::memset(outputs[0], 0, sizeof(float)*frames);
  385. std::memset(outputs[1], 0, sizeof(float)*frames);
  386. }
  387. }
  388. void bufferSizeChanged(const uint32_t newBufferSize) override
  389. {
  390. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  391. delete[] fDummyBuffer;
  392. fDummyBuffer = new float[newBufferSize];
  393. fDummyBuffers[0] = fDummyBuffer;
  394. fDummyBuffers[1] = fDummyBuffer;
  395. std::memset(fDummyBuffer, 0, sizeof(float)*newBufferSize);
  396. #endif
  397. if (fCarlaPluginHandle != nullptr)
  398. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_BUFFER_SIZE_CHANGED,
  399. 0, newBufferSize, nullptr, 0.0f);
  400. }
  401. void sampleRateChanged(const double newSampleRate) override
  402. {
  403. if (fCarlaPluginHandle != nullptr)
  404. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  405. 0, 0, nullptr, newSampleRate);
  406. }
  407. // -------------------------------------------------------------------------------------------------------
  408. /**
  409. Set our plugin class as non-copyable and add a leak detector just in case.
  410. */
  411. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilPlugin)
  412. };
  413. // -----------------------------------------------------------------------------------------------------------
  414. static uint32_t host_get_buffer_size(const NativeHostHandle handle)
  415. {
  416. return static_cast<IldaeilPlugin*>(handle)->getBufferSize();
  417. }
  418. static double host_get_sample_rate(const NativeHostHandle handle)
  419. {
  420. return static_cast<IldaeilPlugin*>(handle)->getSampleRate();
  421. }
  422. static bool host_is_offline(NativeHostHandle)
  423. {
  424. return false;
  425. }
  426. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  427. {
  428. return static_cast<IldaeilPlugin*>(handle)->hostGetTimeInfo();
  429. }
  430. static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
  431. {
  432. #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  433. return static_cast<IldaeilPlugin*>(handle)->hostWriteMidiEvent(event);
  434. #else
  435. return handle != nullptr && event != nullptr && false;
  436. #endif
  437. }
  438. static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
  439. {
  440. ildaeilParameterChangeForUI(static_cast<IldaeilPlugin*>(handle)->fUI, index, value);
  441. }
  442. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
  443. {
  444. d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
  445. }
  446. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
  447. {
  448. d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
  449. }
  450. static void host_ui_closed(NativeHostHandle handle)
  451. {
  452. d_stdout("%s %p", __FUNCTION__, handle);
  453. }
  454. static const char* host_ui_open_file(const NativeHostHandle handle, const bool isDir, const char* const title, const char* const filter)
  455. {
  456. return ildaeilOpenFileForUI(static_cast<IldaeilPlugin*>(handle)->fUI, isDir, title, filter);
  457. }
  458. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
  459. {
  460. return nullptr;
  461. }
  462. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  463. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  464. {
  465. return static_cast<IldaeilPlugin*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  466. }
  467. /* --------------------------------------------------------------------------------------------------------------------
  468. * Plugin entry point, called by DPF to create a new plugin instance. */
  469. Plugin* createPlugin()
  470. {
  471. return new IldaeilPlugin();
  472. }
  473. // --------------------------------------------------------------------------------------------------------------------
  474. END_NAMESPACE_DISTRHO