From 7068e7075846c96076c39c2ef1b5bc327ebbb8eb Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 24 Jan 2022 15:07:34 +0000 Subject: [PATCH] AudioProcessorParameter: Add getVersionHint function --- .../processors/juce_AudioProcessor.cpp | 2 - .../processors/juce_AudioProcessorParameter.h | 58 ++++++++++++++++++- .../juce_HostedAudioProcessorParameter.h | 2 + .../utilities/juce_AudioParameterBool.cpp | 11 ++-- .../utilities/juce_AudioParameterBool.h | 4 +- .../utilities/juce_AudioParameterChoice.cpp | 6 +- .../utilities/juce_AudioParameterChoice.h | 4 +- .../utilities/juce_AudioParameterFloat.cpp | 5 +- .../utilities/juce_AudioParameterFloat.h | 4 +- .../utilities/juce_AudioParameterInt.cpp | 5 +- .../utilities/juce_AudioParameterInt.h | 4 +- .../juce_AudioProcessorParameterWithID.cpp | 12 +++- .../juce_AudioProcessorParameterWithID.h | 12 ++-- .../juce_AudioProcessorValueTreeState.cpp | 6 +- .../juce_AudioProcessorValueTreeState.h | 3 +- .../utilities/juce_RangedAudioParameter.cpp | 8 --- .../utilities/juce_RangedAudioParameter.h | 11 ++-- 17 files changed, 118 insertions(+), 39 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index fbf91cb849..15973487f7 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -1492,8 +1492,6 @@ void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioPro void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} //============================================================================== -AudioProcessorParameter::AudioProcessorParameter() noexcept {} - AudioProcessorParameter::~AudioProcessorParameter() { #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h index bdb5360dd3..9efd021409 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h @@ -39,7 +39,58 @@ class AudioProcessor; class JUCE_API AudioProcessorParameter { public: - AudioProcessorParameter() noexcept; + AudioProcessorParameter() noexcept = default; + + /** The version hint supplied to this constructor is used in Audio Unit plugins to aid ordering + parameter identifiers when JUCE_FORCE_USE_LEGACY_PARAM_IDS is not enabled. + + When adding a parameter that is not present in a previous version of the Audio Unit, you + must ensure that the version hint supplied is a number higher than that of any parameter in + any previous plugin version. + + For example, in the first release of a plugin, every parameter was created with "1" as a + version hint. If you add some parameters in the second release of the plugin, all of the + new parameters should have "2" as a version hint. Additional parameters added in subsequent + plugin versions should have "3", "4", and so forth, increasing monotonically. + + Note that adding or removing parameters with a version hint that is lower than the maximum + version hint of all parameters will break saved automation in some hosts, so be careful! + + A version hint of "0" will be treated as though the version hint has not been set + explicitly. When targeting the AU format, the version hint may be checked at runtime in + debug builds to ensure that it has been set. + + Rationale: + + According to Apple's Documentation: + > An audio unit parameter is uniquely identified by the combination of its scope, element, and ID. + + However, Logic Pro and GarageBand have a known limitation that causes them to use parameter + indices instead of IDs to identify parameters. The effect of this is that adding parameters + to a later version of a plugin can break automation saved with an earlier version of the + plugin if the indices of existing parameters are changed. It is *always* unsafe to remove + parameters from an Audio Unit plugin that will be used in one of these hosts, because + removing a parameter will always modify the indices of following parameters. + + In order to work around this limitation, parameters in AUv2 plugins are sorted first by + their version hint, and then by the hash of their string identifier. As long as the + parameters from later versions of the plugin always have a version hint that is higher than + the parameters from earlier versions of the plugin, recall of automation data will work as + expected in Logic and GarageBand. + + Note that we can't just use the JUCE parameter index directly in order to preserve ordering. + This would require all new parameters to be added at the end of the parameter list, which + would make it impossible to add parameters to existing parameter groups. It would also make + it awkward to structure code sensibly, undoing all of the benefits of string-based parameter + identifiers. + + At time of writing, AUv3 plugins seem to be affected by the same issue, but there does not + appear to be any API to control parameter indices in this format. Therefore, when building + AUv3 plugins you must not add or remove parameters in subsequent plugin versions if you + wish to support Logic and GarageBand. + */ + explicit AudioProcessorParameter (int versionHint) + : version (versionHint) {} /** Destructor. */ virtual ~AudioProcessorParameter(); @@ -224,6 +275,10 @@ public: */ virtual StringArray getAllValueStrings() const; + //============================================================================== + /** @see AudioProcessorParameter(int) */ + int getVersionHint() const { return version; } + //============================================================================== /** A base class for listeners that want to know about changes to an @@ -291,6 +346,7 @@ private: friend class LegacyAudioParameter; AudioProcessor* processor = nullptr; int parameterIndex = -1; + int version = 0; CriticalSection listenerLock; Array listeners; mutable StringArray valueStrings; diff --git a/modules/juce_audio_processors/processors/juce_HostedAudioProcessorParameter.h b/modules/juce_audio_processors/processors/juce_HostedAudioProcessorParameter.h index 2d4990af99..eaeaa186f9 100644 --- a/modules/juce_audio_processors/processors/juce_HostedAudioProcessorParameter.h +++ b/modules/juce_audio_processors/processors/juce_HostedAudioProcessorParameter.h @@ -34,6 +34,8 @@ namespace juce */ struct HostedAudioProcessorParameter : public AudioProcessorParameter { + using AudioProcessorParameter::AudioProcessorParameter; + /** Returns an ID that is unique to this parameter. Parameter indices are unstable across plugin versions, which means that the diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp index 4ba512ddd3..81592455bb 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp @@ -26,11 +26,14 @@ namespace juce { -AudioParameterBool::AudioParameterBool (const String& idToUse, const String& nameToUse, - bool def, const String& labelToUse, +AudioParameterBool::AudioParameterBool (const String& idToUse, + const String& nameToUse, + bool def, + const String& labelToUse, std::function stringFromBool, - std::function boolFromString) - : RangedAudioParameter (idToUse, nameToUse, labelToUse), + std::function boolFromString, + int versionHintToUse) + : RangedAudioParameter (idToUse, nameToUse, labelToUse, Category::genericParameter, versionHintToUse), value (def ? 1.0f : 0.0f), defaultValue (value), stringFromBoolFunction (stringFromBool), diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h index f4a7f2c4b8..18351798cb 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h @@ -48,11 +48,13 @@ public: @param boolFromString An optional lambda function that parses a string and converts it into a bool value. Some hosts use this to allow users to type in parameter values. + @param versionHint See AudioProcessorParameter::getVersionHint() */ AudioParameterBool (const String& parameterID, const String& parameterName, bool defaultValue, const String& parameterLabel = String(), std::function stringFromBool = nullptr, - std::function boolFromString = nullptr); + std::function boolFromString = nullptr, + int versionHint = 0); /** Destructor. */ ~AudioParameterBool() override; diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.cpp b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.cpp index 05ae5856dc..fc7cae08ef 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.cpp @@ -29,8 +29,10 @@ namespace juce AudioParameterChoice::AudioParameterChoice (const String& idToUse, const String& nameToUse, const StringArray& c, int def, const String& labelToUse, std::function stringFromIndex, - std::function indexFromString) - : RangedAudioParameter (idToUse, nameToUse, labelToUse), choices (c), + std::function indexFromString, + int versionHintToUse) + : RangedAudioParameter (idToUse, nameToUse, labelToUse, Category::genericParameter, versionHintToUse), + choices (c), range ([this] { NormalisableRange rangeWithInterval { 0.0f, (float) choices.size() - 1.0f, diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h index 818f3aca36..7e833a8ed8 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h @@ -50,13 +50,15 @@ public: @param indexFromString An optional lambda function that parses a string and converts it into a choice index. Some hosts use this to allow users to type in parameter values. + @param versionHint See AudioProcessorParameter::getVersionHint() */ AudioParameterChoice (const String& parameterID, const String& parameterName, const StringArray& choices, int defaultItemIndex, const String& parameterLabel = String(), std::function stringFromIndex = nullptr, - std::function indexFromString = nullptr); + std::function indexFromString = nullptr, + int versionHint = 0); /** Destructor. */ ~AudioParameterChoice() override; diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp b/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp index ccd31f5d06..91dd34176d 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp @@ -30,8 +30,9 @@ AudioParameterFloat::AudioParameterFloat (const String& idToUse, const String& n NormalisableRange r, float def, const String& labelToUse, Category categoryToUse, std::function stringFromValue, - std::function valueFromString) - : RangedAudioParameter (idToUse, nameToUse, labelToUse, categoryToUse), + std::function valueFromString, + int versionHintToUse) + : RangedAudioParameter (idToUse, nameToUse, labelToUse, categoryToUse, versionHintToUse), range (r), value (def), defaultValue (def), stringFromValueFunction (stringFromValue), valueFromStringFunction (valueFromString) diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h b/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h index 59b058f987..ea3971101a 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h @@ -51,6 +51,7 @@ public: @param valueFromString An optional lambda function that parses a string and converts it into a non-normalised value. Some hosts use this to allow users to type in parameter values. + @param versionHint See AudioProcessorParameter::getVersionHint() */ AudioParameterFloat (const String& parameterID, const String& parameterName, @@ -59,7 +60,8 @@ public: const String& parameterLabel = String(), Category parameterCategory = AudioProcessorParameter::genericParameter, std::function stringFromValue = nullptr, - std::function valueFromString = nullptr); + std::function valueFromString = nullptr, + int versionHint = 0); /** Creates a AudioParameterFloat with an ID, name, and range. On creation, its value is set to the default value. diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterInt.cpp b/modules/juce_audio_processors/utilities/juce_AudioParameterInt.cpp index e0b1f4244e..2a8486980a 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterInt.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterInt.cpp @@ -30,8 +30,9 @@ AudioParameterInt::AudioParameterInt (const String& idToUse, const String& nameT int minValue, int maxValue, int def, const String& labelToUse, std::function stringFromInt, - std::function intFromString) - : RangedAudioParameter (idToUse, nameToUse, labelToUse), + std::function intFromString, + int versionHintToUse) + : RangedAudioParameter (idToUse, nameToUse, labelToUse, Category::genericParameter, versionHintToUse), range ([minValue, maxValue] { NormalisableRange rangeWithInterval { (float) minValue, (float) maxValue, diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h b/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h index 6e969e7945..2d61c50c84 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h @@ -51,13 +51,15 @@ public: @param intFromString An optional lambda function that parses a string and converts it into an int. Some hosts use this to allow users to type in parameter values. + @param versionHint See AudioProcessorParameter::getVersionHint() */ AudioParameterInt (const String& parameterID, const String& parameterName, int minValue, int maxValue, int defaultValue, const String& parameterLabel = String(), std::function stringFromInt = nullptr, - std::function intFromString = nullptr); + std::function intFromString = nullptr, + int versionHint = 0); /** Destructor. */ ~AudioParameterInt() override; diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.cpp b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.cpp index c8cb10819c..9540464d30 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.cpp @@ -29,9 +29,15 @@ namespace juce AudioProcessorParameterWithID::AudioProcessorParameterWithID (const String& idToUse, const String& nameToUse, const String& labelToUse, - AudioProcessorParameter::Category categoryToUse) - : paramID (idToUse), name (nameToUse), label (labelToUse), category (categoryToUse) {} -AudioProcessorParameterWithID::~AudioProcessorParameterWithID() {} + AudioProcessorParameter::Category categoryToUse, + int versionHintToUse) + : HostedAudioProcessorParameter (versionHintToUse), + paramID (idToUse), + name (nameToUse), + label (labelToUse), + category (categoryToUse) +{ +} String AudioProcessorParameterWithID::getName (int maximumStringLength) const { return name.substring (0, maximumStringLength); } String AudioProcessorParameterWithID::getLabel() const { return label; } diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h index 6b26f61c3e..2360d1f6c3 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h @@ -38,14 +38,18 @@ class JUCE_API AudioProcessorParameterWithID : public HostedAudioProcessorPara public: /** The creation of this object requires providing a name and ID which will be constant for its lifetime. + + @param parameterID Used to uniquely identify the parameter + @param parameterName The user-facing name of the parameter + @param parameterLabel An optional label for the parameter's value + @param parameterCategory The semantics of this parameter + @param versionHint See AudioProcessorParameter::getVersionHint() */ AudioProcessorParameterWithID (const String& parameterID, const String& parameterName, const String& parameterLabel = {}, - Category parameterCategory = AudioProcessorParameter::genericParameter); - - /** Destructor. */ - ~AudioProcessorParameterWithID() override; + Category parameterCategory = AudioProcessorParameter::genericParameter, + int versionHint = 0); /** Provides access to the parameter's ID string. */ const String paramID; diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index bf4b550266..e6ba3f8709 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -38,7 +38,8 @@ AudioProcessorValueTreeState::Parameter::Parameter (const String& parameterID, bool isAutomatableParameter, bool isDiscrete, AudioProcessorParameter::Category parameterCategory, - bool isBoolean) + bool isBoolean, + int versionHintToUse) : AudioParameterFloat (parameterID, parameterName, valueRange, @@ -47,7 +48,8 @@ AudioProcessorValueTreeState::Parameter::Parameter (const String& parameterID, parameterCategory, valueToTextFunction == nullptr ? std::function() : [valueToTextFunction] (float v, int) { return valueToTextFunction (v); }, - std::move (textToValueFunction)), + std::move (textToValueFunction), + versionHintToUse), unsnappedDefault (valueRange.convertTo0to1 (defaultParameterValue)), metaParameter (isMetaParameter), automatable (isAutomatableParameter), diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h index e1125680f2..24066245f9 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h @@ -409,7 +409,8 @@ public: bool isAutomatableParameter = true, bool isDiscrete = false, AudioProcessorParameter::Category parameterCategory = AudioProcessorParameter::genericParameter, - bool isBoolean = false); + bool isBoolean = false, + int versionHint = 0); float getDefaultValue() const override; int getNumSteps() const override; diff --git a/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.cpp b/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.cpp index 99226e4905..577cff66d5 100644 --- a/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.cpp +++ b/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.cpp @@ -26,14 +26,6 @@ namespace juce { -RangedAudioParameter::RangedAudioParameter (const String& parameterID, - const String& parameterName, - const String& parameterLabel, - Category parameterCategory) - : AudioProcessorParameterWithID (parameterID, parameterName, parameterLabel, parameterCategory) -{ -} - int RangedAudioParameter::getNumSteps() const { const auto& range = getNormalisableRange(); diff --git a/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h b/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h index 30695fea3a..bb9eb1ffe1 100644 --- a/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h +++ b/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h @@ -38,11 +38,14 @@ class JUCE_API RangedAudioParameter : public AudioProcessorParameterWithID public: /** The creation of this object requires providing a name and ID which will be constant for its lifetime. + + @param parameterID Used to uniquely identify the parameter + @param parameterName The user-facing name of the parameter + @param parameterLabel An optional label for the parameter's value + @param parameterCategory The semantics of this parameter + @param versionHint See AudioProcessorParameter::getVersionHint() */ - RangedAudioParameter (const String& parameterID, - const String& parameterName, - const String& parameterLabel = {}, - Category parameterCategory = AudioProcessorParameter::genericParameter); + using AudioProcessorParameterWithID::AudioProcessorParameterWithID; /** Returns the range of values that the parameter can take. */ virtual const NormalisableRange& getNormalisableRange() const = 0;