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.

535 lines
19KB

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