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.

1616 lines
54KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include "IldaeilBasePlugin.hpp"
  18. #include "DistrhoUI.hpp"
  19. #if ILDAEIL_STANDALONE
  20. #include "DistrhoStandaloneUtils.hpp"
  21. #endif
  22. #include "CarlaBackendUtils.hpp"
  23. #include "PluginHostWindow.hpp"
  24. #include "extra/Runner.hpp"
  25. // IDE helper
  26. #include "DearImGui.hpp"
  27. #include "water/files/File.h"
  28. #include "water/files/FileInputStream.h"
  29. #include "water/files/FileOutputStream.h"
  30. #include "water/memory/MemoryBlock.h"
  31. #include <string>
  32. #include <vector>
  33. // strcasestr
  34. #ifdef DISTRHO_OS_WINDOWS
  35. # include <shlwapi.h>
  36. namespace ildaeil {
  37. inline const char* strcasestr(const char* const haystack, const char* const needle)
  38. {
  39. return StrStrIA(haystack, needle);
  40. }
  41. // using strcasestr = StrStrIA;
  42. }
  43. #else
  44. namespace ildaeil {
  45. using ::strcasestr;
  46. }
  47. #endif
  48. // #define WASM_TESTING
  49. START_NAMESPACE_DISTRHO
  50. using namespace CARLA_BACKEND_NAMESPACE;
  51. // --------------------------------------------------------------------------------------------------------------------
  52. class IldaeilUI : public UI,
  53. public Runner,
  54. public PluginHostWindow::Callbacks
  55. {
  56. static constexpr const uint kGenericWidth = 380;
  57. static constexpr const uint kGenericHeight = 400;
  58. static constexpr const uint kButtonHeight = 20;
  59. static constexpr const uint kMinWidth = 120;
  60. static constexpr const uint kMinHeight = 120;
  61. struct PluginInfoCache {
  62. BinaryType btype;
  63. uint64_t uniqueId;
  64. std::string filename;
  65. std::string name;
  66. std::string label;
  67. };
  68. struct PluginGenericUI {
  69. char* title;
  70. uint parameterCount;
  71. struct Parameter {
  72. char* name;
  73. char* printformat;
  74. uint32_t rindex;
  75. bool boolean, bvalue, log, readonly;
  76. float min, max, power;
  77. Parameter()
  78. : name(nullptr),
  79. printformat(nullptr),
  80. rindex(0),
  81. boolean(false),
  82. bvalue(false),
  83. log(false),
  84. readonly(false),
  85. min(0.0f),
  86. max(1.0f) {}
  87. ~Parameter()
  88. {
  89. std::free(name);
  90. std::free(printformat);
  91. }
  92. }* parameters;
  93. float* values;
  94. uint presetCount;
  95. struct Preset {
  96. uint32_t index;
  97. char* name;
  98. ~Preset()
  99. {
  100. std::free(name);
  101. }
  102. }* presets;
  103. int currentPreset;
  104. const char** presetStrings;
  105. PluginGenericUI()
  106. : title(nullptr),
  107. parameterCount(0),
  108. parameters(nullptr),
  109. values(nullptr),
  110. presetCount(0),
  111. presets(nullptr),
  112. currentPreset(-1),
  113. presetStrings(nullptr) {}
  114. ~PluginGenericUI()
  115. {
  116. std::free(title);
  117. delete[] parameters;
  118. delete[] values;
  119. delete[] presets;
  120. delete[] presetStrings;
  121. }
  122. };
  123. enum {
  124. kDrawingLoading,
  125. kDrawingPluginError,
  126. kDrawingPluginList,
  127. kDrawingPluginEmbedUI,
  128. kDrawingPluginGenericUI,
  129. kDrawingErrorInit,
  130. kDrawingErrorDraw
  131. } fDrawingState;
  132. enum {
  133. kIdleInit,
  134. kIdleInitPluginAlreadyLoaded,
  135. kIdleLoadSelectedPlugin,
  136. kIdlePluginLoadedFromDSP,
  137. kIdleResetPlugin,
  138. kIdleOpenFileUI,
  139. kIdleShowCustomUI,
  140. kIdleHideEmbedAndShowGenericUI,
  141. kIdleHidePluginUI,
  142. kIdleGiveIdleToUI,
  143. kIdleChangePluginType,
  144. kIdleNothing
  145. } fIdleState = kIdleInit;
  146. IldaeilBasePlugin* const fPlugin;
  147. PluginHostWindow fPluginHostWindow;
  148. PluginType fPluginType;
  149. PluginType fNextPluginType;
  150. uint fPluginId;
  151. int fPluginSelected;
  152. bool fPluginHasCustomUI;
  153. bool fPluginHasEmbedUI;
  154. bool fPluginHasFileOpen;
  155. bool fPluginHasOutputParameters;
  156. bool fPluginRunning;
  157. bool fPluginWillRunInBridgeMode;
  158. Mutex fPluginsMutex;
  159. PluginInfoCache fCurrentPluginInfo;
  160. std::vector<PluginInfoCache> fPlugins;
  161. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  162. bool fPluginSearchActive;
  163. bool fPluginSearchFirstShow;
  164. char fPluginSearchString[0xff];
  165. String fPopupError, fPluginFilename;
  166. Size<uint> fNextSize;
  167. struct RunnerData {
  168. bool needsReinit;
  169. CarlaPluginDiscoveryHandle handle;
  170. RunnerData()
  171. : needsReinit(true),
  172. handle(nullptr) {}
  173. void init()
  174. {
  175. needsReinit = true;
  176. if (handle != nullptr)
  177. {
  178. carla_plugin_discovery_stop(handle);
  179. handle = nullptr;
  180. }
  181. }
  182. } fRunnerData;
  183. public:
  184. IldaeilUI()
  185. : UI(kInitialWidth, kInitialHeight),
  186. Runner("IldaeilScanner"),
  187. fDrawingState(kDrawingLoading),
  188. fIdleState(kIdleInit),
  189. fPlugin((IldaeilBasePlugin*)getPluginInstancePointer()),
  190. fPluginHostWindow(getWindow(), this),
  191. fPluginType(PLUGIN_LV2),
  192. fNextPluginType(fPluginType),
  193. fPluginId(0),
  194. fPluginSelected(-1),
  195. fPluginHasCustomUI(false),
  196. fPluginHasEmbedUI(false),
  197. fPluginHasFileOpen(false),
  198. fPluginHasOutputParameters(false),
  199. fPluginRunning(false),
  200. fPluginWillRunInBridgeMode(false),
  201. fCurrentPluginInfo(),
  202. fPluginSearchActive(false),
  203. fPluginSearchFirstShow(false),
  204. fRunnerData()
  205. {
  206. const double scaleFactor = getScaleFactor();
  207. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  208. {
  209. fDrawingState = kDrawingErrorInit;
  210. fIdleState = kIdleNothing;
  211. fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
  212. setGeometryConstraints(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  213. setSize(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  214. return;
  215. }
  216. std::strcpy(fPluginSearchString, "Search...");
  217. ImGuiStyle& style(ImGui::GetStyle());
  218. style.FrameRounding = 4 * scaleFactor;
  219. const double paddingY = style.WindowPadding.y * 2 * scaleFactor;
  220. if (d_isNotEqual(scaleFactor, 1.0))
  221. {
  222. setGeometryConstraints(kMinWidth * scaleFactor, kMinHeight * scaleFactor);
  223. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  224. fPluginHostWindow.setPositionAndSize(0, kButtonHeight * scaleFactor + paddingY,
  225. kInitialWidth * scaleFactor,
  226. (kInitialHeight - kButtonHeight) * scaleFactor - paddingY);
  227. }
  228. else
  229. {
  230. setGeometryConstraints(kMinWidth, kMinHeight);
  231. fPluginHostWindow.setPositionAndSize(0, kButtonHeight + paddingY,
  232. kInitialWidth, kInitialHeight - kButtonHeight - paddingY);
  233. }
  234. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  235. char winIdStr[24];
  236. std::snprintf(winIdStr, sizeof(winIdStr), "%lx", (ulong)getWindow().getNativeWindowHandle());
  237. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  238. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, scaleFactor*1000, nullptr);
  239. if (checkIfPluginIsLoaded())
  240. fIdleState = kIdleInitPluginAlreadyLoaded;
  241. fPlugin->fUI = this;
  242. #ifdef WASM_TESTING
  243. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr,
  244. "midifile", 0, 0x0, PLUGIN_OPTIONS_NULL))
  245. {
  246. d_stdout("Special hack for MIDI file playback activated");
  247. carla_set_custom_data(handle, 0, CUSTOM_DATA_TYPE_PATH, "file", "/furelise.mid");
  248. carla_set_parameter_value(handle, 0, 0, 1.0f);
  249. carla_set_parameter_value(handle, 0, 1, 0.0f);
  250. fPluginId = 2;
  251. }
  252. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "miditranspose", 0, 0x0, PLUGIN_OPTIONS_NULL);
  253. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "bypass", 0, 0x0, PLUGIN_OPTIONS_NULL);
  254. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "3bandeq", 0, 0x0, PLUGIN_OPTIONS_NULL);
  255. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "pingpongpan", 0, 0x0, PLUGIN_OPTIONS_NULL);
  256. carla_set_parameter_value(handle, 4, 1, 0.0f);
  257. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "audiogain_s", 0, 0x0, PLUGIN_OPTIONS_NULL);
  258. for (uint i=0; i<5; ++i)
  259. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "bypass", 0, 0x0, PLUGIN_OPTIONS_NULL);
  260. #endif
  261. }
  262. ~IldaeilUI() override
  263. {
  264. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr)
  265. {
  266. fPlugin->fUI = nullptr;
  267. if (fPluginRunning)
  268. hidePluginUI(fPlugin->fCarlaHostHandle);
  269. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  270. }
  271. stopRunner();
  272. fPluginGenericUI = nullptr;
  273. }
  274. bool checkIfPluginIsLoaded()
  275. {
  276. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  277. if (carla_get_current_plugin_count(handle) == 0)
  278. return false;
  279. const uint hints = carla_get_plugin_info(handle, fPluginId)->hints;
  280. updatePluginFlags(hints);
  281. fPluginRunning = true;
  282. return true;
  283. }
  284. void updatePluginFlags(const uint hints) noexcept
  285. {
  286. if (hints & PLUGIN_HAS_CUSTOM_UI_USING_FILE_OPEN)
  287. {
  288. fPluginHasCustomUI = false;
  289. fPluginHasEmbedUI = false;
  290. fPluginHasFileOpen = true;
  291. }
  292. else
  293. {
  294. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  295. #ifndef DISTRHO_OS_WASM
  296. fPluginHasEmbedUI = hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  297. #endif
  298. fPluginHasFileOpen = false;
  299. }
  300. }
  301. void projectLoadedFromDSP()
  302. {
  303. if (checkIfPluginIsLoaded())
  304. fIdleState = kIdlePluginLoadedFromDSP;
  305. }
  306. void changeParameterFromDSP(const uint32_t index, const float value)
  307. {
  308. if (PluginGenericUI* const ui = fPluginGenericUI)
  309. {
  310. for (uint32_t i=0; i < ui->parameterCount; ++i)
  311. {
  312. if (ui->parameters[i].rindex != index)
  313. continue;
  314. ui->values[i] = value;
  315. if (ui->parameters[i].boolean)
  316. ui->parameters[i].bvalue = value > ui->parameters[i].min;
  317. break;
  318. }
  319. }
  320. repaint();
  321. }
  322. void closeUI()
  323. {
  324. if (fIdleState == kIdleGiveIdleToUI)
  325. fIdleState = kIdleNothing;
  326. }
  327. const char* openFileFromDSP(const bool /*isDir*/, const char* const title, const char* const /*filter*/)
  328. {
  329. DISTRHO_SAFE_ASSERT_RETURN(fPluginType == PLUGIN_INTERNAL || fPluginType == PLUGIN_LV2, nullptr);
  330. FileBrowserOptions opts;
  331. opts.title = title;
  332. openFileBrowser(opts);
  333. return nullptr;
  334. }
  335. void showPluginUI(const CarlaHostHandle handle, const bool showIfNotEmbed)
  336. {
  337. #ifndef DISTRHO_OS_WASM
  338. const uint hints = carla_get_plugin_info(handle, fPluginId)->hints;
  339. if (hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  340. {
  341. fDrawingState = kDrawingPluginEmbedUI;
  342. fIdleState = kIdleGiveIdleToUI;
  343. fPluginHasCustomUI = true;
  344. fPluginHasEmbedUI = true;
  345. fPluginHasFileOpen = false;
  346. carla_embed_custom_ui(handle, fPluginId, fPluginHostWindow.attachAndGetWindowHandle());
  347. }
  348. else
  349. #endif
  350. {
  351. // fPluginHas* flags are updated in the next function
  352. createOrUpdatePluginGenericUI(handle);
  353. if (showIfNotEmbed && fPluginHasCustomUI)
  354. {
  355. fIdleState = kIdleGiveIdleToUI;
  356. carla_show_custom_ui(handle, fPluginId, true);
  357. }
  358. }
  359. repaint();
  360. }
  361. void hidePluginUI(const CarlaHostHandle handle)
  362. {
  363. DISTRHO_SAFE_ASSERT_RETURN(fPluginRunning,);
  364. if (fPluginHostWindow.hide())
  365. carla_show_custom_ui(handle, fPluginId, false);
  366. }
  367. void createOrUpdatePluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* info = nullptr)
  368. {
  369. if (info == nullptr)
  370. info = carla_get_plugin_info(handle, fPluginId);
  371. fDrawingState = kDrawingPluginGenericUI;
  372. updatePluginFlags(info->hints);
  373. if (fPluginGenericUI == nullptr)
  374. createPluginGenericUI(handle, info);
  375. else
  376. updatePluginGenericUI(handle);
  377. #ifndef DISTRHO_OS_WASM
  378. const double scaleFactor = getScaleFactor();
  379. fNextSize = Size<uint>(kGenericWidth * scaleFactor,
  380. (kGenericHeight + ImGui::GetStyle().WindowPadding.y) * scaleFactor);
  381. #endif
  382. }
  383. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  384. {
  385. PluginGenericUI* const ui = new PluginGenericUI;
  386. String title(info->name);
  387. title += " by ";
  388. title += info->maker;
  389. ui->title = title.getAndReleaseBuffer();
  390. fPluginHasOutputParameters = false;
  391. const uint32_t parameterCount = ui->parameterCount = carla_get_parameter_count(handle, fPluginId);
  392. // make count of valid parameters
  393. for (uint32_t i=0; i < parameterCount; ++i)
  394. {
  395. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  396. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  397. {
  398. --ui->parameterCount;
  399. continue;
  400. }
  401. if (pdata->type == PARAMETER_OUTPUT)
  402. fPluginHasOutputParameters = true;
  403. }
  404. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  405. ui->values = new float[ui->parameterCount];
  406. // now safely fill in details
  407. for (uint32_t i=0, j=0; i < parameterCount; ++i)
  408. {
  409. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  410. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  411. continue;
  412. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, fPluginId, i);
  413. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, fPluginId, i);
  414. String printformat;
  415. if (pdata->hints & PARAMETER_IS_INTEGER)
  416. printformat = "%.0f ";
  417. else
  418. printformat = "%.3f ";
  419. printformat += pinfo->unit;
  420. PluginGenericUI::Parameter& param(ui->parameters[j]);
  421. param.name = strdup(pinfo->name);
  422. param.printformat = printformat.getAndReleaseBuffer();
  423. param.rindex = i;
  424. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  425. param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
  426. param.readonly = pdata->type != PARAMETER_INPUT || (pdata->hints & PARAMETER_IS_READ_ONLY);
  427. param.min = pranges->min;
  428. param.max = pranges->max;
  429. ui->values[j] = carla_get_current_parameter_value(handle, fPluginId, i);
  430. if (param.boolean)
  431. param.bvalue = ui->values[j] > param.min;
  432. else
  433. param.bvalue = false;
  434. ++j;
  435. }
  436. // handle presets too
  437. const uint32_t presetCount = ui->presetCount = carla_get_program_count(handle, fPluginId);
  438. for (uint32_t i=0; i < presetCount; ++i)
  439. {
  440. const char* const pname = carla_get_program_name(handle, fPluginId, i);
  441. if (pname[0] == '\0')
  442. {
  443. --ui->presetCount;
  444. continue;
  445. }
  446. }
  447. ui->presets = new PluginGenericUI::Preset[ui->presetCount];
  448. ui->presetStrings = new const char*[ui->presetCount];
  449. for (uint32_t i=0, j=0; i < presetCount; ++i)
  450. {
  451. const char* const pname = carla_get_program_name(handle, fPluginId, i);
  452. if (pname[0] == '\0')
  453. continue;
  454. PluginGenericUI::Preset& preset(ui->presets[j]);
  455. preset.index = i;
  456. preset.name = strdup(pname);
  457. ui->presetStrings[j] = preset.name;
  458. ++j;
  459. }
  460. ui->currentPreset = -1;
  461. fPluginGenericUI = ui;
  462. }
  463. void updatePluginGenericUI(const CarlaHostHandle handle)
  464. {
  465. PluginGenericUI* const ui = fPluginGenericUI;
  466. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  467. for (uint32_t i=0; i < ui->parameterCount; ++i)
  468. {
  469. ui->values[i] = carla_get_current_parameter_value(handle, fPluginId, ui->parameters[i].rindex);
  470. if (ui->parameters[i].boolean)
  471. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  472. }
  473. }
  474. bool loadPlugin(const CarlaHostHandle handle, const PluginInfoCache& info)
  475. {
  476. if (fPluginRunning || fPluginId != 0)
  477. {
  478. hidePluginUI(handle);
  479. carla_replace_plugin(handle, fPluginId);
  480. }
  481. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  482. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  483. const bool ok = carla_add_plugin(handle,
  484. info.btype,
  485. fPluginType,
  486. info.filename.c_str(),
  487. info.name.c_str(),
  488. info.label.c_str(),
  489. info.uniqueId,
  490. nullptr,
  491. PLUGIN_OPTIONS_NULL);
  492. if (ok)
  493. {
  494. fPluginRunning = true;
  495. fPluginGenericUI = nullptr;
  496. fPluginFilename.clear();
  497. showPluginUI(handle, false);
  498. #ifdef WASM_TESTING
  499. d_stdout("loaded a plugin with label '%s'", label);
  500. if (std::strcmp(label, "audiofile") == 0)
  501. {
  502. d_stdout("Loading mp3 file into audiofile plugin");
  503. carla_set_custom_data(handle, fPluginId, CUSTOM_DATA_TYPE_PATH, "file", "/foolme.mp3");
  504. carla_set_parameter_value(handle, fPluginId, 1, 0.0f);
  505. fPluginGenericUI->values[1] = 0.0f;
  506. }
  507. #endif
  508. }
  509. else
  510. {
  511. fPopupError = carla_get_last_error(handle);
  512. d_stdout("got error: %s", fPopupError.buffer());
  513. fDrawingState = kDrawingPluginError;
  514. }
  515. repaint();
  516. return ok;
  517. }
  518. void loadFileAsPlugin(const CarlaHostHandle handle, const char* const filename)
  519. {
  520. if (fPluginRunning || fPluginId != 0)
  521. {
  522. hidePluginUI(handle);
  523. carla_replace_plugin(handle, fPluginId);
  524. }
  525. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  526. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  527. if (carla_load_file(handle, filename))
  528. {
  529. fPluginRunning = true;
  530. fPluginGenericUI = nullptr;
  531. fPluginFilename = filename;
  532. showPluginUI(handle, false);
  533. }
  534. else
  535. {
  536. fPopupError = carla_get_last_error(handle);
  537. d_stdout("got error: %s", fPopupError.buffer());
  538. fDrawingState = kDrawingPluginError;
  539. fPluginFilename.clear();
  540. }
  541. repaint();
  542. }
  543. protected:
  544. void pluginWindowResized(const uint width, const uint height) override
  545. {
  546. const uint extraHeight = kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2;
  547. fNextSize = Size<uint>(width, height + extraHeight);
  548. }
  549. void uiIdle() override
  550. {
  551. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  552. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
  553. // carla_juce_idle();
  554. if (fDrawingState == kDrawingPluginGenericUI && fPluginGenericUI != nullptr && fPluginHasOutputParameters)
  555. {
  556. updatePluginGenericUI(handle);
  557. repaint();
  558. }
  559. if (fNextSize.isValid())
  560. {
  561. setSize(fNextSize);
  562. fNextSize = Size<uint>();
  563. }
  564. switch (fIdleState)
  565. {
  566. case kIdleInit:
  567. fIdleState = kIdleNothing;
  568. initAndStartRunner();
  569. break;
  570. case kIdleInitPluginAlreadyLoaded:
  571. fIdleState = kIdleNothing;
  572. showPluginUI(handle, false);
  573. initAndStartRunner();
  574. break;
  575. case kIdlePluginLoadedFromDSP:
  576. fIdleState = kIdleNothing;
  577. showPluginUI(handle, false);
  578. break;
  579. case kIdleLoadSelectedPlugin:
  580. fIdleState = kIdleNothing;
  581. loadSelectedPlugin(handle);
  582. break;
  583. case kIdleResetPlugin:
  584. fIdleState = kIdleNothing;
  585. if (fPluginFilename.isNotEmpty())
  586. loadFileAsPlugin(handle, fPluginFilename.buffer());
  587. else
  588. loadPlugin(handle, fCurrentPluginInfo);
  589. break;
  590. case kIdleOpenFileUI:
  591. fIdleState = kIdleNothing;
  592. carla_show_custom_ui(handle, fPluginId, true);
  593. break;
  594. case kIdleShowCustomUI:
  595. fIdleState = kIdleNothing;
  596. showPluginUI(handle, true);
  597. break;
  598. case kIdleHideEmbedAndShowGenericUI:
  599. fIdleState = kIdleNothing;
  600. hidePluginUI(handle);
  601. createOrUpdatePluginGenericUI(handle);
  602. break;
  603. case kIdleHidePluginUI:
  604. fIdleState = kIdleNothing;
  605. hidePluginUI(handle);
  606. break;
  607. case kIdleGiveIdleToUI:
  608. if (fPlugin->fCarlaPluginDescriptor->ui_idle != nullptr)
  609. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  610. fPluginHostWindow.idle();
  611. break;
  612. case kIdleChangePluginType:
  613. fIdleState = kIdleNothing;
  614. if (fPluginRunning)
  615. hidePluginUI(handle);
  616. if (fNextPluginType == PLUGIN_TYPE_COUNT)
  617. {
  618. FileBrowserOptions opts;
  619. opts.title = "Load from file";
  620. openFileBrowser(opts);
  621. }
  622. else
  623. {
  624. fPluginSelected = -1;
  625. stopRunner();
  626. fPluginType = fNextPluginType;
  627. initAndStartRunner();
  628. }
  629. break;
  630. case kIdleNothing:
  631. break;
  632. }
  633. }
  634. void loadSelectedPlugin(const CarlaHostHandle handle)
  635. {
  636. DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
  637. PluginInfoCache info;
  638. {
  639. const MutexLocker cml(fPluginsMutex);
  640. info = fPlugins[fPluginSelected];
  641. }
  642. d_stdout("Loading %s...", info.name.c_str());
  643. if (loadPlugin(handle, info))
  644. fCurrentPluginInfo = info;
  645. }
  646. void uiFileBrowserSelected(const char* const filename) override
  647. {
  648. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr && filename != nullptr)
  649. {
  650. if (fNextPluginType == PLUGIN_TYPE_COUNT)
  651. loadFileAsPlugin(fPlugin->fCarlaHostHandle, filename);
  652. else
  653. carla_set_custom_data(fPlugin->fCarlaHostHandle, fPluginId, CUSTOM_DATA_TYPE_PATH, "file", filename);
  654. }
  655. }
  656. bool initAndStartRunner()
  657. {
  658. if (isRunnerActive())
  659. stopRunner();
  660. fRunnerData.init();
  661. return startRunner();
  662. }
  663. bool run() override
  664. {
  665. if (fRunnerData.needsReinit)
  666. {
  667. fRunnerData.needsReinit = false;
  668. {
  669. const MutexLocker cml(fPluginsMutex);
  670. fPlugins.clear();
  671. }
  672. d_stdout("Will scan plugins now...");
  673. fRunnerData.handle = carla_plugin_discovery_start(fPlugin->fDiscoveryTool,
  674. fPluginType,
  675. IldaeilBasePlugin::getPluginPath(fPluginType),
  676. _binaryPluginSearchCallback,
  677. _binaryPluginCheckCacheCallback,
  678. this);
  679. if (fDrawingState == kDrawingLoading)
  680. {
  681. fDrawingState = kDrawingPluginList;
  682. fPluginSearchFirstShow = true;
  683. }
  684. if (fRunnerData.handle == nullptr)
  685. {
  686. d_stdout("Nothing found!");
  687. return false;
  688. }
  689. }
  690. DISTRHO_SAFE_ASSERT_RETURN(fRunnerData.handle != nullptr, false);
  691. if (carla_plugin_discovery_idle(fRunnerData.handle))
  692. return true;
  693. // stop here
  694. d_stdout("Found %lu plugins!", (ulong)fPlugins.size());
  695. carla_plugin_discovery_stop(fRunnerData.handle);
  696. fRunnerData.handle = nullptr;
  697. return false;
  698. }
  699. void binaryPluginSearchCallback(const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum)
  700. {
  701. // save plugin info into cache
  702. if (sha1sum != nullptr)
  703. {
  704. const water::String configDir(ildaeilConfigDir());
  705. const water::File cacheFile(configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum);
  706. if (cacheFile.create().ok())
  707. {
  708. water::FileOutputStream stream(cacheFile);
  709. if (stream.openedOk())
  710. {
  711. if (info != nullptr)
  712. {
  713. stream.writeString(getBinaryTypeAsString(info->btype));
  714. stream.writeString(getPluginTypeAsString(info->ptype));
  715. stream.writeString(info->filename);
  716. stream.writeString(info->label);
  717. stream.writeInt64(info->uniqueId);
  718. stream.writeString(info->metadata.name);
  719. stream.writeString(info->metadata.maker);
  720. stream.writeString(getPluginCategoryAsString(info->metadata.category));
  721. stream.writeInt(info->metadata.hints);
  722. stream.writeCompressedInt(info->io.audioIns);
  723. stream.writeCompressedInt(info->io.audioOuts);
  724. stream.writeCompressedInt(info->io.cvIns);
  725. stream.writeCompressedInt(info->io.cvOuts);
  726. stream.writeCompressedInt(info->io.midiIns);
  727. stream.writeCompressedInt(info->io.midiOuts);
  728. stream.writeCompressedInt(info->io.parameterIns);
  729. stream.writeCompressedInt(info->io.parameterOuts);
  730. }
  731. }
  732. else
  733. {
  734. d_stderr("Failed to write cache file for %s%s%s",
  735. ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum);
  736. }
  737. }
  738. else
  739. {
  740. d_stderr("Failed to write cache file directories for %s%s%s",
  741. ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum);
  742. }
  743. }
  744. if (info == nullptr)
  745. return;
  746. if (info->io.cvIns != 0 || info->io.cvOuts != 0)
  747. return;
  748. if (info->io.midiIns != 0 && info->io.midiIns != 1)
  749. return;
  750. if (info->io.midiOuts != 0 && info->io.midiOuts != 1)
  751. return;
  752. #if ILDAEIL_STANDALONE
  753. if (fPluginType == PLUGIN_INTERNAL)
  754. {
  755. if (std::strcmp(info->label, "audiogain") == 0)
  756. return;
  757. if (std::strcmp(info->label, "midichanfilter") == 0)
  758. return;
  759. if (std::strcmp(info->label, "midichannelize") == 0)
  760. return;
  761. }
  762. #elif DISTRHO_PLUGIN_IS_SYNTH
  763. if (info->io.midiIns != 1)
  764. return;
  765. if (info->io.audioIns == 0)
  766. return;
  767. if ((info->metadata.hints & PLUGIN_IS_SYNTH) == 0x0)
  768. return;
  769. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  770. if ((info->io.midiIns != 1 && info->io.audioIns != 0 && info->io.audioOuts != 0) || info->io.midiOuts != 1)
  771. return;
  772. if (info->io.audioIns != 0 || info->io.audioOuts != 0)
  773. return;
  774. #else
  775. if (info->io.audioIns != 1 && info->io.audioIns != 2)
  776. return;
  777. if (info->io.audioOuts != 1 && info->io.audioOuts != 2)
  778. return;
  779. #endif
  780. if (fPluginType == PLUGIN_INTERNAL)
  781. {
  782. #if !ILDAEIL_STANDALONE
  783. if (std::strcmp(info->label, "audiogain_s") == 0)
  784. return;
  785. #endif
  786. if (std::strcmp(info->label, "lfo") == 0)
  787. return;
  788. if (std::strcmp(info->label, "midi2cv") == 0)
  789. return;
  790. if (std::strcmp(info->label, "midithrough") == 0)
  791. return;
  792. if (std::strcmp(info->label, "3bandsplitter") == 0)
  793. return;
  794. }
  795. const PluginInfoCache pinfo = {
  796. info->btype,
  797. info->uniqueId,
  798. info->filename,
  799. info->metadata.name,
  800. info->label,
  801. };
  802. const MutexLocker cml(fPluginsMutex);
  803. fPlugins.push_back(pinfo);
  804. }
  805. static void _binaryPluginSearchCallback(void* const ptr,
  806. const CarlaPluginDiscoveryInfo* const info,
  807. const char* const sha1sum)
  808. {
  809. static_cast<IldaeilUI*>(ptr)->binaryPluginSearchCallback(info, sha1sum);
  810. }
  811. bool binaryPluginCheckCacheCallback(const char* const filename, const char* const sha1sum)
  812. {
  813. if (sha1sum == nullptr)
  814. return false;
  815. const water::String configDir(ildaeilConfigDir());
  816. const water::File cacheFile(configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum);
  817. if (cacheFile.existsAsFile())
  818. {
  819. water::FileInputStream stream(cacheFile);
  820. if (stream.openedOk())
  821. {
  822. while (! stream.isExhausted())
  823. {
  824. CarlaPluginDiscoveryInfo info = {};
  825. // read back everything the same way and order as we wrote it
  826. info.btype = getBinaryTypeFromString(stream.readString().toRawUTF8());
  827. info.ptype = getPluginTypeFromString(stream.readString().toRawUTF8());
  828. const water::String pfilename(stream.readString());
  829. const water::String label(stream.readString());
  830. info.uniqueId = stream.readInt64();
  831. const water::String name(stream.readString());
  832. const water::String maker(stream.readString());
  833. info.metadata.category = getPluginCategoryFromString(stream.readString().toRawUTF8());
  834. info.metadata.hints = stream.readInt();
  835. info.io.audioIns = stream.readCompressedInt();
  836. info.io.audioOuts = stream.readCompressedInt();
  837. info.io.cvIns = stream.readCompressedInt();
  838. info.io.cvOuts = stream.readCompressedInt();
  839. info.io.midiIns = stream.readCompressedInt();
  840. info.io.midiOuts = stream.readCompressedInt();
  841. info.io.parameterIns = stream.readCompressedInt();
  842. info.io.parameterOuts = stream.readCompressedInt();
  843. // string stuff
  844. info.filename = pfilename.toRawUTF8();
  845. info.label = label.toRawUTF8();
  846. info.metadata.name = name.toRawUTF8();
  847. info.metadata.maker = maker.toRawUTF8();
  848. // check sha1 collisions
  849. if (pfilename != filename)
  850. {
  851. d_stderr("Cache hash collision for %s: \"%s\" vs \"%s\"",
  852. sha1sum, pfilename.toRawUTF8(), filename);
  853. return false;
  854. }
  855. // purposefully not passing sha1sum, to not override cache file
  856. binaryPluginSearchCallback(&info, nullptr);
  857. }
  858. return true;
  859. }
  860. else
  861. {
  862. d_stderr("Failed to read cache file for %s%s%s",
  863. ildaeilConfigDir(), CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR, sha1sum);
  864. }
  865. }
  866. return false;
  867. }
  868. static bool _binaryPluginCheckCacheCallback(void* const ptr, const char* const filename, const char* const sha1)
  869. {
  870. return static_cast<IldaeilUI*>(ptr)->binaryPluginCheckCacheCallback(filename, sha1);
  871. }
  872. void onImGuiDisplay() override
  873. {
  874. switch (fDrawingState)
  875. {
  876. case kDrawingLoading:
  877. drawLoading();
  878. break;
  879. case kDrawingPluginError:
  880. ImGui::OpenPopup("Plugin Error");
  881. // call ourselves again with the plugin list
  882. fDrawingState = kDrawingPluginList;
  883. onImGuiDisplay();
  884. break;
  885. case kDrawingPluginList:
  886. drawPluginList();
  887. break;
  888. case kDrawingPluginGenericUI:
  889. drawTopBar();
  890. drawGenericUI();
  891. break;
  892. case kDrawingPluginEmbedUI:
  893. drawTopBar();
  894. break;
  895. case kDrawingErrorInit:
  896. fDrawingState = kDrawingErrorDraw;
  897. drawError(true);
  898. break;
  899. case kDrawingErrorDraw:
  900. drawError(false);
  901. break;
  902. }
  903. }
  904. void drawError(const bool open)
  905. {
  906. ImGui::SetNextWindowPos(ImVec2(0, 0));
  907. ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight()));
  908. const int flags = ImGuiWindowFlags_NoSavedSettings
  909. | ImGuiWindowFlags_NoTitleBar
  910. | ImGuiWindowFlags_NoResize
  911. | ImGuiWindowFlags_NoCollapse
  912. | ImGuiWindowFlags_NoScrollbar
  913. | ImGuiWindowFlags_NoScrollWithMouse;
  914. if (ImGui::Begin("Error Window", nullptr, flags))
  915. {
  916. if (open)
  917. ImGui::OpenPopup("Engine Error");
  918. const int pflags = ImGuiWindowFlags_NoSavedSettings
  919. | ImGuiWindowFlags_NoResize
  920. | ImGuiWindowFlags_NoCollapse
  921. | ImGuiWindowFlags_NoScrollbar
  922. | ImGuiWindowFlags_NoScrollWithMouse
  923. | ImGuiWindowFlags_AlwaysAutoResize
  924. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  925. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  926. {
  927. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  928. ImGui::EndPopup();
  929. }
  930. }
  931. ImGui::End();
  932. }
  933. void drawTopBar()
  934. {
  935. const double scaleFactor = getScaleFactor();
  936. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  937. ImGui::SetNextWindowPos(ImVec2(0, 0));
  938. ImGui::SetNextWindowSize(ImVec2(getWidth(), kButtonHeight * scaleFactor + padding));
  939. const int flags = ImGuiWindowFlags_NoSavedSettings
  940. | ImGuiWindowFlags_NoTitleBar
  941. | ImGuiWindowFlags_NoResize
  942. | ImGuiWindowFlags_NoCollapse
  943. | ImGuiWindowFlags_NoScrollbar
  944. | ImGuiWindowFlags_NoScrollWithMouse;
  945. if (ImGui::Begin("Current Plugin", nullptr, flags))
  946. {
  947. if (ImGui::Button("Pick Another..."))
  948. {
  949. fIdleState = kIdleHidePluginUI;
  950. fDrawingState = kDrawingPluginList;
  951. #ifndef DISTRHO_OS_WASM
  952. fNextSize = Size<uint>(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  953. #endif
  954. }
  955. ImGui::SameLine();
  956. if (ImGui::Button("Reset"))
  957. fIdleState = kIdleResetPlugin;
  958. if (fDrawingState == kDrawingPluginGenericUI)
  959. {
  960. if (fPluginHasCustomUI)
  961. {
  962. ImGui::SameLine();
  963. if (ImGui::Button("Show Custom GUI"))
  964. fIdleState = kIdleShowCustomUI;
  965. }
  966. if (fPluginHasFileOpen)
  967. {
  968. ImGui::SameLine();
  969. if (ImGui::Button("Open File..."))
  970. fIdleState = kIdleOpenFileUI;
  971. }
  972. #ifdef WASM_TESTING
  973. ImGui::SameLine();
  974. ImGui::TextUnformatted(" Plugin to control:");
  975. for (uint i=1; i<10; ++i)
  976. {
  977. char txt[8];
  978. sprintf(txt, "%d", i);
  979. ImGui::SameLine();
  980. if (ImGui::Button(txt))
  981. {
  982. fPluginId = i;
  983. fPluginGenericUI = nullptr;
  984. fIdleState = kIdleHideEmbedAndShowGenericUI;
  985. }
  986. }
  987. #endif
  988. }
  989. if (fDrawingState == kDrawingPluginEmbedUI)
  990. {
  991. ImGui::SameLine();
  992. if (ImGui::Button("Show Generic GUI"))
  993. fIdleState = kIdleHideEmbedAndShowGenericUI;
  994. }
  995. #if ILDAEIL_STANDALONE
  996. if (isUsingNativeAudio())
  997. {
  998. ImGui::SameLine();
  999. ImGui::Spacing();
  1000. ImGui::SameLine();
  1001. if (supportsAudioInput() && !isAudioInputEnabled() && ImGui::Button("Enable Input"))
  1002. requestAudioInput();
  1003. ImGui::SameLine();
  1004. if (supportsMIDI() && !isMIDIEnabled() && ImGui::Button("Enable MIDI"))
  1005. requestMIDI();
  1006. if (fDrawingState != kDrawingPluginEmbedUI && supportsBufferSizeChanges())
  1007. {
  1008. ImGui::SameLine();
  1009. ImGui::Spacing();
  1010. ImGui::SameLine();
  1011. ImGui::Text("Buffer Size:");
  1012. static constexpr uint bufferSizes_i[] = {
  1013. #ifndef DISTRHO_OS_WASM
  1014. 128,
  1015. #endif
  1016. 256, 512, 1024, 2048, 4096, 8192,
  1017. #ifdef DISTRHO_OS_WASM
  1018. 16384,
  1019. #endif
  1020. };
  1021. static constexpr const char* bufferSizes_s[] = {
  1022. #ifndef DISTRHO_OS_WASM
  1023. "128",
  1024. #endif
  1025. "256", "512", "1024", "2048", "4096", "8192",
  1026. #ifdef DISTRHO_OS_WASM
  1027. "16384",
  1028. #endif
  1029. };
  1030. uint buffersize = getBufferSize();
  1031. int current = -1;
  1032. for (uint i=0; i<ARRAY_SIZE(bufferSizes_i); ++i)
  1033. {
  1034. if (bufferSizes_i[i] == buffersize)
  1035. {
  1036. current = i;
  1037. break;
  1038. }
  1039. }
  1040. ImGui::SameLine();
  1041. if (ImGui::Combo("##buffersize", &current, bufferSizes_s, ARRAY_SIZE(bufferSizes_s)))
  1042. {
  1043. const uint next = bufferSizes_i[current];
  1044. d_stdout("requesting new buffer size: %u -> %u", buffersize, next);
  1045. requestBufferSizeChange(next);
  1046. }
  1047. }
  1048. }
  1049. #endif
  1050. }
  1051. ImGui::End();
  1052. }
  1053. void setupMainWindowPos()
  1054. {
  1055. const float scaleFactor = getScaleFactor();
  1056. float y = 0;
  1057. float height = getHeight();
  1058. if (fDrawingState == kDrawingPluginGenericUI)
  1059. {
  1060. y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
  1061. height -= y;
  1062. }
  1063. ImGui::SetNextWindowPos(ImVec2(0, y));
  1064. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  1065. }
  1066. void drawGenericUI()
  1067. {
  1068. setupMainWindowPos();
  1069. PluginGenericUI* const ui = fPluginGenericUI;
  1070. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1071. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1072. | ImGuiWindowFlags_NoResize
  1073. | ImGuiWindowFlags_NoCollapse
  1074. | ImGuiWindowFlags_AlwaysAutoResize;
  1075. if (ImGui::Begin(ui->title, nullptr, pflags))
  1076. {
  1077. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  1078. if (ui->presetCount != 0)
  1079. {
  1080. ImGui::Text("Preset:");
  1081. ImGui::SameLine();
  1082. if (ImGui::Combo("##presets", &ui->currentPreset, ui->presetStrings, ui->presetCount))
  1083. {
  1084. PluginGenericUI::Preset& preset(ui->presets[ui->currentPreset]);
  1085. carla_set_program(handle, fPluginId, preset.index);
  1086. }
  1087. }
  1088. for (uint32_t i=0; i < ui->parameterCount; ++i)
  1089. {
  1090. PluginGenericUI::Parameter& param(ui->parameters[i]);
  1091. if (param.readonly)
  1092. {
  1093. ImGui::BeginDisabled();
  1094. ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat,
  1095. ImGuiSliderFlags_NoInput | (param.log ? ImGuiSliderFlags_Logarithmic : 0x0));
  1096. ImGui::EndDisabled();
  1097. continue;
  1098. }
  1099. if (param.boolean)
  1100. {
  1101. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  1102. {
  1103. if (ImGui::IsItemActivated())
  1104. {
  1105. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  1106. // editParameter(0, true);
  1107. }
  1108. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  1109. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  1110. // setParameterValue(0, ui->values[i]);
  1111. }
  1112. }
  1113. else
  1114. {
  1115. const bool ret = param.log
  1116. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_Logarithmic)
  1117. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
  1118. if (ret)
  1119. {
  1120. if (ImGui::IsItemActivated())
  1121. {
  1122. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  1123. // editParameter(0, true);
  1124. }
  1125. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  1126. // setParameterValue(0, ui->values[i]);
  1127. }
  1128. }
  1129. if (ImGui::IsItemDeactivated())
  1130. {
  1131. carla_set_parameter_touch(handle, fPluginId, param.rindex, false);
  1132. // editParameter(0, false);
  1133. }
  1134. }
  1135. }
  1136. ImGui::End();
  1137. }
  1138. void drawLoading()
  1139. {
  1140. setupMainWindowPos();
  1141. constexpr const int plflags = ImGuiWindowFlags_NoSavedSettings
  1142. | ImGuiWindowFlags_NoDecoration;
  1143. if (ImGui::Begin("Plugin List", nullptr, plflags))
  1144. ImGui::TextUnformatted("Loading...", nullptr);
  1145. ImGui::End();
  1146. }
  1147. void drawPluginList()
  1148. {
  1149. static const char* pluginTypes[] = {
  1150. getPluginTypeAsString(PLUGIN_INTERNAL),
  1151. getPluginTypeAsString(PLUGIN_LADSPA),
  1152. getPluginTypeAsString(PLUGIN_DSSI),
  1153. getPluginTypeAsString(PLUGIN_LV2),
  1154. getPluginTypeAsString(PLUGIN_VST2),
  1155. getPluginTypeAsString(PLUGIN_VST3),
  1156. getPluginTypeAsString(PLUGIN_CLAP),
  1157. getPluginTypeAsString(PLUGIN_JSFX),
  1158. "Load from file..."
  1159. };
  1160. setupMainWindowPos();
  1161. constexpr const int plflags = ImGuiWindowFlags_NoSavedSettings
  1162. | ImGuiWindowFlags_NoDecoration;
  1163. if (ImGui::Begin("Plugin List", nullptr, plflags))
  1164. {
  1165. constexpr const int errflags = ImGuiWindowFlags_NoSavedSettings
  1166. | ImGuiWindowFlags_NoResize
  1167. | ImGuiWindowFlags_AlwaysAutoResize
  1168. | ImGuiWindowFlags_NoCollapse
  1169. | ImGuiWindowFlags_NoScrollbar
  1170. | ImGuiWindowFlags_NoScrollWithMouse;
  1171. if (ImGui::BeginPopupModal("Plugin Error", nullptr, errflags))
  1172. {
  1173. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  1174. ImGui::Separator();
  1175. if (ImGui::Button("Ok"))
  1176. ImGui::CloseCurrentPopup();
  1177. ImGui::SameLine();
  1178. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  1179. ImGui::EndPopup();
  1180. }
  1181. else if (fPluginSearchFirstShow)
  1182. {
  1183. fPluginSearchFirstShow = false;
  1184. ImGui::SetKeyboardFocusHere();
  1185. }
  1186. if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
  1187. ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  1188. fPluginSearchActive = true;
  1189. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  1190. fPluginSearchActive = false;
  1191. ImGui::SameLine();
  1192. ImGui::PushItemWidth(-1.0f);
  1193. int current;
  1194. switch (fPluginType)
  1195. {
  1196. case PLUGIN_JSFX: current = 7; break;
  1197. case PLUGIN_CLAP: current = 6; break;
  1198. case PLUGIN_VST3: current = 5; break;
  1199. case PLUGIN_VST2: current = 4; break;
  1200. case PLUGIN_LV2: current = 3; break;
  1201. case PLUGIN_DSSI: current = 2; break;
  1202. case PLUGIN_LADSPA: current = 1; break;
  1203. default: current = 0; break;
  1204. }
  1205. if (ImGui::Combo("##plugintypes", &current, pluginTypes, ARRAY_SIZE(pluginTypes)))
  1206. {
  1207. fIdleState = kIdleChangePluginType;
  1208. switch (current)
  1209. {
  1210. case 0: fNextPluginType = PLUGIN_INTERNAL; break;
  1211. case 1: fNextPluginType = PLUGIN_LADSPA; break;
  1212. case 2: fNextPluginType = PLUGIN_DSSI; break;
  1213. case 3: fNextPluginType = PLUGIN_LV2; break;
  1214. case 4: fNextPluginType = PLUGIN_VST2; break;
  1215. case 5: fNextPluginType = PLUGIN_VST3; break;
  1216. case 6: fNextPluginType = PLUGIN_CLAP; break;
  1217. case 7: fNextPluginType = PLUGIN_JSFX; break;
  1218. case 8: fNextPluginType = PLUGIN_TYPE_COUNT; break;
  1219. }
  1220. }
  1221. ImGui::BeginDisabled(fPluginSelected < 0);
  1222. if (ImGui::Button("Load Plugin"))
  1223. fIdleState = kIdleLoadSelectedPlugin;
  1224. // xx cardinal
  1225. if (fPluginType != PLUGIN_INTERNAL /*&& module->canUseBridges*/)
  1226. {
  1227. ImGui::SameLine();
  1228. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  1229. }
  1230. ImGui::EndDisabled();
  1231. if (fPluginRunning)
  1232. {
  1233. ImGui::SameLine();
  1234. if (ImGui::Button("Cancel"))
  1235. fIdleState = kIdleShowCustomUI;
  1236. }
  1237. if (ImGui::BeginChild("pluginlistwindow"))
  1238. {
  1239. if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
  1240. {
  1241. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  1242. switch (fPluginType)
  1243. {
  1244. case PLUGIN_INTERNAL:
  1245. case PLUGIN_AU:
  1246. ImGui::TableSetupColumn("Name");
  1247. ImGui::TableSetupColumn("Label");
  1248. ImGui::TableHeadersRow();
  1249. break;
  1250. case PLUGIN_LV2:
  1251. ImGui::TableSetupColumn("Name");
  1252. ImGui::TableSetupColumn("URI");
  1253. ImGui::TableHeadersRow();
  1254. break;
  1255. default:
  1256. ImGui::TableSetupColumn("Name");
  1257. ImGui::TableSetupColumn("Filename");
  1258. ImGui::TableHeadersRow();
  1259. break;
  1260. }
  1261. const MutexLocker cml(fPluginsMutex);
  1262. for (uint i=0; i<fPlugins.size(); ++i)
  1263. {
  1264. const PluginInfoCache& info(fPlugins[i]);
  1265. if (search != nullptr && ildaeil::strcasestr(info.name.c_str(), search) == nullptr)
  1266. continue;
  1267. bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
  1268. switch (fPluginType)
  1269. {
  1270. case PLUGIN_INTERNAL:
  1271. case PLUGIN_AU:
  1272. ImGui::TableNextRow();
  1273. ImGui::TableSetColumnIndex(0);
  1274. ImGui::Selectable(info.name.c_str(), &selected);
  1275. ImGui::TableSetColumnIndex(1);
  1276. ImGui::Selectable(info.label.c_str(), &selected);
  1277. break;
  1278. case PLUGIN_LV2:
  1279. ImGui::TableNextRow();
  1280. ImGui::TableSetColumnIndex(0);
  1281. ImGui::Selectable(info.name.c_str(), &selected);
  1282. ImGui::TableSetColumnIndex(1);
  1283. ImGui::Selectable(info.label.c_str(), &selected);
  1284. break;
  1285. default:
  1286. ImGui::TableNextRow();
  1287. ImGui::TableSetColumnIndex(0);
  1288. ImGui::Selectable(info.name.c_str(), &selected);
  1289. ImGui::TableSetColumnIndex(1);
  1290. ImGui::Selectable(info.filename.c_str(), &selected);
  1291. break;
  1292. }
  1293. if (selected)
  1294. fPluginSelected = i;
  1295. }
  1296. ImGui::EndTable();
  1297. }
  1298. ImGui::EndChild();
  1299. }
  1300. }
  1301. ImGui::End();
  1302. }
  1303. protected:
  1304. /* --------------------------------------------------------------------------------------------------------
  1305. * DSP/Plugin Callbacks */
  1306. void parameterChanged(uint32_t, float) override
  1307. {
  1308. }
  1309. void stateChanged(const char* /* const key */, const char*) override
  1310. {
  1311. /*
  1312. if (std::strcmp(key, "project") == 0)
  1313. hidePluginUI(fPlugin->fCarlaHostHandle);
  1314. */
  1315. }
  1316. // -------------------------------------------------------------------------------------------------------
  1317. private:
  1318. /**
  1319. Set our UI class as non-copyable and add a leak detector just in case.
  1320. */
  1321. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  1322. };
  1323. // --------------------------------------------------------------------------------------------------------------------
  1324. void ildaeilProjectLoadedFromDSP(void* const ui)
  1325. {
  1326. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1327. static_cast<IldaeilUI*>(ui)->projectLoadedFromDSP();
  1328. }
  1329. void ildaeilParameterChangeForUI(void* const ui, const uint32_t index, const float value)
  1330. {
  1331. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1332. static_cast<IldaeilUI*>(ui)->changeParameterFromDSP(index, value);
  1333. }
  1334. void ildaeilCloseUI(void* ui)
  1335. {
  1336. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1337. static_cast<IldaeilUI*>(ui)->closeUI();
  1338. }
  1339. const char* ildaeilOpenFileForUI(void* const ui, const bool isDir, const char* const title, const char* const filter)
  1340. {
  1341. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  1342. return static_cast<IldaeilUI*>(ui)->openFileFromDSP(isDir, title, filter);
  1343. }
  1344. /* --------------------------------------------------------------------------------------------------------------------
  1345. * UI entry point, called by DPF to create a new UI instance. */
  1346. UI* createUI()
  1347. {
  1348. return new IldaeilUI();
  1349. }
  1350. // --------------------------------------------------------------------------------------------------------------------
  1351. END_NAMESPACE_DISTRHO