The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

589 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: HostPluginDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Plugin that can host other plugins
  24. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  25. juce_audio_plugin_client, juce_audio_processors,
  26. juce_audio_utils, juce_core, juce_data_structures,
  27. juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
  28. exporters: xcode_mac, vs2022, linux_make
  29. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  30. JUCE_PLUGINHOST_LV2=1
  31. JUCE_PLUGINHOST_VST3=1
  32. JUCE_PLUGINHOST_VST=0
  33. JUCE_PLUGINHOST_AU=1
  34. type: AudioProcessor
  35. mainClass: HostAudioProcessor
  36. useLocalCopy: 1
  37. pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn, pluginProducesMidiOut,
  38. pluginEditorRequiresKeys
  39. END_JUCE_PIP_METADATA
  40. *******************************************************************************/
  41. #pragma once
  42. //==============================================================================
  43. enum class EditorStyle { thisWindow, newWindow };
  44. class HostAudioProcessorImpl : public AudioProcessor,
  45. private ChangeListener
  46. {
  47. public:
  48. HostAudioProcessorImpl()
  49. : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
  50. .withOutput ("Output", AudioChannelSet::stereo(), true))
  51. {
  52. appProperties.setStorageParameters ([&]
  53. {
  54. PropertiesFile::Options opt;
  55. opt.applicationName = getName();
  56. opt.commonToAllUsers = false;
  57. opt.doNotSave = false;
  58. opt.filenameSuffix = ".props";
  59. opt.ignoreCaseOfKeyNames = false;
  60. opt.storageFormat = PropertiesFile::StorageFormat::storeAsXML;
  61. opt.osxLibrarySubFolder = "Application Support";
  62. return opt;
  63. }());
  64. pluginFormatManager.addDefaultFormats();
  65. if (auto savedPluginList = appProperties.getUserSettings()->getXmlValue ("pluginList"))
  66. pluginList.recreateFromXml (*savedPluginList);
  67. MessageManagerLock lock;
  68. pluginList.addChangeListener (this);
  69. }
  70. bool isBusesLayoutSupported (const BusesLayout& layouts) const override
  71. {
  72. const auto& mainOutput = layouts.getMainOutputChannelSet();
  73. const auto& mainInput = layouts.getMainInputChannelSet();
  74. if (! mainInput.isDisabled() && mainInput != mainOutput)
  75. return false;
  76. if (mainOutput.size() > 2)
  77. return false;
  78. return true;
  79. }
  80. void prepareToPlay (double sr, int bs) override
  81. {
  82. const ScopedLock sl (innerMutex);
  83. active = true;
  84. if (inner != nullptr)
  85. {
  86. inner->setRateAndBufferSizeDetails (sr, bs);
  87. inner->prepareToPlay (sr, bs);
  88. }
  89. }
  90. void releaseResources() override
  91. {
  92. const ScopedLock sl (innerMutex);
  93. active = false;
  94. if (inner != nullptr)
  95. inner->releaseResources();
  96. }
  97. void reset() override
  98. {
  99. const ScopedLock sl (innerMutex);
  100. if (inner != nullptr)
  101. inner->reset();
  102. }
  103. // In this example, we don't actually pass any audio through the inner processor.
  104. // In a 'real' plugin, we'd need to add some synchronisation to ensure that the inner
  105. // plugin instance was never modified (deleted, replaced etc.) during a call to processBlock.
  106. void processBlock (AudioBuffer<float>&, MidiBuffer&) override
  107. {
  108. jassert (! isUsingDoublePrecision());
  109. }
  110. void processBlock (AudioBuffer<double>&, MidiBuffer&) override
  111. {
  112. jassert (isUsingDoublePrecision());
  113. }
  114. bool hasEditor() const override { return false; }
  115. AudioProcessorEditor* createEditor() override { return nullptr; }
  116. const String getName() const override { return "HostPluginDemo"; }
  117. bool acceptsMidi() const override { return true; }
  118. bool producesMidi() const override { return true; }
  119. double getTailLengthSeconds() const override { return 0.0; }
  120. int getNumPrograms() override { return 0; }
  121. int getCurrentProgram() override { return 0; }
  122. void setCurrentProgram (int) override {}
  123. const String getProgramName (int) override { return "None"; }
  124. void changeProgramName (int, const String&) override {}
  125. void getStateInformation (MemoryBlock& destData) override
  126. {
  127. const ScopedLock sl (innerMutex);
  128. XmlElement xml ("state");
  129. if (inner != nullptr)
  130. {
  131. xml.setAttribute (editorStyleTag, (int) editorStyle);
  132. xml.addChildElement (inner->getPluginDescription().createXml().release());
  133. xml.addChildElement ([this]
  134. {
  135. MemoryBlock innerState;
  136. inner->getStateInformation (innerState);
  137. auto stateNode = std::make_unique<XmlElement> (innerStateTag);
  138. stateNode->addTextElement (innerState.toBase64Encoding());
  139. return stateNode.release();
  140. }());
  141. }
  142. const auto text = xml.toString();
  143. destData.replaceAll (text.toRawUTF8(), text.getNumBytesAsUTF8());
  144. }
  145. void setStateInformation (const void* data, int sizeInBytes) override
  146. {
  147. const ScopedLock sl (innerMutex);
  148. auto xml = XmlDocument::parse (String (CharPointer_UTF8 (static_cast<const char*> (data)), (size_t) sizeInBytes));
  149. if (auto* pluginNode = xml->getChildByName ("PLUGIN"))
  150. {
  151. PluginDescription pd;
  152. pd.loadFromXml (*pluginNode);
  153. MemoryBlock innerState;
  154. innerState.fromBase64Encoding (xml->getChildElementAllSubText (innerStateTag, {}));
  155. setNewPlugin (pd,
  156. (EditorStyle) xml->getIntAttribute (editorStyleTag, 0),
  157. innerState);
  158. }
  159. }
  160. void setNewPlugin (const PluginDescription& pd, EditorStyle where, const MemoryBlock& mb = {})
  161. {
  162. const ScopedLock sl (innerMutex);
  163. const auto callback = [this, where, mb] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
  164. {
  165. if (error.isNotEmpty())
  166. {
  167. NativeMessageBox::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  168. "Plugin Load Failed",
  169. error,
  170. nullptr,
  171. nullptr);
  172. return;
  173. }
  174. inner = std::move (instance);
  175. editorStyle = where;
  176. if (inner != nullptr && ! mb.isEmpty())
  177. inner->setStateInformation (mb.getData(), (int) mb.getSize());
  178. // In a 'real' plugin, we'd also need to set the bus configuration of the inner plugin.
  179. // One possibility would be to match the bus configuration of the wrapper plugin, but
  180. // the inner plugin isn't guaranteed to support the same layout. Alternatively, we
  181. // could try to apply a reasonably similar layout, and maintain a mapping between the
  182. // inner/outer channel layouts.
  183. //
  184. // In any case, it is essential that the inner plugin is told about the bus
  185. // configuration that will be used. The AudioBuffer passed to the inner plugin must also
  186. // exactly match this layout.
  187. if (active)
  188. {
  189. inner->setRateAndBufferSizeDetails (getSampleRate(), getBlockSize());
  190. inner->prepareToPlay (getSampleRate(), getBlockSize());
  191. }
  192. NullCheckedInvocation::invoke (pluginChanged);
  193. };
  194. pluginFormatManager.createPluginInstanceAsync (pd, getSampleRate(), getBlockSize(), callback);
  195. }
  196. void clearPlugin()
  197. {
  198. const ScopedLock sl (innerMutex);
  199. inner = nullptr;
  200. NullCheckedInvocation::invoke (pluginChanged);
  201. }
  202. bool isPluginLoaded() const
  203. {
  204. const ScopedLock sl (innerMutex);
  205. return inner != nullptr;
  206. }
  207. std::unique_ptr<AudioProcessorEditor> createInnerEditor() const
  208. {
  209. const ScopedLock sl (innerMutex);
  210. return rawToUniquePtr (inner->hasEditor() ? inner->createEditorIfNeeded() : nullptr);
  211. }
  212. EditorStyle getEditorStyle() const noexcept { return editorStyle; }
  213. ApplicationProperties appProperties;
  214. AudioPluginFormatManager pluginFormatManager;
  215. KnownPluginList pluginList;
  216. std::function<void()> pluginChanged;
  217. private:
  218. CriticalSection innerMutex;
  219. std::unique_ptr<AudioPluginInstance> inner;
  220. EditorStyle editorStyle = EditorStyle{};
  221. bool active = false;
  222. static constexpr const char* innerStateTag = "inner_state";
  223. static constexpr const char* editorStyleTag = "editor_style";
  224. void changeListenerCallback (ChangeBroadcaster* source) override
  225. {
  226. if (source != &pluginList)
  227. return;
  228. if (auto savedPluginList = pluginList.createXml())
  229. {
  230. appProperties.getUserSettings()->setValue ("pluginList", savedPluginList.get());
  231. appProperties.saveIfNeeded();
  232. }
  233. }
  234. };
  235. constexpr const char* HostAudioProcessorImpl::innerStateTag;
  236. constexpr const char* HostAudioProcessorImpl::editorStyleTag;
  237. //==============================================================================
  238. constexpr auto margin = 10;
  239. static void doLayout (Component* main, Component& bottom, int bottomHeight, Rectangle<int> bounds)
  240. {
  241. Grid grid;
  242. grid.setGap (Grid::Px { margin });
  243. grid.templateColumns = { Grid::TrackInfo { Grid::Fr { 1 } } };
  244. grid.templateRows = { Grid::TrackInfo { Grid::Fr { 1 } },
  245. Grid::TrackInfo { Grid::Px { bottomHeight }} };
  246. grid.items = { GridItem { main }, GridItem { bottom }.withMargin ({ 0, margin, margin, margin }) };
  247. grid.performLayout (bounds);
  248. }
  249. class PluginLoaderComponent : public Component
  250. {
  251. public:
  252. template <typename Callback>
  253. PluginLoaderComponent (AudioPluginFormatManager& manager,
  254. KnownPluginList& list,
  255. Callback&& callback)
  256. : pluginListComponent (manager, list, {}, {})
  257. {
  258. pluginListComponent.getTableListBox().setMultipleSelectionEnabled (false);
  259. addAndMakeVisible (pluginListComponent);
  260. addAndMakeVisible (buttons);
  261. const auto getCallback = [this, &list, callback = std::forward<Callback> (callback)] (EditorStyle style)
  262. {
  263. return [this, &list, callback, style]
  264. {
  265. const auto index = pluginListComponent.getTableListBox().getSelectedRow();
  266. const auto& types = list.getTypes();
  267. if (isPositiveAndBelow (index, types.size()))
  268. NullCheckedInvocation::invoke (callback, types.getReference (index), style);
  269. };
  270. };
  271. buttons.thisWindowButton.onClick = getCallback (EditorStyle::thisWindow);
  272. buttons.newWindowButton .onClick = getCallback (EditorStyle::newWindow);
  273. }
  274. void resized() override
  275. {
  276. doLayout (&pluginListComponent, buttons, 80, getLocalBounds());
  277. }
  278. private:
  279. struct Buttons : public Component
  280. {
  281. Buttons()
  282. {
  283. label.setJustificationType (Justification::centred);
  284. addAndMakeVisible (label);
  285. addAndMakeVisible (thisWindowButton);
  286. addAndMakeVisible (newWindowButton);
  287. }
  288. void resized() override
  289. {
  290. Grid vertical;
  291. vertical.autoFlow = Grid::AutoFlow::row;
  292. vertical.setGap (Grid::Px { margin });
  293. vertical.autoRows = vertical.autoColumns = Grid::TrackInfo { Grid::Fr { 1 } };
  294. vertical.items.insertMultiple (0, GridItem{}, 2);
  295. vertical.performLayout (getLocalBounds());
  296. label.setBounds (vertical.items[0].currentBounds.toNearestInt());
  297. Grid grid;
  298. grid.autoFlow = Grid::AutoFlow::column;
  299. grid.setGap (Grid::Px { margin });
  300. grid.autoRows = grid.autoColumns = Grid::TrackInfo { Grid::Fr { 1 } };
  301. grid.items = { GridItem { thisWindowButton },
  302. GridItem { newWindowButton } };
  303. grid.performLayout (vertical.items[1].currentBounds.toNearestInt());
  304. }
  305. Label label { "", "Select a plugin from the list, then display it using the buttons below." };
  306. TextButton thisWindowButton { "Open In This Window" };
  307. TextButton newWindowButton { "Open In New Window" };
  308. };
  309. PluginListComponent pluginListComponent;
  310. Buttons buttons;
  311. };
  312. //==============================================================================
  313. class PluginEditorComponent : public Component
  314. {
  315. public:
  316. template <typename Callback>
  317. PluginEditorComponent (std::unique_ptr<AudioProcessorEditor> editorIn, Callback&& onClose)
  318. : editor (std::move (editorIn))
  319. {
  320. addAndMakeVisible (editor.get());
  321. addAndMakeVisible (closeButton);
  322. childBoundsChanged (editor.get());
  323. closeButton.onClick = std::forward<Callback> (onClose);
  324. }
  325. void setScaleFactor (float scale)
  326. {
  327. if (editor != nullptr)
  328. editor->setScaleFactor (scale);
  329. }
  330. void resized() override
  331. {
  332. doLayout (editor.get(), closeButton, buttonHeight, getLocalBounds());
  333. }
  334. void childBoundsChanged (Component* child) override
  335. {
  336. if (child != editor.get())
  337. return;
  338. const auto size = editor != nullptr ? editor->getLocalBounds()
  339. : Rectangle<int>();
  340. setSize (size.getWidth(), margin + buttonHeight + size.getHeight());
  341. }
  342. private:
  343. static constexpr auto buttonHeight = 40;
  344. std::unique_ptr<AudioProcessorEditor> editor;
  345. TextButton closeButton { "Close Plugin" };
  346. };
  347. //==============================================================================
  348. class ScaledDocumentWindow : public DocumentWindow
  349. {
  350. public:
  351. ScaledDocumentWindow (Colour bg, float scale)
  352. : DocumentWindow ("Editor", bg, 0), desktopScale (scale) {}
  353. float getDesktopScaleFactor() const override { return Desktop::getInstance().getGlobalScaleFactor() * desktopScale; }
  354. private:
  355. float desktopScale = 1.0f;
  356. };
  357. //==============================================================================
  358. class HostAudioProcessorEditor : public AudioProcessorEditor
  359. {
  360. public:
  361. explicit HostAudioProcessorEditor (HostAudioProcessorImpl& owner)
  362. : AudioProcessorEditor (owner),
  363. hostProcessor (owner),
  364. loader (owner.pluginFormatManager,
  365. owner.pluginList,
  366. [&owner] (const PluginDescription& pd,
  367. EditorStyle editorStyle)
  368. {
  369. owner.setNewPlugin (pd, editorStyle);
  370. }),
  371. scopedCallback (owner.pluginChanged, [this] { pluginChanged(); })
  372. {
  373. setSize (500, 500);
  374. setResizable (false, false);
  375. addAndMakeVisible (closeButton);
  376. addAndMakeVisible (loader);
  377. hostProcessor.pluginChanged();
  378. closeButton.onClick = [this] { clearPlugin(); };
  379. }
  380. void paint (Graphics& g) override
  381. {
  382. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
  383. }
  384. void resized() override
  385. {
  386. closeButton.setBounds (getLocalBounds().withSizeKeepingCentre (200, buttonHeight));
  387. loader.setBounds (getLocalBounds());
  388. }
  389. void childBoundsChanged (Component* child) override
  390. {
  391. if (child != editor.get())
  392. return;
  393. const auto size = editor != nullptr ? editor->getLocalBounds()
  394. : Rectangle<int>();
  395. setSize (size.getWidth(), size.getHeight());
  396. }
  397. void setScaleFactor (float scale) override
  398. {
  399. currentScaleFactor = scale;
  400. AudioProcessorEditor::setScaleFactor (scale);
  401. const auto posted = MessageManager::callAsync ([ref = SafePointer<HostAudioProcessorEditor> (this), scale]
  402. {
  403. if (auto* r = ref.getComponent())
  404. if (auto* e = r->currentEditorComponent)
  405. e->setScaleFactor (scale);
  406. });
  407. jassertquiet (posted);
  408. }
  409. private:
  410. void pluginChanged()
  411. {
  412. loader.setVisible (! hostProcessor.isPluginLoaded());
  413. closeButton.setVisible (hostProcessor.isPluginLoaded());
  414. if (hostProcessor.isPluginLoaded())
  415. {
  416. auto editorComponent = std::make_unique<PluginEditorComponent> (hostProcessor.createInnerEditor(), [this]
  417. {
  418. const auto posted = MessageManager::callAsync ([this] { clearPlugin(); });
  419. jassertquiet (posted);
  420. });
  421. editorComponent->setScaleFactor (currentScaleFactor);
  422. currentEditorComponent = editorComponent.get();
  423. editor = [&]() -> std::unique_ptr<Component>
  424. {
  425. switch (hostProcessor.getEditorStyle())
  426. {
  427. case EditorStyle::thisWindow:
  428. addAndMakeVisible (editorComponent.get());
  429. setSize (editorComponent->getWidth(), editorComponent->getHeight());
  430. return std::move (editorComponent);
  431. case EditorStyle::newWindow:
  432. const auto bg = getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker();
  433. auto window = std::make_unique<ScaledDocumentWindow> (bg, currentScaleFactor);
  434. window->setAlwaysOnTop (true);
  435. window->setContentOwned (editorComponent.release(), true);
  436. window->centreAroundComponent (this, window->getWidth(), window->getHeight());
  437. window->setVisible (true);
  438. return window;
  439. }
  440. jassertfalse;
  441. return nullptr;
  442. }();
  443. }
  444. else
  445. {
  446. editor = nullptr;
  447. setSize (500, 500);
  448. }
  449. }
  450. void clearPlugin()
  451. {
  452. currentEditorComponent = nullptr;
  453. editor = nullptr;
  454. hostProcessor.clearPlugin();
  455. }
  456. static constexpr auto buttonHeight = 30;
  457. HostAudioProcessorImpl& hostProcessor;
  458. PluginLoaderComponent loader;
  459. std::unique_ptr<Component> editor;
  460. PluginEditorComponent* currentEditorComponent = nullptr;
  461. ScopedValueSetter<std::function<void()>> scopedCallback;
  462. TextButton closeButton { "Close Plugin" };
  463. float currentScaleFactor = 1.0f;
  464. };
  465. //==============================================================================
  466. class HostAudioProcessor : public HostAudioProcessorImpl
  467. {
  468. public:
  469. bool hasEditor() const override { return true; }
  470. AudioProcessorEditor* createEditor() override { return new HostAudioProcessorEditor (*this); }
  471. };