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.

1826 lines
60KB

  1. /*
  2. * DISTRHO Cardinal 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 3 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. /**
  18. * This file uses code adapted from VCVRack's CV_MIDI.cpp and midi.hpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include "plugincontext.hpp"
  27. #include "Expander.hpp"
  28. #ifndef HEADLESS
  29. # include "ImGuiWidget.hpp"
  30. # include "ModuleWidgets.hpp"
  31. # include "extra/Mutex.hpp"
  32. # include "extra/Runner.hpp"
  33. # include "extra/ScopedPointer.hpp"
  34. # include "../../src/extra/SharedResourcePointer.hpp"
  35. #else
  36. # include "extra/Mutex.hpp"
  37. #endif
  38. #include "CarlaNativePlugin.h"
  39. #include "CarlaBackendUtils.hpp"
  40. #include "CarlaEngine.hpp"
  41. #include "water/streams/MemoryOutputStream.h"
  42. #include "water/xml/XmlDocument.h"
  43. #include <string>
  44. #ifndef CARDINAL_SYSDEPS
  45. // private method that takes ownership, we can use it to avoid superfulous allocations
  46. extern "C" {
  47. json_t *jsonp_stringn_nocheck_own(const char* value, size_t len);
  48. }
  49. #endif
  50. // defined elsewhere
  51. namespace rack {
  52. #ifdef ARCH_WIN
  53. enum SpecialPath {
  54. kSpecialPathUserProfile,
  55. kSpecialPathCommonProgramFiles,
  56. kSpecialPathProgramFiles,
  57. kSpecialPathAppData,
  58. };
  59. std::string getSpecialPath(const SpecialPath type);
  60. #endif
  61. std::string homeDir();
  62. }
  63. #define BUFFER_SIZE 128
  64. // generates a warning if this is defined as anything else
  65. #define CARLA_API
  66. // --------------------------------------------------------------------------------------------------------------------
  67. // strcasestr
  68. #ifdef DISTRHO_OS_WINDOWS
  69. # include <shlwapi.h>
  70. namespace ildaeil {
  71. inline const char* strcasestr(const char* const haystack, const char* const needle)
  72. {
  73. return StrStrIA(haystack, needle);
  74. }
  75. // using strcasestr = StrStrIA;
  76. }
  77. #else
  78. namespace ildaeil {
  79. using ::strcasestr;
  80. }
  81. #endif
  82. // --------------------------------------------------------------------------------------------------------------------
  83. using namespace CARLA_BACKEND_NAMESPACE;
  84. static uint32_t host_get_buffer_size(NativeHostHandle);
  85. static double host_get_sample_rate(NativeHostHandle);
  86. static bool host_is_offline(NativeHostHandle);
  87. static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
  88. static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
  89. static void host_ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value);
  90. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
  91. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
  92. static void host_ui_closed(NativeHostHandle handle);
  93. static const char* host_ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  94. static const char* host_ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter);
  95. static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt);
  96. static void projectLoadedFromDSP(void* ui);
  97. // --------------------------------------------------------------------------------------------------------------------
  98. static Mutex sPluginInfoLoadMutex;
  99. static const char* getPathForJSFX()
  100. {
  101. static std::string path;
  102. if (path.empty())
  103. {
  104. #if defined(CARLA_OS_MAC)
  105. path = homeDir() + "/Library/Application Support/REAPER/Effects";
  106. #elif defined(CARLA_OS_WASM)
  107. path = "/jsfx";
  108. #elif defined(CARLA_OS_WIN)
  109. path = getSpecialPath(kSpecialPathAppData) + "\\REAPER\\Effects";
  110. if (! system::exists(path))
  111. path = getSpecialPath(kSpecialPathProgramFiles) + "\\REAPER\\InstallData\\Effects";
  112. #else
  113. if (const char* const configHome = std::getenv("XDG_CONFIG_HOME"))
  114. path = configHome;
  115. else
  116. path = homeDir() + "/.config";
  117. path += "/REAPER/Effects";
  118. #endif
  119. }
  120. return path.c_str();
  121. }
  122. /*
  123. #ifndef HEADLESS
  124. struct JuceInitializer {
  125. JuceInitializer() { carla_juce_init(); }
  126. ~JuceInitializer() { carla_juce_cleanup(); }
  127. };
  128. #endif
  129. */
  130. struct IldaeilModule : Module {
  131. enum ParamIds {
  132. NUM_PARAMS
  133. };
  134. enum InputIds {
  135. INPUT1,
  136. INPUT2,
  137. NUM_INPUTS
  138. };
  139. enum OutputIds {
  140. OUTPUT1,
  141. OUTPUT2,
  142. NUM_OUTPUTS
  143. };
  144. enum LightIds {
  145. NUM_LIGHTS
  146. };
  147. /*
  148. #ifndef HEADLESS
  149. SharedResourcePointer<JuceInitializer> juceInitializer;
  150. #endif
  151. */
  152. const CardinalPluginContext* const pcontext;
  153. const NativePluginDescriptor* fCarlaPluginDescriptor = nullptr;
  154. NativePluginHandle fCarlaPluginHandle = nullptr;
  155. NativeHostDescriptor fCarlaHostDescriptor = {};
  156. CarlaHostHandle fCarlaHostHandle = nullptr;
  157. NativeTimeInfo fCarlaTimeInfo;
  158. void* fUI = nullptr;
  159. bool canUseBridges = true;
  160. float audioDataIn1[BUFFER_SIZE];
  161. float audioDataIn2[BUFFER_SIZE];
  162. float audioDataOut1[BUFFER_SIZE];
  163. float audioDataOut2[BUFFER_SIZE];
  164. unsigned audioDataFill = 0;
  165. uint32_t lastProcessCounter = 0;
  166. CardinalExpanderFromCarlaMIDIToCV* midiOutExpander = nullptr;
  167. volatile bool resetMeterIn = true;
  168. volatile bool resetMeterOut = true;
  169. float meterInL = 0.0f;
  170. float meterInR = 0.0f;
  171. float meterOutL = 0.0f;
  172. float meterOutR = 0.0f;
  173. IldaeilModule()
  174. : pcontext(static_cast<CardinalPluginContext*>(APP))
  175. {
  176. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  177. for (uint i=0; i<2; ++i)
  178. {
  179. const char name[] = { 'A','u','d','i','o',' ','#',static_cast<char>('0'+i+1),'\0' };
  180. configInput(i, name);
  181. configOutput(i, name);
  182. }
  183. std::memset(audioDataOut1, 0, sizeof(audioDataOut1));
  184. std::memset(audioDataOut2, 0, sizeof(audioDataOut2));
  185. fCarlaPluginDescriptor = carla_get_native_rack_plugin();
  186. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  187. memset(&fCarlaHostDescriptor, 0, sizeof(fCarlaHostDescriptor));
  188. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  189. fCarlaHostDescriptor.handle = this;
  190. fCarlaHostDescriptor.resourceDir = carla_get_library_folder();
  191. fCarlaHostDescriptor.uiName = "Ildaeil";
  192. fCarlaHostDescriptor.uiParentId = 0;
  193. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  194. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  195. fCarlaHostDescriptor.is_offline = host_is_offline;
  196. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  197. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  198. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  199. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  200. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  201. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  202. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  203. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  204. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  205. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  206. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  207. fCarlaHostHandle = carla_create_native_plugin_host_handle(fCarlaPluginDescriptor, fCarlaPluginHandle);
  208. DISTRHO_SAFE_ASSERT_RETURN(fCarlaHostHandle != nullptr,);
  209. #if defined(CARLA_OS_MAC)
  210. if (system::exists("~/Applications/Carla.app"))
  211. {
  212. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "~/Applications/Carla.app/Contents/MacOS");
  213. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "~/Applications/Carla.app/Contents/MacOS/resources");
  214. }
  215. else if (system::exists("/Applications/Carla.app"))
  216. {
  217. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/Applications/Carla.app/Contents/MacOS");
  218. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/Applications/Carla.app/Contents/MacOS/resources");
  219. }
  220. #elif defined(CARLA_OS_WASM)
  221. if (true)
  222. {}
  223. #elif defined(CARLA_OS_WIN)
  224. const std::string winBinaryDir = system::join(asset::systemDir, "Carla");
  225. if (system::exists(winBinaryDir))
  226. {
  227. const std::string winResourceDir = system::join(winBinaryDir, "resources");
  228. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, winBinaryDir.c_str());
  229. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, winResourceDir.c_str());
  230. }
  231. #else
  232. if (system::exists("/usr/local/lib/carla"))
  233. {
  234. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/local/lib/carla");
  235. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/local/share/carla/resources");
  236. }
  237. else if (system::exists("/usr/lib/carla"))
  238. {
  239. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_BINARIES, 0, "/usr/lib/carla");
  240. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PATH_RESOURCES, 0, "/usr/share/carla/resources");
  241. }
  242. #endif
  243. else
  244. {
  245. canUseBridges = false;
  246. static bool warningShown = false;
  247. if (! warningShown)
  248. {
  249. warningShown = true;
  250. async_dialog_message("Carla is not installed on this system, bridged plugins will not work");
  251. }
  252. }
  253. if (const char* const path = std::getenv("LV2_PATH"))
  254. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, path);
  255. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, PLUGIN_JSFX, getPathForJSFX());
  256. #ifdef CARLA_OS_MAC
  257. carla_set_engine_option(fCarlaHostHandle, ENGINE_OPTION_PREFER_UI_BRIDGES, 0, nullptr);
  258. #endif
  259. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_HOST_USES_EMBED,
  260. 0, 0, nullptr, 0.0f);
  261. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  262. }
  263. ~IldaeilModule() override
  264. {
  265. if (fCarlaPluginHandle != nullptr)
  266. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  267. if (fCarlaHostHandle != nullptr)
  268. carla_host_handle_free(fCarlaHostHandle);
  269. if (fCarlaPluginHandle != nullptr)
  270. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  271. }
  272. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  273. {
  274. return &fCarlaTimeInfo;
  275. }
  276. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  277. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  278. {
  279. switch (opcode)
  280. {
  281. // cannnot be supported
  282. case NATIVE_HOST_OPCODE_HOST_IDLE:
  283. break;
  284. // other stuff
  285. case NATIVE_HOST_OPCODE_NULL:
  286. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  287. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  288. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  289. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  290. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  291. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  292. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  293. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  294. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  295. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  296. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  297. case NATIVE_HOST_OPCODE_UI_RESIZE:
  298. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  299. // TESTING
  300. d_stdout("dispatcher %i, %i, %li, %p, %f", opcode, index, value, ptr, opt);
  301. break;
  302. }
  303. return 0;
  304. }
  305. json_t* dataToJson() override
  306. {
  307. if (fCarlaHostHandle == nullptr)
  308. return nullptr;
  309. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  310. water::MemoryOutputStream projectState;
  311. engine->saveProjectInternal(projectState);
  312. const size_t dataSize = projectState.getDataSize();
  313. #ifndef CARDINAL_SYSDEPS
  314. return jsonp_stringn_nocheck_own(static_cast<const char*>(projectState.getDataAndRelease()), dataSize);
  315. #else
  316. return json_stringn(static_cast<const char*>(projectState.getData()), dataSize);
  317. #endif
  318. }
  319. void dataFromJson(json_t* const rootJ) override
  320. {
  321. if (fCarlaHostHandle == nullptr)
  322. return;
  323. const char* const projectState = json_string_value(rootJ);
  324. DISTRHO_SAFE_ASSERT_RETURN(projectState != nullptr,);
  325. CarlaEngine* const engine = carla_get_engine_from_handle(fCarlaHostHandle);
  326. water::XmlDocument xml(projectState);
  327. {
  328. const MutexLocker cml(sPluginInfoLoadMutex);
  329. engine->loadProjectInternal(xml, true);
  330. }
  331. projectLoadedFromDSP(fUI);
  332. }
  333. void process(const ProcessArgs& args) override
  334. {
  335. if (fCarlaPluginHandle == nullptr)
  336. return;
  337. const unsigned i = audioDataFill++;
  338. audioDataIn1[i] = inputs[INPUT1].getVoltage() * 0.1f;
  339. audioDataIn2[i] = inputs[INPUT2].getVoltage() * 0.1f;
  340. outputs[OUTPUT1].setVoltage(audioDataOut1[i] * 10.0f);
  341. outputs[OUTPUT2].setVoltage(audioDataOut2[i] * 10.0f);
  342. if (audioDataFill == BUFFER_SIZE)
  343. {
  344. const uint32_t processCounter = pcontext->processCounter;
  345. // Update time position if running a new audio block
  346. if (lastProcessCounter != processCounter)
  347. {
  348. lastProcessCounter = processCounter;
  349. fCarlaTimeInfo.playing = pcontext->playing;
  350. fCarlaTimeInfo.frame = pcontext->frame;
  351. fCarlaTimeInfo.bbt.valid = pcontext->bbtValid;
  352. fCarlaTimeInfo.bbt.bar = pcontext->bar;
  353. fCarlaTimeInfo.bbt.beat = pcontext->beat;
  354. fCarlaTimeInfo.bbt.tick = pcontext->tick;
  355. fCarlaTimeInfo.bbt.barStartTick = pcontext->barStartTick;
  356. fCarlaTimeInfo.bbt.beatsPerBar = pcontext->beatsPerBar;
  357. fCarlaTimeInfo.bbt.beatType = pcontext->beatType;
  358. fCarlaTimeInfo.bbt.ticksPerBeat = pcontext->ticksPerBeat;
  359. fCarlaTimeInfo.bbt.beatsPerMinute = pcontext->beatsPerMinute;
  360. }
  361. // or advance time by BUFFER_SIZE frames if still under the same audio block
  362. else if (fCarlaTimeInfo.playing)
  363. {
  364. fCarlaTimeInfo.frame += BUFFER_SIZE;
  365. // adjust BBT as well
  366. if (fCarlaTimeInfo.bbt.valid)
  367. {
  368. const double samplesPerTick = 60.0 * args.sampleRate
  369. / fCarlaTimeInfo.bbt.beatsPerMinute
  370. / fCarlaTimeInfo.bbt.ticksPerBeat;
  371. int32_t newBar = fCarlaTimeInfo.bbt.bar;
  372. int32_t newBeat = fCarlaTimeInfo.bbt.beat;
  373. double newTick = fCarlaTimeInfo.bbt.tick + (double)BUFFER_SIZE / samplesPerTick;
  374. while (newTick >= fCarlaTimeInfo.bbt.ticksPerBeat)
  375. {
  376. newTick -= fCarlaTimeInfo.bbt.ticksPerBeat;
  377. if (++newBeat > fCarlaTimeInfo.bbt.beatsPerBar)
  378. {
  379. newBeat = 1;
  380. ++newBar;
  381. fCarlaTimeInfo.bbt.barStartTick += fCarlaTimeInfo.bbt.beatsPerBar * fCarlaTimeInfo.bbt.ticksPerBeat;
  382. }
  383. }
  384. fCarlaTimeInfo.bbt.bar = newBar;
  385. fCarlaTimeInfo.bbt.beat = newBeat;
  386. fCarlaTimeInfo.bbt.tick = newTick;
  387. }
  388. }
  389. NativeMidiEvent* midiEvents;
  390. uint midiEventCount;
  391. if (CardinalExpanderFromCVToCarlaMIDI* const midiInExpander = leftExpander.module != nullptr && leftExpander.module->model == modelExpanderInputMIDI
  392. ? static_cast<CardinalExpanderFromCVToCarlaMIDI*>(leftExpander.module)
  393. : nullptr)
  394. {
  395. midiEvents = midiInExpander->midiEvents;
  396. midiEventCount = midiInExpander->midiEventCount;
  397. midiInExpander->midiEventCount = midiInExpander->frame = 0;
  398. }
  399. else
  400. {
  401. midiEvents = nullptr;
  402. midiEventCount = 0;
  403. }
  404. if ((midiOutExpander = rightExpander.module != nullptr && rightExpander.module->model == modelExpanderOutputMIDI
  405. ? static_cast<CardinalExpanderFromCarlaMIDIToCV*>(rightExpander.module)
  406. : nullptr))
  407. midiOutExpander->midiEventCount = 0;
  408. audioDataFill = 0;
  409. float* ins[2] = { audioDataIn1, audioDataIn2 };
  410. float* outs[2] = { audioDataOut1, audioDataOut2 };
  411. if (resetMeterIn)
  412. meterInL = meterInR = 0.0f;
  413. meterInL = std::max(meterInL, d_findMaxNormalizedFloat(audioDataIn1, BUFFER_SIZE));
  414. meterInR = std::max(meterInR, d_findMaxNormalizedFloat(audioDataIn2, BUFFER_SIZE));
  415. fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, midiEvents, midiEventCount);
  416. if (resetMeterOut)
  417. meterOutL = meterOutR = 0.0f;
  418. meterOutL = std::max(meterOutL, d_findMaxNormalizedFloat(audioDataOut1, BUFFER_SIZE));
  419. meterOutR = std::max(meterOutR, d_findMaxNormalizedFloat(audioDataOut2, BUFFER_SIZE));
  420. resetMeterIn = resetMeterOut = false;
  421. }
  422. }
  423. void onReset() override
  424. {
  425. resetMeterIn = resetMeterOut = true;
  426. midiOutExpander = nullptr;
  427. }
  428. void onSampleRateChange(const SampleRateChangeEvent& e) override
  429. {
  430. if (fCarlaPluginHandle == nullptr)
  431. return;
  432. resetMeterIn = resetMeterOut = true;
  433. midiOutExpander = nullptr;
  434. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  435. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  436. 0, 0, nullptr, e.sampleRate);
  437. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  438. }
  439. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilModule)
  440. };
  441. // -----------------------------------------------------------------------------------------------------------
  442. static uint32_t host_get_buffer_size(const NativeHostHandle handle)
  443. {
  444. return BUFFER_SIZE;
  445. }
  446. static double host_get_sample_rate(const NativeHostHandle handle)
  447. {
  448. const CardinalPluginContext* const pcontext = static_cast<IldaeilModule*>(handle)->pcontext;
  449. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr, 48000.0);
  450. return pcontext->sampleRate;
  451. }
  452. static bool host_is_offline(NativeHostHandle)
  453. {
  454. return false;
  455. }
  456. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  457. {
  458. return static_cast<IldaeilModule*>(handle)->hostGetTimeInfo();
  459. }
  460. static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
  461. {
  462. if (CardinalExpanderFromCarlaMIDIToCV* const expander = static_cast<IldaeilModule*>(handle)->midiOutExpander)
  463. {
  464. if (expander->midiEventCount == CardinalExpanderFromCarlaMIDIToCV::MAX_MIDI_EVENTS)
  465. return false;
  466. NativeMidiEvent& expanderEvent(expander->midiEvents[expander->midiEventCount++]);
  467. carla_copyStruct(expanderEvent, *event);
  468. return true;
  469. }
  470. return false;
  471. }
  472. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
  473. {
  474. d_stdout("%s %p %u %u %u", __FUNCTION__, handle, channel, bank, program);
  475. }
  476. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
  477. {
  478. d_stdout("%s %p %s %s", __FUNCTION__, handle, key, value);
  479. }
  480. static void host_ui_closed(NativeHostHandle handle)
  481. {
  482. d_stdout("%s %p", __FUNCTION__, handle);
  483. }
  484. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*)
  485. {
  486. return nullptr;
  487. }
  488. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  489. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  490. {
  491. return static_cast<IldaeilModule*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  492. }
  493. // --------------------------------------------------------------------------------------------------------------------
  494. #ifndef HEADLESS
  495. struct IldaeilWidget : ImGuiWidget, IdleCallback, Runner {
  496. static constexpr const uint kButtonHeight = 20;
  497. struct PluginInfoCache {
  498. char* name;
  499. char* label;
  500. PluginInfoCache()
  501. : name(nullptr),
  502. label(nullptr) {}
  503. ~PluginInfoCache()
  504. {
  505. std::free(name);
  506. std::free(label);
  507. }
  508. };
  509. struct PluginGenericUI {
  510. char* title;
  511. uint parameterCount;
  512. struct Parameter {
  513. char* name;
  514. char* printformat;
  515. uint32_t rindex;
  516. bool boolean, bvalue, log, readonly;
  517. float min, max, power;
  518. Parameter()
  519. : name(nullptr),
  520. printformat(nullptr),
  521. rindex(0),
  522. boolean(false),
  523. bvalue(false),
  524. log(false),
  525. readonly(false),
  526. min(0.0f),
  527. max(1.0f) {}
  528. ~Parameter()
  529. {
  530. std::free(name);
  531. std::free(printformat);
  532. }
  533. }* parameters;
  534. float* values;
  535. PluginGenericUI()
  536. : title(nullptr),
  537. parameterCount(0),
  538. parameters(nullptr),
  539. values(nullptr) {}
  540. ~PluginGenericUI()
  541. {
  542. std::free(title);
  543. delete[] parameters;
  544. delete[] values;
  545. }
  546. };
  547. enum {
  548. kDrawingLoading,
  549. kDrawingPluginError,
  550. kDrawingPluginList,
  551. kDrawingPluginGenericUI,
  552. kDrawingErrorInit,
  553. kDrawingErrorDraw
  554. } fDrawingState = kDrawingLoading;
  555. enum {
  556. kIdleInit,
  557. kIdleInitPluginAlreadyLoaded,
  558. kIdleLoadSelectedPlugin,
  559. kIdlePluginLoadedFromDSP,
  560. kIdleResetPlugin,
  561. kIdleShowCustomUI,
  562. kIdleHidePluginUI,
  563. kIdleGiveIdleToUI,
  564. kIdleChangePluginType,
  565. kIdleNothing
  566. } fIdleState = kIdleInit;
  567. struct RunnerData {
  568. bool needsReinit = true;
  569. uint pluginCount = 0;
  570. uint pluginIndex = 0;
  571. void init()
  572. {
  573. needsReinit = true;
  574. pluginCount = 0;
  575. pluginIndex = 0;
  576. }
  577. } fRunnerData;
  578. #ifdef CARLA_OS_WASM
  579. PluginType fPluginType = PLUGIN_JSFX;
  580. #else
  581. PluginType fPluginType = PLUGIN_LV2;
  582. #endif
  583. PluginType fNextPluginType = fPluginType;
  584. uint fPluginCount = 0;
  585. int fPluginSelected = -1;
  586. bool fPluginScanningFinished = false;
  587. bool fPluginHasCustomUI = false;
  588. bool fPluginHasOutputParameters = false;
  589. bool fPluginRunning = false;
  590. bool fPluginWillRunInBridgeMode = false;
  591. PluginInfoCache* fPlugins = nullptr;
  592. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  593. bool fPluginSearchActive = false;
  594. bool fPluginSearchFirstShow = false;
  595. char fPluginSearchString[0xff] = {};
  596. String fPopupError;
  597. bool idleCallbackActive = false;
  598. IldaeilModule* const module;
  599. IldaeilWidget(IldaeilModule* const m)
  600. : ImGuiWidget(),
  601. module(m)
  602. {
  603. std::strcpy(fPluginSearchString, "Search...");
  604. if (m != nullptr)
  605. {
  606. if (m->fCarlaHostHandle == nullptr)
  607. {
  608. fDrawingState = kDrawingErrorInit;
  609. fIdleState = kIdleNothing;
  610. fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
  611. return;
  612. }
  613. if (checkIfPluginIsLoaded())
  614. fIdleState = kIdleInitPluginAlreadyLoaded;
  615. m->fUI = this;
  616. }
  617. else
  618. {
  619. fDrawingState = kDrawingPluginList;
  620. fIdleState = kIdleNothing;
  621. }
  622. }
  623. ~IldaeilWidget() override
  624. {
  625. if (module != nullptr && module->fCarlaHostHandle != nullptr)
  626. {
  627. if (idleCallbackActive)
  628. module->pcontext->removeIdleCallback(this);
  629. if (fPluginRunning)
  630. carla_show_custom_ui(module->fCarlaHostHandle, 0, false);
  631. carla_set_engine_option(module->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  632. module->fUI = nullptr;
  633. }
  634. stopRunner();
  635. fPluginGenericUI = nullptr;
  636. delete[] fPlugins;
  637. }
  638. bool checkIfPluginIsLoaded()
  639. {
  640. const CarlaHostHandle handle = module->fCarlaHostHandle;
  641. if (carla_get_current_plugin_count(handle) != 0)
  642. {
  643. const uint hints = carla_get_plugin_info(handle, 0)->hints;
  644. fPluginRunning = true;
  645. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  646. return true;
  647. }
  648. return false;
  649. }
  650. void projectLoadedFromDSP()
  651. {
  652. if (checkIfPluginIsLoaded())
  653. fIdleState = kIdlePluginLoadedFromDSP;
  654. }
  655. void changeParameterFromDSP(const uint32_t index, const float value)
  656. {
  657. if (PluginGenericUI* const ui = fPluginGenericUI)
  658. {
  659. for (uint32_t i=0; i < ui->parameterCount; ++i)
  660. {
  661. if (ui->parameters[i].rindex != index)
  662. continue;
  663. ui->values[i] = value;
  664. if (ui->parameters[i].boolean)
  665. ui->parameters[i].bvalue = value > ui->parameters[i].min;
  666. break;
  667. }
  668. }
  669. setDirty(true);
  670. }
  671. void openFileFromDSP(bool /* isDir */, const char* const title, const char* /* filter */)
  672. {
  673. DISTRHO_SAFE_ASSERT_RETURN(idleCallbackActive,);
  674. DISTRHO_SAFE_ASSERT_RETURN(fPluginType == PLUGIN_INTERNAL || fPluginType == PLUGIN_LV2,);
  675. const CarlaHostHandle handle = module->fCarlaHostHandle;
  676. async_dialog_filebrowser(false, nullptr, nullptr, title, [handle](char* path)
  677. {
  678. if (path == nullptr)
  679. return;
  680. carla_set_custom_data(handle, 0, CUSTOM_DATA_TYPE_PATH, "file", path);
  681. std::free(path);
  682. });
  683. }
  684. void createOrUpdatePluginGenericUI(const CarlaHostHandle handle)
  685. {
  686. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  687. fDrawingState = kDrawingPluginGenericUI;
  688. fPluginHasCustomUI = info->hints & PLUGIN_HAS_CUSTOM_UI;
  689. if (fPluginGenericUI == nullptr)
  690. createPluginGenericUI(handle, info);
  691. else
  692. updatePluginGenericUI(handle);
  693. setDirty(true);
  694. }
  695. void hidePluginUI(const CarlaHostHandle handle)
  696. {
  697. DISTRHO_SAFE_ASSERT_RETURN(fPluginRunning,);
  698. carla_show_custom_ui(handle, 0, false);
  699. }
  700. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  701. {
  702. PluginGenericUI* const ui = new PluginGenericUI;
  703. String title(info->name);
  704. title += " by ";
  705. title += info->maker;
  706. ui->title = title.getAndReleaseBuffer();
  707. fPluginHasOutputParameters = false;
  708. const uint32_t pcount = ui->parameterCount = carla_get_parameter_count(handle, 0);
  709. // make count of valid parameters
  710. for (uint32_t i=0; i < pcount; ++i)
  711. {
  712. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  713. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  714. {
  715. --ui->parameterCount;
  716. continue;
  717. }
  718. if (pdata->type == PARAMETER_OUTPUT)
  719. fPluginHasOutputParameters = true;
  720. }
  721. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  722. ui->values = new float[ui->parameterCount];
  723. // now safely fill in details
  724. for (uint32_t i=0, j=0; i < pcount; ++i)
  725. {
  726. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  727. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  728. continue;
  729. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, 0, i);
  730. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, 0, i);
  731. String printformat;
  732. if (pdata->hints & PARAMETER_IS_INTEGER)
  733. printformat = "%.0f ";
  734. else
  735. printformat = "%.3f ";
  736. printformat += pinfo->unit;
  737. PluginGenericUI::Parameter& param(ui->parameters[j]);
  738. param.name = strdup(pinfo->name);
  739. param.printformat = printformat.getAndReleaseBuffer();
  740. param.rindex = i;
  741. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  742. param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
  743. param.readonly = pdata->type != PARAMETER_INPUT || (pdata->hints & PARAMETER_IS_READ_ONLY);
  744. param.min = pranges->min;
  745. param.max = pranges->max;
  746. ui->values[j] = carla_get_current_parameter_value(handle, 0, i);
  747. if (param.boolean)
  748. param.bvalue = ui->values[j] > param.min;
  749. else
  750. param.bvalue = false;
  751. ++j;
  752. }
  753. fPluginGenericUI = ui;
  754. }
  755. void updatePluginGenericUI(const CarlaHostHandle handle)
  756. {
  757. PluginGenericUI* const ui = fPluginGenericUI;
  758. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  759. for (uint32_t i=0; i < ui->parameterCount; ++i)
  760. {
  761. ui->values[i] = carla_get_current_parameter_value(handle, 0, ui->parameters[i].rindex);
  762. if (ui->parameters[i].boolean)
  763. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  764. }
  765. }
  766. void loadPlugin(const CarlaHostHandle handle, const char* const label)
  767. {
  768. if (fPluginRunning)
  769. {
  770. carla_show_custom_ui(handle, 0, false);
  771. carla_replace_plugin(handle, 0);
  772. }
  773. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  774. const MutexLocker cml(sPluginInfoLoadMutex);
  775. if (carla_add_plugin(handle, BINARY_NATIVE, fPluginType, nullptr, nullptr,
  776. label, 0, 0x0, PLUGIN_OPTIONS_NULL))
  777. {
  778. fPluginRunning = true;
  779. fPluginGenericUI = nullptr;
  780. createOrUpdatePluginGenericUI(handle);
  781. }
  782. else
  783. {
  784. fPopupError = carla_get_last_error(handle);
  785. d_stdout("got error: %s", fPopupError.buffer());
  786. fDrawingState = kDrawingPluginError;
  787. }
  788. setDirty(true);
  789. }
  790. void onContextCreate(const ContextCreateEvent& e) override
  791. {
  792. ImGuiWidget::onContextCreate(e);
  793. widgetCreated();
  794. }
  795. void onContextDestroy(const ContextDestroyEvent& e) override
  796. {
  797. widgetDestroyed();
  798. ImGuiWidget::onContextDestroy(e);
  799. }
  800. void onAdd(const AddEvent& e) override
  801. {
  802. ImGuiWidget::onAdd(e);
  803. widgetCreated();
  804. }
  805. void onRemove(const RemoveEvent& e) override
  806. {
  807. widgetDestroyed();
  808. ImGuiWidget::onRemove(e);
  809. }
  810. void widgetCreated()
  811. {
  812. if (module == nullptr)
  813. return;
  814. if (const CarlaHostHandle handle = module->fCarlaHostHandle)
  815. {
  816. const CardinalPluginContext* const pcontext = module->pcontext;
  817. char winIdStr[24];
  818. std::snprintf(winIdStr, sizeof(winIdStr), "%llx", (ulonglong)pcontext->nativeWindowId);
  819. module->fCarlaHostDescriptor.uiParentId = pcontext->nativeWindowId;
  820. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  821. if (pcontext->window != nullptr)
  822. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, pcontext->window->pixelRatio*1000, nullptr);
  823. if (! idleCallbackActive)
  824. {
  825. idleCallbackActive = pcontext->addIdleCallback(this);
  826. }
  827. }
  828. }
  829. void widgetDestroyed()
  830. {
  831. if (module == nullptr)
  832. return;
  833. if (const CarlaHostHandle handle = module->fCarlaHostHandle)
  834. {
  835. const CardinalPluginContext* const pcontext = module->pcontext;
  836. module->fCarlaHostDescriptor.uiParentId = 0;
  837. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  838. if (idleCallbackActive)
  839. {
  840. idleCallbackActive = false;
  841. pcontext->removeIdleCallback(this);
  842. }
  843. }
  844. }
  845. void idleCallback() override
  846. {
  847. const CarlaHostHandle handle = module->fCarlaHostHandle;
  848. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
  849. /*
  850. carla_juce_idle();
  851. */
  852. if (fDrawingState == kDrawingPluginGenericUI && fPluginGenericUI != nullptr && fPluginHasOutputParameters)
  853. {
  854. updatePluginGenericUI(handle);
  855. setDirty(true);
  856. }
  857. switch (fIdleState)
  858. {
  859. case kIdleInit:
  860. fIdleState = kIdleNothing;
  861. initAndStartRunner();
  862. break;
  863. case kIdleInitPluginAlreadyLoaded:
  864. fIdleState = kIdleNothing;
  865. createOrUpdatePluginGenericUI(handle);
  866. initAndStartRunner();
  867. break;
  868. case kIdlePluginLoadedFromDSP:
  869. fIdleState = kIdleNothing;
  870. createOrUpdatePluginGenericUI(handle);
  871. break;
  872. case kIdleLoadSelectedPlugin:
  873. fIdleState = kIdleNothing;
  874. loadSelectedPlugin(handle);
  875. break;
  876. case kIdleResetPlugin:
  877. fIdleState = kIdleNothing;
  878. loadPlugin(handle, carla_get_plugin_info(handle, 0)->label);
  879. break;
  880. case kIdleShowCustomUI:
  881. fIdleState = kIdleGiveIdleToUI;
  882. carla_show_custom_ui(handle, 0, true);
  883. break;
  884. case kIdleHidePluginUI:
  885. fIdleState = kIdleNothing;
  886. carla_show_custom_ui(handle, 0, false);
  887. break;
  888. case kIdleGiveIdleToUI:
  889. module->fCarlaPluginDescriptor->ui_idle(module->fCarlaPluginHandle);
  890. break;
  891. case kIdleChangePluginType:
  892. fIdleState = kIdleNothing;
  893. fPluginSelected = -1;
  894. stopRunner();
  895. fPluginType = fNextPluginType;
  896. initAndStartRunner();
  897. break;
  898. case kIdleNothing:
  899. break;
  900. }
  901. }
  902. void loadSelectedPlugin(const CarlaHostHandle handle)
  903. {
  904. DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
  905. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  906. const char* label = nullptr;
  907. switch (fPluginType)
  908. {
  909. case PLUGIN_INTERNAL:
  910. case PLUGIN_AU:
  911. case PLUGIN_JSFX:
  912. case PLUGIN_SFZ:
  913. label = info.label;
  914. break;
  915. case PLUGIN_LV2: {
  916. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  917. DISTRHO_SAFE_ASSERT_RETURN(slash != nullptr,);
  918. label = slash+1;
  919. break;
  920. }
  921. default:
  922. break;
  923. }
  924. DISTRHO_SAFE_ASSERT_RETURN(label != nullptr,);
  925. d_stdout("Loading %s...", info.name);
  926. loadPlugin(handle, label);
  927. }
  928. bool initAndStartRunner()
  929. {
  930. if (isRunnerActive())
  931. stopRunner();
  932. fRunnerData.init();
  933. return startRunner();
  934. }
  935. bool run() override
  936. {
  937. if (fRunnerData.needsReinit)
  938. {
  939. fRunnerData.needsReinit = false;
  940. const char* path;
  941. switch (fPluginType)
  942. {
  943. case PLUGIN_LV2:
  944. path = std::getenv("LV2_PATH");
  945. break;
  946. case PLUGIN_JSFX:
  947. path = getPathForJSFX();
  948. break;
  949. default:
  950. path = nullptr;
  951. break;
  952. }
  953. fPluginCount = 0;
  954. delete[] fPlugins;
  955. {
  956. const MutexLocker cml(sPluginInfoLoadMutex);
  957. d_stdout("Will scan plugins now...");
  958. fRunnerData.pluginCount = carla_get_cached_plugin_count(fPluginType, path);
  959. d_stdout("Scanning found %u plugins", fRunnerData.pluginCount);
  960. }
  961. if (fDrawingState == kDrawingLoading)
  962. {
  963. fDrawingState = kDrawingPluginList;
  964. fPluginSearchFirstShow = true;
  965. }
  966. if (fRunnerData.pluginCount != 0)
  967. {
  968. fPlugins = new PluginInfoCache[fRunnerData.pluginCount];
  969. fPluginScanningFinished = false;
  970. return true;
  971. }
  972. else
  973. {
  974. fPlugins = nullptr;
  975. fPluginScanningFinished = true;
  976. return false;
  977. }
  978. }
  979. const uint index = fRunnerData.pluginIndex++;
  980. DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fRunnerData.pluginCount,
  981. index, fRunnerData.pluginCount, false);
  982. do {
  983. const MutexLocker cml(sPluginInfoLoadMutex);
  984. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(fPluginType, index);
  985. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  986. if (! info->valid)
  987. break;
  988. if (info->audioIns != 0 && info->audioIns != 2)
  989. break;
  990. if (info->midiIns != 0 && info->midiIns != 1)
  991. break;
  992. if (info->midiOuts != 0 && info->midiOuts != 1)
  993. break;
  994. if (fPluginType == PLUGIN_INTERNAL)
  995. {
  996. if (std::strcmp(info->label, "audiogain_s") == 0)
  997. break;
  998. if (std::strcmp(info->label, "cv2audio") == 0)
  999. break;
  1000. if (std::strcmp(info->label, "lfo") == 0)
  1001. break;
  1002. if (std::strcmp(info->label, "midi2cv") == 0)
  1003. break;
  1004. if (std::strcmp(info->label, "midithrough") == 0)
  1005. break;
  1006. if (std::strcmp(info->label, "3bandsplitter") == 0)
  1007. break;
  1008. }
  1009. const uint pindex = fPluginCount;
  1010. fPlugins[pindex].name = strdup(info->name);
  1011. fPlugins[pindex].label = strdup(info->label);
  1012. ++fPluginCount;
  1013. } while (false);
  1014. // run again
  1015. if (fRunnerData.pluginIndex != fRunnerData.pluginCount)
  1016. return true;
  1017. // stop here
  1018. fPluginScanningFinished = true;
  1019. return false;
  1020. }
  1021. void drawImGui() override
  1022. {
  1023. switch (fDrawingState)
  1024. {
  1025. case kDrawingLoading:
  1026. drawLoading();
  1027. break;
  1028. case kDrawingPluginError:
  1029. ImGui::OpenPopup("Plugin Error");
  1030. // call ourselves again with the plugin list
  1031. fDrawingState = kDrawingPluginList;
  1032. drawImGui();
  1033. break;
  1034. case kDrawingPluginList:
  1035. drawPluginList();
  1036. break;
  1037. case kDrawingPluginGenericUI:
  1038. drawTopBar();
  1039. drawGenericUI();
  1040. break;
  1041. case kDrawingErrorInit:
  1042. fDrawingState = kDrawingErrorDraw;
  1043. drawError(true);
  1044. break;
  1045. case kDrawingErrorDraw:
  1046. drawError(false);
  1047. break;
  1048. }
  1049. }
  1050. void drawError(const bool open)
  1051. {
  1052. const float scaleFactor = getScaleFactor();
  1053. ImGui::SetNextWindowPos(ImVec2(0, 0));
  1054. ImGui::SetNextWindowSize(ImVec2(box.size.x * scaleFactor, box.size.y * scaleFactor));
  1055. const int flags = ImGuiWindowFlags_NoSavedSettings
  1056. | ImGuiWindowFlags_NoTitleBar
  1057. | ImGuiWindowFlags_NoResize
  1058. | ImGuiWindowFlags_NoCollapse
  1059. | ImGuiWindowFlags_NoScrollbar
  1060. | ImGuiWindowFlags_NoScrollWithMouse;
  1061. if (ImGui::Begin("Error Window", nullptr, flags))
  1062. {
  1063. if (open)
  1064. ImGui::OpenPopup("Engine Error");
  1065. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1066. | ImGuiWindowFlags_NoResize
  1067. | ImGuiWindowFlags_NoCollapse
  1068. | ImGuiWindowFlags_NoScrollbar
  1069. | ImGuiWindowFlags_NoScrollWithMouse
  1070. | ImGuiWindowFlags_AlwaysAutoResize
  1071. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  1072. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  1073. {
  1074. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  1075. ImGui::EndPopup();
  1076. }
  1077. }
  1078. ImGui::End();
  1079. }
  1080. void drawTopBar()
  1081. {
  1082. const float scaleFactor = getScaleFactor();
  1083. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  1084. ImGui::SetNextWindowPos(ImVec2(0, 0));
  1085. ImGui::SetNextWindowSize(ImVec2(box.size.x * scaleFactor, kButtonHeight * scaleFactor + padding));
  1086. const int flags = ImGuiWindowFlags_NoSavedSettings
  1087. | ImGuiWindowFlags_NoTitleBar
  1088. | ImGuiWindowFlags_NoResize
  1089. | ImGuiWindowFlags_NoCollapse
  1090. | ImGuiWindowFlags_NoScrollbar
  1091. | ImGuiWindowFlags_NoScrollWithMouse;
  1092. if (ImGui::Begin("Current Plugin", nullptr, flags))
  1093. {
  1094. if (ImGui::Button("Pick Another..."))
  1095. {
  1096. fIdleState = kIdleHidePluginUI;
  1097. fDrawingState = kDrawingPluginList;
  1098. }
  1099. ImGui::SameLine();
  1100. if (ImGui::Button("Reset"))
  1101. fIdleState = kIdleResetPlugin;
  1102. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  1103. {
  1104. ImGui::SameLine();
  1105. if (ImGui::Button("Show Custom GUI"))
  1106. fIdleState = kIdleShowCustomUI;
  1107. }
  1108. }
  1109. ImGui::End();
  1110. }
  1111. void setupMainWindowPos()
  1112. {
  1113. const float scaleFactor = getScaleFactor();
  1114. float y = 0;
  1115. float width = box.size.x * scaleFactor;
  1116. float height = box.size.y * scaleFactor;
  1117. if (fDrawingState == kDrawingPluginGenericUI)
  1118. {
  1119. y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
  1120. height -= y;
  1121. }
  1122. ImGui::SetNextWindowPos(ImVec2(0, y));
  1123. ImGui::SetNextWindowSize(ImVec2(width, height));
  1124. }
  1125. void drawGenericUI()
  1126. {
  1127. setupMainWindowPos();
  1128. PluginGenericUI* const ui = fPluginGenericUI;
  1129. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1130. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1131. | ImGuiWindowFlags_NoResize
  1132. | ImGuiWindowFlags_NoCollapse
  1133. | ImGuiWindowFlags_AlwaysAutoResize;
  1134. if (ImGui::Begin(ui->title, nullptr, pflags))
  1135. {
  1136. const CarlaHostHandle handle = module->fCarlaHostHandle;
  1137. for (uint32_t i=0; i < ui->parameterCount; ++i)
  1138. {
  1139. PluginGenericUI::Parameter& param(ui->parameters[i]);
  1140. if (param.readonly)
  1141. {
  1142. ImGui::BeginDisabled();
  1143. ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_NoInput);
  1144. ImGui::EndDisabled();
  1145. continue;
  1146. }
  1147. if (param.boolean)
  1148. {
  1149. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  1150. {
  1151. if (ImGui::IsItemActivated())
  1152. {
  1153. carla_set_parameter_touch(handle, 0, param.rindex, true);
  1154. // editParameter(0, true);
  1155. }
  1156. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  1157. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  1158. // setParameterValue(0, ui->values[i]);
  1159. }
  1160. }
  1161. else
  1162. {
  1163. const bool ret = param.log
  1164. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, 2.0f)
  1165. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
  1166. if (ret)
  1167. {
  1168. if (ImGui::IsItemActivated())
  1169. {
  1170. carla_set_parameter_touch(handle, 0, param.rindex, true);
  1171. // editParameter(0, true);
  1172. }
  1173. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  1174. // setParameterValue(0, ui->values[i]);
  1175. }
  1176. }
  1177. if (ImGui::IsItemDeactivated())
  1178. {
  1179. carla_set_parameter_touch(handle, 0, param.rindex, false);
  1180. // editParameter(0, false);
  1181. }
  1182. }
  1183. }
  1184. ImGui::End();
  1185. }
  1186. void drawLoading()
  1187. {
  1188. setupMainWindowPos();
  1189. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  1190. ImGui::TextUnformatted("Loading...", nullptr);
  1191. ImGui::End();
  1192. }
  1193. void drawPluginList()
  1194. {
  1195. static const char* pluginTypes[] = {
  1196. getPluginTypeAsString(PLUGIN_INTERNAL),
  1197. getPluginTypeAsString(PLUGIN_LV2),
  1198. getPluginTypeAsString(PLUGIN_JSFX),
  1199. };
  1200. setupMainWindowPos();
  1201. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  1202. {
  1203. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1204. | ImGuiWindowFlags_NoResize
  1205. | ImGuiWindowFlags_NoCollapse
  1206. | ImGuiWindowFlags_NoScrollbar
  1207. | ImGuiWindowFlags_NoScrollWithMouse
  1208. | ImGuiWindowFlags_AlwaysAutoResize;
  1209. if (ImGui::BeginPopupModal("Plugin Error", nullptr, pflags))
  1210. {
  1211. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  1212. ImGui::Separator();
  1213. if (ImGui::Button("Ok"))
  1214. ImGui::CloseCurrentPopup();
  1215. ImGui::SameLine();
  1216. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  1217. ImGui::EndPopup();
  1218. }
  1219. else if (fPluginSearchFirstShow)
  1220. {
  1221. fPluginSearchFirstShow = false;
  1222. ImGui::SetKeyboardFocusHere();
  1223. }
  1224. if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
  1225. ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  1226. fPluginSearchActive = true;
  1227. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  1228. fPluginSearchActive = false;
  1229. ImGui::SameLine();
  1230. ImGui::PushItemWidth(-1.0f);
  1231. int current;
  1232. switch (fPluginType)
  1233. {
  1234. case PLUGIN_JSFX:
  1235. current = 2;
  1236. break;
  1237. case PLUGIN_LV2:
  1238. current = 1;
  1239. break;
  1240. default:
  1241. current = 0;
  1242. break;
  1243. }
  1244. if (ImGui::Combo("##plugintypes", &current, pluginTypes, ARRAY_SIZE(pluginTypes)))
  1245. {
  1246. fIdleState = kIdleChangePluginType;
  1247. switch (current)
  1248. {
  1249. case 0:
  1250. fNextPluginType = PLUGIN_INTERNAL;
  1251. break;
  1252. case 1:
  1253. fNextPluginType = PLUGIN_LV2;
  1254. break;
  1255. case 2:
  1256. fNextPluginType = PLUGIN_JSFX;
  1257. break;
  1258. }
  1259. }
  1260. ImGui::BeginDisabled(!fPluginScanningFinished || fPluginSelected < 0);
  1261. if (ImGui::Button("Load Plugin"))
  1262. fIdleState = kIdleLoadSelectedPlugin;
  1263. #ifndef CARLA_OS_WASM
  1264. if (fPluginType != PLUGIN_INTERNAL && (module == nullptr || module->canUseBridges))
  1265. {
  1266. ImGui::SameLine();
  1267. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  1268. }
  1269. #endif
  1270. ImGui::EndDisabled();
  1271. if (fPluginRunning)
  1272. {
  1273. ImGui::SameLine();
  1274. if (ImGui::Button("Cancel"))
  1275. fDrawingState = kDrawingPluginGenericUI;
  1276. }
  1277. if (ImGui::BeginChild("pluginlistwindow"))
  1278. {
  1279. if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
  1280. {
  1281. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  1282. switch (fPluginType)
  1283. {
  1284. case PLUGIN_INTERNAL:
  1285. case PLUGIN_AU:
  1286. case PLUGIN_SFZ:
  1287. case PLUGIN_JSFX:
  1288. ImGui::TableSetupColumn("Name");
  1289. ImGui::TableSetupColumn("Label");
  1290. ImGui::TableHeadersRow();
  1291. break;
  1292. case PLUGIN_LV2:
  1293. ImGui::TableSetupColumn("Name");
  1294. ImGui::TableSetupColumn("URI");
  1295. ImGui::TableHeadersRow();
  1296. break;
  1297. default:
  1298. break;
  1299. }
  1300. for (uint i=0; i<fPluginCount; ++i)
  1301. {
  1302. const PluginInfoCache& info(fPlugins[i]);
  1303. if (search != nullptr && ildaeil::strcasestr(info.name, search) == nullptr)
  1304. continue;
  1305. bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
  1306. switch (fPluginType)
  1307. {
  1308. case PLUGIN_INTERNAL:
  1309. case PLUGIN_AU:
  1310. case PLUGIN_JSFX:
  1311. case PLUGIN_SFZ:
  1312. ImGui::TableNextRow();
  1313. ImGui::TableSetColumnIndex(0);
  1314. ImGui::Selectable(info.name, &selected);
  1315. ImGui::TableSetColumnIndex(1);
  1316. ImGui::Selectable(info.label, &selected);
  1317. break;
  1318. case PLUGIN_LV2: {
  1319. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  1320. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  1321. ImGui::TableNextRow();
  1322. ImGui::TableSetColumnIndex(0);
  1323. ImGui::Selectable(info.name, &selected);
  1324. ImGui::TableSetColumnIndex(1);
  1325. ImGui::Selectable(slash+1, &selected);
  1326. break;
  1327. }
  1328. default:
  1329. break;
  1330. }
  1331. if (selected)
  1332. fPluginSelected = i;
  1333. }
  1334. ImGui::EndTable();
  1335. }
  1336. ImGui::EndChild();
  1337. }
  1338. }
  1339. ImGui::End();
  1340. }
  1341. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilWidget)
  1342. };
  1343. // --------------------------------------------------------------------------------------------------------------------
  1344. static void host_ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
  1345. {
  1346. if (IldaeilWidget* const ui = static_cast<IldaeilWidget*>(static_cast<IldaeilModule*>(handle)->fUI))
  1347. ui->changeParameterFromDSP(index, value);
  1348. }
  1349. static const char* host_ui_open_file(const NativeHostHandle handle,
  1350. const bool isDir, const char* const title, const char* const filter)
  1351. {
  1352. if (IldaeilWidget* const ui = static_cast<IldaeilWidget*>(static_cast<IldaeilModule*>(handle)->fUI))
  1353. ui->openFileFromDSP(isDir, title, filter);
  1354. return nullptr;
  1355. }
  1356. static void projectLoadedFromDSP(void* const ui)
  1357. {
  1358. if (IldaeilWidget* const uiw = static_cast<IldaeilWidget*>(ui))
  1359. uiw->projectLoadedFromDSP();
  1360. }
  1361. // --------------------------------------------------------------------------------------------------------------------
  1362. struct IldaeilNanoMeterIn : NanoMeter {
  1363. IldaeilModule* const module;
  1364. IldaeilNanoMeterIn(IldaeilModule* const m)
  1365. : module(m)
  1366. {
  1367. withBackground = false;
  1368. }
  1369. void updateMeters() override
  1370. {
  1371. if (module == nullptr || module->resetMeterIn)
  1372. return;
  1373. // Only fetch new values once DSP side is updated
  1374. gainMeterL = module->meterInL;
  1375. gainMeterR = module->meterInR;
  1376. module->resetMeterIn = true;
  1377. }
  1378. };
  1379. struct IldaeilNanoMeterOut : NanoMeter {
  1380. IldaeilModule* const module;
  1381. IldaeilNanoMeterOut(IldaeilModule* const m)
  1382. : module(m)
  1383. {
  1384. withBackground = false;
  1385. }
  1386. void updateMeters() override
  1387. {
  1388. if (module == nullptr || module->resetMeterOut)
  1389. return;
  1390. // Only fetch new values once DSP side is updated
  1391. gainMeterL = module->meterOutL;
  1392. gainMeterR = module->meterOutR;
  1393. module->resetMeterOut = true;
  1394. }
  1395. };
  1396. // --------------------------------------------------------------------------------------------------------------------
  1397. struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
  1398. bool hasLeftSideExpander = false;
  1399. bool hasRightSideExpander = false;
  1400. IldaeilWidget* ildaeilWidget = nullptr;
  1401. IldaeilModuleWidget(IldaeilModule* const module)
  1402. {
  1403. setModule(module);
  1404. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Ildaeil.svg")));
  1405. createAndAddScrews();
  1406. if (module == nullptr || module->pcontext != nullptr)
  1407. {
  1408. ildaeilWidget = new IldaeilWidget(module);
  1409. ildaeilWidget->box.pos = Vec(3 * RACK_GRID_WIDTH, 0);
  1410. ildaeilWidget->box.size = Vec(box.size.x - 6 * RACK_GRID_WIDTH, box.size.y);
  1411. addChild(ildaeilWidget);
  1412. }
  1413. for (uint i=0; i<IldaeilModule::NUM_INPUTS; ++i)
  1414. createAndAddInput(i);
  1415. for (uint i=0; i<IldaeilModule::NUM_OUTPUTS; ++i)
  1416. createAndAddOutput(i);
  1417. IldaeilNanoMeterIn* const meterIn = new IldaeilNanoMeterIn(module);
  1418. meterIn->box.pos = Vec(2.0f, startY + padding * 2);
  1419. meterIn->box.size = Vec(RACK_GRID_WIDTH * 3 - 2.0f, box.size.y - meterIn->box.pos.y - 19.0f);
  1420. addChild(meterIn);
  1421. IldaeilNanoMeterOut* const meterOut = new IldaeilNanoMeterOut(module);
  1422. meterOut->box.pos = Vec(box.size.x - RACK_GRID_WIDTH * 3 + 1.0f, startY + padding * 2);
  1423. meterOut->box.size = Vec(RACK_GRID_WIDTH * 3 - 2.0f, box.size.y - meterOut->box.pos.y - 19.0f);
  1424. addChild(meterOut);
  1425. }
  1426. void draw(const DrawArgs& args) override
  1427. {
  1428. drawBackground(args.vg);
  1429. if (hasLeftSideExpander)
  1430. {
  1431. nvgBeginPath(args.vg);
  1432. nvgRect(args.vg, 1, 90 - 19, 18, 49 * 6 - 4);
  1433. nvgFillPaint(args.vg, nvgLinearGradient(args.vg, 0, 0, 18, 0, nvgRGB(0xd0, 0xd0, 0xd0), nvgRGBA(0xd0, 0xd0, 0xd0, 0)));
  1434. nvgFill(args.vg);
  1435. for (int i=1; i<6; ++i)
  1436. {
  1437. const float y = 90 + 49 * i - 23;
  1438. const int col1 = 0x18 + static_cast<int>((y / box.size.y) * (0x21 - 0x18) + 0.5f);
  1439. const int col2 = 0x19 + static_cast<int>((y / box.size.y) * (0x22 - 0x19) + 0.5f);
  1440. nvgBeginPath(args.vg);
  1441. nvgRect(args.vg, 1, y, 18, 4);
  1442. nvgFillColor(args.vg, nvgRGB(col1, col2, col2));
  1443. nvgFill(args.vg);
  1444. }
  1445. }
  1446. if (hasRightSideExpander)
  1447. {
  1448. // i == 0
  1449. nvgBeginPath(args.vg);
  1450. nvgRect(args.vg, box.size.x - 19, 90 - 19, 18, 49 - 4);
  1451. nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
  1452. nvgFill(args.vg);
  1453. // gradient for i > 0
  1454. nvgBeginPath(args.vg);
  1455. nvgRect(args.vg, box.size.x - 19, 90 + 49 - 23, 18, 49 * 5);
  1456. nvgFillPaint(args.vg, nvgLinearGradient(args.vg,
  1457. box.size.x - 19, 0, box.size.x - 1, 0,
  1458. nvgRGBA(0xd0, 0xd0, 0xd0, 0), nvgRGB(0xd0, 0xd0, 0xd0)));
  1459. nvgFill(args.vg);
  1460. for (int i=1; i<6; ++i)
  1461. {
  1462. const float y = 90 + 49 * i - 23;
  1463. const int col1 = 0x18 + static_cast<int>((y / box.size.y) * (0x21 - 0x18) + 0.5f);
  1464. const int col2 = 0x19 + static_cast<int>((y / box.size.y) * (0x22 - 0x19) + 0.5f);
  1465. nvgBeginPath(args.vg);
  1466. nvgRect(args.vg, box.size.x - 19, y, 18, 4);
  1467. nvgFillColor(args.vg, nvgRGB(col1, col2, col2));
  1468. nvgFill(args.vg);
  1469. }
  1470. }
  1471. drawOutputJacksArea(args.vg, 2);
  1472. ModuleWidgetWithSideScrews<26>::draw(args);
  1473. }
  1474. void step() override
  1475. {
  1476. hasLeftSideExpander = module != nullptr
  1477. && module->leftExpander.module != nullptr
  1478. && module->leftExpander.module->model == modelExpanderInputMIDI;
  1479. hasRightSideExpander = module != nullptr
  1480. && module->rightExpander.module != nullptr
  1481. && module->rightExpander.module->model == modelExpanderOutputMIDI;
  1482. ModuleWidgetWithSideScrews<26>::step();
  1483. }
  1484. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilModuleWidget)
  1485. };
  1486. #else
  1487. static void host_ui_parameter_changed(NativeHostHandle, uint32_t, float) {}
  1488. static const char* host_ui_open_file(NativeHostHandle, bool, const char*, const char*) { return nullptr; }
  1489. static void projectLoadedFromDSP(void*) {}
  1490. struct IldaeilModuleWidget : ModuleWidget {
  1491. IldaeilModuleWidget(IldaeilModule* const module) {
  1492. setModule(module);
  1493. addInput(createInput<PJ301MPort>({}, module, IldaeilModule::INPUT1));
  1494. addInput(createInput<PJ301MPort>({}, module, IldaeilModule::INPUT2));
  1495. addOutput(createOutput<PJ301MPort>({}, module, IldaeilModule::OUTPUT1));
  1496. addOutput(createOutput<PJ301MPort>({}, module, IldaeilModule::OUTPUT2));
  1497. }
  1498. };
  1499. #endif
  1500. // --------------------------------------------------------------------------------------------------------------------
  1501. Model* modelIldaeil = createModel<IldaeilModule, IldaeilModuleWidget>("Ildaeil");
  1502. // --------------------------------------------------------------------------------------------------------------------