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.

1008 lines
33KB

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