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.

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