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.

725 lines
22KB

  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 "DistrhoUI.hpp"
  19. #include "DistrhoPlugin.hpp"
  20. #include "PluginHostWindow.hpp"
  21. #include "extra/Thread.hpp"
  22. // IDE helper
  23. #include "DearImGui.hpp"
  24. #include <vector>
  25. START_NAMESPACE_DISTRHO
  26. class IldaeilPlugin : public Plugin
  27. {
  28. public:
  29. const NativePluginDescriptor* fCarlaPluginDescriptor;
  30. NativePluginHandle fCarlaPluginHandle;
  31. NativeHostDescriptor fCarlaHostDescriptor;
  32. CarlaHostHandle fCarlaHostHandle;
  33. // ...
  34. };
  35. // -----------------------------------------------------------------------------------------------------------
  36. using namespace CarlaBackend;
  37. // shared resource pointer
  38. // carla_juce_init();
  39. class IldaeilUI : public UI,
  40. public Thread,
  41. public PluginHostWindow::Callbacks
  42. {
  43. static constexpr const uint kInitialWidth = 1220;
  44. static constexpr const uint kInitialHeight = 640;
  45. static constexpr const uint kGenericWidth = 300;
  46. static constexpr const uint kGenericHeight = 400;
  47. static constexpr const uint kExtraHeight = 35;
  48. enum {
  49. kDrawingInit,
  50. kDrawingError,
  51. kDrawingLoading,
  52. kDrawingPluginList,
  53. kDrawingPluginEmbedUI,
  54. kDrawingPluginGenericUI,
  55. kDrawingPluginPendingFromInit
  56. } fDrawingState;
  57. struct PluginInfoCache {
  58. char* name;
  59. char* label;
  60. PluginInfoCache()
  61. : name(nullptr),
  62. label(nullptr) {}
  63. ~PluginInfoCache()
  64. {
  65. std::free(name);
  66. std::free(label);
  67. }
  68. };
  69. struct PluginGenericUI {
  70. char* title;
  71. uint parameterCount;
  72. struct Parameter {
  73. char* name;
  74. char* format;
  75. uint32_t rindex;
  76. bool boolean, bvalue;
  77. float min, max;
  78. Parameter()
  79. : name(nullptr),
  80. format(nullptr),
  81. rindex(0),
  82. boolean(false),
  83. bvalue(false),
  84. min(0.0f),
  85. max(1.0f) {}
  86. ~Parameter()
  87. {
  88. std::free(name);
  89. std::free(format);
  90. }
  91. }* parameters;
  92. float* values;
  93. PluginGenericUI()
  94. : title(nullptr),
  95. parameterCount(0),
  96. parameters(nullptr),
  97. values(nullptr) {}
  98. ~PluginGenericUI()
  99. {
  100. std::free(title);
  101. delete[] parameters;
  102. delete[] values;
  103. }
  104. };
  105. IldaeilPlugin* const fPlugin;
  106. PluginHostWindow fPluginHostWindow;
  107. uint fPluginCount;
  108. uint fPluginSelected;
  109. bool fPluginScanningFinished;
  110. bool fPluginHasCustomUI;
  111. bool fPluginHasEmbedUI;
  112. PluginInfoCache* fPlugins;
  113. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  114. bool fPluginSearchActive;
  115. char fPluginSearchString[0xff];
  116. public:
  117. IldaeilUI()
  118. : UI(kInitialWidth, kInitialHeight),
  119. Thread("IldaeilScanner"),
  120. fDrawingState(kDrawingInit),
  121. fPlugin((IldaeilPlugin*)getPluginInstancePointer()),
  122. fPluginHostWindow(getWindow(), this),
  123. fPluginCount(0),
  124. fPluginSelected(0),
  125. fPluginScanningFinished(false),
  126. fPluginHasCustomUI(false),
  127. fPluginHasEmbedUI(false),
  128. fPlugins(nullptr),
  129. fPluginSearchActive(false)
  130. {
  131. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  132. {
  133. fDrawingState = kDrawingError;
  134. return;
  135. }
  136. std::strcpy(fPluginSearchString, "Search...");
  137. // fPlugin->setUI(this);
  138. const double scaleFactor = getScaleFactor();
  139. if (d_isNotEqual(scaleFactor, 1.0))
  140. {
  141. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  142. fPluginHostWindow.setPositionAndSize(0, kExtraHeight * scaleFactor,
  143. kInitialWidth * scaleFactor, (kInitialHeight - kExtraHeight) * scaleFactor);
  144. }
  145. else
  146. {
  147. fPluginHostWindow.setPositionAndSize(0, kExtraHeight, kInitialWidth, kInitialHeight-kExtraHeight);
  148. }
  149. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  150. char winIdStr[24];
  151. std::snprintf(winIdStr, sizeof(winIdStr), "%lx", (ulong)getWindow().getNativeWindowHandle());
  152. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  153. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, getScaleFactor()*1000, nullptr);
  154. if (carla_get_current_plugin_count(handle) != 0)
  155. {
  156. const uint hints = carla_get_plugin_info(handle, 0)->hints;
  157. fDrawingState = kDrawingPluginPendingFromInit;
  158. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  159. fPluginHasEmbedUI = hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  160. }
  161. }
  162. ~IldaeilUI() override
  163. {
  164. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr)
  165. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  166. if (isThreadRunning())
  167. stopThread(-1);
  168. // fPlugin->fUI = nullptr;
  169. hidePluginUI();
  170. fPluginGenericUI = nullptr;
  171. delete[] fPlugins;
  172. }
  173. void showPluginUI(const CarlaHostHandle handle)
  174. {
  175. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  176. if (info->hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  177. {
  178. fDrawingState = kDrawingPluginEmbedUI;
  179. fPluginHasCustomUI = true;
  180. fPluginHasEmbedUI = true;
  181. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  182. }
  183. else
  184. {
  185. fDrawingState = kDrawingPluginGenericUI;
  186. fPluginHasCustomUI = info->hints & PLUGIN_HAS_CUSTOM_UI;
  187. fPluginHasEmbedUI = false;
  188. if (fPluginGenericUI == nullptr)
  189. createPluginGenericUI(handle, info);
  190. else
  191. updatePluginGenericUI(handle);
  192. const double scaleFactor = getScaleFactor();
  193. setSize(kGenericWidth * scaleFactor, (kGenericHeight + kExtraHeight) * scaleFactor);
  194. }
  195. repaint();
  196. }
  197. void hidePluginUI()
  198. {
  199. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  200. return;
  201. if (fDrawingState == kDrawingPluginGenericUI || fDrawingState == kDrawingPluginEmbedUI)
  202. carla_show_custom_ui(fPlugin->fCarlaHostHandle, 0, false);
  203. fPluginHostWindow.hide();
  204. }
  205. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  206. {
  207. PluginGenericUI* const ui = new PluginGenericUI;
  208. String title(info->name);
  209. title += " by ";
  210. title += info->maker;
  211. ui->title = title.getAndReleaseBuffer();
  212. const uint32_t pcount = ui->parameterCount = carla_get_parameter_count(handle, 0);
  213. // make count of valid parameters
  214. for (uint32_t i=0; i < pcount; ++i)
  215. {
  216. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  217. if (pdata->type != PARAMETER_INPUT ||
  218. (pdata->hints & PARAMETER_IS_ENABLED) == 0x0 ||
  219. (pdata->hints & PARAMETER_IS_READ_ONLY) != 0x0)
  220. {
  221. --ui->parameterCount;
  222. continue;
  223. }
  224. }
  225. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  226. ui->values = new float[ui->parameterCount];
  227. // now safely fill in details
  228. for (uint32_t i=0, j=0; i < pcount; ++i)
  229. {
  230. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  231. if (pdata->type != PARAMETER_INPUT ||
  232. (pdata->hints & PARAMETER_IS_ENABLED) == 0x0 ||
  233. (pdata->hints & PARAMETER_IS_READ_ONLY) != 0x0)
  234. continue;
  235. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, 0, i);
  236. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, 0, i);
  237. String format;
  238. if (pdata->hints & PARAMETER_IS_INTEGER)
  239. format = "%.0f ";
  240. else
  241. format = "%.3f ";
  242. format += pinfo->unit;
  243. PluginGenericUI::Parameter& param(ui->parameters[j]);
  244. param.name = strdup(pinfo->name);
  245. param.format = format.getAndReleaseBuffer();
  246. param.rindex = i;
  247. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  248. param.min = pranges->min;
  249. param.max = pranges->max;
  250. ui->values[j] = carla_get_current_parameter_value(handle, 0, i);
  251. if (param.boolean)
  252. param.bvalue = ui->values[j] > param.min;
  253. else
  254. param.bvalue = false;
  255. ++j;
  256. }
  257. fPluginGenericUI = ui;
  258. }
  259. void updatePluginGenericUI(const CarlaHostHandle handle)
  260. {
  261. PluginGenericUI* const ui = fPluginGenericUI;
  262. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  263. for (uint32_t i=0; i < ui->parameterCount; ++i)
  264. {
  265. ui->values[i] = carla_get_current_parameter_value(handle, 0, ui->parameters[i].rindex);
  266. if (ui->parameters[i].boolean)
  267. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  268. }
  269. }
  270. bool loadPlugin(const CarlaHostHandle handle, const char* const label)
  271. {
  272. if (carla_get_current_plugin_count(handle) != 0)
  273. {
  274. hidePluginUI();
  275. carla_replace_plugin(handle, 0);
  276. }
  277. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_LV2, nullptr, nullptr,
  278. label, 0, 0x0, PLUGIN_OPTIONS_NULL))
  279. {
  280. fPluginGenericUI = nullptr;
  281. showPluginUI(handle);
  282. return true;
  283. }
  284. return false;
  285. }
  286. protected:
  287. void pluginWindowResized(uint width, uint height) override
  288. {
  289. setSize(width, height + kExtraHeight * getScaleFactor());
  290. }
  291. void uiIdle() override
  292. {
  293. switch (fDrawingState)
  294. {
  295. case kDrawingInit:
  296. fDrawingState = kDrawingLoading;
  297. startThread();
  298. repaint();
  299. break;
  300. case kDrawingPluginPendingFromInit:
  301. showPluginUI(fPlugin->fCarlaHostHandle);
  302. startThread();
  303. break;
  304. case kDrawingPluginEmbedUI:
  305. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  306. fPluginHostWindow.idle();
  307. break;
  308. case kDrawingPluginGenericUI:
  309. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  310. break;
  311. default:
  312. break;
  313. }
  314. }
  315. void run() override
  316. {
  317. if (const uint count = carla_get_cached_plugin_count(PLUGIN_LV2, nullptr))
  318. {
  319. fPluginCount = 0;
  320. fPlugins = new PluginInfoCache[count];
  321. if (fDrawingState == kDrawingLoading)
  322. fDrawingState = kDrawingPluginList;
  323. for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
  324. {
  325. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(PLUGIN_LV2, i);
  326. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  327. if (! info->valid)
  328. continue;
  329. #if DISTRHO_PLUGIN_IS_SYNTH
  330. if (info->midiIns != 1 || info->audioOuts != 2)
  331. continue;
  332. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0)
  333. continue;
  334. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  335. if (info->midiIns != 1 || info->midiOuts != 1)
  336. continue;
  337. if (info->audioIns != 0 || info->audioOuts != 0)
  338. continue;
  339. #else
  340. if (info->audioIns != 2 || info->audioOuts != 2)
  341. continue;
  342. #endif
  343. j = fPluginCount;
  344. fPlugins[j].name = strdup(info->name);
  345. fPlugins[j].label = strdup(info->label);
  346. ++fPluginCount;
  347. }
  348. }
  349. if (! shouldThreadExit())
  350. fPluginScanningFinished = true;
  351. }
  352. void onImGuiDisplay() override
  353. {
  354. switch (fDrawingState)
  355. {
  356. case kDrawingInit:
  357. case kDrawingLoading:
  358. case kDrawingPluginPendingFromInit:
  359. drawLoading();
  360. break;
  361. case kDrawingPluginList:
  362. drawPluginList();
  363. break;
  364. case kDrawingError:
  365. // TODO display error message
  366. break;
  367. case kDrawingPluginGenericUI:
  368. drawGenericUI();
  369. // fall-through
  370. case kDrawingPluginEmbedUI:
  371. drawTopBar();
  372. break;
  373. }
  374. }
  375. void drawTopBar()
  376. {
  377. ImGui::SetNextWindowPos(ImVec2(0, 0));
  378. ImGui::SetNextWindowSize(ImVec2(getWidth(), kExtraHeight * getScaleFactor()));
  379. if (ImGui::Begin("Current Plugin", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  380. {
  381. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  382. if (ImGui::Button("Pick Another..."))
  383. {
  384. hidePluginUI();
  385. fDrawingState = kDrawingPluginList;
  386. const double scaleFactor = getScaleFactor();
  387. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  388. }
  389. ImGui::SameLine();
  390. if (ImGui::Button("Reset"))
  391. {
  392. loadPlugin(handle, carla_get_plugin_info(handle, 0)->label);
  393. }
  394. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  395. {
  396. ImGui::SameLine();
  397. if (ImGui::Button("Show Custom GUI"))
  398. {
  399. if (fPluginHasEmbedUI)
  400. {
  401. fDrawingState = kDrawingPluginEmbedUI;
  402. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  403. }
  404. else
  405. {
  406. carla_show_custom_ui(handle, 0, true);
  407. }
  408. }
  409. }
  410. if (fDrawingState == kDrawingPluginEmbedUI)
  411. {
  412. ImGui::SameLine();
  413. if (ImGui::Button("Show Generic GUI"))
  414. {
  415. hidePluginUI();
  416. fDrawingState = kDrawingPluginGenericUI;
  417. if (fPluginGenericUI == nullptr)
  418. createPluginGenericUI(handle, carla_get_plugin_info(handle, 0));
  419. else
  420. updatePluginGenericUI(handle);
  421. const double scaleFactor = getScaleFactor();
  422. setSize(std::max(getWidth(), static_cast<uint>(kGenericWidth * scaleFactor + 0.5)),
  423. (kGenericHeight + kExtraHeight) * scaleFactor);
  424. }
  425. }
  426. }
  427. ImGui::End();
  428. }
  429. void setupMainWindowPos()
  430. {
  431. float y = 0;
  432. float height = getHeight();
  433. if (fDrawingState == kDrawingPluginGenericUI)
  434. {
  435. y = (kExtraHeight - 1) * getScaleFactor();
  436. height -= y;
  437. }
  438. ImGui::SetNextWindowPos(ImVec2(0, y));
  439. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  440. }
  441. void drawGenericUI()
  442. {
  443. setupMainWindowPos();
  444. PluginGenericUI* const ui = fPluginGenericUI;
  445. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  446. if (ImGui::Begin(ui->title, nullptr, ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoCollapse))
  447. {
  448. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  449. for (uint32_t i=0; i < ui->parameterCount; ++i)
  450. {
  451. PluginGenericUI::Parameter& param(ui->parameters[i]);
  452. if (param.boolean)
  453. {
  454. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  455. {
  456. if (ImGui::IsItemActivated())
  457. {
  458. carla_set_parameter_touch(handle, 0, param.rindex, true);
  459. // editParameter(0, true);
  460. }
  461. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  462. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  463. // setParameterValue(0, ui->values[i]);
  464. }
  465. }
  466. else
  467. {
  468. if (ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.format))
  469. {
  470. if (ImGui::IsItemActivated())
  471. {
  472. carla_set_parameter_touch(handle, 0, param.rindex, true);
  473. // editParameter(0, true);
  474. }
  475. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  476. // setParameterValue(0, ui->values[i]);
  477. }
  478. }
  479. if (ImGui::IsItemDeactivated())
  480. {
  481. carla_set_parameter_touch(handle, 0, param.rindex, false);
  482. // editParameter(0, false);
  483. }
  484. }
  485. }
  486. ImGui::End();
  487. }
  488. void drawLoading()
  489. {
  490. setupMainWindowPos();
  491. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  492. {
  493. ImGui::TextUnformatted("Loading...", nullptr);
  494. }
  495. ImGui::End();
  496. }
  497. void drawPluginList()
  498. {
  499. setupMainWindowPos();
  500. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  501. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  502. {
  503. if (ImGui::InputText("", fPluginSearchString, sizeof(fPluginSearchString)-1, ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  504. fPluginSearchActive = true;
  505. ImGui::BeginDisabled(!fPluginScanningFinished);
  506. if (ImGui::Button("Load Plugin"))
  507. {
  508. do {
  509. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  510. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  511. DISTRHO_SAFE_ASSERT_BREAK(slash != nullptr);
  512. d_stdout("Loading %s...", info.name);
  513. if (loadPlugin(handle, slash+1))
  514. {
  515. ImGui::EndDisabled();
  516. ImGui::End();
  517. return;
  518. }
  519. } while (false);
  520. }
  521. if (carla_get_current_plugin_count(handle) != 0)
  522. {
  523. ImGui::SameLine();
  524. if (ImGui::Button("Cancel"))
  525. {
  526. showPluginUI(handle);
  527. }
  528. }
  529. ImGui::EndDisabled();
  530. if (ImGui::BeginChild("pluginlistwindow"))
  531. {
  532. if (ImGui::BeginTable("pluginlist", 3, ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_NoClip))
  533. {
  534. ImGui::TableSetupColumn("Name");
  535. ImGui::TableSetupColumn("Bundle");
  536. ImGui::TableSetupColumn("URI");
  537. ImGui::TableHeadersRow();
  538. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  539. for (uint i=0; i<fPluginCount; ++i)
  540. {
  541. const PluginInfoCache& info(fPlugins[i]);
  542. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  543. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  544. if (search != nullptr && strcasestr(info.name, search) == nullptr)
  545. continue;
  546. bool selected = fPluginSelected == i;
  547. ImGui::TableNextRow();
  548. ImGui::TableSetColumnIndex(0);
  549. ImGui::Selectable(info.name, &selected);
  550. ImGui::TableSetColumnIndex(1);
  551. ImGui::Selectable(slash+1, &selected);
  552. ImGui::TableSetColumnIndex(2);
  553. ImGui::TextUnformatted(info.label, slash);
  554. if (selected)
  555. fPluginSelected = i;
  556. }
  557. ImGui::EndTable();
  558. }
  559. ImGui::EndChild();
  560. }
  561. }
  562. ImGui::End();
  563. }
  564. protected:
  565. /* --------------------------------------------------------------------------------------------------------
  566. * DSP/Plugin Callbacks */
  567. void parameterChanged(uint32_t, float) override
  568. {
  569. }
  570. void stateChanged(const char* const key, const char* const) override
  571. {
  572. if (std::strcmp(key, "project") == 0)
  573. hidePluginUI();
  574. }
  575. // -------------------------------------------------------------------------------------------------------
  576. private:
  577. /**
  578. Set our UI class as non-copyable and add a leak detector just in case.
  579. */
  580. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  581. };
  582. /* ------------------------------------------------------------------------------------------------------------
  583. * UI entry point, called by DPF to create a new UI instance. */
  584. UI* createUI()
  585. {
  586. return new IldaeilUI();
  587. }
  588. // -----------------------------------------------------------------------------------------------------------
  589. END_NAMESPACE_DISTRHO