| @@ -172,15 +172,20 @@ class JuceDemoPluginAudioProcessor : public AudioProcessor | |||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| JuceDemoPluginAudioProcessor() | JuceDemoPluginAudioProcessor() | ||||
| : AudioProcessor (getBusesProperties()) | |||||
| : AudioProcessor (getBusesProperties()), | |||||
| state (*this, nullptr) | |||||
| { | { | ||||
| lastPosInfo.resetToDefault(); | lastPosInfo.resetToDefault(); | ||||
| // This creates our parameters. We'll keep some raw pointers to them in this class, | |||||
| // so that we can easily access them later, but the base class will take care of | |||||
| // deleting them for us. | |||||
| addParameter (gainParam = new AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.9f)); | |||||
| addParameter (delayParam = new AudioParameterFloat ("delay", "Delay Feedback", 0.0f, 1.0f, 0.5f)); | |||||
| // This creates our parameters | |||||
| state.createAndAddParameter ("gain", "Gain", {}, {}, 0.9f, {}, {}); | |||||
| state.createAndAddParameter ("delay", "Delay Feedback", {}, {}, 0.5f, {}, {}); | |||||
| state.state = ValueTree ("state", {}, | |||||
| { | |||||
| // add a sub-tree to store the state of our UI | |||||
| {"uiState", {{"width", 400}, {"height", 200}}, {}} | |||||
| }); | |||||
| initialiseSynth(); | initialiseSynth(); | ||||
| } | } | ||||
| @@ -282,48 +287,21 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| void getStateInformation (MemoryBlock& destData) override | void getStateInformation (MemoryBlock& destData) override | ||||
| { | { | ||||
| // You should use this method to store your parameters in the memory block. | |||||
| // Here's an example of how you can use XML to make it easy and more robust: | |||||
| // Create an outer XML element.. | |||||
| XmlElement xml ("MYPLUGINSETTINGS"); | |||||
| // add some attributes to it.. | |||||
| xml.setAttribute ("uiWidth", lastUIWidth); | |||||
| xml.setAttribute ("uiHeight", lastUIHeight); | |||||
| // Store the values of all our parameters, using their param ID as the XML attribute | |||||
| for (auto* param : getParameters()) | |||||
| if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param)) | |||||
| xml.setAttribute (p->paramID, p->getValue()); | |||||
| // Store an xml representation of our state. | |||||
| std::unique_ptr<XmlElement> xmlState (state.copyState().createXml()); | |||||
| // then use this helper function to stuff it into the binary blob and return it.. | |||||
| copyXmlToBinary (xml, destData); | |||||
| if (xmlState.get() != nullptr) | |||||
| copyXmlToBinary (*xmlState, destData); | |||||
| } | } | ||||
| void setStateInformation (const void* data, int sizeInBytes) override | void setStateInformation (const void* data, int sizeInBytes) override | ||||
| { | { | ||||
| // You should use this method to restore your parameters from this memory block, | |||||
| // whose contents will have been created by the getStateInformation() call. | |||||
| // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. | |||||
| // Restore our plug-in's state from the xml representation stored in the above | |||||
| // method. | |||||
| std::unique_ptr<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes)); | std::unique_ptr<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes)); | ||||
| if (xmlState.get() != nullptr) | if (xmlState.get() != nullptr) | ||||
| { | |||||
| // make sure that it's actually our type of XML object.. | |||||
| if (xmlState->hasTagName ("MYPLUGINSETTINGS")) | |||||
| { | |||||
| // ok, now pull out our last window size.. | |||||
| lastUIWidth = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400); | |||||
| lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 200); | |||||
| // Now reload our parameters.. | |||||
| for (auto* param : getParameters()) | |||||
| if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param)) | |||||
| p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue())); | |||||
| } | |||||
| } | |||||
| state.replaceState (ValueTree::fromXml (*xmlState)); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -348,14 +326,8 @@ public: | |||||
| // callback - the UI component will read this and display it. | // callback - the UI component will read this and display it. | ||||
| AudioPlayHead::CurrentPositionInfo lastPosInfo; | AudioPlayHead::CurrentPositionInfo lastPosInfo; | ||||
| // these are used to persist the UI's size - the values are stored along with the | |||||
| // filter's other parameters, and the UI component will update them when it gets | |||||
| // resized. | |||||
| int lastUIWidth = 400, lastUIHeight = 200; | |||||
| // Our parameters | |||||
| AudioParameterFloat* gainParam = nullptr; | |||||
| AudioParameterFloat* delayParam = nullptr; | |||||
| // Our plug-in's current state | |||||
| AudioProcessorValueTreeState state; | |||||
| // Current track colour and name | // Current track colour and name | ||||
| TrackProperties trackProperties; | TrackProperties trackProperties; | ||||
| @@ -364,27 +336,27 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** This is the editor component that our filter will display. */ | /** This is the editor component that our filter will display. */ | ||||
| class JuceDemoPluginAudioProcessorEditor : public AudioProcessorEditor, | class JuceDemoPluginAudioProcessorEditor : public AudioProcessorEditor, | ||||
| private Timer | |||||
| private Timer, private Value::Listener | |||||
| { | { | ||||
| public: | public: | ||||
| JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor& owner) | JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor& owner) | ||||
| : AudioProcessorEditor (owner), | : AudioProcessorEditor (owner), | ||||
| midiKeyboard (owner.keyboardState, MidiKeyboardComponent::horizontalKeyboard) | |||||
| midiKeyboard (owner.keyboardState, MidiKeyboardComponent::horizontalKeyboard), | |||||
| gainAttachment (owner.state, "gain", gainSlider), | |||||
| delayAttachment (owner.state, "delay", delaySlider) | |||||
| { | { | ||||
| // add some sliders.. | // add some sliders.. | ||||
| gainSlider.reset (new ParameterSlider (*owner.gainParam)); | |||||
| addAndMakeVisible (gainSlider.get()); | |||||
| gainSlider->setSliderStyle (Slider::Rotary); | |||||
| addAndMakeVisible (gainSlider); | |||||
| gainSlider.setSliderStyle (Slider::Rotary); | |||||
| delaySlider.reset (new ParameterSlider (*owner.delayParam)); | |||||
| addAndMakeVisible (delaySlider.get()); | |||||
| delaySlider->setSliderStyle (Slider::Rotary); | |||||
| addAndMakeVisible (delaySlider); | |||||
| delaySlider.setSliderStyle (Slider::Rotary); | |||||
| // add some labels for the sliders.. | // add some labels for the sliders.. | ||||
| gainLabel.attachToComponent (gainSlider.get(), false); | |||||
| gainLabel.attachToComponent (&gainSlider, false); | |||||
| gainLabel.setFont (Font (11.0f)); | gainLabel.setFont (Font (11.0f)); | ||||
| delayLabel.attachToComponent (delaySlider.get(), false); | |||||
| delayLabel.attachToComponent (&delaySlider, false); | |||||
| delayLabel.setFont (Font (11.0f)); | delayLabel.setFont (Font (11.0f)); | ||||
| // add the midi keyboard component.. | // add the midi keyboard component.. | ||||
| @@ -397,9 +369,14 @@ private: | |||||
| // set resize limits for this plug-in | // set resize limits for this plug-in | ||||
| setResizeLimits (400, 200, 1024, 700); | setResizeLimits (400, 200, 1024, 700); | ||||
| lastUIWidth .referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr)); | |||||
| lastUIHeight.referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr)); | |||||
| // set our component's initial size to be the last one that was stored in the filter's settings | // set our component's initial size to be the last one that was stored in the filter's settings | ||||
| setSize (owner.lastUIWidth, | |||||
| owner.lastUIHeight); | |||||
| setSize (lastUIWidth.getValue(), lastUIHeight.getValue()); | |||||
| lastUIWidth. addListener (this); | |||||
| lastUIHeight.addListener (this); | |||||
| updateTrackProperties(); | updateTrackProperties(); | ||||
| @@ -427,11 +404,11 @@ private: | |||||
| r.removeFromTop (20); | r.removeFromTop (20); | ||||
| auto sliderArea = r.removeFromTop (60); | auto sliderArea = r.removeFromTop (60); | ||||
| gainSlider->setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth() / 2))); | |||||
| delaySlider->setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth()))); | |||||
| gainSlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth() / 2))); | |||||
| delaySlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth()))); | |||||
| getProcessor().lastUIWidth = getWidth(); | |||||
| getProcessor().lastUIHeight = getHeight(); | |||||
| lastUIWidth = getWidth(); | |||||
| lastUIHeight = getHeight(); | |||||
| } | } | ||||
| void timerCallback() override | void timerCallback() override | ||||
| @@ -455,53 +432,21 @@ private: | |||||
| } | } | ||||
| private: | private: | ||||
| //============================================================================== | |||||
| // This is a handy slider subclass that controls an AudioProcessorParameter | |||||
| // (may move this class into the library itself at some point in the future..) | |||||
| class ParameterSlider : public Slider, | |||||
| private Timer | |||||
| { | |||||
| public: | |||||
| ParameterSlider (AudioProcessorParameter& p) | |||||
| : Slider (p.getName (256)), param (p) | |||||
| { | |||||
| setRange (0.0, 1.0, 0.0); | |||||
| startTimerHz (30); | |||||
| updateSliderPos(); | |||||
| } | |||||
| void valueChanged() override { param.setValueNotifyingHost ((float) Slider::getValue()); } | |||||
| void timerCallback() override { updateSliderPos(); } | |||||
| void startedDragging() override { param.beginChangeGesture(); } | |||||
| void stoppedDragging() override { param.endChangeGesture(); } | |||||
| double getValueFromText (const String& text) override { return param.getValueForText (text); } | |||||
| String getTextFromValue (double value) override { return param.getText ((float) value, 1024); } | |||||
| void updateSliderPos() | |||||
| { | |||||
| auto newValue = param.getValue(); | |||||
| if (newValue != (float) Slider::getValue() && ! isMouseButtonDown()) | |||||
| Slider::setValue (newValue, NotificationType::dontSendNotification); | |||||
| } | |||||
| AudioProcessorParameter& param; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterSlider) | |||||
| }; | |||||
| MidiKeyboardComponent midiKeyboard; | MidiKeyboardComponent midiKeyboard; | ||||
| Label timecodeDisplayLabel, | Label timecodeDisplayLabel, | ||||
| gainLabel { {}, "Throughput level:" }, | gainLabel { {}, "Throughput level:" }, | ||||
| delayLabel { {}, "Delay:" }; | delayLabel { {}, "Delay:" }; | ||||
| std::unique_ptr<ParameterSlider> gainSlider, delaySlider; | |||||
| Slider gainSlider, delaySlider; | |||||
| AudioProcessorValueTreeState::SliderAttachment gainAttachment, delayAttachment; | |||||
| Colour backgroundColour; | Colour backgroundColour; | ||||
| // these are used to persist the UI's size - the values are stored along with the | |||||
| // filter's other parameters, and the UI component will update them when it gets | |||||
| // resized. | |||||
| Value lastUIWidth, lastUIHeight; | |||||
| //============================================================================== | //============================================================================== | ||||
| JuceDemoPluginAudioProcessor& getProcessor() const | JuceDemoPluginAudioProcessor& getProcessor() const | ||||
| { | { | ||||
| @@ -558,12 +503,20 @@ private: | |||||
| timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification); | timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification); | ||||
| } | } | ||||
| // called when the stored window size changes | |||||
| void valueChanged (Value&) override | |||||
| { | |||||
| setSize (lastUIWidth.getValue(), lastUIHeight.getValue()); | |||||
| } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| template <typename FloatType> | template <typename FloatType> | ||||
| void process (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<FloatType>& delayBuffer) | void process (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<FloatType>& delayBuffer) | ||||
| { | { | ||||
| auto gainParamValue = state.getParameter ("gain") ->getValue(); | |||||
| auto delayParamValue = state.getParameter ("delay")->getValue(); | |||||
| auto numSamples = buffer.getNumSamples(); | auto numSamples = buffer.getNumSamples(); | ||||
| // In case we have more outputs than inputs, we'll clear any output | // In case we have more outputs than inputs, we'll clear any output | ||||
| @@ -580,29 +533,27 @@ private: | |||||
| synth.renderNextBlock (buffer, midiMessages, 0, numSamples); | synth.renderNextBlock (buffer, midiMessages, 0, numSamples); | ||||
| // Apply our delay effect to the new output.. | // Apply our delay effect to the new output.. | ||||
| applyDelay (buffer, delayBuffer); | |||||
| applyDelay (buffer, delayBuffer, gainParamValue); | |||||
| applyGain (buffer, delayBuffer); // apply our gain-change to the outgoing data.. | |||||
| applyGain (buffer, delayBuffer, delayParamValue); // apply our gain-change to the outgoing data.. | |||||
| // Now ask the host for the current time so we can store it to be displayed later... | // Now ask the host for the current time so we can store it to be displayed later... | ||||
| updateCurrentTimeInfoFromHost(); | updateCurrentTimeInfoFromHost(); | ||||
| } | } | ||||
| template <typename FloatType> | template <typename FloatType> | ||||
| void applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer) | |||||
| void applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float gainLevel) | |||||
| { | { | ||||
| ignoreUnused (delayBuffer); | ignoreUnused (delayBuffer); | ||||
| auto gainLevel = gainParam->get(); | |||||
| for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel) | for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel) | ||||
| buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel); | buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel); | ||||
| } | } | ||||
| template <typename FloatType> | template <typename FloatType> | ||||
| void applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer) | |||||
| void applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float delayLevel) | |||||
| { | { | ||||
| auto numSamples = buffer.getNumSamples(); | auto numSamples = buffer.getNumSamples(); | ||||
| auto delayLevel = delayParam->get(); | |||||
| auto delayPos = 0; | auto delayPos = 0; | ||||