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.

1232 lines
39KB

  1. /*
  2. * DISTRHO Ildaeil 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 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. #include "CarlaBackendUtils.hpp"
  20. #include "PluginHostWindow.hpp"
  21. #include "extra/Runner.hpp"
  22. // IDE helper
  23. #include "DearImGui.hpp"
  24. #include <vector>
  25. // strcasestr
  26. #ifdef DISTRHO_OS_WINDOWS
  27. # include <shlwapi.h>
  28. namespace ildaeil {
  29. inline const char* strcasestr(const char* const haystack, const char* const needle)
  30. {
  31. return StrStrIA(haystack, needle);
  32. }
  33. // using strcasestr = StrStrIA;
  34. }
  35. #else
  36. namespace ildaeil {
  37. using ::strcasestr;
  38. }
  39. #endif
  40. START_NAMESPACE_DISTRHO
  41. // --------------------------------------------------------------------------------------------------------------------
  42. using namespace CARLA_BACKEND_NAMESPACE;
  43. class IldaeilUI : public UI,
  44. public Runner,
  45. public PluginHostWindow::Callbacks
  46. {
  47. static constexpr const uint kInitialWidth = 520;
  48. #ifdef DISTRHO_OS_WASM
  49. static constexpr const uint kInitialHeight = 350;
  50. #else
  51. static constexpr const uint kInitialHeight = 520;
  52. #endif
  53. static constexpr const uint kGenericWidth = 380;
  54. static constexpr const uint kGenericHeight = 400;
  55. static constexpr const uint kButtonHeight = 20;
  56. struct PluginInfoCache {
  57. char* name;
  58. char* label;
  59. PluginInfoCache()
  60. : name(nullptr),
  61. label(nullptr) {}
  62. ~PluginInfoCache()
  63. {
  64. std::free(name);
  65. std::free(label);
  66. }
  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. min(0.0f),
  85. max(1.0f) {}
  86. ~Parameter()
  87. {
  88. std::free(name);
  89. std::free(printformat);
  90. }
  91. }* parameters;
  92. float* values;
  93. PluginGenericUI()
  94. : title(nullptr),
  95. parameterCount(0),
  96. parameters(nullptr),
  97. values(nullptr) {}
  98. ~PluginGenericUI()
  99. {
  100. std::free(title);
  101. delete[] parameters;
  102. delete[] values;
  103. }
  104. };
  105. enum {
  106. kDrawingLoading,
  107. kDrawingPluginError,
  108. kDrawingPluginList,
  109. kDrawingPluginEmbedUI,
  110. kDrawingPluginGenericUI,
  111. kDrawingErrorInit,
  112. kDrawingErrorDraw
  113. } fDrawingState;
  114. enum {
  115. kIdleInit,
  116. kIdleInitPluginAlreadyLoaded,
  117. kIdleLoadSelectedPlugin,
  118. kIdlePluginLoadedFromDSP,
  119. kIdleResetPlugin,
  120. kIdleShowCustomUI,
  121. kIdleHideEmbedAndShowGenericUI,
  122. kIdleHidePluginUI,
  123. kIdleGiveIdleToUI,
  124. kIdleChangePluginType,
  125. kIdleNothing
  126. } fIdleState = kIdleInit;
  127. IldaeilBasePlugin* const fPlugin;
  128. PluginHostWindow fPluginHostWindow;
  129. PluginType fPluginType;
  130. PluginType fNextPluginType;
  131. uint fPluginCount;
  132. uint fPluginId;
  133. int fPluginSelected;
  134. bool fPluginScanningFinished;
  135. bool fPluginHasCustomUI;
  136. bool fPluginHasEmbedUI;
  137. bool fPluginHasOutputParameters;
  138. bool fPluginRunning;
  139. bool fPluginWillRunInBridgeMode;
  140. PluginInfoCache* fPlugins;
  141. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  142. bool fPluginSearchActive;
  143. bool fPluginSearchFirstShow;
  144. char fPluginSearchString[0xff];
  145. String fPopupError;
  146. Size<uint> fNextSize;
  147. struct RunnerData {
  148. bool needsReinit;
  149. uint pluginCount;
  150. uint pluginIndex;
  151. RunnerData()
  152. : needsReinit(true),
  153. pluginCount(0),
  154. pluginIndex(0) {}
  155. void init()
  156. {
  157. needsReinit = true;
  158. pluginCount = 0;
  159. pluginIndex = 0;
  160. }
  161. } fRunnerData;
  162. public:
  163. IldaeilUI()
  164. : UI(kInitialWidth, kInitialHeight),
  165. Runner("IldaeilScanner"),
  166. fDrawingState(kDrawingLoading),
  167. fIdleState(kIdleInit),
  168. fPlugin((IldaeilBasePlugin*)getPluginInstancePointer()),
  169. fPluginHostWindow(getWindow(), this),
  170. #ifdef DISTRHO_OS_WASM
  171. fPluginType(PLUGIN_INTERNAL),
  172. #else
  173. fPluginType(PLUGIN_LV2),
  174. #endif
  175. fNextPluginType(fPluginType),
  176. fPluginCount(0),
  177. fPluginId(0),
  178. fPluginSelected(-1),
  179. fPluginScanningFinished(false),
  180. fPluginHasCustomUI(false),
  181. fPluginHasEmbedUI(false),
  182. fPluginHasOutputParameters(false),
  183. fPluginRunning(false),
  184. fPluginWillRunInBridgeMode(false),
  185. fPlugins(nullptr),
  186. fPluginSearchActive(false),
  187. fPluginSearchFirstShow(false),
  188. fRunnerData()
  189. {
  190. const double scaleFactor = getScaleFactor();
  191. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  192. {
  193. fDrawingState = kDrawingErrorInit;
  194. fIdleState = kIdleNothing;
  195. fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
  196. setSize(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  197. return;
  198. }
  199. std::strcpy(fPluginSearchString, "Search...");
  200. ImGuiStyle& style(ImGui::GetStyle());
  201. style.FrameRounding = 4;
  202. const double padding = style.WindowPadding.y * 2;
  203. if (d_isNotEqual(scaleFactor, 1.0))
  204. {
  205. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  206. fPluginHostWindow.setPositionAndSize(0, kButtonHeight * scaleFactor + padding,
  207. kInitialWidth * scaleFactor,
  208. (kInitialHeight - kButtonHeight) * scaleFactor - padding);
  209. }
  210. else
  211. {
  212. fPluginHostWindow.setPositionAndSize(0, kButtonHeight + padding,
  213. kInitialWidth, kInitialHeight - kButtonHeight - padding);
  214. }
  215. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  216. char winIdStr[24];
  217. std::snprintf(winIdStr, sizeof(winIdStr), "%lx", (ulong)getWindow().getNativeWindowHandle());
  218. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  219. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, scaleFactor*1000, nullptr);
  220. if (checkIfPluginIsLoaded())
  221. fIdleState = kIdleInitPluginAlreadyLoaded;
  222. fPlugin->fUI = this;
  223. /* TESTING
  224. if (carla_add_plugin(handle, BINARY_NATIVE, fPluginType, nullptr, nullptr,
  225. "midifile", 0, 0x0, PLUGIN_OPTIONS_NULL))
  226. {
  227. d_stdout("Special hack for MIDI file playback activated");
  228. carla_set_custom_data(handle, 0, CUSTOM_DATA_TYPE_PATH, "file", "/furelise.mid");
  229. carla_set_parameter_value(handle, 0, 0, 1.0f);
  230. carla_set_parameter_value(handle, 0, 1, 0.0f);
  231. fPluginId = 1;
  232. }
  233. */
  234. }
  235. ~IldaeilUI() override
  236. {
  237. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr)
  238. {
  239. fPlugin->fUI = nullptr;
  240. if (fPluginRunning)
  241. hidePluginUI(fPlugin->fCarlaHostHandle);
  242. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  243. }
  244. stopRunner();
  245. fPluginGenericUI = nullptr;
  246. delete[] fPlugins;
  247. }
  248. bool checkIfPluginIsLoaded()
  249. {
  250. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  251. if (carla_get_current_plugin_count(handle) != 0)
  252. {
  253. #ifndef DISTRHO_OS_WASM
  254. // FIXME
  255. const uint hints = carla_get_plugin_info(handle, fPluginId)->hints;
  256. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  257. fPluginHasEmbedUI = hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  258. #endif
  259. fPluginRunning = true;
  260. return true;
  261. }
  262. return false;
  263. }
  264. void projectLoadedFromDSP()
  265. {
  266. if (checkIfPluginIsLoaded())
  267. fIdleState = kIdlePluginLoadedFromDSP;
  268. }
  269. void changeParameterFromDSP(const uint32_t index, const float value)
  270. {
  271. if (PluginGenericUI* const ui = fPluginGenericUI)
  272. {
  273. for (uint32_t i=0; i < ui->parameterCount; ++i)
  274. {
  275. if (ui->parameters[i].rindex != index)
  276. continue;
  277. ui->values[i] = value;
  278. if (ui->parameters[i].boolean)
  279. ui->parameters[i].bvalue = value > ui->parameters[i].min;
  280. break;
  281. }
  282. }
  283. repaint();
  284. }
  285. const char* openFileFromDSP(const bool /*isDir*/, const char* const title, const char* const /*filter*/)
  286. {
  287. DISTRHO_SAFE_ASSERT_RETURN(fPluginType == PLUGIN_INTERNAL || fPluginType == PLUGIN_LV2, nullptr);
  288. FileBrowserOptions opts;
  289. opts.title = title;
  290. openFileBrowser(opts);
  291. return nullptr;
  292. }
  293. void showPluginUI(const CarlaHostHandle handle, const bool showIfNotEmbed)
  294. {
  295. #ifndef DISTRHO_OS_WASM
  296. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, fPluginId);
  297. // FIXME
  298. if (info->hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  299. {
  300. fDrawingState = kDrawingPluginEmbedUI;
  301. fIdleState = kIdleGiveIdleToUI;
  302. fPluginHasCustomUI = true;
  303. fPluginHasEmbedUI = true;
  304. carla_embed_custom_ui(handle, fPluginId, fPluginHostWindow.attachAndGetWindowHandle());
  305. }
  306. else
  307. #endif
  308. {
  309. createOrUpdatePluginGenericUI(handle);
  310. if (showIfNotEmbed && fPluginHasCustomUI)
  311. {
  312. fIdleState = kIdleGiveIdleToUI;
  313. carla_show_custom_ui(handle, fPluginId, true);
  314. }
  315. }
  316. repaint();
  317. }
  318. void hidePluginUI(const CarlaHostHandle handle)
  319. {
  320. DISTRHO_SAFE_ASSERT_RETURN(fPluginRunning,);
  321. fPluginHostWindow.hide();
  322. carla_show_custom_ui(handle, fPluginId, false);
  323. }
  324. void createOrUpdatePluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* info = nullptr)
  325. {
  326. if (info == nullptr)
  327. info = carla_get_plugin_info(handle, fPluginId);
  328. fDrawingState = kDrawingPluginGenericUI;
  329. #ifndef DISTRHO_OS_WASM
  330. // FIXME
  331. fPluginHasCustomUI = info->hints & PLUGIN_HAS_CUSTOM_UI;
  332. fPluginHasEmbedUI = info->hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  333. #endif
  334. if (fPluginGenericUI == nullptr)
  335. createPluginGenericUI(handle, info);
  336. else
  337. updatePluginGenericUI(handle);
  338. #ifndef DISTRHO_OS_WASM
  339. ImGuiStyle& style(ImGui::GetStyle());
  340. const double scaleFactor = getScaleFactor();
  341. fNextSize = Size<uint>(kGenericWidth * scaleFactor, (kGenericHeight + style.FramePadding.x) * scaleFactor);
  342. #endif
  343. }
  344. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  345. {
  346. PluginGenericUI* const ui = new PluginGenericUI;
  347. String title(info->name);
  348. title += " by ";
  349. title += info->maker;
  350. ui->title = title.getAndReleaseBuffer();
  351. const uint32_t pcount = ui->parameterCount = carla_get_parameter_count(handle, fPluginId);
  352. // make count of valid parameters
  353. for (uint32_t i=0; i < pcount; ++i)
  354. {
  355. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  356. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  357. {
  358. --ui->parameterCount;
  359. continue;
  360. }
  361. if (pdata->type == PARAMETER_OUTPUT)
  362. fPluginHasOutputParameters = true;
  363. }
  364. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  365. ui->values = new float[ui->parameterCount];
  366. // now safely fill in details
  367. for (uint32_t i=0, j=0; i < pcount; ++i)
  368. {
  369. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  370. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  371. continue;
  372. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, fPluginId, i);
  373. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, fPluginId, i);
  374. String printformat;
  375. if (pdata->hints & PARAMETER_IS_INTEGER)
  376. printformat = "%.0f ";
  377. else
  378. printformat = "%.3f ";
  379. printformat += pinfo->unit;
  380. PluginGenericUI::Parameter& param(ui->parameters[j]);
  381. param.name = strdup(pinfo->name);
  382. param.printformat = printformat.getAndReleaseBuffer();
  383. param.rindex = i;
  384. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  385. param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
  386. param.readonly = pdata->type != PARAMETER_INPUT || (pdata->hints & PARAMETER_IS_READ_ONLY);
  387. param.min = pranges->min;
  388. param.max = pranges->max;
  389. ui->values[j] = carla_get_current_parameter_value(handle, fPluginId, i);
  390. if (param.boolean)
  391. param.bvalue = ui->values[j] > param.min;
  392. else
  393. param.bvalue = false;
  394. ++j;
  395. }
  396. fPluginGenericUI = ui;
  397. }
  398. void updatePluginGenericUI(const CarlaHostHandle handle)
  399. {
  400. PluginGenericUI* const ui = fPluginGenericUI;
  401. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  402. for (uint32_t i=0; i < ui->parameterCount; ++i)
  403. {
  404. ui->values[i] = carla_get_current_parameter_value(handle, fPluginId, ui->parameters[i].rindex);
  405. if (ui->parameters[i].boolean)
  406. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  407. }
  408. }
  409. void loadPlugin(const CarlaHostHandle handle, const char* const label)
  410. {
  411. if (fPluginRunning)
  412. {
  413. hidePluginUI(handle);
  414. carla_replace_plugin(handle, fPluginId);
  415. }
  416. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  417. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  418. if (carla_add_plugin(handle, BINARY_NATIVE, fPluginType, nullptr, nullptr,
  419. label, 0, 0x0, PLUGIN_OPTIONS_NULL))
  420. {
  421. fPluginRunning = true;
  422. fPluginGenericUI = nullptr;
  423. showPluginUI(handle, false);
  424. /* TESTING
  425. d_stdout("loaded a plugin with label '%s'", label);
  426. if (std::strcmp(label, "audiofile") == 0)
  427. {
  428. d_stdout("Loading mp3 file into audiofile plugin");
  429. carla_set_custom_data(handle, fPluginId, CUSTOM_DATA_TYPE_PATH, "file", "/foolme.mp3");
  430. carla_set_parameter_value(handle, fPluginId, 1, 0.0f);
  431. fPluginGenericUI->values[1] = 0.0f;
  432. }
  433. */
  434. }
  435. else
  436. {
  437. fPopupError = carla_get_last_error(handle);
  438. d_stdout("got error: %s", fPopupError.buffer());
  439. fDrawingState = kDrawingPluginError;
  440. }
  441. repaint();
  442. }
  443. protected:
  444. void pluginWindowResized(const uint width, const uint height) override
  445. {
  446. const uint extraHeight = kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2;
  447. fNextSize = Size<uint>(width, height + extraHeight);
  448. }
  449. void uiIdle() override
  450. {
  451. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  452. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
  453. // carla_juce_idle();
  454. if (fDrawingState == kDrawingPluginGenericUI && fPluginGenericUI != nullptr && fPluginHasOutputParameters)
  455. {
  456. updatePluginGenericUI(handle);
  457. repaint();
  458. }
  459. if (fNextSize.isValid())
  460. {
  461. setSize(fNextSize);
  462. fNextSize = Size<uint>();
  463. }
  464. switch (fIdleState)
  465. {
  466. case kIdleInit:
  467. fIdleState = kIdleNothing;
  468. startRunner();
  469. break;
  470. case kIdleInitPluginAlreadyLoaded:
  471. fIdleState = kIdleNothing;
  472. showPluginUI(handle, false);
  473. startRunner();
  474. break;
  475. case kIdlePluginLoadedFromDSP:
  476. fIdleState = kIdleNothing;
  477. showPluginUI(handle, false);
  478. break;
  479. case kIdleLoadSelectedPlugin:
  480. fIdleState = kIdleNothing;
  481. loadSelectedPlugin(handle);
  482. break;
  483. case kIdleResetPlugin:
  484. fIdleState = kIdleNothing;
  485. loadPlugin(handle, carla_get_plugin_info(handle, fPluginId)->label);
  486. break;
  487. case kIdleShowCustomUI:
  488. fIdleState = kIdleNothing;
  489. showPluginUI(handle, true);
  490. break;
  491. case kIdleHideEmbedAndShowGenericUI:
  492. fIdleState = kIdleNothing;
  493. hidePluginUI(handle);
  494. createOrUpdatePluginGenericUI(handle);
  495. break;
  496. case kIdleHidePluginUI:
  497. fIdleState = kIdleNothing;
  498. carla_show_custom_ui(handle, fPluginId, false);
  499. break;
  500. case kIdleGiveIdleToUI:
  501. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  502. fPluginHostWindow.idle();
  503. break;
  504. case kIdleChangePluginType:
  505. fIdleState = kIdleNothing;
  506. if (fPluginRunning)
  507. hidePluginUI(handle);
  508. fPluginSelected = -1;
  509. stopRunner();
  510. fPluginType = fNextPluginType;
  511. startRunner();
  512. break;
  513. case kIdleNothing:
  514. break;
  515. }
  516. }
  517. void loadSelectedPlugin(const CarlaHostHandle handle)
  518. {
  519. DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
  520. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  521. const char* label = nullptr;
  522. switch (fPluginType)
  523. {
  524. case PLUGIN_INTERNAL:
  525. case PLUGIN_AU:
  526. // case PLUGIN_JSFX:
  527. case PLUGIN_SFZ:
  528. label = info.label;
  529. break;
  530. case PLUGIN_LV2: {
  531. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  532. DISTRHO_SAFE_ASSERT_RETURN(slash != nullptr,);
  533. label = slash+1;
  534. break;
  535. }
  536. default:
  537. break;
  538. }
  539. DISTRHO_SAFE_ASSERT_RETURN(label != nullptr,);
  540. d_stdout("Loading %s...", info.name);
  541. loadPlugin(handle, label);
  542. }
  543. void uiFileBrowserSelected(const char* const filename) override
  544. {
  545. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr && filename != nullptr)
  546. carla_set_custom_data(fPlugin->fCarlaHostHandle, fPluginId, CUSTOM_DATA_TYPE_STRING, "file", filename);
  547. }
  548. bool startRunner()
  549. {
  550. if (isRunnerActive())
  551. stopRunner();
  552. fRunnerData.needsReinit = true;
  553. return Runner::startRunner();
  554. }
  555. bool run() override
  556. {
  557. if (fRunnerData.needsReinit)
  558. {
  559. fRunnerData.needsReinit = false;
  560. const char* path;
  561. switch (fPluginType)
  562. {
  563. case PLUGIN_LV2:
  564. path = std::getenv("LV2_PATH");
  565. break;
  566. default:
  567. path = nullptr;
  568. break;
  569. }
  570. if (path != nullptr)
  571. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, fPluginType, path);
  572. fPluginCount = 0;
  573. delete[] fPlugins;
  574. {
  575. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  576. d_stdout("Will scan plugins now...");
  577. fRunnerData.pluginCount = carla_get_cached_plugin_count(fPluginType, path);
  578. d_stdout("Scanning found %u plugins", fRunnerData.pluginCount);
  579. }
  580. if (fDrawingState == kDrawingLoading)
  581. {
  582. fDrawingState = kDrawingPluginList;
  583. fPluginSearchFirstShow = true;
  584. }
  585. if (fRunnerData.pluginCount != 0)
  586. {
  587. fPlugins = new PluginInfoCache[fRunnerData.pluginCount];
  588. fPluginScanningFinished = false;
  589. return true;
  590. }
  591. else
  592. {
  593. fPlugins = nullptr;
  594. fPluginScanningFinished = true;
  595. return false;
  596. }
  597. }
  598. const uint index = fRunnerData.pluginIndex++;
  599. DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fRunnerData.pluginCount,
  600. index, fRunnerData.pluginCount, false);
  601. do {
  602. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  603. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(fPluginType, index);
  604. DISTRHO_SAFE_ASSERT_RETURN(info != nullptr, true);
  605. if (! info->valid)
  606. break;
  607. if (info->cvIns != 0 || info->cvOuts != 0)
  608. break;
  609. #if DISTRHO_PLUGIN_IS_SYNTH
  610. if (info->midiIns != 1 && info->audioIns != 0)
  611. break;
  612. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0 && info->audioIns != 0)
  613. break;
  614. if (info->audioOuts != 1 && info->audioOuts != 2)
  615. break;
  616. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  617. if ((info->midiIns != 1 && info->audioIns != 0 && info->audioOuts != 0) || info->midiOuts != 1)
  618. break;
  619. if (info->audioIns != 0 || info->audioOuts != 0)
  620. break;
  621. #else
  622. if (info->audioIns != 1 && info->audioIns != 2)
  623. break;
  624. if (info->audioOuts != 1 && info->audioOuts != 2)
  625. break;
  626. #endif
  627. if (fPluginType == PLUGIN_INTERNAL)
  628. {
  629. if (std::strcmp(info->label, "audiogain_s") == 0)
  630. break;
  631. if (std::strcmp(info->label, "cv2audio") == 0)
  632. break;
  633. if (std::strcmp(info->label, "lfo") == 0)
  634. break;
  635. if (std::strcmp(info->label, "midi2cv") == 0)
  636. break;
  637. if (std::strcmp(info->label, "midithrough") == 0)
  638. break;
  639. if (std::strcmp(info->label, "3bandsplitter") == 0)
  640. break;
  641. }
  642. const uint pindex = fPluginCount;
  643. fPlugins[pindex].name = strdup(info->name);
  644. fPlugins[pindex].label = strdup(info->label);
  645. ++fPluginCount;
  646. } while (false);
  647. // run again
  648. if (fRunnerData.pluginIndex != fRunnerData.pluginCount)
  649. return true;
  650. // stop here
  651. fPluginScanningFinished = true;
  652. return false;
  653. }
  654. void onImGuiDisplay() override
  655. {
  656. switch (fDrawingState)
  657. {
  658. case kDrawingLoading:
  659. drawLoading();
  660. break;
  661. case kDrawingPluginError:
  662. ImGui::OpenPopup("Plugin Error");
  663. // call ourselves again with the plugin list
  664. fDrawingState = kDrawingPluginList;
  665. onImGuiDisplay();
  666. break;
  667. case kDrawingPluginList:
  668. drawPluginList();
  669. break;
  670. case kDrawingPluginGenericUI:
  671. drawTopBar();
  672. drawGenericUI();
  673. break;
  674. case kDrawingPluginEmbedUI:
  675. drawTopBar();
  676. break;
  677. case kDrawingErrorInit:
  678. fDrawingState = kDrawingErrorDraw;
  679. drawError(true);
  680. break;
  681. case kDrawingErrorDraw:
  682. drawError(false);
  683. break;
  684. }
  685. }
  686. void drawError(const bool open)
  687. {
  688. ImGui::SetNextWindowPos(ImVec2(0, 0));
  689. ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight()));
  690. const int flags = ImGuiWindowFlags_NoSavedSettings
  691. | ImGuiWindowFlags_NoTitleBar
  692. | ImGuiWindowFlags_NoResize
  693. | ImGuiWindowFlags_NoCollapse
  694. | ImGuiWindowFlags_NoScrollbar
  695. | ImGuiWindowFlags_NoScrollWithMouse;
  696. if (ImGui::Begin("Error Window", nullptr, flags))
  697. {
  698. if (open)
  699. ImGui::OpenPopup("Engine Error");
  700. const int pflags = ImGuiWindowFlags_NoSavedSettings
  701. | ImGuiWindowFlags_NoResize
  702. | ImGuiWindowFlags_NoCollapse
  703. | ImGuiWindowFlags_NoScrollbar
  704. | ImGuiWindowFlags_NoScrollWithMouse
  705. | ImGuiWindowFlags_AlwaysAutoResize
  706. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  707. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  708. {
  709. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  710. ImGui::EndPopup();
  711. }
  712. }
  713. ImGui::End();
  714. }
  715. void drawTopBar()
  716. {
  717. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  718. ImGui::SetNextWindowPos(ImVec2(0, 0));
  719. ImGui::SetNextWindowSize(ImVec2(getWidth(), kButtonHeight * getScaleFactor() + padding));
  720. const int flags = ImGuiWindowFlags_NoSavedSettings
  721. | ImGuiWindowFlags_NoTitleBar
  722. | ImGuiWindowFlags_NoResize
  723. | ImGuiWindowFlags_NoCollapse
  724. | ImGuiWindowFlags_NoScrollbar
  725. | ImGuiWindowFlags_NoScrollWithMouse;
  726. if (ImGui::Begin("Current Plugin", nullptr, flags))
  727. {
  728. if (ImGui::Button("Pick Another..."))
  729. {
  730. fIdleState = kIdleHidePluginUI;
  731. fDrawingState = kDrawingPluginList;
  732. #ifndef DISTRHO_OS_WASM
  733. const double scaleFactor = getScaleFactor();
  734. fNextSize = Size<uint>(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  735. #endif
  736. }
  737. ImGui::SameLine();
  738. if (ImGui::Button("Reset"))
  739. fIdleState = kIdleResetPlugin;
  740. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  741. {
  742. ImGui::SameLine();
  743. if (ImGui::Button("Show Custom GUI"))
  744. fIdleState = kIdleShowCustomUI;
  745. }
  746. if (fDrawingState == kDrawingPluginEmbedUI)
  747. {
  748. ImGui::SameLine();
  749. if (ImGui::Button("Show Generic GUI"))
  750. fIdleState = kIdleHideEmbedAndShowGenericUI;
  751. }
  752. }
  753. ImGui::End();
  754. }
  755. void setupMainWindowPos()
  756. {
  757. const float scaleFactor = getScaleFactor();
  758. float y = 0;
  759. float height = getHeight();
  760. if (fDrawingState == kDrawingPluginGenericUI)
  761. {
  762. y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
  763. height -= y;
  764. }
  765. ImGui::SetNextWindowPos(ImVec2(0, y));
  766. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  767. }
  768. void drawGenericUI()
  769. {
  770. setupMainWindowPos();
  771. PluginGenericUI* const ui = fPluginGenericUI;
  772. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  773. const int pflags = ImGuiWindowFlags_NoSavedSettings
  774. | ImGuiWindowFlags_NoResize
  775. | ImGuiWindowFlags_NoCollapse
  776. | ImGuiWindowFlags_AlwaysAutoResize;
  777. if (ImGui::Begin(ui->title, nullptr, pflags))
  778. {
  779. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  780. for (uint32_t i=0; i < ui->parameterCount; ++i)
  781. {
  782. PluginGenericUI::Parameter& param(ui->parameters[i]);
  783. if (param.readonly)
  784. {
  785. ImGui::BeginDisabled();
  786. ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_NoInput);
  787. ImGui::EndDisabled();
  788. continue;
  789. }
  790. if (param.boolean)
  791. {
  792. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  793. {
  794. if (ImGui::IsItemActivated())
  795. {
  796. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  797. // editParameter(0, true);
  798. }
  799. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  800. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  801. // setParameterValue(0, ui->values[i]);
  802. }
  803. }
  804. else
  805. {
  806. const bool ret = param.log
  807. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, 2.0f)
  808. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
  809. if (ret)
  810. {
  811. if (ImGui::IsItemActivated())
  812. {
  813. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  814. // editParameter(0, true);
  815. }
  816. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  817. // setParameterValue(0, ui->values[i]);
  818. }
  819. }
  820. if (ImGui::IsItemDeactivated())
  821. {
  822. carla_set_parameter_touch(handle, fPluginId, param.rindex, false);
  823. // editParameter(0, false);
  824. }
  825. }
  826. }
  827. ImGui::End();
  828. }
  829. void drawLoading()
  830. {
  831. setupMainWindowPos();
  832. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  833. ImGui::TextUnformatted("Loading...", nullptr);
  834. ImGui::End();
  835. }
  836. void drawPluginList()
  837. {
  838. static const char* pluginTypes[] = {
  839. getPluginTypeAsString(PLUGIN_INTERNAL),
  840. getPluginTypeAsString(PLUGIN_LV2),
  841. };
  842. setupMainWindowPos();
  843. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  844. {
  845. const int pflags = ImGuiWindowFlags_NoSavedSettings
  846. | ImGuiWindowFlags_NoResize
  847. | ImGuiWindowFlags_NoCollapse
  848. | ImGuiWindowFlags_NoScrollbar
  849. | ImGuiWindowFlags_NoScrollWithMouse
  850. | ImGuiWindowFlags_AlwaysAutoResize;
  851. if (ImGui::BeginPopupModal("Plugin Error", nullptr, pflags))
  852. {
  853. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  854. ImGui::Separator();
  855. if (ImGui::Button("Ok"))
  856. ImGui::CloseCurrentPopup();
  857. ImGui::SameLine();
  858. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  859. ImGui::EndPopup();
  860. }
  861. else if (fPluginSearchFirstShow)
  862. {
  863. fPluginSearchFirstShow = false;
  864. ImGui::SetKeyboardFocusHere();
  865. }
  866. if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
  867. ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  868. fPluginSearchActive = true;
  869. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  870. fPluginSearchActive = false;
  871. ImGui::SameLine();
  872. ImGui::PushItemWidth(-1.0f);
  873. int current;
  874. switch (fPluginType)
  875. {
  876. case PLUGIN_LV2:
  877. current = 1;
  878. break;
  879. default:
  880. current = 0;
  881. break;
  882. }
  883. if (ImGui::Combo("##plugintypes", &current, pluginTypes, ARRAY_SIZE(pluginTypes)))
  884. {
  885. fIdleState = kIdleChangePluginType;
  886. switch (current)
  887. {
  888. case 0:
  889. fNextPluginType = PLUGIN_INTERNAL;
  890. break;
  891. case 1:
  892. fNextPluginType = PLUGIN_LV2;
  893. break;
  894. }
  895. }
  896. ImGui::BeginDisabled(!fPluginScanningFinished || fPluginSelected < 0);
  897. if (ImGui::Button("Load Plugin"))
  898. fIdleState = kIdleLoadSelectedPlugin;
  899. // xx cardinal
  900. if (fPluginType != PLUGIN_INTERNAL /*&& module->canUseBridges*/)
  901. {
  902. ImGui::SameLine();
  903. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  904. }
  905. ImGui::EndDisabled();
  906. if (fPluginRunning)
  907. {
  908. ImGui::SameLine();
  909. if (ImGui::Button("Cancel"))
  910. fIdleState = kIdleShowCustomUI;
  911. }
  912. if (ImGui::BeginChild("pluginlistwindow"))
  913. {
  914. if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
  915. {
  916. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  917. switch (fPluginType)
  918. {
  919. case PLUGIN_INTERNAL:
  920. case PLUGIN_AU:
  921. case PLUGIN_SFZ:
  922. // case PLUGIN_JSFX:
  923. ImGui::TableSetupColumn("Name");
  924. ImGui::TableSetupColumn("Label");
  925. ImGui::TableHeadersRow();
  926. break;
  927. case PLUGIN_LV2:
  928. ImGui::TableSetupColumn("Name");
  929. ImGui::TableSetupColumn("URI");
  930. ImGui::TableHeadersRow();
  931. break;
  932. default:
  933. break;
  934. }
  935. for (uint i=0; i<fPluginCount; ++i)
  936. {
  937. const PluginInfoCache& info(fPlugins[i]);
  938. if (search != nullptr && ildaeil::strcasestr(info.name, search) == nullptr)
  939. continue;
  940. bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
  941. switch (fPluginType)
  942. {
  943. case PLUGIN_INTERNAL:
  944. case PLUGIN_AU:
  945. // case PLUGIN_JSFX:
  946. case PLUGIN_SFZ:
  947. ImGui::TableNextRow();
  948. ImGui::TableSetColumnIndex(0);
  949. ImGui::Selectable(info.name, &selected);
  950. ImGui::TableSetColumnIndex(1);
  951. ImGui::Selectable(info.label, &selected);
  952. break;
  953. case PLUGIN_LV2: {
  954. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  955. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  956. ImGui::TableNextRow();
  957. ImGui::TableSetColumnIndex(0);
  958. ImGui::Selectable(info.name, &selected);
  959. ImGui::TableSetColumnIndex(1);
  960. ImGui::Selectable(slash+1, &selected);
  961. break;
  962. }
  963. default:
  964. break;
  965. }
  966. if (selected)
  967. fPluginSelected = i;
  968. }
  969. ImGui::EndTable();
  970. }
  971. ImGui::EndChild();
  972. }
  973. }
  974. ImGui::End();
  975. }
  976. protected:
  977. /* --------------------------------------------------------------------------------------------------------
  978. * DSP/Plugin Callbacks */
  979. void parameterChanged(uint32_t, float) override
  980. {
  981. }
  982. void stateChanged(const char* const key, const char* const) override
  983. {
  984. if (std::strcmp(key, "project") == 0)
  985. hidePluginUI(fPlugin->fCarlaHostHandle);
  986. }
  987. // -------------------------------------------------------------------------------------------------------
  988. private:
  989. /**
  990. Set our UI class as non-copyable and add a leak detector just in case.
  991. */
  992. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  993. };
  994. // --------------------------------------------------------------------------------------------------------------------
  995. void ildaeilProjectLoadedFromDSP(void* const ui)
  996. {
  997. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  998. static_cast<IldaeilUI*>(ui)->projectLoadedFromDSP();
  999. }
  1000. void ildaeilParameterChangeForUI(void* const ui, const uint32_t index, const float value)
  1001. {
  1002. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1003. static_cast<IldaeilUI*>(ui)->changeParameterFromDSP(index, value);
  1004. }
  1005. const char* ildaeilOpenFileForUI(void* const ui, const bool isDir, const char* const title, const char* const filter)
  1006. {
  1007. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  1008. return static_cast<IldaeilUI*>(ui)->openFileFromDSP(isDir, title, filter);
  1009. }
  1010. /* --------------------------------------------------------------------------------------------------------------------
  1011. * UI entry point, called by DPF to create a new UI instance. */
  1012. UI* createUI()
  1013. {
  1014. return new IldaeilUI();
  1015. }
  1016. // --------------------------------------------------------------------------------------------------------------------
  1017. END_NAMESPACE_DISTRHO