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.

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