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.

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