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;