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.

487 lines
14KB

  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. START_NAMESPACE_DISTRHO
  23. class IldaeilPlugin : public Plugin
  24. {
  25. public:
  26. const NativePluginDescriptor* fCarlaPluginDescriptor;
  27. NativePluginHandle fCarlaPluginHandle;
  28. NativeHostDescriptor fCarlaHostDescriptor;
  29. CarlaHostHandle fCarlaHostHandle;
  30. // ...
  31. };
  32. // -----------------------------------------------------------------------------------------------------------
  33. using namespace CarlaBackend;
  34. // shared resource pointer
  35. // carla_juce_init();
  36. class IldaeilUI : public UI,
  37. public Thread,
  38. public PluginHostWindow::Callbacks
  39. {
  40. static constexpr const uint kInitialWidth = 1220;
  41. static constexpr const uint kInitialHeight = 640;
  42. static constexpr const uint kGenericWidth = 600;
  43. static constexpr const uint kGenericHeight = 400;
  44. static constexpr const uint kExtraHeight = 35;
  45. enum {
  46. kDrawingInit,
  47. kDrawingError,
  48. kDrawingLoading,
  49. kDrawingPluginList,
  50. kDrawingPluginCustomUI,
  51. kDrawingPluginGenericUI,
  52. kDrawingPluginPendingFromInit
  53. } fDrawingState;
  54. struct PluginInfoCache {
  55. char* name;
  56. char* label;
  57. PluginInfoCache()
  58. : name(nullptr),
  59. label(nullptr) {}
  60. ~PluginInfoCache()
  61. {
  62. std::free(name);
  63. std::free(label);
  64. }
  65. };
  66. IldaeilPlugin* const fPlugin;
  67. PluginHostWindow fPluginHostWindow;
  68. uint fPluginCount;
  69. uint fPluginSelected;
  70. bool fPluginScanningFinished;
  71. PluginInfoCache* fPlugins;
  72. bool fPluginSearchActive;
  73. char fPluginSearchString[0xff];
  74. public:
  75. IldaeilUI()
  76. : UI(kInitialWidth, kInitialHeight),
  77. Thread("IldaeilScanner"),
  78. fDrawingState(kDrawingInit),
  79. fPlugin((IldaeilPlugin*)getPluginInstancePointer()),
  80. fPluginHostWindow(getWindow(), this),
  81. fPluginCount(0),
  82. fPluginSelected(0),
  83. fPluginScanningFinished(false),
  84. fPlugins(nullptr),
  85. fPluginSearchActive(false)
  86. {
  87. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  88. {
  89. fDrawingState = kDrawingError;
  90. return;
  91. }
  92. std::strcpy(fPluginSearchString, "Search...");
  93. // fPlugin->setUI(this);
  94. const double scaleFactor = getScaleFactor();
  95. if (d_isNotEqual(scaleFactor, 1.0))
  96. {
  97. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  98. fPluginHostWindow.setPositionAndSize(0, kExtraHeight * scaleFactor,
  99. kInitialWidth * scaleFactor, (kInitialHeight - kExtraHeight) * scaleFactor);
  100. }
  101. else
  102. {
  103. fPluginHostWindow.setPositionAndSize(0, kExtraHeight, kInitialWidth, kInitialHeight-kExtraHeight);
  104. }
  105. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  106. // carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  107. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, getScaleFactor()*1000, nullptr);
  108. if (carla_get_current_plugin_count(handle) != 0)
  109. fDrawingState = kDrawingPluginPendingFromInit;
  110. }
  111. ~IldaeilUI() override
  112. {
  113. if (isThreadRunning())
  114. stopThread(-1);
  115. // fPlugin->fUI = nullptr;
  116. hidePluginUI();
  117. delete[] fPlugins;
  118. }
  119. void showPluginUI(const CarlaHostHandle handle)
  120. {
  121. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  122. if (info->hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  123. {
  124. fDrawingState = kDrawingPluginCustomUI;
  125. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  126. }
  127. else
  128. {
  129. fDrawingState = kDrawingPluginGenericUI;
  130. // TODO query parameter information and store it
  131. const double scaleFactor = getScaleFactor();
  132. setSize(kGenericWidth * scaleFactor, (kGenericHeight + kExtraHeight) * scaleFactor);
  133. }
  134. repaint();
  135. }
  136. void hidePluginUI()
  137. {
  138. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  139. return;
  140. if (fDrawingState == kDrawingPluginGenericUI || fDrawingState == kDrawingPluginCustomUI)
  141. carla_show_custom_ui(fPlugin->fCarlaHostHandle, 0, false);
  142. fPluginHostWindow.hide();
  143. }
  144. protected:
  145. void pluginWindowResized(uint width, uint height) override
  146. {
  147. setSize(width, height + kExtraHeight * getScaleFactor());
  148. }
  149. void uiIdle() override
  150. {
  151. switch (fDrawingState)
  152. {
  153. case kDrawingInit:
  154. fDrawingState = kDrawingLoading;
  155. startThread();
  156. repaint();
  157. break;
  158. case kDrawingPluginPendingFromInit:
  159. showPluginUI(fPlugin->fCarlaHostHandle);
  160. startThread();
  161. break;
  162. case kDrawingPluginCustomUI:
  163. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  164. fPluginHostWindow.idle();
  165. break;
  166. case kDrawingPluginGenericUI:
  167. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  168. break;
  169. default:
  170. break;
  171. }
  172. }
  173. void run() override
  174. {
  175. if (const uint count = carla_get_cached_plugin_count(PLUGIN_LV2, nullptr))
  176. {
  177. fPluginCount = 0;
  178. fPlugins = new PluginInfoCache[count];
  179. if (fDrawingState == kDrawingLoading)
  180. fDrawingState = kDrawingPluginList;
  181. for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
  182. {
  183. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(PLUGIN_LV2, i);
  184. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  185. #if DISTRHO_PLUGIN_IS_SYNTH
  186. if (info->midiIns != 1 || info->audioOuts != 2)
  187. continue;
  188. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0)
  189. continue;
  190. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  191. if (info->midiIns != 1 || info->midiOuts != 1)
  192. continue;
  193. if (info->audioIns != 0 || info->audioOuts != 0)
  194. continue;
  195. #else
  196. if (info->audioIns != 2 || info->audioOuts != 2)
  197. continue;
  198. #endif
  199. j = fPluginCount;
  200. fPlugins[j].name = strdup(info->name);
  201. fPlugins[j].label = strdup(info->label);
  202. ++fPluginCount;
  203. }
  204. }
  205. if (! shouldThreadExit())
  206. fPluginScanningFinished = true;
  207. }
  208. void onImGuiDisplay() override
  209. {
  210. switch (fDrawingState)
  211. {
  212. case kDrawingInit:
  213. case kDrawingLoading:
  214. case kDrawingPluginPendingFromInit:
  215. drawLoading();
  216. break;
  217. case kDrawingPluginList:
  218. drawPluginList();
  219. break;
  220. case kDrawingError:
  221. // TODO display error message
  222. break;
  223. case kDrawingPluginGenericUI:
  224. drawGenericUI();
  225. // fall-through
  226. case kDrawingPluginCustomUI:
  227. drawTopBar();
  228. break;
  229. }
  230. }
  231. void drawTopBar()
  232. {
  233. ImGui::SetNextWindowPos(ImVec2(0, 0));
  234. ImGui::SetNextWindowSize(ImVec2(getWidth(), kExtraHeight * getScaleFactor()));
  235. if (ImGui::Begin("Current Plugin", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  236. {
  237. if (ImGui::Button("Pick Another..."))
  238. {
  239. hidePluginUI();
  240. fDrawingState = kDrawingPluginList;
  241. const double scaleFactor = getScaleFactor();
  242. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  243. return ImGui::End();
  244. }
  245. if (fDrawingState == kDrawingPluginGenericUI)
  246. {
  247. ImGui::SameLine();
  248. if (ImGui::Button("Show Custom GUI"))
  249. {
  250. carla_show_custom_ui(fPlugin->fCarlaHostHandle, 0, true);
  251. return ImGui::End();
  252. }
  253. }
  254. }
  255. ImGui::End();
  256. }
  257. void setupMainWindowPos()
  258. {
  259. float y = 0;
  260. float height = getHeight();
  261. if (fDrawingState == kDrawingPluginGenericUI)
  262. {
  263. y = (kExtraHeight - 1) * getScaleFactor();
  264. height -= y;
  265. }
  266. ImGui::SetNextWindowPos(ImVec2(0, y));
  267. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  268. }
  269. void drawGenericUI()
  270. {
  271. setupMainWindowPos();
  272. if (ImGui::Begin("Plugin Parameters", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  273. {
  274. ImGui::TextUnformatted("TODO :: here will go plugin parameters", nullptr);
  275. }
  276. ImGui::End();
  277. }
  278. void drawLoading()
  279. {
  280. setupMainWindowPos();
  281. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  282. {
  283. ImGui::TextUnformatted("Loading...", nullptr);
  284. }
  285. ImGui::End();
  286. }
  287. void drawPluginList()
  288. {
  289. setupMainWindowPos();
  290. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  291. const bool pluginIsRunning = carla_get_current_plugin_count(handle) != 0;
  292. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  293. {
  294. if (ImGui::InputText("", fPluginSearchString, sizeof(fPluginSearchString)-1, ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  295. fPluginSearchActive = true;
  296. ImGui::BeginDisabled(!fPluginScanningFinished);
  297. if (ImGui::Button("Load Plugin"))
  298. {
  299. if (pluginIsRunning)
  300. {
  301. hidePluginUI();
  302. carla_replace_plugin(handle, 0);
  303. }
  304. do {
  305. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  306. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  307. DISTRHO_SAFE_ASSERT_BREAK(slash != nullptr);
  308. d_stdout("Loading %s...", info.name);
  309. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_LV2, nullptr, nullptr,
  310. slash+1, 0, 0x0, PLUGIN_OPTIONS_NULL))
  311. {
  312. showPluginUI(handle);
  313. /*
  314. delete[] fPlugins;
  315. fPlugins = nullptr;
  316. fPluginCount = 0;
  317. */
  318. break;
  319. }
  320. } while (false);
  321. }
  322. ImGui::EndDisabled();
  323. if (pluginIsRunning)
  324. {
  325. ImGui::SameLine();
  326. if (ImGui::Button("Cancel"))
  327. {
  328. showPluginUI(handle);
  329. }
  330. }
  331. if (ImGui::BeginChild("pluginlistwindow"))
  332. {
  333. if (ImGui::BeginTable("pluginlist", 3, ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_NoClip))
  334. {
  335. ImGui::TableSetupColumn("Name");
  336. ImGui::TableSetupColumn("Bundle");
  337. ImGui::TableSetupColumn("URI");
  338. ImGui::TableHeadersRow();
  339. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  340. for (uint i=0; i<fPluginCount; ++i)
  341. {
  342. const PluginInfoCache& info(fPlugins[i]);
  343. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  344. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  345. if (search != nullptr && strcasestr(info.name, search) == nullptr)
  346. continue;
  347. bool selected = fPluginSelected == i;
  348. ImGui::TableNextRow();
  349. ImGui::TableSetColumnIndex(0);
  350. ImGui::Selectable(info.name, &selected);
  351. ImGui::TableSetColumnIndex(1);
  352. ImGui::Selectable(slash+1, &selected);
  353. ImGui::TableSetColumnIndex(2);
  354. ImGui::TextUnformatted(info.label, slash);
  355. if (selected)
  356. fPluginSelected = i;
  357. }
  358. ImGui::EndTable();
  359. }
  360. ImGui::EndChild();
  361. }
  362. }
  363. ImGui::End();
  364. }
  365. protected:
  366. /* --------------------------------------------------------------------------------------------------------
  367. * DSP/Plugin Callbacks */
  368. void parameterChanged(uint32_t, float) override
  369. {
  370. }
  371. void stateChanged(const char* const key, const char* const) override
  372. {
  373. if (std::strcmp(key, "project") == 0)
  374. hidePluginUI();
  375. }
  376. // -------------------------------------------------------------------------------------------------------
  377. private:
  378. /**
  379. Set our UI class as non-copyable and add a leak detector just in case.
  380. */
  381. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  382. };
  383. /* ------------------------------------------------------------------------------------------------------------
  384. * UI entry point, called by DPF to create a new UI instance. */
  385. UI* createUI()
  386. {
  387. return new IldaeilUI();
  388. }
  389. // -----------------------------------------------------------------------------------------------------------
  390. END_NAMESPACE_DISTRHO