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.

1296 lines
41KB

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