From 2db263c12245d89c01fd4f97385c0777d35ae2ca Mon Sep 17 00:00:00 2001 From: jules Date: Wed, 16 Apr 2014 15:25:21 +0100 Subject: [PATCH] Refactored StandaloneFilterWindow, breaking it up so that there's a separate class StandalonePluginHolder which can be used inside more customised windows. --- .../Standalone/juce_StandaloneFilterWindow.h | 409 ++++++++++-------- 1 file changed, 238 insertions(+), 171 deletions(-) diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index bb8abe544f..9ff86b23be 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -29,152 +29,58 @@ extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); //============================================================================== /** - A class that can be used to run a simple standalone application containing your filter. + An object that creates and plays a standalone instance of an AudioProcessor. - Just create one of these objects in your JUCEApplicationBase::initialise() method, and - let it do its work. It will create your filter object using the same createPluginFilter() function - that the other plugin wrappers use. + The object will create your processor using the same createPluginFilter() + function that the other plugin wrappers use, and will run it through the + computer's audio/MIDI devices using AudioDeviceManager and AudioProcessorPlayer. */ -class StandaloneFilterWindow : public DocumentWindow, - public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) +class StandalonePluginHolder { public: - //============================================================================== - /** Creates a window with a given title and colour. + /** Creates an instance of the default plugin. + The settings object can be a PropertySet that the class should use to store its settings - the object that is passed-in will be owned by this class and deleted automatically when no longer needed. (It can also be null) */ - StandaloneFilterWindow (const String& title, - Colour backgroundColour, - PropertySet* settingsToUse) - : DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), - settings (settingsToUse), - optionsButton ("options") + StandalonePluginHolder (PropertySet* settingsToUse) + : settings (settingsToUse) { - setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); - - Component::addAndMakeVisible (optionsButton); - optionsButton.addListener (this); - optionsButton.setTriggeredOnMouseDown (true); - - createFilter(); - - if (filter == nullptr) - { - jassertfalse // Your filter didn't create correctly! In a standalone app that's not too great. - JUCEApplicationBase::quit(); - } - - filter->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, - JucePlugin_MaxNumOutputChannels, - 44100, 512); - - deviceManager = new AudioDeviceManager(); - deviceManager->addAudioCallback (&player); - deviceManager->addMidiInputCallback (String::empty, &player); - - player.setProcessor (filter); - - ScopedPointer savedState; - - if (settings != nullptr) - savedState = settings->getXmlValue ("audioSetup"); - - deviceManager->initialise (filter->getNumInputChannels(), - filter->getNumOutputChannels(), - savedState, - true); - - if (settings != nullptr) - { - MemoryBlock data; - - if (data.fromBase64Encoding (settings->getValue ("filterState")) - && data.getSize() > 0) - { - filter->setStateInformation (data.getData(), (int) data.getSize()); - } - } - - setContentOwned (filter->createEditorIfNeeded(), true); - - if (settings != nullptr) - { - const int x = settings->getIntValue ("windowX", -100); - const int y = settings->getIntValue ("windowY", -100); - - if (x != -100 && y != -100) - setBoundsConstrained (juce::Rectangle (x, y, getWidth(), getHeight())); - else - centreWithSize (getWidth(), getHeight()); - } - else - { - centreWithSize (getWidth(), getHeight()); - } + createPlugin(); + setupAudioDevices(); + reloadPluginState(); + startPlaying(); } - ~StandaloneFilterWindow() + ~StandalonePluginHolder() { - if (settings != nullptr) - { - settings->setValue ("windowX", getX()); - settings->setValue ("windowY", getY()); - - if (deviceManager != nullptr) - { - ScopedPointer xml (deviceManager->createStateXml()); - settings->setValue ("audioSetup", xml); - } - } - - deviceManager->removeMidiInputCallback (String::empty, &player); - deviceManager->removeAudioCallback (&player); - deviceManager = nullptr; - - if (settings != nullptr && filter != nullptr) - { - MemoryBlock data; - filter->getStateInformation (data); - - settings->setValue ("filterState", data.toBase64Encoding()); - } - - deleteFilter(); + deletePlugin(); + shutDownAudioDevices(); } //============================================================================== - AudioProcessor* getAudioProcessor() const noexcept { return filter; } - AudioDeviceManager* getDeviceManager() const noexcept { return deviceManager; } - - void createFilter() + void createPlugin() { AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Standalone); - filter = createPluginFilter(); + processor = createPluginFilter(); + jassert (processor != nullptr); // Your createPluginFilter() function must return a valid object! AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Undefined); + + processor->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, + JucePlugin_MaxNumOutputChannels, + 44100, 512); } - /** Deletes and re-creates the filter and its UI. */ - void resetFilter() + void deletePlugin() { - deleteFilter(); - createFilter(); - - if (filter != nullptr) - { - if (deviceManager != nullptr) - player.setProcessor (filter); - - setContentOwned (filter->createEditorIfNeeded(), true); - } - - if (settings != nullptr) - settings->removeValue ("filterState"); + stopPlaying(); + processor = nullptr; } - /** Pops up a dialog letting the user save the filter's state to a file. */ - void saveState() + //============================================================================== + /** Pops up a dialog letting the user save the processor's state to a file. */ + void askUserToSaveState() { FileChooser fc (TRANS("Save current state"), settings != nullptr ? File (settings->getValue ("lastStateFile")) @@ -183,7 +89,7 @@ public: if (fc.browseForFileToSave (true)) { MemoryBlock data; - filter->getStateInformation (data); + processor->getStateInformation (data); if (! fc.getResult().replaceWithData (data.getData(), data.getSize())) { @@ -194,8 +100,8 @@ public: } } - /** Pops up a dialog letting the user re-load the filter's state from a file. */ - void loadState() + /** Pops up a dialog letting the user re-load the processor's state from a file. */ + void askUserToLoadState() { FileChooser fc (TRANS("Load a saved state"), settings != nullptr ? File (settings->getValue ("lastStateFile")) @@ -207,7 +113,7 @@ public: if (fc.getResult().loadFileAsData (data)) { - filter->setStateInformation (data.getData(), (int) data.getSize()); + processor->setStateInformation (data.getData(), (int) data.getSize()); } else { @@ -218,16 +124,29 @@ public: } } - /** Shows the audio properties dialog box modally. */ - virtual void showAudioSettingsDialog() + //============================================================================== + void startPlaying() + { + player.setProcessor (processor); + } + + void stopPlaying() + { + player.setProcessor (nullptr); + } + + //============================================================================== + /** Shows an audio properties dialog box modally. */ + void showAudioSettingsDialog() { DialogWindow::LaunchOptions o; - o.content.setOwned (new AudioDeviceSelectorComponent (*deviceManager, - filter->getNumInputChannels(), - filter->getNumInputChannels(), - filter->getNumOutputChannels(), - filter->getNumOutputChannels(), - true, false, true, false)); + o.content.setOwned (new AudioDeviceSelectorComponent (deviceManager, + processor->getNumInputChannels(), + processor->getNumInputChannels(), + processor->getNumOutputChannels(), + processor->getNumOutputChannels(), + true, false, + true, false)); o.content->setSize (500, 450); o.dialogTitle = TRANS("Audio Settings"); @@ -239,66 +158,214 @@ public: o.launchAsync(); } - //============================================================================== - /** @internal */ - void closeButtonPressed() override + void saveAudioDeviceState() { - JUCEApplicationBase::quit(); + if (settings != nullptr) + { + ScopedPointer xml (deviceManager.createStateXml()); + settings->setValue ("audioSetup", xml); + } } - /** @internal */ - void buttonClicked (Button*) override + void reloadAudioDeviceState() + { + ScopedPointer savedState; + + if (settings != nullptr) + savedState = settings->getXmlValue ("audioSetup"); + + deviceManager.initialise (processor->getNumInputChannels(), + processor->getNumOutputChannels(), + savedState, + true); + } + + //============================================================================== + void savePluginState() { - if (filter != nullptr) + if (settings != nullptr && processor != nullptr) { - PopupMenu m; - m.addItem (1, TRANS("Audio Settings...")); - m.addSeparator(); - m.addItem (2, TRANS("Save current state...")); - m.addItem (3, TRANS("Load a saved state...")); - m.addSeparator(); - m.addItem (4, TRANS("Reset to default state")); - - switch (m.showAt (&optionsButton)) - { - case 1: showAudioSettingsDialog(); break; - case 2: saveState(); break; - case 3: loadState(); break; - case 4: resetFilter(); break; - default: break; - } + MemoryBlock data; + processor->getStateInformation (data); + + settings->setValue ("filterState", data.toBase64Encoding()); } } - /** @internal */ - void resized() override + void reloadPluginState() { - DocumentWindow::resized(); - optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); + if (settings != nullptr) + { + MemoryBlock data; + + if (data.fromBase64Encoding (settings->getValue ("filterState")) && data.getSize() > 0) + processor->setStateInformation (data.getData(), (int) data.getSize()); + } } -private: //============================================================================== ScopedPointer settings; - ScopedPointer filter; - ScopedPointer deviceManager; + ScopedPointer processor; + AudioDeviceManager deviceManager; AudioProcessorPlayer player; - TextButton optionsButton; - void deleteFilter() +private: + void setupAudioDevices() { - player.setProcessor (nullptr); + deviceManager.addAudioCallback (&player); + deviceManager.addMidiInputCallback (String::empty, &player); + + reloadAudioDeviceState(); + } + + void shutDownAudioDevices() + { + saveAudioDeviceState(); + + deviceManager.removeMidiInputCallback (String::empty, &player); + deviceManager.removeAudioCallback (&player); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandalonePluginHolder) +}; + + +//============================================================================== +/** + A class that can be used to run a simple standalone application containing your filter. + + Just create one of these objects in your JUCEApplicationBase::initialise() method, and + let it do its work. It will create your filter object using the same createPluginFilter() function + that the other plugin wrappers use. +*/ +class StandaloneFilterWindow : public DocumentWindow, + public ButtonListener // (can't use Button::Listener due to VC2005 bug) +{ +public: + //============================================================================== + /** Creates a window with a given title and colour. + The settings object can be a PropertySet that the class should use to + store its settings - the object that is passed-in will be owned by this + class and deleted automatically when no longer needed. (It can also be null) + */ + StandaloneFilterWindow (const String& title, + Colour backgroundColour, + PropertySet* settingsToUse) + : DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), + optionsButton ("options") + { + setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); + + Component::addAndMakeVisible (optionsButton); + optionsButton.addListener (this); + optionsButton.setTriggeredOnMouseDown (true); + + pluginHolder = new StandalonePluginHolder (settingsToUse); + + createEditorComp(); + + if (PropertySet* props = pluginHolder->settings) + { + const int x = props->getIntValue ("windowX", -100); + const int y = props->getIntValue ("windowY", -100); + + if (x != -100 && y != -100) + setBoundsConstrained (juce::Rectangle (x, y, getWidth(), getHeight())); + else + centreWithSize (getWidth(), getHeight()); + } + else + { + centreWithSize (getWidth(), getHeight()); + } + } - if (filter != nullptr && getContentComponent() != nullptr) + ~StandaloneFilterWindow() + { + if (PropertySet* props = pluginHolder->settings) { - filter->editorBeingDeleted (dynamic_cast (getContentComponent())); + props->setValue ("windowX", getX()); + props->setValue ("windowY", getY()); + } + + pluginHolder->stopPlaying(); + deleteEditorComp(); + pluginHolder = nullptr; + } + + //============================================================================== + AudioProcessor* getAudioProcessor() const noexcept { return pluginHolder->processor; } + AudioDeviceManager& getDeviceManager() const noexcept { return pluginHolder->deviceManager; } + + void createEditorComp() + { + setContentOwned (getAudioProcessor()->createEditorIfNeeded(), true); + } + + void deleteEditorComp() + { + if (AudioProcessorEditor* ed = dynamic_cast (getContentComponent())) + { + pluginHolder->processor->editorBeingDeleted (ed); clearContentComponent(); } + } - filter = nullptr; + /** Deletes and re-creates the plugin, resetting it to its default state. */ + void resetToDefaultState() + { + pluginHolder->stopPlaying(); + deleteEditorComp(); + pluginHolder->deletePlugin(); + + if (PropertySet* props = pluginHolder->settings) + props->removeValue ("filterState"); + + pluginHolder->createPlugin(); + createEditorComp(); + pluginHolder->startPlaying(); + } + + //============================================================================== + void closeButtonPressed() override + { + JUCEApplicationBase::quit(); + } + + void buttonClicked (Button*) override + { + PopupMenu m; + m.addItem (1, TRANS("Audio Settings...")); + m.addSeparator(); + m.addItem (2, TRANS("Save current state...")); + m.addItem (3, TRANS("Load a saved state...")); + m.addSeparator(); + m.addItem (4, TRANS("Reset to default state")); + + switch (m.showAt (&optionsButton)) + { + case 1: pluginHolder->showAudioSettingsDialog(); break; + case 2: pluginHolder->askUserToSaveState(); break; + case 3: pluginHolder->askUserToLoadState(); break; + case 4: resetToDefaultState(); break; + default: break; + } + } + + void resized() override + { + DocumentWindow::resized(); + optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); } + ScopedPointer pluginHolder; + +private: + //============================================================================== + TextButton optionsButton; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow) }; + #endif // JUCE_STANDALONEFILTERWINDOW_H_INCLUDED