From d3d1eeb7708fa2f73ccd2ab95eb5cd2856c82391 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Wed, 14 Feb 2018 18:20:59 +0000 Subject: [PATCH] Added an AudioProcessorParameter listener class --- .../processors/juce_AudioProcessor.cpp | 162 ++++++++++++++---- .../processors/juce_AudioProcessor.h | 4 +- .../processors/juce_AudioProcessorParameter.h | 68 ++++++++ 3 files changed, 197 insertions(+), 37 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 9c1d339e5b..f38eb615b9 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -416,8 +416,15 @@ void AudioProcessor::setLatencySamples (const int newLatency) void AudioProcessor::setParameterNotifyingHost (int parameterIndex, float newValue) { - setParameter (parameterIndex, newValue); - sendParamChangeMessageToListeners (parameterIndex, newValue); + if (auto* param = getParameters()[parameterIndex]) + { + param->setValueNotifyingHost (newValue); + } + else + { + setParameter (parameterIndex, newValue); + sendParamChangeMessageToListeners (parameterIndex, newValue); + } } AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noexcept @@ -428,58 +435,79 @@ AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noex void AudioProcessor::sendParamChangeMessageToListeners (int parameterIndex, float newValue) { - if (isPositiveAndBelow (parameterIndex, getNumParameters())) + if (auto* param = getParameters()[parameterIndex]) { - for (int i = listeners.size(); --i >= 0;) - if (auto* l = getListenerLocked (i)) - l->audioProcessorParameterChanged (this, parameterIndex, newValue); + param->sendValueChangedMessageToListeners (newValue); } else { - jassertfalse; // called with an out-of-range parameter index! + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + for (int i = listeners.size(); --i >= 0;) + if (auto* l = getListenerLocked (i)) + l->audioProcessorParameterChanged (this, parameterIndex, newValue); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } } } void AudioProcessor::beginParameterChangeGesture (int parameterIndex) { - if (isPositiveAndBelow (parameterIndex, getNumParameters())) + if (auto* param = getParameters()[parameterIndex]) { - #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING - // This means you've called beginParameterChangeGesture twice in succession without a matching - // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. - jassert (! changingParams[parameterIndex]); - changingParams.setBit (parameterIndex); - #endif - - for (int i = listeners.size(); --i >= 0;) - if (auto* l = getListenerLocked (i)) - l->audioProcessorParameterChangeGestureBegin (this, parameterIndex); + param->beginChangeGesture(); } else { - jassertfalse; // called with an out-of-range parameter index! + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called beginParameterChangeGesture twice in succession without a matching + // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. + jassert (! changingParams[parameterIndex]); + changingParams.setBit (parameterIndex); + #endif + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = getListenerLocked (i)) + l->audioProcessorParameterChangeGestureBegin (this, parameterIndex); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } } } void AudioProcessor::endParameterChangeGesture (int parameterIndex) { - if (isPositiveAndBelow (parameterIndex, getNumParameters())) + if (auto* param = getParameters()[parameterIndex]) { - #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING - // This means you've called endParameterChangeGesture without having previously called - // beginParameterChangeGesture. That might be fine in most hosts, but better to keep the - // calls matched correctly. - jassert (changingParams[parameterIndex]); - changingParams.clearBit (parameterIndex); - #endif - - for (int i = listeners.size(); --i >= 0;) - if (auto* l = getListenerLocked (i)) - l->audioProcessorParameterChangeGestureEnd (this, parameterIndex); + param->endChangeGesture(); } else { - jassertfalse; // called with an out-of-range parameter index! + if (isPositiveAndBelow (parameterIndex, getNumParameters())) + { + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called endParameterChangeGesture without having previously called + // beginParameterChangeGesture. That might be fine in most hosts, but better to keep the + // calls matched correctly. + jassert (changingParams[parameterIndex]); + changingParams.clearBit (parameterIndex); + #endif + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = getListenerLocked (i)) + l->audioProcessorParameterChangeGestureEnd (this, parameterIndex); + } + else + { + jassertfalse; // called with an out-of-range parameter index! + } } } @@ -1340,7 +1368,8 @@ void AudioProcessorParameter::setValueNotifyingHost (float newValue) // This method can't be used until the parameter has been attached to a processor! jassert (processor != nullptr && parameterIndex >= 0); - return processor->setParameterNotifyingHost (parameterIndex, newValue); + setValue (newValue); + sendValueChangedMessageToListeners (newValue); } void AudioProcessorParameter::beginChangeGesture() @@ -1348,7 +1377,25 @@ void AudioProcessorParameter::beginChangeGesture() // This method can't be used until the parameter has been attached to a processor! jassert (processor != nullptr && parameterIndex >= 0); - processor->beginParameterChangeGesture (parameterIndex); + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called beginChangeGesture twice in succession without + // a matching call to endChangeGesture. That might be fine in most hosts, + // but it would be better to avoid doing it. + jassert (! isPerformingGesture); + isPerformingGesture = true; + #endif + + ScopedLock lock (listenerLock); + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = listeners[i]) + l->parameterGestureChanged (getParameterIndex(), true); + + // audioProcessorParameterChangeGestureBegin callbacks will shortly be deprecated and + // this code will be removed. + for (int i = processor->listeners.size(); --i >= 0;) + if (auto* l = processor->listeners[i]) + l->audioProcessorParameterChangeGestureBegin (processor, getParameterIndex()); } void AudioProcessorParameter::endChangeGesture() @@ -1356,7 +1403,40 @@ void AudioProcessorParameter::endChangeGesture() // This method can't be used until the parameter has been attached to a processor! jassert (processor != nullptr && parameterIndex >= 0); - processor->endParameterChangeGesture (parameterIndex); + #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING + // This means you've called endChangeGesture without having previously + // called beginChangeGesture. That might be fine in most hosts, but it + // would be better to keep the calls matched correctly. + jassert (isPerformingGesture); + isPerformingGesture = false; + #endif + + ScopedLock lock (listenerLock); + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = listeners[i]) + l->parameterGestureChanged (getParameterIndex(), false); + + // audioProcessorParameterChangeGestureEnd callbacks will shortly be deprecated and + // this code will be removed. + for (int i = processor->listeners.size(); --i >= 0;) + if (auto* l = processor->listeners[i]) + l->audioProcessorParameterChangeGestureEnd (processor, getParameterIndex()); +} + +void AudioProcessorParameter::sendValueChangedMessageToListeners (float newValue) +{ + ScopedLock lock (listenerLock); + + for (int i = listeners.size(); --i >= 0;) + if (auto* l = listeners [i]) + l->parameterValueChanged (getParameterIndex(), newValue); + + // audioProcessorParameterChanged callbacks will shortly be deprecated and + // this code will be removed. + for (int i = processor->listeners.size(); --i >= 0;) + if (auto* l = processor->listeners[i]) + l->audioProcessorParameterChanged (processor, getParameterIndex(), newValue); } bool AudioProcessorParameter::isOrientationInverted() const { return false; } @@ -1372,6 +1452,18 @@ String AudioProcessorParameter::getText (float value, int /*maximumStringLength* return String (value, 2); } +void AudioProcessorParameter::addListener (AudioProcessorParameter::Listener* newListener) +{ + const ScopedLock sl (listenerLock); + listeners.addIfNotAlreadyThere (newListener); +} + +void AudioProcessorParameter::removeListener (AudioProcessorParameter::Listener* listenerToRemove) +{ + const ScopedLock sl (listenerLock); + listeners.removeFirstMatchingValue (listenerToRemove); +} + //============================================================================== bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept { diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 5bb3241a8f..b040e71c82 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1616,10 +1616,10 @@ private: template void processBypassed (AudioBuffer&, MidiBuffer&); - #if JucePlugin_Build_VST3 + friend class AudioProcessorParameter; + friend class JuceVST3EditController; friend class JuceVST3Component; - #endif Atomic vst3IsPlaying { 0 }; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h index 0cdd8254a4..2820ff59ee 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h @@ -199,10 +199,78 @@ public: /** Returns the index of this parameter in its parent processor's parameter list. */ int getParameterIndex() const noexcept { return parameterIndex; } + //============================================================================== + /** + A base class for listeners that want to know about changes to an + AudioProcessorParameter. + + Use AudioProcessorParameter::addListener() to register your listener with + an AudioProcessorParameter. + + This Listener replaces most of the functionality in the + AudioProcessorListener class, which will be deprecated and removed. + */ + class JUCE_API Listener + { + public: + /** Destructor. */ + virtual ~Listener() {} + + /** Receives a callback when a parameter has been changed. + + IMPORTANT NOTE: this will be called synchronously when a parameter changes, and + many audio processors will change their parameter during their audio callback. + This means that not only has your handler code got to be completely thread-safe, + but it's also got to be VERY fast, and avoid blocking. If you need to handle + this event on your message thread, use this callback to trigger an AsyncUpdater + or ChangeBroadcaster which you can respond to on the message thread. + */ + virtual void parameterValueChanged (int parameterIndex, float newValue) = 0; + + /** Indicates that a parameter change gesture has started. + + E.g. if the user is dragging a slider, this would be called with gestureIsStarting + being true when they first press the mouse button, and it will be called again with + gestureIsStarting being false when they release it. + + IMPORTANT NOTE: this will be called synchronously, and many audio processors will + call it during their audio callback. This means that not only has your handler code + got to be completely thread-safe, but it's also got to be VERY fast, and avoid + blocking. If you need to handle this event on your message thread, use this callback + to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the + message thread. + */ + virtual void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) = 0; + }; + + /** Registers a listener to receive events when the parameter's state changes. + If the listener is already registered, this will not register it again. + + @see removeListener + */ + void addListener (Listener* newListener); + + /** Removes a previously registered parameter listener + + @see addListener + */ + void removeListener (Listener* listener); + + //============================================================================== + /** @internal */ + void sendValueChangedMessageToListeners (float newValue); + private: + //============================================================================== friend class AudioProcessor; AudioProcessor* processor = nullptr; int parameterIndex = -1; + CriticalSection listenerLock; + Array listeners; + + #if JUCE_DEBUG + bool isPerformingGesture = false; + #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameter) };