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.

511 lines
18KB

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