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.

1879 lines
63KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021-2025 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 "IldaeilBasePlugin.hpp"
  18. #include "DistrhoPluginUtils.hpp"
  19. #include "DistrhoUI.hpp"
  20. #if ILDAEIL_STANDALONE
  21. #include "DistrhoStandaloneUtils.hpp"
  22. #endif
  23. #include "CarlaBackendUtils.hpp"
  24. #include "PluginHostWindow.hpp"
  25. #include "extra/Runner.hpp"
  26. // IDE helper
  27. #include "DearImGui.hpp"
  28. #include "water/files/File.h"
  29. #include "water/files/FileInputStream.h"
  30. #include "water/files/FileOutputStream.h"
  31. #include "water/memory/MemoryBlock.h"
  32. #include <string>
  33. #include <vector>
  34. // strcasestr
  35. #ifdef DISTRHO_OS_WINDOWS
  36. # include <shlwapi.h>
  37. namespace ildaeil {
  38. inline const char* strcasestr(const char* const haystack, const char* const needle)
  39. {
  40. return StrStrIA(haystack, needle);
  41. }
  42. // using strcasestr = StrStrIA;
  43. }
  44. #else
  45. namespace ildaeil {
  46. using ::strcasestr;
  47. }
  48. #endif
  49. // #define WASM_TESTING
  50. START_NAMESPACE_DISTRHO
  51. using namespace CARLA_BACKEND_NAMESPACE;
  52. // --------------------------------------------------------------------------------------------------------------------
  53. class IldaeilUI : public UI,
  54. public Runner,
  55. public PluginHostWindow::Callbacks
  56. {
  57. static constexpr const uint kGenericWidth = 380;
  58. static constexpr const uint kGenericHeight = 400;
  59. static constexpr const uint kButtonHeight = 20;
  60. struct PluginInfoCache {
  61. BinaryType btype;
  62. uint64_t uniqueId;
  63. std::string filename;
  64. std::string name;
  65. std::string label;
  66. };
  67. struct PluginGenericUI {
  68. char* title = nullptr;
  69. uint parameterCount = 0;
  70. struct Parameter {
  71. char* name = nullptr;
  72. char* group = nullptr;
  73. char* printformat = nullptr;
  74. uint32_t rindex = 0;
  75. bool boolean = false;
  76. bool bvalue = false;
  77. bool log = false;
  78. bool readonly = false;
  79. float min = 0.f;
  80. float max = 1.f;
  81. ~Parameter()
  82. {
  83. std::free(name);
  84. std::free(group);
  85. std::free(printformat);
  86. }
  87. }* parameters = nullptr;
  88. float* values = nullptr;
  89. uint presetCount = 0;
  90. struct Preset {
  91. uint32_t index = 0;
  92. char* name = nullptr;
  93. ~Preset()
  94. {
  95. std::free(name);
  96. }
  97. }* presets = nullptr;
  98. int currentPreset = -1;
  99. const char** presetStrings = nullptr;
  100. ~PluginGenericUI()
  101. {
  102. std::free(title);
  103. delete[] parameters;
  104. delete[] values;
  105. delete[] presets;
  106. delete[] presetStrings;
  107. }
  108. };
  109. enum {
  110. kDrawingLoading,
  111. kDrawingPluginError,
  112. kDrawingPluginList,
  113. kDrawingPluginEmbedUI,
  114. kDrawingPluginGenericUI,
  115. kDrawingErrorInit,
  116. kDrawingErrorDraw
  117. } fDrawingState = kDrawingLoading;
  118. enum {
  119. kIdleInit,
  120. kIdleInitPluginAlreadyLoaded,
  121. kIdleLoadSelectedPlugin,
  122. kIdlePluginLoadedFromDSP,
  123. kIdleResetPlugin,
  124. kIdleOpenFileUI,
  125. kIdleShowCustomUI,
  126. kIdleHideEmbedAndShowGenericUI,
  127. kIdleHidePluginUI,
  128. kIdleGiveIdleToUI,
  129. kIdleChangePluginType,
  130. kIdleNothing
  131. } fIdleState = kIdleInit;
  132. IldaeilBasePlugin* const fPlugin = static_cast<IldaeilBasePlugin*>(getPluginInstancePointer());
  133. void* const fNativeWindowHandle = reinterpret_cast<void*>(getWindow().getNativeWindowHandle());
  134. PluginHostWindow fPluginHostWindow { fNativeWindowHandle, this };
  135. BinaryType fBinaryType = BINARY_NATIVE;
  136. PluginType fPluginType = PLUGIN_LV2;
  137. PluginType fNextPluginType = fPluginType;
  138. uint fPluginId = 0;
  139. int fPluginSelected = -1;
  140. bool fPluginHasCustomUI = false;
  141. bool fPluginHasEmbedUI = false;
  142. bool fPluginHasResizableUI = false;
  143. bool fPluginHasFileOpen = false;
  144. bool fPluginHasOutputParameters = false;
  145. bool fPluginIsBridge = false;
  146. bool fPluginIsIdling = false;
  147. bool fPluginRunning = false;
  148. bool fPluginWillRunInBridgeMode = false;
  149. Mutex fPluginsMutex;
  150. PluginInfoCache fCurrentPluginInfo{};
  151. std::vector<PluginInfoCache> fPlugins;
  152. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  153. bool fPluginSearchActive = false;
  154. bool fPluginSearchFirstShow = false;
  155. char fPluginSearchString[0xff] = {};
  156. String fPopupError, fPluginFilename, fDiscoveryTool;
  157. Size<uint> fCurrentConstraintSize, fLastSize, fNextSize;
  158. bool fIgnoreNextHostWindowResize = false;
  159. bool fInitialHostWindowShow = false;
  160. bool fShowingHostWindow = false;
  161. bool fUpdateGeometryConstraints = false;
  162. struct RunnerData {
  163. bool needsReinit = true;
  164. CarlaPluginDiscoveryHandle handle = nullptr;
  165. void init()
  166. {
  167. needsReinit = true;
  168. if (handle != nullptr)
  169. {
  170. carla_plugin_discovery_stop(handle);
  171. handle = nullptr;
  172. }
  173. }
  174. } fRunnerData;
  175. public:
  176. IldaeilUI()
  177. : UI(kInitialWidth, kInitialHeight),
  178. Runner("IldaeilScanner")
  179. {
  180. const double scaleFactor = getScaleFactor();
  181. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  182. {
  183. fDrawingState = kDrawingErrorInit;
  184. fIdleState = kIdleNothing;
  185. fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
  186. setGeometryConstraints(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  187. setSize(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  188. return;
  189. }
  190. std::strcpy(fPluginSearchString, "Search...");
  191. ImGuiStyle& style(ImGui::GetStyle());
  192. style.FrameRounding = 4 * scaleFactor;
  193. const double paddingY = style.WindowPadding.y * 2;
  194. if (d_isNotEqual(scaleFactor, 1.0))
  195. {
  196. fPluginHostWindow.setOffset(0, kButtonHeight * scaleFactor + paddingY);
  197. setGeometryConstraints(kInitialWidth * scaleFactor, kInitialWidth * scaleFactor);
  198. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  199. }
  200. else
  201. {
  202. fPluginHostWindow.setOffset(0, kButtonHeight + paddingY);
  203. setGeometryConstraints(kInitialWidth, kInitialWidth);
  204. }
  205. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  206. char winIdStr[24];
  207. std::snprintf(winIdStr, sizeof(winIdStr), "%lx", (ulong)getWindow().getNativeWindowHandle());
  208. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  209. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, scaleFactor*1000, nullptr);
  210. if (checkIfPluginIsLoaded())
  211. fIdleState = kIdleInitPluginAlreadyLoaded;
  212. fPlugin->fUI = this;
  213. #ifdef WASM_TESTING
  214. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr,
  215. "midifile", 0, 0x0, PLUGIN_OPTIONS_NULL))
  216. {
  217. d_stdout("Special hack for MIDI file playback activated");
  218. carla_set_custom_data(handle, 0, CUSTOM_DATA_TYPE_PATH, "file", "/furelise.mid");
  219. carla_set_parameter_value(handle, 0, 0, 1.0f);
  220. carla_set_parameter_value(handle, 0, 1, 0.0f);
  221. fPluginId = 2;
  222. }
  223. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "miditranspose", 0, 0x0, PLUGIN_OPTIONS_NULL);
  224. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "bypass", 0, 0x0, PLUGIN_OPTIONS_NULL);
  225. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "3bandeq", 0, 0x0, PLUGIN_OPTIONS_NULL);
  226. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "pingpongpan", 0, 0x0, PLUGIN_OPTIONS_NULL);
  227. carla_set_parameter_value(handle, 4, 1, 0.0f);
  228. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "audiogain_s", 0, 0x0, PLUGIN_OPTIONS_NULL);
  229. for (uint i=0; i<5; ++i)
  230. carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_INTERNAL, nullptr, nullptr, "bypass", 0, 0x0, PLUGIN_OPTIONS_NULL);
  231. #endif
  232. }
  233. ~IldaeilUI() override
  234. {
  235. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr)
  236. {
  237. fPlugin->fUI = nullptr;
  238. if (fPluginRunning)
  239. hidePluginUI(fPlugin->fCarlaHostHandle);
  240. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  241. }
  242. stopRunner();
  243. fPluginGenericUI = nullptr;
  244. }
  245. bool checkIfPluginIsLoaded()
  246. {
  247. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  248. if (carla_get_current_plugin_count(handle) == 0)
  249. return false;
  250. const uint hints = carla_get_plugin_info(handle, fPluginId)->hints;
  251. updatePluginFlags(hints);
  252. fPluginRunning = true;
  253. return true;
  254. }
  255. void updatePluginFlags(const uint hints) noexcept
  256. {
  257. if (hints & PLUGIN_HAS_CUSTOM_UI_USING_FILE_OPEN)
  258. {
  259. fPluginHasCustomUI = false;
  260. fPluginHasEmbedUI = false;
  261. fPluginHasResizableUI = false;
  262. fPluginHasFileOpen = true;
  263. }
  264. else
  265. {
  266. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  267. #ifndef DISTRHO_OS_WASM
  268. fPluginHasEmbedUI = hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  269. fPluginHasResizableUI = hints & PLUGIN_HAS_CUSTOM_RESIZABLE_UI;
  270. #endif
  271. fPluginHasFileOpen = false;
  272. }
  273. fPluginIsBridge = hints & PLUGIN_IS_BRIDGE;
  274. }
  275. void projectLoadedFromDSP()
  276. {
  277. if (checkIfPluginIsLoaded())
  278. fIdleState = kIdlePluginLoadedFromDSP;
  279. }
  280. void changeParameterFromDSP(const uint32_t index, const float value)
  281. {
  282. if (PluginGenericUI* const ui = fPluginGenericUI)
  283. {
  284. for (uint32_t i=0; i < ui->parameterCount; ++i)
  285. {
  286. if (ui->parameters[i].rindex != index)
  287. continue;
  288. ui->values[i] = value;
  289. if (ui->parameters[i].boolean)
  290. ui->parameters[i].bvalue = value > ui->parameters[i].min;
  291. break;
  292. }
  293. }
  294. repaint();
  295. }
  296. void resizeUI(const uint32_t width, const uint32_t height)
  297. {
  298. if (fDrawingState != kDrawingPluginEmbedUI)
  299. return;
  300. const uint extraHeight = kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2;
  301. fNextSize = Size<uint>(width, height + extraHeight);
  302. if (fInitialHostWindowShow)
  303. fUpdateGeometryConstraints = true;
  304. }
  305. void closeUI()
  306. {
  307. if (fIdleState == kIdleGiveIdleToUI)
  308. fIdleState = kIdleNothing;
  309. }
  310. const char* openFileFromDSP(const bool /*isDir*/, const char* const title, const char* const /*filter*/)
  311. {
  312. DISTRHO_SAFE_ASSERT_RETURN(fPluginType == PLUGIN_INTERNAL || fPluginType == PLUGIN_LV2, nullptr);
  313. FileBrowserOptions opts;
  314. opts.title = title;
  315. openFileBrowser(opts);
  316. return nullptr;
  317. }
  318. void showPluginUI(const CarlaHostHandle handle, const bool showIfNotEmbed)
  319. {
  320. #ifndef DISTRHO_OS_WASM
  321. const uint hints = carla_get_plugin_info(handle, fPluginId)->hints;
  322. if (hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  323. {
  324. fDrawingState = kDrawingPluginEmbedUI;
  325. fIdleState = kIdleGiveIdleToUI;
  326. fPluginHasCustomUI = true;
  327. fPluginHasEmbedUI = true;
  328. fPluginHasResizableUI = hints & PLUGIN_HAS_CUSTOM_RESIZABLE_UI;
  329. fPluginHasFileOpen = false;
  330. fIgnoreNextHostWindowResize = false;
  331. fInitialHostWindowShow = true;
  332. fShowingHostWindow = true;
  333. #if DISTRHO_UI_USER_RESIZABLE
  334. // getWindow().setResizable(fPluginHasResizableUI);
  335. #endif
  336. fPluginHostWindow.restart();
  337. carla_embed_custom_ui(handle, fPluginId, fNativeWindowHandle);
  338. fPluginHostWindow.idle();
  339. }
  340. else
  341. #endif
  342. {
  343. #if DISTRHO_UI_USER_RESIZABLE
  344. // getWindow().setResizable(true);
  345. #endif
  346. // fPluginHas* flags are updated in the next function
  347. createOrUpdatePluginGenericUI(handle);
  348. if (showIfNotEmbed && fPluginHasCustomUI)
  349. {
  350. fIdleState = kIdleGiveIdleToUI;
  351. carla_show_custom_ui(handle, fPluginId, true);
  352. }
  353. }
  354. repaint();
  355. }
  356. void hidePluginUI(const CarlaHostHandle handle)
  357. {
  358. DISTRHO_SAFE_ASSERT_RETURN(fPluginRunning,);
  359. fPluginHostWindow.hide();
  360. carla_show_custom_ui(handle, fPluginId, false);
  361. #if DISTRHO_UI_USER_RESIZABLE
  362. // getWindow().setResizable(true);
  363. #endif
  364. }
  365. void createOrUpdatePluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* info = nullptr)
  366. {
  367. if (info == nullptr)
  368. info = carla_get_plugin_info(handle, fPluginId);
  369. fDrawingState = kDrawingPluginGenericUI;
  370. updatePluginFlags(info->hints);
  371. if (fPluginGenericUI == nullptr)
  372. createPluginGenericUI(handle, info);
  373. else
  374. updatePluginGenericUI(handle);
  375. const double scaleFactor = getScaleFactor();
  376. fNextSize = Size<uint>(kGenericWidth * scaleFactor,
  377. (kGenericHeight + ImGui::GetStyle().WindowPadding.y) * scaleFactor);
  378. fLastSize = Size<uint>();
  379. fUpdateGeometryConstraints = true;
  380. }
  381. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  382. {
  383. PluginGenericUI* const ui = new PluginGenericUI;
  384. String title(info->name);
  385. title += " by ";
  386. title += info->maker;
  387. ui->title = title.getAndReleaseBuffer();
  388. fPluginHasOutputParameters = false;
  389. const uint32_t parameterCount = ui->parameterCount = carla_get_parameter_count(handle, fPluginId);
  390. // make count of valid parameters
  391. for (uint32_t i=0; i < parameterCount; ++i)
  392. {
  393. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  394. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  395. {
  396. --ui->parameterCount;
  397. continue;
  398. }
  399. if (pdata->type == PARAMETER_OUTPUT)
  400. fPluginHasOutputParameters = true;
  401. }
  402. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  403. ui->values = new float[ui->parameterCount];
  404. // now safely fill in details
  405. for (uint32_t i=0, j=0; i < parameterCount; ++i)
  406. {
  407. const ParameterData* const pdata = carla_get_parameter_data(handle, fPluginId, i);
  408. if ((pdata->hints & PARAMETER_IS_ENABLED) == 0x0)
  409. continue;
  410. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, fPluginId, i);
  411. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, fPluginId, i);
  412. String printformat;
  413. if (pdata->hints & PARAMETER_IS_INTEGER)
  414. printformat = "%.0f ";
  415. else
  416. printformat = "%.3f ";
  417. printformat += pinfo->unit;
  418. PluginGenericUI::Parameter& param(ui->parameters[j]);
  419. param.name = strdup(pinfo->name);
  420. if (const char* const groupName = std::strchr(pinfo->groupName, ':'))
  421. param.group = strdup(groupName + 1);
  422. param.printformat = printformat.getAndReleaseBuffer();
  423. param.rindex = i;
  424. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  425. param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
  426. param.readonly = pdata->type != PARAMETER_INPUT || (pdata->hints & PARAMETER_IS_READ_ONLY);
  427. param.min = pranges->min;
  428. param.max = pranges->max;
  429. ui->values[j] = carla_get_current_parameter_value(handle, fPluginId, i);
  430. if (param.boolean)
  431. param.bvalue = ui->values[j] > param.min;
  432. else
  433. param.bvalue = false;
  434. ++j;
  435. }
  436. // handle presets too
  437. const uint32_t presetCount = ui->presetCount = carla_get_program_count(handle, fPluginId);
  438. for (uint32_t i=0; i < presetCount; ++i)
  439. {
  440. const char* const pname = carla_get_program_name(handle, fPluginId, i);
  441. if (pname[0] == '\0')
  442. {
  443. --ui->presetCount;
  444. continue;
  445. }
  446. }
  447. ui->presets = new PluginGenericUI::Preset[ui->presetCount];
  448. ui->presetStrings = new const char*[ui->presetCount];
  449. for (uint32_t i=0, j=0; i < presetCount; ++i)
  450. {
  451. const char* const pname = carla_get_program_name(handle, fPluginId, i);
  452. if (pname[0] == '\0')
  453. continue;
  454. PluginGenericUI::Preset& preset(ui->presets[j]);
  455. preset.index = i;
  456. preset.name = strdup(pname);
  457. ui->presetStrings[j] = preset.name;
  458. ++j;
  459. }
  460. ui->currentPreset = -1;
  461. fPluginGenericUI = ui;
  462. }
  463. void updatePluginGenericUI(const CarlaHostHandle handle)
  464. {
  465. PluginGenericUI* const ui = fPluginGenericUI;
  466. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  467. for (uint32_t i=0; i < ui->parameterCount; ++i)
  468. {
  469. ui->values[i] = carla_get_current_parameter_value(handle, fPluginId, ui->parameters[i].rindex);
  470. if (ui->parameters[i].boolean)
  471. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  472. }
  473. }
  474. bool loadPlugin(const CarlaHostHandle handle, const PluginInfoCache& info)
  475. {
  476. if (fPluginRunning || fPluginId != 0)
  477. {
  478. hidePluginUI(handle);
  479. carla_replace_plugin(handle, fPluginId);
  480. }
  481. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  482. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  483. const bool ok = carla_add_plugin(handle,
  484. info.btype,
  485. fPluginType,
  486. info.filename.c_str(),
  487. info.name.c_str(),
  488. info.label.c_str(),
  489. info.uniqueId,
  490. nullptr,
  491. PLUGIN_OPTIONS_NULL);
  492. if (ok)
  493. {
  494. d_debug("loadeded a plugin with label '%s' and name '%s' %lu",
  495. info.name.c_str(), info.label.c_str(), info.uniqueId);
  496. fPluginRunning = true;
  497. fPluginGenericUI = nullptr;
  498. fPluginFilename.clear();
  499. #ifdef DISTRHO_OS_MAC
  500. const bool brokenOffset = fPluginType == PLUGIN_VST2
  501. && info.name == "Renoise Redux"
  502. && info.uniqueId == d_cconst('R', 'R', 'D', 'X');
  503. fPluginHostWindow.setOffsetBroken(brokenOffset);
  504. #endif
  505. showPluginUI(handle, false);
  506. #ifdef WASM_TESTING
  507. d_stdout("loaded a plugin with label '%s'", label);
  508. if (std::strcmp(label, "audiofile") == 0)
  509. {
  510. d_stdout("Loading mp3 file into audiofile plugin");
  511. carla_set_custom_data(handle, fPluginId, CUSTOM_DATA_TYPE_PATH, "file", "/foolme.mp3");
  512. carla_set_parameter_value(handle, fPluginId, 1, 0.0f);
  513. fPluginGenericUI->values[1] = 0.0f;
  514. }
  515. #endif
  516. }
  517. else
  518. {
  519. fPopupError = carla_get_last_error(handle);
  520. d_stdout("got error: %s", fPopupError.buffer());
  521. fDrawingState = kDrawingPluginError;
  522. }
  523. repaint();
  524. return ok;
  525. }
  526. void loadFileAsPlugin(const CarlaHostHandle handle, const char* const filename)
  527. {
  528. if (fPluginRunning || fPluginId != 0)
  529. {
  530. hidePluginUI(handle);
  531. carla_replace_plugin(handle, fPluginId);
  532. }
  533. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  534. const MutexLocker cml(fPlugin->sPluginInfoLoadMutex);
  535. if (carla_load_file(handle, filename))
  536. {
  537. fPluginRunning = true;
  538. fPluginGenericUI = nullptr;
  539. fPluginFilename = filename;
  540. showPluginUI(handle, false);
  541. }
  542. else
  543. {
  544. fPopupError = carla_get_last_error(handle);
  545. d_stdout("got error: %s", fPopupError.buffer());
  546. fDrawingState = kDrawingPluginError;
  547. fPluginFilename.clear();
  548. }
  549. repaint();
  550. }
  551. protected:
  552. void pluginWindowResized(const uint width, const uint height) override
  553. {
  554. if (fIgnoreNextHostWindowResize)
  555. {
  556. fIgnoreNextHostWindowResize = false;
  557. return;
  558. }
  559. if (fShowingHostWindow)
  560. {
  561. fShowingHostWindow = false;
  562. fIgnoreNextHostWindowResize = true;
  563. fUpdateGeometryConstraints = true;
  564. }
  565. const uint extraHeight = kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2;
  566. fNextSize = Size<uint>(width, height + extraHeight);
  567. // reduce geometry constraint if needed
  568. if (fIgnoreNextHostWindowResize)
  569. return;
  570. if (width < fCurrentConstraintSize.getWidth() || height + extraHeight < fCurrentConstraintSize.getHeight())
  571. fUpdateGeometryConstraints = true;
  572. if (fPluginIsIdling && fLastSize != fNextSize)
  573. {
  574. fLastSize = fNextSize;
  575. if (fUpdateGeometryConstraints)
  576. {
  577. fUpdateGeometryConstraints = false;
  578. fCurrentConstraintSize = fNextSize;
  579. setGeometryConstraints(fNextSize.getWidth(), fNextSize.getHeight());
  580. }
  581. setSize(fNextSize);
  582. }
  583. }
  584. #if DISTRHO_UI_USER_RESIZABLE
  585. void onResize(const ResizeEvent& ev) override
  586. {
  587. UI::onResize(ev);
  588. if (fIgnoreNextHostWindowResize)
  589. return;
  590. if (fShowingHostWindow)
  591. return;
  592. if (fDrawingState != kDrawingPluginEmbedUI)
  593. return;
  594. const double scaleFactor = getScaleFactor();
  595. const uint extraHeight = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2;
  596. uint width = ev.size.getWidth();
  597. uint height = ev.size.getHeight() - extraHeight;
  598. #ifdef DISTRHO_OS_MAC
  599. width /= scaleFactor;
  600. height /= scaleFactor;
  601. #endif
  602. fPluginHostWindow.setSize(width, height);
  603. }
  604. #endif
  605. void uiIdle() override
  606. {
  607. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  608. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
  609. if (fDrawingState == kDrawingPluginGenericUI && fPluginGenericUI != nullptr && fPluginHasOutputParameters)
  610. {
  611. updatePluginGenericUI(handle);
  612. repaint();
  613. }
  614. if (fNextSize.isValid() && fLastSize != fNextSize)
  615. {
  616. fLastSize = fNextSize;
  617. if (fUpdateGeometryConstraints)
  618. {
  619. fUpdateGeometryConstraints = false;
  620. fCurrentConstraintSize = fNextSize;
  621. setGeometryConstraints(fNextSize.getWidth(), fNextSize.getHeight());
  622. }
  623. #if ILDAEIL_STANDALONE
  624. if (fInitialHostWindowShow)
  625. #endif
  626. {
  627. fInitialHostWindowShow = false;
  628. setSize(fNextSize);
  629. }
  630. }
  631. switch (fIdleState)
  632. {
  633. case kIdleInit:
  634. fIdleState = kIdleNothing;
  635. initAndStartRunner();
  636. break;
  637. case kIdleInitPluginAlreadyLoaded:
  638. fIdleState = kIdleNothing;
  639. showPluginUI(handle, false);
  640. initAndStartRunner();
  641. break;
  642. case kIdlePluginLoadedFromDSP:
  643. fIdleState = kIdleNothing;
  644. showPluginUI(handle, false);
  645. break;
  646. case kIdleLoadSelectedPlugin:
  647. fIdleState = kIdleNothing;
  648. loadSelectedPlugin(handle);
  649. break;
  650. case kIdleResetPlugin:
  651. fIdleState = kIdleNothing;
  652. if (fPluginFilename.isNotEmpty())
  653. loadFileAsPlugin(handle, fPluginFilename.buffer());
  654. else
  655. loadPlugin(handle, fCurrentPluginInfo);
  656. break;
  657. case kIdleOpenFileUI:
  658. fIdleState = kIdleNothing;
  659. carla_show_custom_ui(handle, fPluginId, true);
  660. break;
  661. case kIdleShowCustomUI:
  662. fIdleState = kIdleNothing;
  663. showPluginUI(handle, true);
  664. break;
  665. case kIdleHideEmbedAndShowGenericUI:
  666. fIdleState = kIdleNothing;
  667. hidePluginUI(handle);
  668. createOrUpdatePluginGenericUI(handle);
  669. break;
  670. case kIdleHidePluginUI:
  671. fIdleState = kIdleNothing;
  672. hidePluginUI(handle);
  673. break;
  674. case kIdleGiveIdleToUI:
  675. if (fPlugin->fCarlaPluginDescriptor->ui_idle != nullptr)
  676. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  677. fPluginIsIdling = true;
  678. fPluginHostWindow.idle();
  679. fPluginIsIdling = false;
  680. break;
  681. case kIdleChangePluginType:
  682. fIdleState = kIdleNothing;
  683. if (fPluginRunning)
  684. hidePluginUI(handle);
  685. if (fNextPluginType == PLUGIN_TYPE_COUNT)
  686. {
  687. FileBrowserOptions opts;
  688. opts.title = "Load from file";
  689. openFileBrowser(opts);
  690. }
  691. else
  692. {
  693. fPluginSelected = -1;
  694. stopRunner();
  695. fPluginType = fNextPluginType;
  696. initAndStartRunner();
  697. }
  698. break;
  699. case kIdleNothing:
  700. break;
  701. }
  702. }
  703. void loadSelectedPlugin(const CarlaHostHandle handle)
  704. {
  705. DISTRHO_SAFE_ASSERT_RETURN(fPluginSelected >= 0,);
  706. PluginInfoCache info;
  707. {
  708. const MutexLocker cml(fPluginsMutex);
  709. info = fPlugins[fPluginSelected];
  710. }
  711. d_stdout("Loading %s...", info.name.c_str());
  712. if (loadPlugin(handle, info))
  713. fCurrentPluginInfo = info;
  714. }
  715. void uiFileBrowserSelected(const char* const filename) override
  716. {
  717. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr && filename != nullptr)
  718. {
  719. if (fNextPluginType == PLUGIN_TYPE_COUNT)
  720. loadFileAsPlugin(fPlugin->fCarlaHostHandle, filename);
  721. else
  722. carla_set_custom_data(fPlugin->fCarlaHostHandle, fPluginId, CUSTOM_DATA_TYPE_PATH, "file", filename);
  723. }
  724. }
  725. bool initAndStartRunner()
  726. {
  727. if (isRunnerActive())
  728. stopRunner();
  729. fRunnerData.init();
  730. return startRunner();
  731. }
  732. bool run() override
  733. {
  734. if (fRunnerData.needsReinit)
  735. {
  736. fRunnerData.needsReinit = false;
  737. {
  738. const MutexLocker cml(fPluginsMutex);
  739. fPlugins.clear();
  740. }
  741. d_stdout("Will scan plugins now...");
  742. const String& binaryPath(fPlugin->fBinaryPath);
  743. if (binaryPath.isNotEmpty())
  744. {
  745. fBinaryType = BINARY_NATIVE;
  746. fDiscoveryTool = binaryPath;
  747. fDiscoveryTool += DISTRHO_OS_SEP_STR "carla-discovery-native";
  748. #ifdef CARLA_OS_WIN
  749. fDiscoveryTool += ".exe";
  750. #endif
  751. fRunnerData.handle = carla_plugin_discovery_start(fDiscoveryTool,
  752. fBinaryType,
  753. fPluginType,
  754. IldaeilBasePlugin::getPluginPath(fPluginType),
  755. _binaryPluginSearchCallback,
  756. _binaryPluginCheckCacheCallback,
  757. this);
  758. }
  759. if (fDrawingState == kDrawingLoading)
  760. {
  761. fDrawingState = kDrawingPluginList;
  762. fPluginSearchFirstShow = true;
  763. }
  764. if (binaryPath.isEmpty() || (fRunnerData.handle == nullptr && !startNextDiscovery()))
  765. {
  766. d_stdout("Nothing found!");
  767. return false;
  768. }
  769. }
  770. DISTRHO_SAFE_ASSERT_RETURN(fRunnerData.handle != nullptr, false);
  771. if (carla_plugin_discovery_idle(fRunnerData.handle))
  772. return true;
  773. // stop here
  774. carla_plugin_discovery_stop(fRunnerData.handle);
  775. fRunnerData.handle = nullptr;
  776. if (startNextDiscovery())
  777. return true;
  778. d_stdout("Found %lu plugins!", (ulong)fPlugins.size());
  779. return false;
  780. }
  781. bool startNextDiscovery()
  782. {
  783. if (! setNextDiscoveryTool())
  784. return false;
  785. fRunnerData.handle = carla_plugin_discovery_start(fDiscoveryTool,
  786. fBinaryType,
  787. fPluginType,
  788. IldaeilBasePlugin::getPluginPath(fPluginType),
  789. _binaryPluginSearchCallback,
  790. _binaryPluginCheckCacheCallback,
  791. this);
  792. if (fRunnerData.handle == nullptr)
  793. return startNextDiscovery();
  794. return true;
  795. }
  796. bool setNextDiscoveryTool()
  797. {
  798. switch (fPluginType)
  799. {
  800. case PLUGIN_VST2:
  801. case PLUGIN_VST3:
  802. case PLUGIN_CLAP:
  803. break;
  804. default:
  805. return false;
  806. }
  807. #ifdef CARLA_OS_WIN
  808. #ifdef CARLA_OS_WIN64
  809. // look for win32 plugins on win64
  810. if (fBinaryType == BINARY_NATIVE)
  811. {
  812. fBinaryType = BINARY_WIN32;
  813. fDiscoveryTool = fPlugin->fBinaryPath;
  814. fDiscoveryTool += CARLA_OS_SEP_STR "carla-discovery-win32.exe";
  815. if (water::File(fDiscoveryTool.buffer()).existsAsFile())
  816. return true;
  817. }
  818. #endif
  819. // no other types to try
  820. return false;
  821. #else // CARLA_OS_WIN
  822. #ifndef CARLA_OS_MAC
  823. // try 32bit plugins on 64bit systems, skipping macOS where 32bit is no longer supported
  824. if (fBinaryType == BINARY_NATIVE)
  825. {
  826. fBinaryType = BINARY_POSIX32;
  827. fDiscoveryTool = fPlugin->fBinaryPath;
  828. fDiscoveryTool += CARLA_OS_SEP_STR "carla-discovery-posix32";
  829. if (water::File(fDiscoveryTool.buffer()).existsAsFile())
  830. return true;
  831. }
  832. #endif
  833. // try wine bridges
  834. #ifdef CARLA_OS_64BIT
  835. if (fBinaryType == BINARY_NATIVE || fBinaryType == BINARY_POSIX32)
  836. {
  837. fBinaryType = BINARY_WIN64;
  838. fDiscoveryTool = fPlugin->fBinaryPath;
  839. fDiscoveryTool += CARLA_OS_SEP_STR "carla-discovery-win64.exe";
  840. if (water::File(fDiscoveryTool.buffer()).existsAsFile())
  841. return true;
  842. }
  843. #endif
  844. if (fBinaryType != BINARY_WIN32)
  845. {
  846. fBinaryType = BINARY_WIN32;
  847. fDiscoveryTool = fPlugin->fBinaryPath;
  848. fDiscoveryTool += CARLA_OS_SEP_STR "carla-discovery-win32.exe";
  849. if (water::File(fDiscoveryTool.buffer()).existsAsFile())
  850. return true;
  851. }
  852. return false;
  853. #endif // CARLA_OS_WIN
  854. }
  855. void binaryPluginSearchCallback(const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum)
  856. {
  857. // save plugin info into cache
  858. if (sha1sum != nullptr)
  859. {
  860. const water::String configDir(getSpecialDir(kSpecialDirConfigForPlugin));
  861. const water::File cacheFile((configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum).toRawUTF8());
  862. if (cacheFile.create().ok())
  863. {
  864. water::FileOutputStream stream(cacheFile);
  865. if (stream.openedOk())
  866. {
  867. if (info != nullptr)
  868. {
  869. stream.writeString(getBinaryTypeAsString(info->btype));
  870. stream.writeString(getPluginTypeAsString(info->ptype));
  871. stream.writeString(info->filename);
  872. stream.writeString(info->label);
  873. stream.writeInt64(info->uniqueId);
  874. stream.writeString(info->metadata.name);
  875. stream.writeString(info->metadata.maker);
  876. stream.writeString(getPluginCategoryAsString(info->metadata.category));
  877. stream.writeInt(info->metadata.hints);
  878. stream.writeCompressedInt(info->io.audioIns);
  879. stream.writeCompressedInt(info->io.audioOuts);
  880. stream.writeCompressedInt(info->io.cvIns);
  881. stream.writeCompressedInt(info->io.cvOuts);
  882. stream.writeCompressedInt(info->io.midiIns);
  883. stream.writeCompressedInt(info->io.midiOuts);
  884. stream.writeCompressedInt(info->io.parameterIns);
  885. stream.writeCompressedInt(info->io.parameterOuts);
  886. }
  887. }
  888. else
  889. {
  890. d_stderr("Failed to write cache file for %s%s%s",
  891. getSpecialDir(kSpecialDirConfigForPlugin),
  892. CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR,
  893. sha1sum);
  894. }
  895. }
  896. else
  897. {
  898. d_stderr("Failed to write cache file directories for %s%s%s",
  899. getSpecialDir(kSpecialDirConfigForPlugin),
  900. CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR,
  901. sha1sum);
  902. }
  903. }
  904. if (info == nullptr)
  905. return;
  906. if (info->io.cvIns != 0 || info->io.cvOuts != 0)
  907. return;
  908. if (info->io.midiIns != 0 && info->io.midiIns != 1)
  909. return;
  910. if (info->io.midiOuts != 0 && info->io.midiOuts != 1)
  911. return;
  912. #if ILDAEIL_STANDALONE
  913. if (fPluginType == PLUGIN_INTERNAL)
  914. {
  915. if (std::strcmp(info->label, "audiogain") == 0)
  916. return;
  917. if (std::strcmp(info->label, "midichanfilter") == 0)
  918. return;
  919. if (std::strcmp(info->label, "midichannelize") == 0)
  920. return;
  921. }
  922. #elif DISTRHO_PLUGIN_IS_SYNTH
  923. if (info->io.midiIns != 1)
  924. return;
  925. if (info->io.audioOuts == 0)
  926. return;
  927. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  928. if ((info->io.midiIns != 1 && info->io.audioIns != 0 && info->io.audioOuts != 0) || info->io.midiOuts != 1)
  929. return;
  930. if (info->io.audioIns != 0 || info->io.audioOuts != 0)
  931. return;
  932. #else
  933. if (info->io.audioIns != 1 && info->io.audioIns != 2)
  934. return;
  935. if (info->io.audioOuts != 1 && info->io.audioOuts != 2)
  936. return;
  937. #endif
  938. if (fPluginType == PLUGIN_INTERNAL)
  939. {
  940. #if !ILDAEIL_STANDALONE
  941. if (std::strcmp(info->label, "audiogain_s") == 0)
  942. return;
  943. #endif
  944. if (std::strcmp(info->label, "lfo") == 0)
  945. return;
  946. if (std::strcmp(info->label, "midi2cv") == 0)
  947. return;
  948. if (std::strcmp(info->label, "midithrough") == 0)
  949. return;
  950. if (std::strcmp(info->label, "3bandsplitter") == 0)
  951. return;
  952. }
  953. const PluginInfoCache pinfo = {
  954. info->btype,
  955. info->uniqueId,
  956. info->filename,
  957. info->metadata.name,
  958. info->label,
  959. };
  960. const MutexLocker cml(fPluginsMutex);
  961. fPlugins.push_back(pinfo);
  962. }
  963. static void _binaryPluginSearchCallback(void* const ptr,
  964. const CarlaPluginDiscoveryInfo* const info,
  965. const char* const sha1sum)
  966. {
  967. static_cast<IldaeilUI*>(ptr)->binaryPluginSearchCallback(info, sha1sum);
  968. }
  969. bool binaryPluginCheckCacheCallback(const char* const filename, const char* const sha1sum)
  970. {
  971. if (sha1sum == nullptr)
  972. return false;
  973. const water::String configDir(getSpecialDir(kSpecialDirConfigForPlugin));
  974. const water::File cacheFile((configDir + CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR + sha1sum).toRawUTF8());
  975. if (cacheFile.existsAsFile())
  976. {
  977. water::FileInputStream stream(cacheFile);
  978. if (stream.openedOk())
  979. {
  980. while (! stream.isExhausted())
  981. {
  982. CarlaPluginDiscoveryInfo info = {};
  983. // read back everything the same way and order as we wrote it
  984. info.btype = getBinaryTypeFromString(stream.readString().toRawUTF8());
  985. info.ptype = getPluginTypeFromString(stream.readString().toRawUTF8());
  986. const water::String pfilename(stream.readString());
  987. const water::String label(stream.readString());
  988. info.uniqueId = stream.readInt64();
  989. const water::String name(stream.readString());
  990. const water::String maker(stream.readString());
  991. info.metadata.category = getPluginCategoryFromString(stream.readString().toRawUTF8());
  992. info.metadata.hints = stream.readInt();
  993. info.io.audioIns = stream.readCompressedInt();
  994. info.io.audioOuts = stream.readCompressedInt();
  995. info.io.cvIns = stream.readCompressedInt();
  996. info.io.cvOuts = stream.readCompressedInt();
  997. info.io.midiIns = stream.readCompressedInt();
  998. info.io.midiOuts = stream.readCompressedInt();
  999. info.io.parameterIns = stream.readCompressedInt();
  1000. info.io.parameterOuts = stream.readCompressedInt();
  1001. // string stuff
  1002. info.filename = pfilename.toRawUTF8();
  1003. info.label = label.toRawUTF8();
  1004. info.metadata.name = name.toRawUTF8();
  1005. info.metadata.maker = maker.toRawUTF8();
  1006. // check sha1 collisions
  1007. if (pfilename != filename)
  1008. {
  1009. d_stderr("Cache hash collision for %s: \"%s\" vs \"%s\"",
  1010. sha1sum, pfilename.toRawUTF8(), filename);
  1011. return false;
  1012. }
  1013. // purposefully not passing sha1sum, to not override cache file
  1014. binaryPluginSearchCallback(&info, nullptr);
  1015. }
  1016. return true;
  1017. }
  1018. else
  1019. {
  1020. d_stderr("Failed to read cache file for %s%s%s",
  1021. getSpecialDir(kSpecialDirConfigForPlugin),
  1022. CARLA_OS_SEP_STR "cache" CARLA_OS_SEP_STR,
  1023. sha1sum);
  1024. }
  1025. }
  1026. return false;
  1027. }
  1028. static bool _binaryPluginCheckCacheCallback(void* const ptr, const char* const filename, const char* const sha1)
  1029. {
  1030. return static_cast<IldaeilUI*>(ptr)->binaryPluginCheckCacheCallback(filename, sha1);
  1031. }
  1032. void onImGuiDisplay() override
  1033. {
  1034. switch (fDrawingState)
  1035. {
  1036. case kDrawingLoading:
  1037. drawLoading();
  1038. break;
  1039. case kDrawingPluginError:
  1040. ImGui::OpenPopup("Plugin Error");
  1041. // call ourselves again with the plugin list
  1042. fDrawingState = kDrawingPluginList;
  1043. onImGuiDisplay();
  1044. break;
  1045. case kDrawingPluginList:
  1046. drawPluginList();
  1047. break;
  1048. case kDrawingPluginGenericUI:
  1049. drawTopBar();
  1050. drawGenericUI();
  1051. break;
  1052. case kDrawingPluginEmbedUI:
  1053. drawTopBar();
  1054. break;
  1055. case kDrawingErrorInit:
  1056. fDrawingState = kDrawingErrorDraw;
  1057. drawError(true);
  1058. break;
  1059. case kDrawingErrorDraw:
  1060. drawError(false);
  1061. break;
  1062. }
  1063. }
  1064. void drawError(const bool open)
  1065. {
  1066. ImGui::SetNextWindowPos(ImVec2(0, 0));
  1067. ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight()));
  1068. const int flags = ImGuiWindowFlags_NoSavedSettings
  1069. | ImGuiWindowFlags_NoTitleBar
  1070. | ImGuiWindowFlags_NoResize
  1071. | ImGuiWindowFlags_NoCollapse
  1072. | ImGuiWindowFlags_NoScrollbar
  1073. | ImGuiWindowFlags_NoScrollWithMouse;
  1074. if (ImGui::Begin("Error Window", nullptr, flags))
  1075. {
  1076. if (open)
  1077. ImGui::OpenPopup("Engine Error");
  1078. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1079. | ImGuiWindowFlags_NoResize
  1080. | ImGuiWindowFlags_NoCollapse
  1081. | ImGuiWindowFlags_NoScrollbar
  1082. | ImGuiWindowFlags_NoScrollWithMouse
  1083. | ImGuiWindowFlags_AlwaysAutoResize
  1084. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  1085. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  1086. {
  1087. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  1088. ImGui::EndPopup();
  1089. }
  1090. }
  1091. ImGui::End();
  1092. }
  1093. void drawTopBar()
  1094. {
  1095. const double scaleFactor = getScaleFactor();
  1096. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  1097. #if ILDAEIL_STANDALONE
  1098. const uint width = std::min(getWidth(), d_roundToUnsignedInt(kInitialWidth * scaleFactor));
  1099. ImGui::SetNextWindowPos(ImVec2((getWidth() - width) / 2, 0));
  1100. ImGui::SetNextWindowSize(ImVec2(width, kButtonHeight * scaleFactor + padding));
  1101. #else
  1102. ImGui::SetNextWindowPos(ImVec2(0, 0));
  1103. ImGui::SetNextWindowSize(ImVec2(getWidth(), kButtonHeight * scaleFactor + padding));
  1104. #endif
  1105. const int flags = ImGuiWindowFlags_NoSavedSettings
  1106. | ImGuiWindowFlags_NoTitleBar
  1107. | ImGuiWindowFlags_NoResize
  1108. | ImGuiWindowFlags_NoCollapse
  1109. | ImGuiWindowFlags_NoScrollbar
  1110. | ImGuiWindowFlags_NoScrollWithMouse;
  1111. if (ImGui::Begin("Current Plugin", nullptr, flags))
  1112. {
  1113. if (ImGui::Button("Pick Another..."))
  1114. {
  1115. fIdleState = kIdleHidePluginUI;
  1116. fDrawingState = kDrawingPluginList;
  1117. fNextSize = Size<uint>(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  1118. fLastSize = Size<uint>();
  1119. fUpdateGeometryConstraints = true;
  1120. }
  1121. ImGui::SameLine();
  1122. if (ImGui::Button("Reset"))
  1123. fIdleState = kIdleResetPlugin;
  1124. if (fDrawingState == kDrawingPluginGenericUI)
  1125. {
  1126. if (fPluginHasCustomUI)
  1127. {
  1128. ImGui::SameLine();
  1129. if (ImGui::Button("Show Custom GUI"))
  1130. fIdleState = kIdleShowCustomUI;
  1131. }
  1132. if (fPluginHasFileOpen)
  1133. {
  1134. ImGui::SameLine();
  1135. if (ImGui::Button("Open File..."))
  1136. fIdleState = kIdleOpenFileUI;
  1137. }
  1138. #ifdef WASM_TESTING
  1139. ImGui::SameLine();
  1140. ImGui::TextUnformatted(" Plugin to control:");
  1141. for (uint i=1; i<10; ++i)
  1142. {
  1143. char txt[8];
  1144. sprintf(txt, "%d", i);
  1145. ImGui::SameLine();
  1146. if (ImGui::Button(txt))
  1147. {
  1148. fPluginId = i;
  1149. fPluginGenericUI = nullptr;
  1150. fIdleState = kIdleHideEmbedAndShowGenericUI;
  1151. }
  1152. }
  1153. #endif
  1154. }
  1155. if (fDrawingState == kDrawingPluginEmbedUI)
  1156. {
  1157. ImGui::SameLine();
  1158. if (ImGui::Button("Show Generic GUI"))
  1159. fIdleState = kIdleHideEmbedAndShowGenericUI;
  1160. }
  1161. #if ILDAEIL_STANDALONE
  1162. if (isUsingNativeAudio())
  1163. {
  1164. ImGui::SameLine();
  1165. ImGui::Spacing();
  1166. ImGui::SameLine();
  1167. if (supportsAudioInput() && !isAudioInputEnabled() && ImGui::Button("Enable Input"))
  1168. requestAudioInput();
  1169. ImGui::SameLine();
  1170. if (supportsMIDI() && !isMIDIEnabled() && ImGui::Button("Enable MIDI"))
  1171. requestMIDI();
  1172. if (fDrawingState != kDrawingPluginEmbedUI && supportsBufferSizeChanges())
  1173. {
  1174. ImGui::SameLine();
  1175. ImGui::Spacing();
  1176. ImGui::SameLine();
  1177. ImGui::Text("Buffer Size:");
  1178. static constexpr uint bufferSizes_i[] = {
  1179. #ifndef DISTRHO_OS_WASM
  1180. 128,
  1181. #endif
  1182. 256, 512, 1024, 2048, 4096, 8192,
  1183. #ifdef DISTRHO_OS_WASM
  1184. 16384,
  1185. #endif
  1186. };
  1187. static constexpr const char* bufferSizes_s[] = {
  1188. #ifndef DISTRHO_OS_WASM
  1189. "128",
  1190. #endif
  1191. "256", "512", "1024", "2048", "4096", "8192",
  1192. #ifdef DISTRHO_OS_WASM
  1193. "16384",
  1194. #endif
  1195. };
  1196. uint buffersize = getBufferSize();
  1197. int current = -1;
  1198. for (uint i=0; i<ARRAY_SIZE(bufferSizes_i); ++i)
  1199. {
  1200. if (bufferSizes_i[i] == buffersize)
  1201. {
  1202. current = i;
  1203. break;
  1204. }
  1205. }
  1206. ImGui::SameLine();
  1207. ImGui::SetNextItemWidth(72 * scaleFactor);
  1208. if (ImGui::Combo("##buffersize", &current, bufferSizes_s, ARRAY_SIZE(bufferSizes_s)))
  1209. {
  1210. const uint next = bufferSizes_i[current];
  1211. d_stdout("requesting new buffer size: %u -> %u", buffersize, next);
  1212. requestBufferSizeChange(next);
  1213. }
  1214. }
  1215. }
  1216. #endif
  1217. }
  1218. ImGui::End();
  1219. }
  1220. void setupMainWindowPos()
  1221. {
  1222. const double scaleFactor = getScaleFactor();
  1223. float y = 0;
  1224. float height = getHeight();
  1225. if (fDrawingState == kDrawingPluginGenericUI)
  1226. {
  1227. y = kButtonHeight * scaleFactor + ImGui::GetStyle().WindowPadding.y * 2 - scaleFactor;
  1228. height -= y;
  1229. }
  1230. #if ILDAEIL_STANDALONE
  1231. const uint width = std::min(getWidth(), d_roundToUnsignedInt(kInitialWidth * scaleFactor));
  1232. ImGui::SetNextWindowPos(ImVec2((getWidth() - width) * 0.5, y));
  1233. ImGui::SetNextWindowSize(ImVec2(width, height));
  1234. #else
  1235. ImGui::SetNextWindowPos(ImVec2(0, y));
  1236. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  1237. #endif
  1238. }
  1239. void drawGenericUI()
  1240. {
  1241. setupMainWindowPos();
  1242. PluginGenericUI* const ui = fPluginGenericUI;
  1243. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1244. const int pflags = ImGuiWindowFlags_NoSavedSettings
  1245. | ImGuiWindowFlags_NoResize
  1246. | ImGuiWindowFlags_NoCollapse
  1247. | ImGuiWindowFlags_AlwaysAutoResize;
  1248. if (ImGui::Begin(ui->title, nullptr, pflags))
  1249. {
  1250. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  1251. if (fPluginIsBridge)
  1252. {
  1253. const bool active = carla_get_internal_parameter_value(handle, 0, PARAMETER_ACTIVE) > 0.5f;
  1254. if (active)
  1255. {
  1256. ImGui::BeginDisabled();
  1257. ImGui::Button("Reload bridge");
  1258. ImGui::EndDisabled();
  1259. }
  1260. else
  1261. {
  1262. if (ImGui::Button("Reload bridge"))
  1263. carla_set_active(handle, 0, true);
  1264. }
  1265. }
  1266. if (ui->presetCount != 0)
  1267. {
  1268. ImGui::Text("Preset:");
  1269. ImGui::SameLine();
  1270. if (ImGui::Combo("##presets", &ui->currentPreset, ui->presetStrings, ui->presetCount))
  1271. {
  1272. PluginGenericUI::Preset& preset(ui->presets[ui->currentPreset]);
  1273. carla_set_program(handle, fPluginId, preset.index);
  1274. }
  1275. }
  1276. const char* groupName = "";
  1277. for (uint32_t i=0; i < ui->parameterCount; ++i)
  1278. {
  1279. PluginGenericUI::Parameter& param(ui->parameters[i]);
  1280. if (param.group != nullptr && std::strcmp(param.group, groupName) != 0)
  1281. {
  1282. groupName = param.group;
  1283. ImGui::SeparatorText(groupName);
  1284. }
  1285. if (param.readonly)
  1286. {
  1287. ImGui::BeginDisabled();
  1288. ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat,
  1289. ImGuiSliderFlags_NoInput | (param.log ? ImGuiSliderFlags_Logarithmic : 0x0));
  1290. ImGui::EndDisabled();
  1291. continue;
  1292. }
  1293. if (param.boolean)
  1294. {
  1295. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  1296. {
  1297. if (ImGui::IsItemActivated())
  1298. {
  1299. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  1300. // editParameter(0, true);
  1301. }
  1302. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  1303. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  1304. // setParameterValue(0, ui->values[i]);
  1305. }
  1306. }
  1307. else
  1308. {
  1309. const bool ret = param.log
  1310. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat, ImGuiSliderFlags_Logarithmic)
  1311. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.printformat);
  1312. if (ret)
  1313. {
  1314. if (ImGui::IsItemActivated())
  1315. {
  1316. carla_set_parameter_touch(handle, fPluginId, param.rindex, true);
  1317. // editParameter(0, true);
  1318. }
  1319. carla_set_parameter_value(handle, fPluginId, param.rindex, ui->values[i]);
  1320. // setParameterValue(0, ui->values[i]);
  1321. }
  1322. }
  1323. if (ImGui::IsItemDeactivated())
  1324. {
  1325. carla_set_parameter_touch(handle, fPluginId, param.rindex, false);
  1326. // editParameter(0, false);
  1327. }
  1328. }
  1329. }
  1330. ImGui::End();
  1331. }
  1332. void drawLoading()
  1333. {
  1334. setupMainWindowPos();
  1335. constexpr const int plflags = ImGuiWindowFlags_NoSavedSettings
  1336. | ImGuiWindowFlags_NoDecoration;
  1337. if (ImGui::Begin("Plugin List", nullptr, plflags))
  1338. ImGui::TextUnformatted("Loading...", nullptr);
  1339. ImGui::End();
  1340. }
  1341. void drawPluginList()
  1342. {
  1343. static const char* pluginTypes[] = {
  1344. getPluginTypeAsString(PLUGIN_INTERNAL),
  1345. #ifndef DISTRHO_OS_WASM
  1346. getPluginTypeAsString(PLUGIN_LADSPA),
  1347. getPluginTypeAsString(PLUGIN_DSSI),
  1348. #endif
  1349. getPluginTypeAsString(PLUGIN_LV2),
  1350. #ifndef DISTRHO_OS_WASM
  1351. getPluginTypeAsString(PLUGIN_VST2),
  1352. getPluginTypeAsString(PLUGIN_VST3),
  1353. getPluginTypeAsString(PLUGIN_CLAP),
  1354. #endif
  1355. getPluginTypeAsString(PLUGIN_JSFX),
  1356. "Load from file..."
  1357. };
  1358. setupMainWindowPos();
  1359. constexpr const int plflags = ImGuiWindowFlags_NoSavedSettings
  1360. | ImGuiWindowFlags_NoDecoration;
  1361. if (ImGui::Begin("Plugin List", nullptr, plflags))
  1362. {
  1363. constexpr const int errflags = ImGuiWindowFlags_NoSavedSettings
  1364. | ImGuiWindowFlags_NoResize
  1365. | ImGuiWindowFlags_AlwaysAutoResize
  1366. | ImGuiWindowFlags_NoCollapse
  1367. | ImGuiWindowFlags_NoScrollbar
  1368. | ImGuiWindowFlags_NoScrollWithMouse;
  1369. if (ImGui::BeginPopupModal("Plugin Error", nullptr, errflags))
  1370. {
  1371. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  1372. ImGui::Separator();
  1373. if (ImGui::Button("Ok"))
  1374. ImGui::CloseCurrentPopup();
  1375. ImGui::SameLine();
  1376. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  1377. ImGui::EndPopup();
  1378. }
  1379. else if (fPluginSearchFirstShow)
  1380. {
  1381. fPluginSearchFirstShow = false;
  1382. ImGui::SetKeyboardFocusHere();
  1383. }
  1384. if (ImGui::InputText("##pluginsearch", fPluginSearchString, sizeof(fPluginSearchString)-1,
  1385. ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  1386. fPluginSearchActive = true;
  1387. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  1388. fPluginSearchActive = false;
  1389. ImGui::SameLine();
  1390. ImGui::PushItemWidth(-1.0f);
  1391. int current;
  1392. switch (fPluginType)
  1393. {
  1394. #ifdef DISTRHO_OS_WASM
  1395. case PLUGIN_JSFX: current = 2; break;
  1396. case PLUGIN_LV2: current = 1; break;
  1397. #else
  1398. case PLUGIN_JSFX: current = 7; break;
  1399. case PLUGIN_CLAP: current = 6; break;
  1400. case PLUGIN_VST3: current = 5; break;
  1401. case PLUGIN_VST2: current = 4; break;
  1402. case PLUGIN_LV2: current = 3; break;
  1403. case PLUGIN_DSSI: current = 2; break;
  1404. case PLUGIN_LADSPA: current = 1; break;
  1405. #endif
  1406. default: current = 0; break;
  1407. }
  1408. if (ImGui::Combo("##plugintypes", &current, pluginTypes, ARRAY_SIZE(pluginTypes)))
  1409. {
  1410. fIdleState = kIdleChangePluginType;
  1411. switch (current)
  1412. {
  1413. #ifdef DISTRHO_OS_WASM
  1414. case 0: fNextPluginType = PLUGIN_INTERNAL; break;
  1415. case 1: fNextPluginType = PLUGIN_LV2; break;
  1416. case 2: fNextPluginType = PLUGIN_JSFX; break;
  1417. case 3: fNextPluginType = PLUGIN_TYPE_COUNT; break;
  1418. #else
  1419. case 0: fNextPluginType = PLUGIN_INTERNAL; break;
  1420. case 1: fNextPluginType = PLUGIN_LADSPA; break;
  1421. case 2: fNextPluginType = PLUGIN_DSSI; break;
  1422. case 3: fNextPluginType = PLUGIN_LV2; break;
  1423. case 4: fNextPluginType = PLUGIN_VST2; break;
  1424. case 5: fNextPluginType = PLUGIN_VST3; break;
  1425. case 6: fNextPluginType = PLUGIN_CLAP; break;
  1426. case 7: fNextPluginType = PLUGIN_JSFX; break;
  1427. case 8: fNextPluginType = PLUGIN_TYPE_COUNT; break;
  1428. #endif
  1429. }
  1430. }
  1431. ImGui::BeginDisabled(fPluginSelected < 0);
  1432. if (ImGui::Button("Load Plugin"))
  1433. fIdleState = kIdleLoadSelectedPlugin;
  1434. // xx cardinal
  1435. if (fPluginType != PLUGIN_INTERNAL /*&& module->canUseBridges*/)
  1436. {
  1437. ImGui::SameLine();
  1438. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  1439. }
  1440. ImGui::EndDisabled();
  1441. if (fPluginRunning)
  1442. {
  1443. ImGui::SameLine();
  1444. if (ImGui::Button("Cancel"))
  1445. fIdleState = kIdleShowCustomUI;
  1446. }
  1447. if (ImGui::BeginChild("pluginlistwindow"))
  1448. {
  1449. if (ImGui::BeginTable("pluginlist", 2, ImGuiTableFlags_NoSavedSettings))
  1450. {
  1451. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  1452. switch (fPluginType)
  1453. {
  1454. case PLUGIN_INTERNAL:
  1455. case PLUGIN_AU:
  1456. ImGui::TableSetupColumn("Name");
  1457. ImGui::TableSetupColumn("Label");
  1458. ImGui::TableHeadersRow();
  1459. break;
  1460. case PLUGIN_LV2:
  1461. ImGui::TableSetupColumn("Name");
  1462. ImGui::TableSetupColumn("URI");
  1463. ImGui::TableHeadersRow();
  1464. break;
  1465. default:
  1466. ImGui::TableSetupColumn("Name");
  1467. ImGui::TableSetupColumn("Filename");
  1468. ImGui::TableHeadersRow();
  1469. break;
  1470. }
  1471. const MutexLocker cml(fPluginsMutex);
  1472. for (uint i=0; i<fPlugins.size(); ++i)
  1473. {
  1474. const PluginInfoCache& info(fPlugins[i]);
  1475. if (search != nullptr && ildaeil::strcasestr(info.name.c_str(), search) == nullptr)
  1476. continue;
  1477. bool selected = fPluginSelected >= 0 && static_cast<uint>(fPluginSelected) == i;
  1478. switch (fPluginType)
  1479. {
  1480. case PLUGIN_INTERNAL:
  1481. case PLUGIN_AU:
  1482. ImGui::TableNextRow();
  1483. ImGui::TableSetColumnIndex(0);
  1484. ImGui::Selectable(info.name.c_str(), &selected);
  1485. ImGui::TableSetColumnIndex(1);
  1486. ImGui::Selectable(info.label.c_str(), &selected);
  1487. break;
  1488. case PLUGIN_LV2:
  1489. ImGui::TableNextRow();
  1490. ImGui::TableSetColumnIndex(0);
  1491. ImGui::Selectable(info.name.c_str(), &selected);
  1492. ImGui::TableSetColumnIndex(1);
  1493. ImGui::Selectable(info.label.c_str(), &selected);
  1494. break;
  1495. default:
  1496. ImGui::TableNextRow();
  1497. ImGui::TableSetColumnIndex(0);
  1498. ImGui::Selectable(info.name.c_str(), &selected);
  1499. ImGui::TableSetColumnIndex(1);
  1500. ImGui::Selectable(info.filename.c_str(), &selected);
  1501. break;
  1502. }
  1503. if (selected)
  1504. fPluginSelected = i;
  1505. }
  1506. ImGui::EndTable();
  1507. }
  1508. ImGui::EndChild();
  1509. }
  1510. }
  1511. ImGui::End();
  1512. }
  1513. protected:
  1514. /* --------------------------------------------------------------------------------------------------------
  1515. * DSP/Plugin Callbacks */
  1516. void parameterChanged(uint32_t, float) override
  1517. {
  1518. }
  1519. void stateChanged(const char* /* const key */, const char*) override
  1520. {
  1521. /*
  1522. if (std::strcmp(key, "project") == 0)
  1523. hidePluginUI(fPlugin->fCarlaHostHandle);
  1524. */
  1525. }
  1526. // -------------------------------------------------------------------------------------------------------
  1527. private:
  1528. /**
  1529. Set our UI class as non-copyable and add a leak detector just in case.
  1530. */
  1531. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  1532. };
  1533. // --------------------------------------------------------------------------------------------------------------------
  1534. void ildaeilProjectLoadedFromDSP(void* const ui)
  1535. {
  1536. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1537. static_cast<IldaeilUI*>(ui)->projectLoadedFromDSP();
  1538. }
  1539. void ildaeilParameterChangeForUI(void* const ui, const uint32_t index, const float value)
  1540. {
  1541. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1542. static_cast<IldaeilUI*>(ui)->changeParameterFromDSP(index, value);
  1543. }
  1544. void ildaeilResizeUI(void* const ui, const uint32_t width, const uint32_t height)
  1545. {
  1546. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1547. static_cast<IldaeilUI*>(ui)->resizeUI(width, height);
  1548. }
  1549. void ildaeilCloseUI(void* const ui)
  1550. {
  1551. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  1552. static_cast<IldaeilUI*>(ui)->closeUI();
  1553. }
  1554. const char* ildaeilOpenFileForUI(void* const ui, const bool isDir, const char* const title, const char* const filter)
  1555. {
  1556. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  1557. return static_cast<IldaeilUI*>(ui)->openFileFromDSP(isDir, title, filter);
  1558. }
  1559. /* --------------------------------------------------------------------------------------------------------------------
  1560. * UI entry point, called by DPF to create a new UI instance. */
  1561. UI* createUI()
  1562. {
  1563. return new IldaeilUI();
  1564. }
  1565. // --------------------------------------------------------------------------------------------------------------------
  1566. END_NAMESPACE_DISTRHO