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.

532 lines
19KB

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