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.

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