| @@ -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<XmlElement> 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<int> (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<XmlElement> 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<XmlElement> xml (deviceManager.createStateXml()); | |||
| settings->setValue ("audioSetup", xml); | |||
| } | |||
| } | |||
| /** @internal */ | |||
| void buttonClicked (Button*) override | |||
| void reloadAudioDeviceState() | |||
| { | |||
| ScopedPointer<XmlElement> 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<PropertySet> settings; | |||
| ScopedPointer<AudioProcessor> filter; | |||
| ScopedPointer<AudioDeviceManager> deviceManager; | |||
| ScopedPointer<AudioProcessor> 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<int> (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 <AudioProcessorEditor*> (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<AudioProcessorEditor*> (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<StandalonePluginHolder> pluginHolder; | |||
| private: | |||
| //============================================================================== | |||
| TextButton optionsButton; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow) | |||
| }; | |||
| #endif // JUCE_STANDALONEFILTERWINDOW_H_INCLUDED | |||