From 3f315ddd0041fdb6904eae0b2251cdd9256041fe Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 21 Jan 2022 17:56:29 +0000 Subject: [PATCH] Plugin clients: Fix bypass behaviours to match getBypassParameter() documentation --- BREAKING-CHANGES.txt | 30 +++++++++++++++++++ .../AAX/juce_AAX_Wrapper.cpp | 2 +- .../AU/juce_AUv3_Wrapper.mm | 2 +- .../RTAS/juce_RTAS_Wrapper.cpp | 6 +++- .../Unity/juce_Unity_Wrapper.cpp | 10 +++++-- .../VST/juce_VST_Wrapper.cpp | 10 +++---- .../VST3/juce_VST3_Wrapper.cpp | 10 ++++--- 7 files changed, 55 insertions(+), 15 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index bdfe0191e0..1a028ff644 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,36 @@ JUCE breaking changes develop ======= +Change +------ +Plugin wrappers will no longer call processBlockBypassed() if the wrapped +AudioProcessor returns a parameter from getBypassParameter(). + +Possible Issues +--------------- +Plugins that used to depend on processBlockBypassed() being called may no +longer correctly enter a bypassed state. + +Workaround +---------- +AudioProcessors that implement getBypassParameter() must check the current +value of the bypass parameter on each call to processBlock(), and bypass +processing as appropriate. When switching between bypassed and non-bypassed +states, the plugin must use some sort of ramping or smoothing to avoid +discontinuities in the output. If the plugin introduces latency when not +bypassed, the plugin must delay its output when in bypassed mode so that the +overall latency does not change when enabling/disabling bypass. + +Rationale +--------- +The documentation for AudioProcessor::getBypassParameter() says +> if this method returns a non-null value, you should never call + processBlockBypassed but use the returned parameter to control the bypass + state instead. +Some plugin wrappers were not following this rule. After this change, the +behaviour of all plugin wrappers is consistent with the documented behaviour. + + Change ------ The ComponentPeer::getFrameSize() function has been deprecated on Linux. diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index ecbc4dbdb4..67f29bee0b 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -1533,7 +1533,7 @@ namespace AAXClasses } } - if (bypass) + if (bypass && pluginInstance->getBypassParameter() == nullptr) pluginInstance->processBlockBypassed (buffer, midiBuffer); else pluginInstance->processBlock (buffer, midiBuffer); diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index 00fca3d20d..f6f7597224 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -1652,7 +1652,7 @@ private: if (processor.isSuspended()) buffer.clear(); - else if (bypassParam != nullptr && [au shouldBypassEffect]) + else if (bypassParam == nullptr && [au shouldBypassEffect]) processor.processBlockBypassed (buffer, midiBuffer); else processor.processBlock (buffer, midiBuffer); diff --git a/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp b/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp index 99defb774e..91a49867d6 100644 --- a/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp @@ -588,7 +588,7 @@ public: AudioBuffer chans (channels, totalChans, numSamples); - if (mBypassed) + if (mBypassed && juceFilter->getBypassParameter() == nullptr) juceFilter->processBlockBypassed (chans, midiEvents); else juceFilter->processBlock (chans, midiEvents); @@ -685,6 +685,10 @@ public: else { mBypassed = (value > 0); + + if (auto* param = juceFilter->getBypassParameter()) + if (mBypassed != (param->getValue() >= 0.5f)) + param.setValueNotifyingHost (mBypassed ? 1.0f : 0.0f); } return CProcess::UpdateControlValue (controlIndex, value); diff --git a/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp b/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp index df7d30a3da..fcfd4b7786 100644 --- a/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp @@ -350,6 +350,11 @@ public: void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) { + // If the plugin has a bypass parameter, set it to the current bypass state + if (auto* param = pluginInstance->getBypassParameter()) + if (isBypassed != (param->getValue() >= 0.5f)) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + for (int pos = 0; pos < bufferSize;) { auto max = jmin (bufferSize - pos, samplesPerBlock); @@ -452,7 +457,7 @@ private: { MidiBuffer mb; - if (isBypassed) + if (isBypassed && pluginInstance->getBypassParameter() == nullptr) pluginInstance->processBlockBypassed (scratchBuffer, mb); else pluginInstance->processBlock (scratchBuffer, mb); @@ -619,8 +624,7 @@ namespace UnityCallbacks auto isMuted = ((state->flags & stateIsMuted) != 0); auto isPaused = ((state->flags & stateIsPaused) != 0); - auto bypassed = ! isPlaying || (isMuted || isPaused); - + const auto bypassed = ! isPlaying || (isMuted || isPaused); pluginInstance->process (inBuffer, outBuffer, static_cast (bufferSize), numInChannels, numOutChannels, bypassed); } else diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 24fc168aa9..bca6cd7423 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -447,7 +447,7 @@ public: const int numChannels = jmax (numIn, numOut); AudioBuffer chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); - if (isBypassed) + if (isBypassed && processor->getBypassParameter() == nullptr) processor->processBlockBypassed (chans, midiEvents); else processor->processBlock (chans, midiEvents); @@ -737,7 +737,7 @@ public: void parameterValueChanged (int, float newValue) override { // this can only come from the bypass parameter - isBypassed = (newValue != 0.0f); + isBypassed = (newValue >= 0.5f); } void parameterGestureChanged (int, bool) override {} @@ -1800,10 +1800,10 @@ private: pointer_sized_int handleSetBypass (VstOpCodeArguments args) { - isBypassed = (args.value != 0); + isBypassed = args.value != 0; - if (auto* bypass = processor->getBypassParameter()) - bypass->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + if (auto* param = processor->getBypassParameter()) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); return 1; } diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index ded1cf1a7a..0289992e02 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -2370,10 +2370,10 @@ public: tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } //============================================================================== - bool isBypassed() + bool isBypassed() const { if (auto* bypassParam = comPluginInstance->getBypassParameter()) - return (bypassParam->getValue() != 0.0f); + return bypassParam->getValue() >= 0.5f; return false; } @@ -3374,7 +3374,9 @@ private: if (totalInputChans == pluginInstance->getTotalNumInputChannels() && totalOutputChans == pluginInstance->getTotalNumOutputChannels()) { - if (isBypassed()) + // processBlockBypassed should only ever be called if the AudioProcessor doesn't + // return a valid parameter from getBypassParameter + if (pluginInstance->getBypassParameter() == nullptr && comPluginInstance->getBypassParameter()->getValue() >= 0.5f) pluginInstance->processBlockBypassed (buffer, midiBuffer); else pluginInstance->processBlock (buffer, midiBuffer); @@ -3489,7 +3491,7 @@ private: ptr = {}; } - T* operator->() { return ptr.operator->(); } + T* operator->() const { return ptr.operator->(); } T* get() const noexcept { return ptr.get(); } operator T*() const noexcept { return ptr.get(); }