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.

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