Create juce_StandaloneFilterWindow.h
JUCE 5 Standalone application code has been modified to make the window and menu look like macOS native on Mac builds.
This commit is contained in:
parent
f9fdff1a95
commit
13847a2ac8
1 changed files with 902 additions and 0 deletions
|
@ -0,0 +1,902 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
|
||||
extern juce::AudioProcessor* JUCE_API JUCE_CALLTYPE createPluginFilterOfType (juce::AudioProcessor::WrapperType type);
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An object that creates and plays a standalone instance of an AudioProcessor.
|
||||
|
||||
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.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class StandalonePluginHolder : private AudioIODeviceCallback,
|
||||
private Timer,
|
||||
private Value::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Structure used for the number of inputs and outputs. */
|
||||
struct PluginInOuts { short numIns, numOuts; };
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an instance of the default plugin.
|
||||
|
||||
The settings object can be a PropertySet that the class should use to store its
|
||||
settings - the takeOwnershipOfSettings indicates whether this object will delete
|
||||
the settings automatically when no longer needed. The settings can also be nullptr.
|
||||
|
||||
A default device name can be passed in.
|
||||
|
||||
Preferably a complete setup options object can be used, which takes precedence over
|
||||
the preferredDefaultDeviceName and allows you to select the input & output device names,
|
||||
sample rate, buffer size etc.
|
||||
|
||||
In all instances, the settingsToUse will take precedence over the "preferred" options if not null.
|
||||
*/
|
||||
StandalonePluginHolder (PropertySet* settingsToUse,
|
||||
bool takeOwnershipOfSettings = true,
|
||||
const String& preferredDefaultDeviceName = String(),
|
||||
const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions = nullptr,
|
||||
const Array<PluginInOuts>& channels = Array<PluginInOuts>(),
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
bool shouldAutoOpenMidiDevices = true
|
||||
#else
|
||||
bool shouldAutoOpenMidiDevices = true
|
||||
#endif
|
||||
)
|
||||
|
||||
: settings (settingsToUse, takeOwnershipOfSettings),
|
||||
channelConfiguration (channels),
|
||||
autoOpenMidiDevices (shouldAutoOpenMidiDevices)
|
||||
{
|
||||
shouldMuteInput.addListener (this);
|
||||
shouldMuteInput = ! isInterAppAudioConnected();
|
||||
|
||||
createPlugin();
|
||||
|
||||
auto inChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns
|
||||
: processor->getMainBusNumInputChannels());
|
||||
|
||||
if (preferredSetupOptions != nullptr)
|
||||
options.reset (new AudioDeviceManager::AudioDeviceSetup (*preferredSetupOptions));
|
||||
|
||||
auto audioInputRequired = (inChannels > 0);
|
||||
|
||||
if (audioInputRequired && RuntimePermissions::isRequired (RuntimePermissions::recordAudio)
|
||||
&& ! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
|
||||
RuntimePermissions::request (RuntimePermissions::recordAudio,
|
||||
[this, preferredDefaultDeviceName] (bool granted) { init (granted, preferredDefaultDeviceName); });
|
||||
else
|
||||
init (audioInputRequired, preferredDefaultDeviceName);
|
||||
}
|
||||
|
||||
void init (bool enableAudioInput, const String& preferredDefaultDeviceName)
|
||||
{
|
||||
setupAudioDevices (enableAudioInput, preferredDefaultDeviceName, options.get());
|
||||
reloadPluginState();
|
||||
startPlaying();
|
||||
|
||||
if (autoOpenMidiDevices)
|
||||
startTimer (500);
|
||||
}
|
||||
|
||||
virtual ~StandalonePluginHolder() override
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
deletePlugin();
|
||||
shutDownAudioDevices();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
virtual void createPlugin()
|
||||
{
|
||||
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
|
||||
processor.reset (::createPluginFilterOfType (AudioProcessor::wrapperType_Standalone));
|
||||
#else
|
||||
AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Standalone);
|
||||
processor.reset (createPluginFilter());
|
||||
AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Undefined);
|
||||
#endif
|
||||
jassert (processor != nullptr); // Your createPluginFilter() function must return a valid object!
|
||||
|
||||
processor->disableNonMainBuses();
|
||||
processor->setRateAndBufferSizeDetails (44100, 512);
|
||||
|
||||
int inChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns
|
||||
: processor->getMainBusNumInputChannels());
|
||||
|
||||
int outChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numOuts
|
||||
: processor->getMainBusNumOutputChannels());
|
||||
|
||||
processorHasPotentialFeedbackLoop = (inChannels > 0 && outChannels > 0);
|
||||
}
|
||||
|
||||
virtual void deletePlugin()
|
||||
{
|
||||
stopPlaying();
|
||||
processor = nullptr;
|
||||
}
|
||||
|
||||
static String getFilePatterns (const String& fileSuffix)
|
||||
{
|
||||
if (fileSuffix.isEmpty())
|
||||
return {};
|
||||
|
||||
return (fileSuffix.startsWithChar ('.') ? "*" : "*.") + fileSuffix;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Value& getMuteInputValue() { return shouldMuteInput; }
|
||||
bool getProcessorHasPotentialFeedbackLoop() const { return processorHasPotentialFeedbackLoop; }
|
||||
void valueChanged (Value& value) override { muteInput = (bool) value.getValue(); }
|
||||
|
||||
//==============================================================================
|
||||
File getLastFile() const
|
||||
{
|
||||
File f;
|
||||
|
||||
if (settings != nullptr)
|
||||
f = File (settings->getValue ("lastStateFile"));
|
||||
|
||||
if (f == File())
|
||||
f = File::getSpecialLocation (File::userDocumentsDirectory);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void setLastFile (const FileChooser& fc)
|
||||
{
|
||||
if (settings != nullptr)
|
||||
settings->setValue ("lastStateFile", fc.getResult().getFullPathName());
|
||||
}
|
||||
|
||||
/** Pops up a dialog letting the user save the processor's state to a file. */
|
||||
void askUserToSaveState (const String& fileSuffix = String())
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser fc (TRANS("Save current state"), getLastFile(), getFilePatterns (fileSuffix));
|
||||
|
||||
if (fc.browseForFileToSave (true))
|
||||
{
|
||||
setLastFile (fc);
|
||||
|
||||
MemoryBlock data;
|
||||
processor->getStateInformation (data);
|
||||
|
||||
if (! fc.getResult().replaceWithData (data.getData(), data.getSize()))
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
TRANS("Error whilst saving"),
|
||||
TRANS("Couldn't write to the specified file!"));
|
||||
}
|
||||
#else
|
||||
ignoreUnused (fileSuffix);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Pops up a dialog letting the user re-load the processor's state from a file. */
|
||||
void askUserToLoadState (const String& fileSuffix = String())
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser fc (TRANS("Load a saved state"), getLastFile(), getFilePatterns (fileSuffix));
|
||||
|
||||
if (fc.browseForFileToOpen())
|
||||
{
|
||||
setLastFile (fc);
|
||||
|
||||
MemoryBlock data;
|
||||
|
||||
if (fc.getResult().loadFileAsData (data))
|
||||
processor->setStateInformation (data.getData(), (int) data.getSize());
|
||||
else
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
TRANS("Error whilst loading"),
|
||||
TRANS("Couldn't read from the specified file!"));
|
||||
}
|
||||
#else
|
||||
ignoreUnused (fileSuffix);
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void startPlaying()
|
||||
{
|
||||
player.setProcessor (processor.get());
|
||||
|
||||
#if JucePlugin_Enable_IAA && JUCE_IOS
|
||||
if (auto device = dynamic_cast<iOSAudioIODevice*> (deviceManager.getCurrentAudioDevice()))
|
||||
{
|
||||
processor->setPlayHead (device->getAudioPlayHead());
|
||||
device->setMidiMessageCollector (&player.getMidiMessageCollector());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void stopPlaying()
|
||||
{
|
||||
player.setProcessor (nullptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Shows an audio properties dialog box modally. */
|
||||
void showAudioSettingsDialog()
|
||||
{
|
||||
DialogWindow::LaunchOptions o;
|
||||
|
||||
int maxNumInputs = 0, maxNumOutputs = 0;
|
||||
|
||||
if (channelConfiguration.size() > 0)
|
||||
{
|
||||
auto& defaultConfig = channelConfiguration.getReference (0);
|
||||
|
||||
maxNumInputs = jmax (0, (int) defaultConfig.numIns);
|
||||
maxNumOutputs = jmax (0, (int) defaultConfig.numOuts);
|
||||
}
|
||||
|
||||
if (auto* bus = processor->getBus (true, 0))
|
||||
maxNumInputs = jmax (0, bus->getDefaultLayout().size());
|
||||
|
||||
if (auto* bus = processor->getBus (false, 0))
|
||||
maxNumOutputs = jmax (0, bus->getDefaultLayout().size());
|
||||
|
||||
o.content.setOwned (new SettingsComponent (*this, deviceManager, maxNumInputs, maxNumOutputs));
|
||||
o.content->setSize (500, 550);
|
||||
|
||||
o.dialogTitle = TRANS("Audio/MIDI Settings");
|
||||
o.dialogBackgroundColour = o.content->getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
|
||||
o.escapeKeyTriggersCloseButton = true;
|
||||
o.useNativeTitleBar = true;
|
||||
o.resizable = false;
|
||||
|
||||
o.launchAsync();
|
||||
}
|
||||
|
||||
void saveAudioDeviceState()
|
||||
{
|
||||
if (settings != nullptr)
|
||||
{
|
||||
auto xml = deviceManager.createStateXml();
|
||||
|
||||
settings->setValue ("audioSetup", xml.get());
|
||||
|
||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||
settings->setValue ("shouldMuteInput", (bool) shouldMuteInput.getValue());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void reloadAudioDeviceState (bool enableAudioInput,
|
||||
const String& preferredDefaultDeviceName,
|
||||
const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions)
|
||||
{
|
||||
std::unique_ptr<XmlElement> savedState;
|
||||
|
||||
if (settings != nullptr)
|
||||
{
|
||||
savedState = settings->getXmlValue ("audioSetup");
|
||||
|
||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||
shouldMuteInput.setValue (settings->getBoolValue ("shouldMuteInput", true));
|
||||
#endif
|
||||
}
|
||||
|
||||
auto totalInChannels = processor->getMainBusNumInputChannels();
|
||||
auto totalOutChannels = processor->getMainBusNumOutputChannels();
|
||||
|
||||
if (channelConfiguration.size() > 0)
|
||||
{
|
||||
auto defaultConfig = channelConfiguration.getReference (0);
|
||||
totalInChannels = defaultConfig.numIns;
|
||||
totalOutChannels = defaultConfig.numOuts;
|
||||
}
|
||||
|
||||
deviceManager.initialise (enableAudioInput ? totalInChannels : 0,
|
||||
totalOutChannels,
|
||||
savedState.get(),
|
||||
true,
|
||||
preferredDefaultDeviceName,
|
||||
preferredSetupOptions);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void savePluginState()
|
||||
{
|
||||
if (settings != nullptr && processor != nullptr)
|
||||
{
|
||||
MemoryBlock data;
|
||||
processor->getStateInformation (data);
|
||||
|
||||
settings->setValue ("filterState", data.toBase64Encoding());
|
||||
}
|
||||
}
|
||||
|
||||
void reloadPluginState()
|
||||
{
|
||||
if (settings != nullptr)
|
||||
{
|
||||
MemoryBlock data;
|
||||
|
||||
if (data.fromBase64Encoding (settings->getValue ("filterState")) && data.getSize() > 0)
|
||||
processor->setStateInformation (data.getData(), (int) data.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void switchToHostApplication()
|
||||
{
|
||||
#if JUCE_IOS
|
||||
if (auto device = dynamic_cast<iOSAudioIODevice*> (deviceManager.getCurrentAudioDevice()))
|
||||
device->switchApplication();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isInterAppAudioConnected()
|
||||
{
|
||||
#if JUCE_IOS
|
||||
if (auto device = dynamic_cast<iOSAudioIODevice*> (deviceManager.getCurrentAudioDevice()))
|
||||
return device->isInterAppAudioConnected();
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_basics
|
||||
Image getIAAHostIcon (int size)
|
||||
{
|
||||
#if JUCE_IOS && JucePlugin_Enable_IAA
|
||||
if (auto device = dynamic_cast<iOSAudioIODevice*> (deviceManager.getCurrentAudioDevice()))
|
||||
return device->getIcon (size);
|
||||
#else
|
||||
ignoreUnused (size);
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
static StandalonePluginHolder* getInstance();
|
||||
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<PropertySet> settings;
|
||||
std::unique_ptr<AudioProcessor> processor;
|
||||
AudioDeviceManager deviceManager;
|
||||
AudioProcessorPlayer player;
|
||||
Array<PluginInOuts> channelConfiguration;
|
||||
|
||||
// avoid feedback loop by default
|
||||
bool processorHasPotentialFeedbackLoop = true;
|
||||
std::atomic<bool> muteInput { true };
|
||||
Value shouldMuteInput;
|
||||
AudioBuffer<float> emptyBuffer;
|
||||
bool autoOpenMidiDevices;
|
||||
|
||||
std::unique_ptr<AudioDeviceManager::AudioDeviceSetup> options;
|
||||
Array<MidiDeviceInfo> lastMidiDevices;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class SettingsComponent : public Component
|
||||
{
|
||||
public:
|
||||
SettingsComponent (StandalonePluginHolder& pluginHolder,
|
||||
AudioDeviceManager& deviceManagerToUse,
|
||||
int maxAudioInputChannels,
|
||||
int maxAudioOutputChannels)
|
||||
: owner (pluginHolder),
|
||||
deviceSelector (deviceManagerToUse,
|
||||
0, maxAudioInputChannels,
|
||||
0, maxAudioOutputChannels,
|
||||
true,
|
||||
(pluginHolder.processor.get() != nullptr && pluginHolder.processor->producesMidi()),
|
||||
true, false),
|
||||
shouldMuteLabel ("Feedback Loop:", "Feedback Loop:"),
|
||||
shouldMuteButton ("Mute audio input")
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
shouldMuteButton.setClickingTogglesState (true);
|
||||
shouldMuteButton.getToggleStateValue().referTo (owner.shouldMuteInput);
|
||||
|
||||
addAndMakeVisible (deviceSelector);
|
||||
|
||||
if (owner.getProcessorHasPotentialFeedbackLoop())
|
||||
{
|
||||
addAndMakeVisible (shouldMuteButton);
|
||||
addAndMakeVisible (shouldMuteLabel);
|
||||
|
||||
shouldMuteLabel.attachToComponent (&shouldMuteButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (owner.getProcessorHasPotentialFeedbackLoop())
|
||||
{
|
||||
auto itemHeight = deviceSelector.getItemHeight();
|
||||
auto extra = r.removeFromTop (itemHeight);
|
||||
|
||||
auto seperatorHeight = (itemHeight >> 1);
|
||||
shouldMuteButton.setBounds (Rectangle<int> (extra.proportionOfWidth (0.35f), seperatorHeight,
|
||||
extra.proportionOfWidth (0.60f), deviceSelector.getItemHeight()));
|
||||
|
||||
r.removeFromTop (seperatorHeight);
|
||||
}
|
||||
|
||||
deviceSelector.setBounds (r);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
StandalonePluginHolder& owner;
|
||||
AudioDeviceSelectorComponent deviceSelector;
|
||||
Label shouldMuteLabel;
|
||||
ToggleButton shouldMuteButton;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void audioDeviceIOCallback (const float** inputChannelData,
|
||||
int numInputChannels,
|
||||
float** outputChannelData,
|
||||
int numOutputChannels,
|
||||
int numSamples) override
|
||||
{
|
||||
if (muteInput)
|
||||
{
|
||||
emptyBuffer.clear();
|
||||
inputChannelData = emptyBuffer.getArrayOfReadPointers();
|
||||
}
|
||||
|
||||
player.audioDeviceIOCallback (inputChannelData, numInputChannels,
|
||||
outputChannelData, numOutputChannels, numSamples);
|
||||
}
|
||||
|
||||
void audioDeviceAboutToStart (AudioIODevice* device) override
|
||||
{
|
||||
emptyBuffer.setSize (device->getActiveInputChannels().countNumberOfSetBits(), device->getCurrentBufferSizeSamples());
|
||||
emptyBuffer.clear();
|
||||
|
||||
player.audioDeviceAboutToStart (device);
|
||||
player.setMidiOutput (deviceManager.getDefaultMidiOutput());
|
||||
}
|
||||
|
||||
void audioDeviceStopped() override
|
||||
{
|
||||
player.setMidiOutput (nullptr);
|
||||
player.audioDeviceStopped();
|
||||
emptyBuffer.setSize (0, 0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setupAudioDevices (bool enableAudioInput,
|
||||
const String& preferredDefaultDeviceName,
|
||||
const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions)
|
||||
{
|
||||
deviceManager.addAudioCallback (this);
|
||||
deviceManager.addMidiInputDeviceCallback ({}, &player);
|
||||
|
||||
reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions);
|
||||
}
|
||||
|
||||
void shutDownAudioDevices()
|
||||
{
|
||||
saveAudioDeviceState();
|
||||
|
||||
deviceManager.removeMidiInputDeviceCallback ({}, &player);
|
||||
deviceManager.removeAudioCallback (this);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
auto newMidiDevices = MidiInput::getAvailableDevices();
|
||||
|
||||
if (newMidiDevices != lastMidiDevices)
|
||||
{
|
||||
for (auto& oldDevice : lastMidiDevices)
|
||||
if (! newMidiDevices.contains (oldDevice))
|
||||
deviceManager.setMidiInputDeviceEnabled (oldDevice.identifier, false);
|
||||
|
||||
for (auto& newDevice : newMidiDevices)
|
||||
if (! lastMidiDevices.contains (newDevice))
|
||||
deviceManager.setMidiInputDeviceEnabled (newDevice.identifier, true);
|
||||
|
||||
lastMidiDevices = newMidiDevices;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class StandaloneFilterWindow : public DocumentWindow,
|
||||
public Button::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
typedef StandalonePluginHolder::PluginInOuts PluginInOuts;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (it can also be null). If takeOwnershipOfSettings is
|
||||
true, then the settings object will be owned and deleted by this object.
|
||||
*/
|
||||
StandaloneFilterWindow (const String& title,
|
||||
Colour backgroundColour,
|
||||
PropertySet* settingsToUse,
|
||||
bool takeOwnershipOfSettings,
|
||||
const String& preferredDefaultDeviceName = String(),
|
||||
const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions = nullptr,
|
||||
const Array<PluginInOuts>& constrainToConfiguration = {},
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
bool autoOpenMidiDevices = true
|
||||
#else
|
||||
bool autoOpenMidiDevices = true
|
||||
#endif
|
||||
)
|
||||
: DocumentWindow ("", Colours::black, DocumentWindow::minimiseButton | DocumentWindow::closeButton),
|
||||
optionsButton ("Options")
|
||||
{
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
setTitleBarHeight (0);
|
||||
#else
|
||||
setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false);
|
||||
#if JUCE_MAC
|
||||
// setUsingNativeTitleBar(true);
|
||||
/* todo menu bar */
|
||||
#endif
|
||||
Component::addAndMakeVisible (optionsButton);
|
||||
optionsButton.addListener (this);
|
||||
optionsButton.setTriggeredOnMouseDown (true);
|
||||
#endif
|
||||
|
||||
pluginHolder.reset (new StandalonePluginHolder (settingsToUse, takeOwnershipOfSettings,
|
||||
preferredDefaultDeviceName, preferredSetupOptions,
|
||||
constrainToConfiguration, autoOpenMidiDevices));
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
setFullScreen (true);
|
||||
setContentOwned (new MainContentComponent (*this), false);
|
||||
#else
|
||||
|
||||
setContentOwned (new MainContentComponent (*this), true);
|
||||
|
||||
if (auto* props = pluginHolder->settings.get())
|
||||
{
|
||||
const int x = props->getIntValue ("windowX", -100);
|
||||
const int y = props->getIntValue ("windowY", -100);
|
||||
|
||||
if (x != -100 && y != -100)
|
||||
setBoundsConstrained ({ x, y, getWidth(), getHeight() });
|
||||
else
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~StandaloneFilterWindow() override
|
||||
{
|
||||
#if (! JUCE_IOS) && (! JUCE_ANDROID)
|
||||
if (auto* props = pluginHolder->settings.get())
|
||||
{
|
||||
props->setValue ("windowX", getX());
|
||||
props->setValue ("windowY", getY());
|
||||
}
|
||||
#endif
|
||||
pluginHolder->stopPlaying();
|
||||
clearContentComponent();
|
||||
pluginHolder = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessor* getAudioProcessor() const noexcept { return pluginHolder->processor.get(); }
|
||||
AudioDeviceManager& getDeviceManager() const noexcept { return pluginHolder->deviceManager; }
|
||||
|
||||
/** Deletes and re-creates the plugin, resetting it to its default state. */
|
||||
void resetToDefaultState()
|
||||
{
|
||||
pluginHolder->stopPlaying();
|
||||
clearContentComponent();
|
||||
pluginHolder->deletePlugin();
|
||||
|
||||
if (auto* props = pluginHolder->settings.get())
|
||||
props->removeValue ("filterState");
|
||||
|
||||
pluginHolder->createPlugin();
|
||||
setContentOwned (new MainContentComponent (*this), true);
|
||||
pluginHolder->startPlaying();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void closeButtonPressed() override
|
||||
{
|
||||
pluginHolder->savePluginState();
|
||||
|
||||
JUCEApplicationBase::quit();
|
||||
}
|
||||
|
||||
void buttonClicked (Button*) override
|
||||
{
|
||||
pluginHolder->showAudioSettingsDialog();
|
||||
/*
|
||||
PopupMenu m;
|
||||
m.addItem (1, TRANS("Audio/MIDI 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"));
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options(),
|
||||
ModalCallbackFunction::forComponent (menuCallback, this));
|
||||
*/
|
||||
}
|
||||
|
||||
void handleMenuResult (int result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case 1: pluginHolder->showAudioSettingsDialog(); break;
|
||||
case 2: pluginHolder->askUserToSaveState(); break;
|
||||
case 3: pluginHolder->askUserToLoadState(); break;
|
||||
case 4: resetToDefaultState(); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
static void menuCallback (int result, StandaloneFilterWindow* button)
|
||||
{
|
||||
if (button != nullptr && result != 0)
|
||||
button->handleMenuResult (result);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
DocumentWindow::resized();
|
||||
optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8);
|
||||
}
|
||||
|
||||
virtual StandalonePluginHolder* getPluginHolder() { return pluginHolder.get(); }
|
||||
|
||||
std::unique_ptr<StandalonePluginHolder> pluginHolder;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class MainContentComponent : public Component,
|
||||
private Value::Listener,
|
||||
private Button::Listener,
|
||||
private ComponentListener
|
||||
{
|
||||
public:
|
||||
MainContentComponent (StandaloneFilterWindow& filterWindow)
|
||||
: owner (filterWindow), notification (this),
|
||||
editor (owner.getAudioProcessor()->hasEditor() ? owner.getAudioProcessor()->createEditorIfNeeded()
|
||||
: new GenericAudioProcessorEditor (*owner.getAudioProcessor()))
|
||||
{
|
||||
Value& inputMutedValue = owner.pluginHolder->getMuteInputValue();
|
||||
|
||||
if (editor != nullptr)
|
||||
{
|
||||
editor->addComponentListener (this);
|
||||
componentMovedOrResized (*editor, false, true);
|
||||
|
||||
addAndMakeVisible (editor.get());
|
||||
}
|
||||
|
||||
addChildComponent (notification);
|
||||
|
||||
if (owner.pluginHolder->getProcessorHasPotentialFeedbackLoop())
|
||||
{
|
||||
inputMutedValue.addListener (this);
|
||||
shouldShowNotification = inputMutedValue.getValue();
|
||||
}
|
||||
|
||||
inputMutedChanged (shouldShowNotification);
|
||||
}
|
||||
|
||||
~MainContentComponent() override
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
editor->removeComponentListener (this);
|
||||
owner.pluginHolder->processor->editorBeingDeleted (editor.get());
|
||||
editor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (shouldShowNotification)
|
||||
notification.setBounds (r.removeFromTop (NotificationArea::height));
|
||||
|
||||
if (editor != nullptr)
|
||||
editor->setBounds (editor->getLocalArea (this, r)
|
||||
.withPosition (r.getTopLeft().transformedBy (editor->getTransform().inverted())));
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class NotificationArea : public Component
|
||||
{
|
||||
public:
|
||||
enum { height = 30 };
|
||||
|
||||
NotificationArea (Button::Listener* settingsButtonListener)
|
||||
: notification ("notification", "Audio input is muted to avoid feedback loop"),
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
settingsButton ("Unmute Input")
|
||||
#else
|
||||
settingsButton ("Settings...")
|
||||
#endif
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
notification.setColour (Label::textColourId, Colours::black);
|
||||
|
||||
settingsButton.addListener (settingsButtonListener);
|
||||
|
||||
addAndMakeVisible (notification);
|
||||
addAndMakeVisible (settingsButton);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
g.setColour (Colours::darkgoldenrod);
|
||||
g.fillRect (r.removeFromBottom (1));
|
||||
|
||||
g.setColour (Colours::lightgoldenrodyellow);
|
||||
g.fillRect (r);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (5);
|
||||
|
||||
settingsButton.setBounds (r.removeFromRight (70));
|
||||
notification.setBounds (r);
|
||||
}
|
||||
private:
|
||||
Label notification;
|
||||
TextButton settingsButton;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void inputMutedChanged (bool newInputMutedValue)
|
||||
{
|
||||
shouldShowNotification = newInputMutedValue;
|
||||
notification.setVisible (shouldShowNotification);
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
resized();
|
||||
#else
|
||||
if (editor != nullptr)
|
||||
{
|
||||
auto rect = getSizeToContainEditor();
|
||||
|
||||
setSize (rect.getWidth(),
|
||||
rect.getHeight() + (shouldShowNotification ? NotificationArea::height : 0));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void valueChanged (Value& value) override { inputMutedChanged (value.getValue()); }
|
||||
void buttonClicked (Button*) override
|
||||
{
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
owner.pluginHolder->getMuteInputValue().setValue (false);
|
||||
#else
|
||||
owner.pluginHolder->showAudioSettingsDialog();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void componentMovedOrResized (Component&, bool, bool) override
|
||||
{
|
||||
if (editor != nullptr)
|
||||
{
|
||||
auto rect = getSizeToContainEditor();
|
||||
|
||||
setSize (rect.getWidth(),
|
||||
rect.getHeight() + (shouldShowNotification ? NotificationArea::height : 0));
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> getSizeToContainEditor() const
|
||||
{
|
||||
if (editor != nullptr)
|
||||
return getLocalArea (editor.get(), editor->getLocalBounds());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StandaloneFilterWindow& owner;
|
||||
NotificationArea notification;
|
||||
std::unique_ptr<AudioProcessorEditor> editor;
|
||||
bool shouldShowNotification = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
TextButton optionsButton;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow)
|
||||
};
|
||||
|
||||
inline StandalonePluginHolder* StandalonePluginHolder::getInstance()
|
||||
{
|
||||
#if JucePlugin_Enable_IAA || JucePlugin_Build_Standalone
|
||||
if (PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Standalone)
|
||||
{
|
||||
auto& desktop = Desktop::getInstance();
|
||||
const int numTopLevelWindows = desktop.getNumComponents();
|
||||
|
||||
for (int i = 0; i < numTopLevelWindows; ++i)
|
||||
if (auto window = dynamic_cast<StandaloneFilterWindow*> (desktop.getComponent (i)))
|
||||
return window->getPluginHolder();
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
Loading…
Reference in a new issue