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.

686 lines
21KB

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