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.

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