diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 955d7ccc11..0d0586b399 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,30 @@ JUCE breaking changes Develop ======= +Change +------ +AudioProcessorListener::audioProcessorChanged gained a new parameter describing +the nature of any change. + +Possible Issues +--------------- +Code using the old function signature will not build until updated to use +the new signature. + +Workaround +---------- +Listeners should add the new parameter to any overrides of +audioProcessorChanged. + +Rationale +--------- +The new function signature means that wrappers can be smarter about the +requests that they make to hosts whenever some aspect of the processor changes. +In particular, plugin wrappers can now distinguish between changes to latency, +parameter attributes, and the current program. This means that hosts will no +longer assume parameters have changed when `setLatencySamples` is called. + + Change ------ CharacterFunctions::readDoubleValue now returns values consistent with other diff --git a/extras/AudioPluginHost/Source/Plugins/PluginGraph.h b/extras/AudioPluginHost/Source/Plugins/PluginGraph.h index 59217f0e0e..e97c4b6193 100644 --- a/extras/AudioPluginHost/Source/Plugins/PluginGraph.h +++ b/extras/AudioPluginHost/Source/Plugins/PluginGraph.h @@ -60,7 +60,7 @@ public: //============================================================================== void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} - void audioProcessorChanged (AudioProcessor*) override { changed(); } + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override { changed(); } //============================================================================== std::unique_ptr createXml() const; diff --git a/extras/AudioPluginHost/Source/UI/PluginWindow.h b/extras/AudioPluginHost/Source/UI/PluginWindow.h index 7cec3d810b..36bc0f2728 100644 --- a/extras/AudioPluginHost/Source/UI/PluginWindow.h +++ b/extras/AudioPluginHost/Source/UI/PluginWindow.h @@ -287,7 +287,7 @@ private: } void refresh() override {} - void audioProcessorChanged (AudioProcessor*) override {} + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} AudioProcessor& owner; 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 95a0913245..3c01ac8a4c 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -1085,24 +1085,28 @@ namespace AAXClasses SetParameterNormalizedValue (paramID, (double) newValue); } - void audioProcessorChanged (AudioProcessor* processor) override + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override { ++mNumPlugInChanges; - auto numParameters = juceParameters.getNumParameters(); - - for (int i = 0; i < numParameters; ++i) + if (details.parameterInfoChanged) { - if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) - { - auto newName = juceParameters.getParamForIndex (i)->getName (31); + auto numParameters = juceParameters.getNumParameters(); - if (p->Name() != newName.toRawUTF8()) - p->SetName (AAX_CString (newName.toRawUTF8())); + for (int i = 0; i < numParameters; ++i) + { + if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) + { + auto newName = juceParameters.getParamForIndex (i)->getName (31); + + if (p->Name() != newName.toRawUTF8()) + p->SetName (AAX_CString (newName.toRawUTF8())); + } } } - check (Controller()->SetSignalLatency (processor->getLatencySamples())); + if (details.latencyChanged) + check (Controller()->SetSignalLatency (processor->getLatencySamples())); } void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 60d9bad8ef..6048cc8cea 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -1166,16 +1166,24 @@ public: sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { - PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); + if (details.latencyChanged) + PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); - refreshCurrentPreset(); + if (details.parameterInfoChanged) + { + PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); + PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); + } - PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); + + if (details.programChanged) + { + refreshCurrentPreset(); + PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + } } //============================================================================== 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 42718f2039..8f3106ced3 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -914,7 +914,7 @@ public: #endif //============================================================================== - void audioProcessorChanged (AudioProcessor* processor) override + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails&) override { ignoreUnused (processor); 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 c7d780291a..7836bf1179 100644 --- a/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp @@ -790,7 +790,7 @@ public: ReleaseControl (index + 2); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override { // xxx is there an RTAS equivalent? } 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 290837a0c7..c826f0b9df 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -254,7 +254,6 @@ struct AbletonLiveHostSpecific class JuceVSTWrapper : public AudioProcessorListener, public AudioPlayHead, private Timer, - private AsyncUpdater, private AudioProcessorParameter::Listener { private: @@ -802,19 +801,9 @@ public: void parameterGestureChanged (int, bool) override {} - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { - vstEffect.initialDelay = processor->getLatencySamples(); - triggerAsyncUpdate(); - } - - void handleAsyncUpdate() override - { - if (hostCallback != nullptr) - { - hostCallback (&vstEffect, Vst2::audioMasterUpdateDisplay, 0, 0, nullptr, 0); - hostCallback (&vstEffect, Vst2::audioMasterIOChanged, 0, 0, nullptr, 0); - } + hostChangeUpdater.update (details); } bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const @@ -1398,6 +1387,33 @@ public: //============================================================================== private: + struct HostChangeUpdater : private AsyncUpdater + { + explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} + ~HostChangeUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + if (details.latencyChanged) + owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); + + if (details.parameterInfoChanged || details.programChanged) + triggerAsyncUpdate(); + } + + private: + void handleAsyncUpdate() override + { + if (auto* callback = owner.hostCallback) + { + callback (&owner.vstEffect, Vst2::audioMasterUpdateDisplay, 0, 0, nullptr, 0); + callback (&owner.vstEffect, Vst2::audioMasterIOChanged, 0, 0, nullptr, 0); + } + } + + JuceVSTWrapper& owner; + }; + static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } bool isProcessLevelOffline() @@ -2121,6 +2137,8 @@ private: ThreadLocalValue inParameterChangedCallback; + HostChangeUpdater hostChangeUpdater { *this }; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) }; 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 640c374b05..ed779b3b7b 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -960,18 +960,21 @@ public: paramChanged (audioProcessor->getVSTParamIDForIndex (index), newValue); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { int32 flags = 0; - for (int32 i = 0; i < parameters.getParameterCount(); ++i) - if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) - if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) - flags |= Vst::kParamTitlesChanged; + if (details.parameterInfoChanged) + { + for (int32 i = 0; i < parameters.getParameterCount(); ++i) + if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) + if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) + flags |= Vst::kParamTitlesChanged; + } if (auto* pluginInstance = getPluginInstance()) { - if (audioProcessor->getProgramParameter() != nullptr) + if (details.programChanged && audioProcessor->getProgramParameter() != nullptr) { auto currentProgram = pluginInstance->getCurrentProgram(); auto paramValue = roundToInt (EditController::normalizedParamToPlain (JuceAudioProcessor::paramPreset, @@ -990,7 +993,7 @@ public: auto latencySamples = pluginInstance->getLatencySamples(); - if (latencySamples != lastLatencySamples) + if (details.latencyChanged && latencySamples != lastLatencySamples) { flags |= Vst::kLatencyChanged; lastLatencySamples = latencySamples; @@ -1116,7 +1119,7 @@ private: initialiseMidiControllerMappings(); #endif - audioProcessorChanged (pluginInstance); + audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); } } diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index d5c65b7b23..9aa4a338e9 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1822,12 +1822,12 @@ private: default: if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) { - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); } else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) { sendAllParametersChangedEvents(); - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); } else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) { diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index c13c3fd5c0..7ccdcadff3 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -3179,7 +3179,9 @@ tresult VST3HostContext::restartComponent (Steinberg::int32 flags) if (plugin->processor != nullptr) plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); - plugin->updateHostDisplay(); + plugin->updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) + .withParameterInfoChanged (true)); + return kResultTrue; } diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 1a51ae11d2..70b75f686b 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -1602,8 +1602,8 @@ struct VSTPluginInstance : public AudioPluginInstance, void handleAsyncUpdate() override { - // indicates that something about the plugin has changed.. - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) + .withParameterInfoChanged (true)); } pointer_sized_int handleCallback (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 1c23e9d3ec..040accf2e5 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -410,7 +410,7 @@ void AudioProcessor::setLatencySamples (int newLatency) if (latencySamples != newLatency) { latencySamples = newLatency; - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withLatencyChanged (true)); } } @@ -421,11 +421,11 @@ AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noex return listeners[index]; } -void AudioProcessor::updateHostDisplay() +void AudioProcessor::updateHostDisplay (const AudioProcessorListener::ChangeDetails& details) { for (int i = listeners.size(); --i >= 0;) if (auto l = getListenerLocked (i)) - l->audioProcessorChanged (this); + l->audioProcessorChanged (this, details); } void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 27e4338640..39eac82112 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -991,7 +991,7 @@ public: It sends a hint to the host that something like the program, number of parameters, etc, has changed, and that it should update itself. */ - void updateHostDisplay(); + void updateHostDisplay (const AudioProcessorListener::ChangeDetails& details = {}); //============================================================================== /** Adds a parameter to the AudioProcessor. diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h b/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h index 7e075b394a..9a83d06c83 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h @@ -57,6 +57,28 @@ public: int parameterIndex, float newValue) = 0; + /** Provides details about aspects of an AudioProcessor which have changed. + */ + struct JUCE_API ChangeDetails + { + bool latencyChanged = false; + bool parameterInfoChanged = false; + bool programChanged = false; + + ChangeDetails withLatencyChanged (bool b) const noexcept { return with (&ChangeDetails::latencyChanged, b); } + ChangeDetails withParameterInfoChanged (bool b) const noexcept { return with (&ChangeDetails::parameterInfoChanged, b); } + ChangeDetails withProgramChanged (bool b) const noexcept { return with (&ChangeDetails::programChanged, b); } + + private: + template + ChangeDetails with (Member&& member, Value&& value) const noexcept + { + auto copy = *this; + copy.*member = std::forward (value); + return copy; + } + }; + /** Called to indicate that something else in the plugin has changed, like its program, number of parameters, etc. @@ -67,7 +89,7 @@ public: to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the message thread. */ - virtual void audioProcessorChanged (AudioProcessor* processor) = 0; + virtual void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) = 0; /** Indicates that a parameter change gesture has started. diff --git a/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index 1d1c0df1ce..9018ad17c9 100644 --- a/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -73,7 +73,7 @@ private: parameterValueHasChanged = 1; } - void audioProcessorChanged (AudioProcessor*) override {} + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} //============================================================================== void timerCallback() override