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.

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