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.

1146 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. FileBrowserOptions opts;
  250. opts.title = title;
  251. 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. if (fPluginRunning)
  450. hidePluginUI(handle);
  451. fPluginSelected = -1;
  452. if (isThreadRunning())
  453. stopThread(-1);
  454. fPluginType = fNextPluginType;
  455. startThread();
  456. break;
  457. case kIdleNothing:
  458. break;
  459. }
  460. }
  461. void loadSelectedPlugin(const CarlaHostHandle handle)
  462. {
  463. DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
  464. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  465. const char* label = nullptr;
  466. switch (fPluginType)
  467. {
  468. case PLUGIN_INTERNAL:
  469. case PLUGIN_AU:
  470. // case PLUGIN_JSFX:
  471. case PLUGIN_SFZ:
  472. label = info.label;
  473. break;
  474. case PLUGIN_LV2: {
  475. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  476. DISTRHO_SAFE_ASSERT_RETURN(slash != nullptr,);
  477. label = slash+1;
  478. break;
  479. }
  480. default:
  481. break;
  482. }
  483. DISTRHO_SAFE_ASSERT_RETURN(label != nullptr,);
  484. d_stdout("Loading %s...", info.name);
  485. loadPlugin(handle, label);
  486. }
  487. void uiFileBrowserSelected(const char* const filename) override
  488. {
  489. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr && filename != nullptr)
  490. carla_set_custom_data(fPlugin->fCarlaHostHandle, 0, CUSTOM_DATA_TYPE_STRING, "file", filename);
  491. }
  492. void run() override
  493. {
  494. const char* path;
  495. switch (fPluginType)
  496. {
  497. case PLUGIN_LV2:
  498. path = std::getenv("LV2_PATH");
  499. break;
  500. default:
  501. path = nullptr;
  502. break;
  503. }
  504. if (path != nullptr)
  505. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_PLUGIN_PATH, fPluginType, path);
  506. fPluginCount = 0;
  507. delete[] fPlugins;
  508. uint count;
  509. {
  510. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  511. d_stdout("Will scan plugins now...");
  512. count = carla_get_cached_plugin_count(fPluginType, path);
  513. d_stdout("Scanning found %u plugins", count);
  514. }
  515. if (fDrawingState == kDrawingLoading)
  516. {
  517. fDrawingState = kDrawingPluginList;
  518. fPluginSearchFirstShow = true;
  519. }
  520. if (count != 0)
  521. {
  522. fPlugins = new PluginInfoCache[count];
  523. for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
  524. {
  525. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  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 (info->cvIns != 0 || info->cvOuts != 0)
  531. continue;
  532. #if DISTRHO_PLUGIN_IS_SYNTH
  533. if (info->midiIns != 1 && info->audioIns != 0)
  534. continue;
  535. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0 && info->audioIns != 0)
  536. continue;
  537. if (info->audioOuts != 1 && info->audioOuts != 2)
  538. continue;
  539. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  540. if ((info->midiIns != 1 && info->audioIns != 0 && info->audioOuts != 0) || info->midiOuts != 1)
  541. continue;
  542. if (info->audioIns != 0 || info->audioOuts != 0)
  543. continue;
  544. #else
  545. if (info->audioIns != 1 && info->audioIns != 2)
  546. continue;
  547. if (info->audioOuts != 1 && info->audioOuts != 2)
  548. continue;
  549. #endif
  550. if (fPluginType == PLUGIN_INTERNAL)
  551. {
  552. if (std::strcmp(info->label, "audiogain_s") == 0)
  553. continue;
  554. if (std::strcmp(info->label, "cv2audio") == 0)
  555. continue;
  556. if (std::strcmp(info->label, "lfo") == 0)
  557. continue;
  558. if (std::strcmp(info->label, "midi2cv") == 0)
  559. continue;
  560. if (std::strcmp(info->label, "midithrough") == 0)
  561. continue;
  562. if (std::strcmp(info->label, "3bandsplitter") == 0)
  563. continue;
  564. }
  565. j = fPluginCount;
  566. fPlugins[j].name = strdup(info->name);
  567. fPlugins[j].label = strdup(info->label);
  568. ++fPluginCount;
  569. }
  570. }
  571. else
  572. {
  573. fPlugins = nullptr;
  574. }
  575. if (! shouldThreadExit())
  576. fPluginScanningFinished = true;
  577. }
  578. void onImGuiDisplay() override
  579. {
  580. switch (fDrawingState)
  581. {
  582. case kDrawingLoading:
  583. drawLoading();
  584. break;
  585. case kDrawingPluginError:
  586. ImGui::OpenPopup("Plugin Error");
  587. // call ourselves again with the plugin list
  588. fDrawingState = kDrawingPluginList;
  589. onImGuiDisplay();
  590. break;
  591. case kDrawingPluginList:
  592. drawPluginList();
  593. break;
  594. case kDrawingPluginGenericUI:
  595. drawTopBar();
  596. drawGenericUI();
  597. break;
  598. case kDrawingPluginEmbedUI:
  599. drawTopBar();
  600. break;
  601. case kDrawingErrorInit:
  602. fDrawingState = kDrawingErrorDraw;
  603. drawError(true);
  604. break;
  605. case kDrawingErrorDraw:
  606. drawError(false);
  607. break;
  608. }
  609. }
  610. void drawError(const bool open)
  611. {
  612. ImGui::SetNextWindowPos(ImVec2(0, 0));
  613. ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight()));
  614. const int flags = ImGuiWindowFlags_NoSavedSettings
  615. | ImGuiWindowFlags_NoTitleBar
  616. | ImGuiWindowFlags_NoResize
  617. | ImGuiWindowFlags_NoCollapse
  618. | ImGuiWindowFlags_NoScrollbar
  619. | ImGuiWindowFlags_NoScrollWithMouse;
  620. if (ImGui::Begin("Error Window", nullptr, flags))
  621. {
  622. if (open)
  623. ImGui::OpenPopup("Engine Error");
  624. const int pflags = ImGuiWindowFlags_NoSavedSettings
  625. | ImGuiWindowFlags_NoResize
  626. | ImGuiWindowFlags_NoCollapse
  627. | ImGuiWindowFlags_NoScrollbar
  628. | ImGuiWindowFlags_NoScrollWithMouse
  629. | ImGuiWindowFlags_AlwaysAutoResize
  630. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  631. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  632. {
  633. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  634. ImGui::EndPopup();
  635. }
  636. }
  637. ImGui::End();
  638. }
  639. void drawTopBar()
  640. {
  641. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  642. ImGui::SetNextWindowPos(ImVec2(0, 0));
  643. ImGui::SetNextWindowSize(ImVec2(getWidth(), kButtonHeight * getScaleFactor() + padding));
  644. const int flags = ImGuiWindowFlags_NoSavedSettings
  645. | ImGuiWindowFlags_NoTitleBar
  646. | ImGuiWindowFlags_NoResize
  647. | ImGuiWindowFlags_NoCollapse
  648. | ImGuiWindowFlags_NoScrollbar
  649. | ImGuiWindowFlags_NoScrollWithMouse;
  650. if (ImGui::Begin("Current Plugin", nullptr, flags))
  651. {
  652. if (ImGui::Button("Pick Another..."))
  653. {
  654. fIdleState = kIdleHidePluginUI;
  655. fDrawingState = kDrawingPluginList;
  656. const double scaleFactor = getScaleFactor();
  657. fNextSize = Size<uint>(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  658. }
  659. ImGui::SameLine();
  660. if (ImGui::Button("Reset"))
  661. fIdleState = kIdleResetPlugin;
  662. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  663. {
  664. ImGui::SameLine();
  665. if (ImGui::Button("Show Custom GUI"))
  666. fIdleState = kIdleShowCustomUI;
  667. }
  668. if (fDrawingState == kDrawingPluginEmbedUI)
  669. {
  670. ImGui::SameLine();
  671. if (ImGui::Button("Show Generic GUI"))
  672. fIdleState = kIdleHideEmbedAndShowGenericUI;
  673. }
  674. }
  675. ImGui::End();
  676. }
  677. void setupMainWindowPos()
  678. {
  679. const float scaleFactor = getScaleFactor();
  680. float y = 0;
  681. float height = getHeight();
  682. if (fDrawingState == kDrawingPluginGenericUI)
  683. {
  684. y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
  685. height -= y;
  686. }
  687. ImGui::SetNextWindowPos(ImVec2(0, y));
  688. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  689. }
  690. void drawGenericUI()
  691. {
  692. setupMainWindowPos();
  693. PluginGenericUI* const ui = fPluginGenericUI;
  694. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  695. const int pflags = ImGuiWindowFlags_NoSavedSettings
  696. | ImGuiWindowFlags_NoResize
  697. | ImGuiWindowFlags_NoCollapse
  698. | ImGuiWindowFlags_AlwaysAutoResize;
  699. if (ImGui::Begin(ui->title, nullptr, pflags))
  700. {
  701. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  702. for (uint32_t i=0; i < ui->parameterCount; ++i)
  703. {
  704. PluginGenericUI::Parameter& param(ui->parameters[i]);
  705. if (param.readonly)
  706. {
  707. ImGui::BeginDisabled();
  708. ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_NoInput);
  709. ImGui::EndDisabled();
  710. continue;
  711. }
  712. if (param.boolean)
  713. {
  714. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  715. {
  716. if (ImGui::IsItemActivated())
  717. {
  718. carla_set_parameter_touch(handle, 0, param.rindex, true);
  719. // editParameter(0, true);
  720. }
  721. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  722. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  723. // setParameterValue(0, ui->values[i]);
  724. }
  725. }
  726. else
  727. {
  728. const bool ret = param.log
  729. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, 2.0f)
  730. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
  731. if (ret)
  732. {
  733. if (ImGui::IsItemActivated())
  734. {
  735. carla_set_parameter_touch(handle, 0, param.rindex, true);
  736. // editParameter(0, true);
  737. }
  738. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  739. // setParameterValue(0, ui->values[i]);
  740. }
  741. }
  742. if (ImGui::IsItemDeactivated())
  743. {
  744. carla_set_parameter_touch(handle, 0, param.rindex, false);
  745. // editParameter(0, false);
  746. }
  747. }
  748. }
  749. ImGui::End();
  750. }
  751. void drawLoading()
  752. {
  753. setupMainWindowPos();
  754. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  755. ImGui::TextUnformatted("Loading...", nullptr);
  756. ImGui::End();
  757. }
  758. void drawPluginList()
  759. {
  760. static const char* pluginTypes[] = {
  761. getPluginTypeAsString(PLUGIN_INTERNAL),
  762. getPluginTypeAsString(PLUGIN_LV2),
  763. };
  764. setupMainWindowPos();
  765. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  766. {
  767. const int pflags = ImGuiWindowFlags_NoSavedSettings
  768. | ImGuiWindowFlags_NoResize
  769. | ImGuiWindowFlags_NoCollapse
  770. | ImGuiWindowFlags_NoScrollbar
  771. | ImGuiWindowFlags_NoScrollWithMouse
  772. | ImGuiWindowFlags_AlwaysAutoResize;
  773. if (ImGui::BeginPopupModal("Plugin Error", nullptr, pflags))
  774. {
  775. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  776. ImGui::Separator();
  777. if (ImGui::Button("Ok"))
  778. ImGui::CloseCurrentPopup();
  779. ImGui::SameLine();
  780. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  781. ImGui::EndPopup();
  782. }
  783. else if (fPluginSearchFirstShow)
  784. {
  785. fPluginSearchFirstShow = false;
  786. ImGui::SetKeyboardFocusHere();
  787. }
  788. if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
  789. ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  790. fPluginSearchActive = true;
  791. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  792. fPluginSearchActive = false;
  793. ImGui::SameLine();
  794. ImGui::PushItemWidth(-1.0f);
  795. int current;
  796. switch (fPluginType)
  797. {
  798. case PLUGIN_LV2:
  799. current = 1;
  800. break;
  801. default:
  802. current = 0;
  803. break;
  804. }
  805. if (ImGui::Combo("##plugintypes", &current, pluginTypes, ARRAY_SIZE(pluginTypes)))
  806. {
  807. fIdleState = kIdleChangePluginType;
  808. switch (current)
  809. {
  810. case 0:
  811. fNextPluginType = PLUGIN_INTERNAL;
  812. break;
  813. case 1:
  814. fNextPluginType = PLUGIN_LV2;
  815. break;
  816. }
  817. }
  818. ImGui::BeginDisabled(!fPluginScanningFinished || fPluginSelected < 0);
  819. if (ImGui::Button("Load Plugin"))
  820. fIdleState = kIdleLoadSelectedPlugin;
  821. // xx cardinal
  822. if (fPluginType != PLUGIN_INTERNAL /*&& module->canUseBridges*/)
  823. {
  824. ImGui::SameLine();
  825. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  826. }
  827. ImGui::EndDisabled();
  828. if (fPluginRunning)
  829. {
  830. ImGui::SameLine();
  831. if (ImGui::Button("Cancel"))
  832. fIdleState = kIdleShowCustomUI;
  833. }
  834. if (ImGui::BeginChild("pluginlistwindow"))
  835. {
  836. if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
  837. {
  838. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  839. switch (fPluginType)
  840. {
  841. case PLUGIN_INTERNAL:
  842. case PLUGIN_AU:
  843. case PLUGIN_SFZ:
  844. // case PLUGIN_JSFX:
  845. ImGui::TableSetupColumn("Name");
  846. ImGui::TableSetupColumn("Label");
  847. ImGui::TableHeadersRow();
  848. break;
  849. case PLUGIN_LV2:
  850. ImGui::TableSetupColumn("Name");
  851. ImGui::TableSetupColumn("URI");
  852. ImGui::TableHeadersRow();
  853. break;
  854. default:
  855. break;
  856. }
  857. for (uint i=0; i<fPluginCount; ++i)
  858. {
  859. const PluginInfoCache& info(fPlugins[i]);
  860. if (search != nullptr && ildaeil::strcasestr(info.name, search) == nullptr)
  861. continue;
  862. bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
  863. switch (fPluginType)
  864. {
  865. case PLUGIN_INTERNAL:
  866. case PLUGIN_AU:
  867. // case PLUGIN_JSFX:
  868. case PLUGIN_SFZ:
  869. ImGui::TableNextRow();
  870. ImGui::TableSetColumnIndex(0);
  871. ImGui::Selectable(info.name, &selected);
  872. ImGui::TableSetColumnIndex(1);
  873. ImGui::Selectable(info.label, &selected);
  874. break;
  875. case PLUGIN_LV2: {
  876. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  877. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  878. ImGui::TableNextRow();
  879. ImGui::TableSetColumnIndex(0);
  880. ImGui::Selectable(info.name, &selected);
  881. ImGui::TableSetColumnIndex(1);
  882. ImGui::Selectable(slash+1, &selected);
  883. break;
  884. }
  885. default:
  886. break;
  887. }
  888. if (selected)
  889. fPluginSelected = i;
  890. }
  891. ImGui::EndTable();
  892. }
  893. ImGui::EndChild();
  894. }
  895. }
  896. ImGui::End();
  897. }
  898. protected:
  899. /* --------------------------------------------------------------------------------------------------------
  900. * DSP/Plugin Callbacks */
  901. void parameterChanged(uint32_t, float) override
  902. {
  903. }
  904. void stateChanged(const char* const key, const char* const) override
  905. {
  906. if (std::strcmp(key, "project") == 0)
  907. hidePluginUI(fPlugin->fCarlaHostHandle);
  908. }
  909. // -------------------------------------------------------------------------------------------------------
  910. private:
  911. /**
  912. Set our UI class as non-copyable and add a leak detector just in case.
  913. */
  914. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  915. };
  916. // --------------------------------------------------------------------------------------------------------------------
  917. void ildaeilProjectLoadedFromDSP(void* const ui)
  918. {
  919. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  920. static_cast<IldaeilUI*>(ui)->projectLoadedFromDSP();
  921. }
  922. void ildaeilParameterChangeForUI(void* const ui, const uint32_t index, const float value)
  923. {
  924. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  925. static_cast<IldaeilUI*>(ui)->changeParameterFromDSP(index, value);
  926. }
  927. const char* ildaeilOpenFileForUI(void* const ui, const bool isDir, const char* const title, const char* const filter)
  928. {
  929. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  930. return static_cast<IldaeilUI*>(ui)->openFileFromDSP(isDir, title, filter);
  931. }
  932. /* --------------------------------------------------------------------------------------------------------------------
  933. * UI entry point, called by DPF to create a new UI instance. */
  934. UI* createUI()
  935. {
  936. return new IldaeilUI();
  937. }
  938. // --------------------------------------------------------------------------------------------------------------------
  939. END_NAMESPACE_DISTRHO