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.

1138 lines
36KB

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