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.

1152 lines
36KB

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