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.

704 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. protected:
  267. void pluginWindowResized(uint width, uint height) override
  268. {
  269. setSize(width, height + kExtraHeight * getScaleFactor());
  270. }
  271. void uiIdle() override
  272. {
  273. switch (fDrawingState)
  274. {
  275. case kDrawingInit:
  276. fDrawingState = kDrawingLoading;
  277. startThread();
  278. repaint();
  279. break;
  280. case kDrawingPluginPendingFromInit:
  281. showPluginUI(fPlugin->fCarlaHostHandle);
  282. startThread();
  283. break;
  284. case kDrawingPluginEmbedUI:
  285. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  286. fPluginHostWindow.idle();
  287. break;
  288. case kDrawingPluginGenericUI:
  289. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  290. break;
  291. default:
  292. break;
  293. }
  294. }
  295. void run() override
  296. {
  297. if (const uint count = carla_get_cached_plugin_count(PLUGIN_LV2, nullptr))
  298. {
  299. fPluginCount = 0;
  300. fPlugins = new PluginInfoCache[count];
  301. if (fDrawingState == kDrawingLoading)
  302. fDrawingState = kDrawingPluginList;
  303. for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
  304. {
  305. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(PLUGIN_LV2, i);
  306. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  307. if (! info->valid)
  308. continue;
  309. #if DISTRHO_PLUGIN_IS_SYNTH
  310. if (info->midiIns != 1 || info->audioOuts != 2)
  311. continue;
  312. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0)
  313. continue;
  314. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  315. if (info->midiIns != 1 || info->midiOuts != 1)
  316. continue;
  317. if (info->audioIns != 0 || info->audioOuts != 0)
  318. continue;
  319. #else
  320. if (info->audioIns != 2 || info->audioOuts != 2)
  321. continue;
  322. #endif
  323. j = fPluginCount;
  324. fPlugins[j].name = strdup(info->name);
  325. fPlugins[j].label = strdup(info->label);
  326. ++fPluginCount;
  327. }
  328. }
  329. if (! shouldThreadExit())
  330. fPluginScanningFinished = true;
  331. }
  332. void onImGuiDisplay() override
  333. {
  334. switch (fDrawingState)
  335. {
  336. case kDrawingInit:
  337. case kDrawingLoading:
  338. case kDrawingPluginPendingFromInit:
  339. drawLoading();
  340. break;
  341. case kDrawingPluginList:
  342. drawPluginList();
  343. break;
  344. case kDrawingError:
  345. // TODO display error message
  346. break;
  347. case kDrawingPluginGenericUI:
  348. drawGenericUI();
  349. // fall-through
  350. case kDrawingPluginEmbedUI:
  351. drawTopBar();
  352. break;
  353. }
  354. }
  355. void drawTopBar()
  356. {
  357. ImGui::SetNextWindowPos(ImVec2(0, 0));
  358. ImGui::SetNextWindowSize(ImVec2(getWidth(), kExtraHeight * getScaleFactor()));
  359. if (ImGui::Begin("Current Plugin", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  360. {
  361. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  362. if (ImGui::Button("Pick Another..."))
  363. {
  364. hidePluginUI();
  365. fDrawingState = kDrawingPluginList;
  366. const double scaleFactor = getScaleFactor();
  367. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  368. }
  369. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  370. {
  371. ImGui::SameLine();
  372. if (ImGui::Button("Show Custom GUI"))
  373. {
  374. if (fPluginHasEmbedUI)
  375. {
  376. fDrawingState = kDrawingPluginEmbedUI;
  377. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  378. }
  379. else
  380. {
  381. carla_show_custom_ui(handle, 0, true);
  382. }
  383. }
  384. }
  385. if (fDrawingState == kDrawingPluginEmbedUI)
  386. {
  387. ImGui::SameLine();
  388. if (ImGui::Button("Show Generic GUI"))
  389. {
  390. hidePluginUI();
  391. fDrawingState = kDrawingPluginGenericUI;
  392. if (fPluginGenericUI == nullptr)
  393. createPluginGenericUI(handle, carla_get_plugin_info(handle, 0));
  394. else
  395. updatePluginGenericUI(handle);
  396. const double scaleFactor = getScaleFactor();
  397. setSize(std::max(getWidth(), static_cast<uint>(kGenericWidth * scaleFactor + 0.5)),
  398. (kGenericHeight + kExtraHeight) * scaleFactor);
  399. }
  400. }
  401. }
  402. ImGui::End();
  403. }
  404. void setupMainWindowPos()
  405. {
  406. float y = 0;
  407. float height = getHeight();
  408. if (fDrawingState == kDrawingPluginGenericUI)
  409. {
  410. y = (kExtraHeight - 1) * getScaleFactor();
  411. height -= y;
  412. }
  413. ImGui::SetNextWindowPos(ImVec2(0, y));
  414. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  415. }
  416. void drawGenericUI()
  417. {
  418. setupMainWindowPos();
  419. PluginGenericUI* const ui = fPluginGenericUI;
  420. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  421. if (ImGui::Begin(ui->title, nullptr, ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoCollapse))
  422. {
  423. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  424. for (uint32_t i=0; i < ui->parameterCount; ++i)
  425. {
  426. PluginGenericUI::Parameter& param(ui->parameters[i]);
  427. if (param.boolean)
  428. {
  429. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  430. {
  431. if (ImGui::IsItemActivated())
  432. {
  433. carla_set_parameter_touch(handle, 0, param.rindex, true);
  434. // editParameter(0, true);
  435. }
  436. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  437. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  438. // setParameterValue(0, ui->values[i]);
  439. }
  440. }
  441. else
  442. {
  443. if (ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.format))
  444. {
  445. if (ImGui::IsItemActivated())
  446. {
  447. carla_set_parameter_touch(handle, 0, param.rindex, true);
  448. // editParameter(0, true);
  449. }
  450. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  451. // setParameterValue(0, ui->values[i]);
  452. }
  453. }
  454. if (ImGui::IsItemDeactivated())
  455. {
  456. carla_set_parameter_touch(handle, 0, param.rindex, false);
  457. // editParameter(0, false);
  458. }
  459. }
  460. }
  461. ImGui::End();
  462. }
  463. void drawLoading()
  464. {
  465. setupMainWindowPos();
  466. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  467. {
  468. ImGui::TextUnformatted("Loading...", nullptr);
  469. }
  470. ImGui::End();
  471. }
  472. void drawPluginList()
  473. {
  474. setupMainWindowPos();
  475. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  476. const bool pluginIsRunning = carla_get_current_plugin_count(handle) != 0;
  477. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  478. {
  479. if (ImGui::InputText("", fPluginSearchString, sizeof(fPluginSearchString)-1, ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  480. fPluginSearchActive = true;
  481. ImGui::BeginDisabled(!fPluginScanningFinished);
  482. if (ImGui::Button("Load Plugin"))
  483. {
  484. if (pluginIsRunning)
  485. {
  486. hidePluginUI();
  487. carla_replace_plugin(handle, 0);
  488. }
  489. do {
  490. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  491. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  492. DISTRHO_SAFE_ASSERT_BREAK(slash != nullptr);
  493. d_stdout("Loading %s...", info.name);
  494. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_LV2, nullptr, nullptr,
  495. slash+1, 0, 0x0, PLUGIN_OPTIONS_NULL))
  496. {
  497. fPluginGenericUI = nullptr;
  498. showPluginUI(handle);
  499. ImGui::EndDisabled();
  500. ImGui::End();
  501. return;
  502. }
  503. } while (false);
  504. }
  505. ImGui::EndDisabled();
  506. if (pluginIsRunning)
  507. {
  508. ImGui::SameLine();
  509. if (ImGui::Button("Cancel"))
  510. {
  511. showPluginUI(handle);
  512. }
  513. }
  514. if (ImGui::BeginChild("pluginlistwindow"))
  515. {
  516. if (ImGui::BeginTable("pluginlist", 3, ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_NoClip))
  517. {
  518. ImGui::TableSetupColumn("Name");
  519. ImGui::TableSetupColumn("Bundle");
  520. ImGui::TableSetupColumn("URI");
  521. ImGui::TableHeadersRow();
  522. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  523. for (uint i=0; i<fPluginCount; ++i)
  524. {
  525. const PluginInfoCache& info(fPlugins[i]);
  526. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  527. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  528. if (search != nullptr && strcasestr(info.name, search) == nullptr)
  529. continue;
  530. bool selected = fPluginSelected == i;
  531. ImGui::TableNextRow();
  532. ImGui::TableSetColumnIndex(0);
  533. ImGui::Selectable(info.name, &selected);
  534. ImGui::TableSetColumnIndex(1);
  535. ImGui::Selectable(slash+1, &selected);
  536. ImGui::TableSetColumnIndex(2);
  537. ImGui::TextUnformatted(info.label, slash);
  538. if (selected)
  539. fPluginSelected = i;
  540. }
  541. ImGui::EndTable();
  542. }
  543. ImGui::EndChild();
  544. }
  545. }
  546. ImGui::End();
  547. }
  548. protected:
  549. /* --------------------------------------------------------------------------------------------------------
  550. * DSP/Plugin Callbacks */
  551. void parameterChanged(uint32_t, float) override
  552. {
  553. }
  554. void stateChanged(const char* const key, const char* const) override
  555. {
  556. if (std::strcmp(key, "project") == 0)
  557. hidePluginUI();
  558. }
  559. // -------------------------------------------------------------------------------------------------------
  560. private:
  561. /**
  562. Set our UI class as non-copyable and add a leak detector just in case.
  563. */
  564. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  565. };
  566. /* ------------------------------------------------------------------------------------------------------------
  567. * UI entry point, called by DPF to create a new UI instance. */
  568. UI* createUI()
  569. {
  570. return new IldaeilUI();
  571. }
  572. // -----------------------------------------------------------------------------------------------------------
  573. END_NAMESPACE_DISTRHO