@@ -6,14 +6,44 @@ Develop Branch | |||||
Change | Change | ||||
------ | ------ | ||||
A new FrameRateType fps23976 has been added to AudioPlayHead | |||||
The method used to classify AudioUnit, VST3 and AAX plug-in parameters as | |||||
either continuous or discrete has changed. | |||||
Possible Issues | Possible Issues | ||||
--------------- | --------------- | ||||
Previously JUCE would report the FrameRateType fps24 for both 24 and | |||||
23.976 fps. If your code uses switch statements (or similar) to handle | |||||
all possible frame rate types, then this change may cause it to fall | |||||
through. | |||||
Plug-ins: DAW projects with automation data written by an AudioUnit, VST3 or | |||||
AAX plug-in built with JUCE version 5.1.1 or earlier may load incorrectly when | |||||
opened by an AudioUnit, VST3 or AAX plug-in built with JUCE version 5.2.0 and | |||||
later. | |||||
Hosts: The AudioPluginInstance::getParameterNumSteps method now returns correct | |||||
values for AU and VST3 plug-ins. | |||||
Workaround | |||||
---------- | |||||
Plug-ins: Enable JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE in the | |||||
juce_audio_plugin_client module config page in the Projucer. | |||||
Hosts: Use AudioPluginInstance::getDefaultNumParameterSteps as the number of | |||||
steps for all parameters. | |||||
Rationale | |||||
--------- | |||||
The old system for presenting plug-in parameters to a host as either continuous | |||||
or discrete is inconsistent between plug-in types and lacks sufficient | |||||
flexibility. This change harmonises the behaviour and allows individual | |||||
parameters to be marked as continuous or discrete. | |||||
Change | |||||
------ | |||||
A new FrameRateType fps23976 has been added to AudioPlayHead, | |||||
Possible Issues | |||||
--------------- | |||||
Previously JUCE would report the FrameRateType fps24 for both 24 and 23.976 | |||||
fps. If your code uses switch statements (or similar) to handle all possible | |||||
frame rate types, then this change may cause it to fall through. | |||||
Workaround | Workaround | ||||
---------- | ---------- | ||||
@@ -21,8 +51,8 @@ Add fps23976 to your switch statement and handle it appropriately. | |||||
Rationale | Rationale | ||||
--------- | --------- | ||||
JUCE should be able to handle all popular frame rate codes but was | |||||
missing support for 23.976. | |||||
JUCE should be able to handle all popular frame rate codes but was missing | |||||
support for 23.976. | |||||
Change | Change | ||||
@@ -120,7 +150,7 @@ or | |||||
2. Override the Look&Feel method | 2. Override the Look&Feel method | ||||
PopupMenu::LookAndFeelMethods::shouldPopupMenuScaleWithTargetComponent and | PopupMenu::LookAndFeelMethods::shouldPopupMenuScaleWithTargetComponent and | ||||
return false. See | |||||
return false. See | |||||
https://github.com/WeAreROLI/JUCE/blob/c288c94c2914af20f36c03ca9c5401fcb555e4e9/modules/juce_gui_basics/menus/juce_PopupMenu.h#725 | https://github.com/WeAreROLI/JUCE/blob/c288c94c2914af20f36c03ca9c5401fcb555e4e9/modules/juce_gui_basics/menus/juce_PopupMenu.h#725 | ||||
Rationale | Rationale | ||||
@@ -1319,12 +1319,14 @@ namespace AAXClasses | |||||
for (int parameterIndex = 0; parameterIndex < numParameters; ++parameterIndex) | for (int parameterIndex = 0; parameterIndex < numParameters; ++parameterIndex) | ||||
{ | { | ||||
const AudioProcessorParameter::Category category = audioProcessor.getParameterCategory (parameterIndex); | |||||
auto* processorParam = audioProcessor.getParameters().getUnchecked (parameterIndex); | |||||
const AudioProcessorParameter::Category category = processorParam->getCategory(); | |||||
aaxParamIDs.add (usingManagedParameters ? audioProcessor.getParameterID (parameterIndex) | aaxParamIDs.add (usingManagedParameters ? audioProcessor.getParameterID (parameterIndex) | ||||
: String (parameterIndex)); | : String (parameterIndex)); | ||||
AAX_CString paramName (audioProcessor.getParameterName (parameterIndex, 31).toRawUTF8()); | |||||
AAX_CString paramName (processorParam->getName (31).toRawUTF8()); | |||||
AAX_CParamID paramID = aaxParamIDs.getReference (parameterIndex).getCharPointer(); | AAX_CParamID paramID = aaxParamIDs.getReference (parameterIndex).getCharPointer(); | ||||
paramMap.set (AAXClasses::getAAXParamHash (paramID), parameterIndex); | paramMap.set (AAXClasses::getAAXParamHash (paramID), parameterIndex); | ||||
@@ -1339,19 +1341,25 @@ namespace AAXClasses | |||||
AAX_IParameter* parameter | AAX_IParameter* parameter | ||||
= new AAX_CParameter<float> (paramID, | = new AAX_CParameter<float> (paramID, | ||||
paramName, | paramName, | ||||
audioProcessor.getParameterDefaultValue (parameterIndex), | |||||
processorParam->getDefaultValue(), | |||||
AAX_CLinearTaperDelegate<float, 0>(), | AAX_CLinearTaperDelegate<float, 0>(), | ||||
AAX_CNumberDisplayDelegate<float, 3>(), | AAX_CNumberDisplayDelegate<float, 3>(), | ||||
audioProcessor.isParameterAutomatable (parameterIndex)); | audioProcessor.isParameterAutomatable (parameterIndex)); | ||||
parameter->AddShortenedName (audioProcessor.getParameterName (parameterIndex, 4).toRawUTF8()); | |||||
parameter->AddShortenedName (processorParam->getName (4).toRawUTF8()); | |||||
const int parameterNumSteps = audioProcessor.getParameterNumSteps (parameterIndex); | |||||
const int parameterNumSteps = processorParam->getNumSteps(); | |||||
parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); | parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); | ||||
#if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous | parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous | ||||
: AAX_eParameterType_Discrete); | : AAX_eParameterType_Discrete); | ||||
#else | |||||
parameter->SetType (processorParam->isDiscrete() ? AAX_eParameterType_Discrete | |||||
: AAX_eParameterType_Continuous); | |||||
#endif | |||||
parameter->SetOrientation (audioProcessor.isParameterOrientationInverted (parameterIndex) | |||||
parameter->SetOrientation (processorParam->isOrientationInverted() | |||||
? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax | ? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax | ||||
| AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) | | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) | ||||
: (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax | : (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax | ||||
@@ -525,7 +525,7 @@ public: | |||||
const String text (String::fromCFString (pv->inString)); | const String text (String::fromCFString (pv->inString)); | ||||
if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | ||||
pv->outValue = param->getValueForText (text); | |||||
pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); | |||||
else | else | ||||
pv->outValue = text.getFloatValue(); | pv->outValue = text.getFloatValue(); | ||||
@@ -546,11 +546,12 @@ public: | |||||
String text; | String text; | ||||
if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | ||||
text = param->getText (value, 0); | |||||
text = param->getText (value / getMaximumParameterValue (param), 0); | |||||
else | else | ||||
text = String (value); | text = String (value); | ||||
pv->outString = text.toCFString(); | pv->outString = text.toCFString(); | ||||
return noErr; | return noErr; | ||||
} | } | ||||
} | } | ||||
@@ -803,6 +804,17 @@ public: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
// When parameters are discrete we need to use integer values. | |||||
static float getMaximumParameterValue (AudioProcessorParameter* param) | |||||
{ | |||||
#if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
ignoreUnused (param); | |||||
return 1.0f; | |||||
#else | |||||
return param->isDiscrete() ? (float) (param->getNumSteps() - 1) : 1.0f; | |||||
#endif | |||||
} | |||||
ComponentResult GetParameterInfo (AudioUnitScope inScope, | ComponentResult GetParameterInfo (AudioUnitScope inScope, | ||||
AudioUnitParameterID inParameterID, | AudioUnitParameterID inParameterID, | ||||
AudioUnitParameterInfo& outParameterInfo) override | AudioUnitParameterInfo& outParameterInfo) override | ||||
@@ -811,7 +823,7 @@ public: | |||||
if (inScope == kAudioUnitScope_Global | if (inScope == kAudioUnitScope_Global | ||||
&& juceFilter != nullptr | && juceFilter != nullptr | ||||
&& index < juceFilter->getNumParameters()) | |||||
&& isPositiveAndBelow (index, juceFilter->getNumParameters())) | |||||
{ | { | ||||
outParameterInfo.unit = kAudioUnitParameterUnit_Generic; | outParameterInfo.unit = kAudioUnitParameterUnit_Generic; | ||||
outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | ||||
@@ -823,28 +835,40 @@ public: | |||||
outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; | outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; | ||||
#endif | #endif | ||||
const String name (juceFilter->getParameterName (index)); | |||||
auto* param = juceFilter->getParameters().getUnchecked (index); | |||||
const String name (param->getName (512)); | |||||
// set whether the param is automatable (unnamed parameters aren't allowed to be automated) | |||||
if (name.isEmpty() || ! juceFilter->isParameterAutomatable (index)) | |||||
// Set whether the param is automatable (unnamed parameters aren't allowed to be automated) | |||||
if (name.isEmpty() || ! param->isAutomatable()) | |||||
outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; | outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; | ||||
if (juceFilter->isMetaParameter (index)) | |||||
if (! param->isDiscrete()) | |||||
outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; | |||||
if (param->isMetaParameter()) | |||||
outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; | outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; | ||||
// is this a meter? | |||||
if (((juceFilter->getParameterCategory (index) & 0xffff0000) >> 16) == 2) | |||||
// Is this a meter? | |||||
if (((param->getCategory() & 0xffff0000) >> 16) == 2) | |||||
{ | { | ||||
outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; | outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; | ||||
outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; | outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; | ||||
outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; | outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; | ||||
} | } | ||||
else | |||||
{ | |||||
#if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
outParameterInfo.unit = param->isDiscrete() ? kAudioUnitParameterUnit_Indexed | |||||
: kAudioUnitParameterUnit_Generic; | |||||
#endif | |||||
} | |||||
MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); | MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); | ||||
outParameterInfo.minValue = 0.0f; | outParameterInfo.minValue = 0.0f; | ||||
outParameterInfo.maxValue = 1.0f; | |||||
outParameterInfo.defaultValue = juceFilter->getParameterDefaultValue (index); | |||||
outParameterInfo.maxValue = getMaximumParameterValue (param); | |||||
outParameterInfo.defaultValue = param->getDefaultValue(); | |||||
jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue | jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue | ||||
&& outParameterInfo.defaultValue <= outParameterInfo.maxValue); | && outParameterInfo.defaultValue <= outParameterInfo.maxValue); | ||||
@@ -861,9 +885,9 @@ public: | |||||
{ | { | ||||
if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) | if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) | ||||
{ | { | ||||
const int index = getJuceIndexForAUParameterID (inID); | |||||
auto* param = juceFilter->getParameters().getUnchecked (getJuceIndexForAUParameterID (inID)); | |||||
outValue = juceFilter->getParameter (index); | |||||
outValue = param->getValue() * getMaximumParameterValue (param); | |||||
return noErr; | return noErr; | ||||
} | } | ||||
@@ -878,9 +902,9 @@ public: | |||||
{ | { | ||||
if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) | if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) | ||||
{ | { | ||||
const int index = getJuceIndexForAUParameterID (inID); | |||||
auto* param = juceFilter->getParameters().getUnchecked (getJuceIndexForAUParameterID (inID)); | |||||
juceFilter->setParameter (index, inValue); | |||||
param->setValue (inValue / getMaximumParameterValue (param)); | |||||
return noErr; | return noErr; | ||||
} | } | ||||
@@ -1727,6 +1751,14 @@ private: | |||||
{ | { | ||||
Globals()->UseIndexedParameters (numParams); | Globals()->UseIndexedParameters (numParams); | ||||
} | } | ||||
#if JUCE_DEBUG | |||||
// Some hosts can't handle the huge numbers of discrete parameter values created when | |||||
// using the default number of steps. | |||||
for (auto* param : juceFilter->getParameters()) | |||||
if (param->isDiscrete()) | |||||
jassert (param->getNumSteps() != juceFilter->getDefaultNumParameterSteps()); | |||||
#endif | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -204,21 +204,32 @@ public: | |||||
Param (AudioProcessor& p, int index, Vst::ParamID paramID) : owner (p), paramIndex (index) | Param (AudioProcessor& p, int index, Vst::ParamID paramID) : owner (p), paramIndex (index) | ||||
{ | { | ||||
info.id = paramID; | info.id = paramID; | ||||
toString128 (info.title, p.getParameterName (index)); | |||||
toString128 (info.shortTitle, p.getParameterName (index, 8)); | |||||
toString128 (info.units, p.getParameterLabel (index)); | |||||
const int numSteps = p.getParameterNumSteps (index); | |||||
info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); | |||||
info.defaultNormalizedValue = p.getParameterDefaultValue (index); | |||||
auto* param = p.getParameters().getUnchecked (index); | |||||
toString128 (info.title, param->getName (128)); | |||||
toString128 (info.shortTitle, param->getName (8)); | |||||
toString128 (info.units, param->getLabel()); | |||||
info.stepCount = (Steinberg::int32) 0; | |||||
#if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
if (param->isDiscrete()) | |||||
#endif | |||||
{ | |||||
const int numSteps = param->getNumSteps(); | |||||
info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); | |||||
} | |||||
info.defaultNormalizedValue = param->getDefaultValue(); | |||||
jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); | jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); | ||||
info.unitId = Vst::kRootUnitId; | info.unitId = Vst::kRootUnitId; | ||||
// is this a meter? | |||||
if (((p.getParameterCategory (index) & 0xffff0000) >> 16) == 2) | |||||
// Is this a meter? | |||||
if (((param->getCategory() & 0xffff0000) >> 16) == 2) | |||||
info.flags = Vst::ParameterInfo::kIsReadOnly; | info.flags = Vst::ParameterInfo::kIsReadOnly; | ||||
else | else | ||||
info.flags = p.isParameterAutomatable (index) ? Vst::ParameterInfo::kCanAutomate : 0; | |||||
info.flags = param->isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; | |||||
valueNormalized = info.defaultNormalizedValue; | valueNormalized = info.defaultNormalizedValue; | ||||
} | } | ||||
@@ -65,15 +65,28 @@ | |||||
#define JUCE_FORCE_USE_LEGACY_PARAM_IDS 0 | #define JUCE_FORCE_USE_LEGACY_PARAM_IDS 0 | ||||
#endif | #endif | ||||
/** Config: JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
Enable this if you want to force JUCE to use a legacy scheme for | |||||
identifying plug-in parameters as either continuous or discrete. | |||||
DAW projects with automation data written by an AudioUnit, VST3 or | |||||
AAX plug-in built with JUCE version 5.1.1 or earlier may load | |||||
incorrectly when opened by an AudioUnit, VST3 or AAX plug-in built | |||||
with JUCE version 5.2.0 and later. | |||||
*/ | |||||
#ifndef JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
#define JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE 0 | |||||
#endif | |||||
/** Config: JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS | /** Config: JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS | ||||
Enable this if you want JUCE to use parameter ids which are compatible | Enable this if you want JUCE to use parameter ids which are compatible | ||||
to Studio One. Studio One ignores any parameter ids which are negative. | |||||
with Studio One. Studio One ignores any parameter ids which are negative. | |||||
Enabling this option will make JUCE generate only positive parameter ids. | Enabling this option will make JUCE generate only positive parameter ids. | ||||
Note that if you have already released a plug-in prio to JUCE 4.3.0 then | |||||
Note that if you have already released a plug-in prior to JUCE 4.3.0 then | |||||
enabling this will change your parameter ids making your plug-in | enabling this will change your parameter ids making your plug-in | ||||
incompatible to old automation data. | incompatible to old automation data. | ||||
*/ | |||||
*/ | |||||
#ifndef JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS | #ifndef JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS | ||||
#define JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS 1 | #define JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS 1 | ||||
#endif | #endif | ||||
@@ -986,6 +986,22 @@ public: | |||||
const String getParameterText (int index) override { return String (getParameter (index)); } | const String getParameterText (int index) override { return String (getParameter (index)); } | ||||
int getParameterNumSteps (int index) override | |||||
{ | |||||
if (auto* p = parameters[index]) | |||||
return p->numSteps; | |||||
return AudioProcessor::getDefaultNumParameterSteps(); | |||||
} | |||||
bool isParameterDiscrete (int index) const override | |||||
{ | |||||
if (auto* p = parameters[index]) | |||||
return p->discrete; | |||||
return false; | |||||
} | |||||
bool isParameterAutomatable (int index) const override | bool isParameterAutomatable (int index) const override | ||||
{ | { | ||||
if (auto* p = parameters[index]) | if (auto* p = parameters[index]) | ||||
@@ -1178,6 +1194,8 @@ public: | |||||
param->minValue = info.minValue; | param->minValue = info.minValue; | ||||
param->maxValue = info.maxValue; | param->maxValue = info.maxValue; | ||||
param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | ||||
param->discrete = (info.unit == kAudioUnitParameterUnit_Indexed); | |||||
param->numSteps = param->discrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(); | |||||
if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) | if ((info.flags & kAudioUnitParameterFlag_HasCFNameString) != 0) | ||||
{ | { | ||||
@@ -1263,7 +1281,8 @@ private: | |||||
UInt32 paramID; | UInt32 paramID; | ||||
String name; | String name; | ||||
AudioUnitParameterValue minValue, maxValue; | AudioUnitParameterValue minValue, maxValue; | ||||
bool automatable; | |||||
bool automatable, discrete; | |||||
int numSteps; | |||||
}; | }; | ||||
OwnedArray<ParamInfo> parameters; | OwnedArray<ParamInfo> parameters; | ||||
@@ -2247,30 +2247,54 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
return toString (getParameterInfoForIndex (parameterIndex).title); | return toString (getParameterInfoForIndex (parameterIndex).title); | ||||
} | } | ||||
float getParameter (int parameterIndex) override | |||||
const String getParameterText (int parameterIndex) override | |||||
{ | { | ||||
if (editController != nullptr) | if (editController != nullptr) | ||||
{ | { | ||||
auto id = getParameterInfoForIndex (parameterIndex).id; | auto id = getParameterInfoForIndex (parameterIndex).id; | ||||
return (float) editController->getParamNormalized (id); | |||||
Vst::String128 result; | |||||
warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); | |||||
return toString (result); | |||||
} | } | ||||
return 0.0f; | |||||
return {}; | |||||
} | } | ||||
const String getParameterText (int parameterIndex) override | |||||
int getParameterNumSteps (int parameterIndex) override | |||||
{ | { | ||||
if (editController != nullptr) | if (editController != nullptr) | ||||
{ | { | ||||
auto id = getParameterInfoForIndex (parameterIndex).id; | |||||
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; | |||||
Vst::String128 result; | |||||
warnOnFailure (editController->getParamStringByValue (id, editController->getParamNormalized (id), result)); | |||||
if (numSteps > 0) | |||||
return numSteps; | |||||
} | |||||
return toString (result); | |||||
return AudioProcessor::getDefaultNumParameterSteps(); | |||||
} | |||||
bool isParameterDiscrete (int parameterIndex) const override | |||||
{ | |||||
if (editController != nullptr) | |||||
{ | |||||
const auto numSteps = getParameterInfoForIndex (parameterIndex).stepCount; | |||||
return numSteps > 0; | |||||
} | } | ||||
return {}; | |||||
return false; | |||||
} | |||||
float getParameter (int parameterIndex) override | |||||
{ | |||||
if (editController != nullptr) | |||||
{ | |||||
auto id = getParameterInfoForIndex (parameterIndex).id; | |||||
return (float) editController->getParamNormalized (id); | |||||
} | |||||
return 0.0f; | |||||
} | } | ||||
void setParameter (int parameterIndex, float newValue) override | void setParameter (int parameterIndex, float newValue) override | ||||
@@ -56,6 +56,18 @@ | |||||
#include <juce_gui_basics/juce_gui_basics.h> | #include <juce_gui_basics/juce_gui_basics.h> | ||||
#include <juce_audio_basics/juce_audio_basics.h> | #include <juce_audio_basics/juce_audio_basics.h> | ||||
/** Config: JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
Enable this if you want to force JUCE to use an old scheme for selecting | |||||
when parameters are classified as discrete or continuous in a host. If this | |||||
is not enabled then DAW projects with automation data written by an | |||||
AudioUnit, VST3 or AAX plug-in built with JUCE version 5.1.1 or earlier may | |||||
load incorrectly when opened by an AudioUnit, VST3 or AAX plug-in built | |||||
with JUCE version 5.2.0 and later. | |||||
*/ | |||||
#ifndef JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||||
#define JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE 0 | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
/** Config: JUCE_PLUGINHOST_VST | /** Config: JUCE_PLUGINHOST_VST | ||||
@@ -625,6 +625,14 @@ int AudioProcessor::getDefaultNumParameterSteps() noexcept | |||||
return 0x7fffffff; | return 0x7fffffff; | ||||
} | } | ||||
bool AudioProcessor::isParameterDiscrete (int index) const | |||||
{ | |||||
if (auto* p = managedParameters[index]) | |||||
return p->isDiscrete(); | |||||
return false; | |||||
} | |||||
String AudioProcessor::getParameterLabel (int index) const | String AudioProcessor::getParameterLabel (int index) const | ||||
{ | { | ||||
if (auto* p = managedParameters[index]) | if (auto* p = managedParameters[index]) | ||||
@@ -1401,11 +1409,12 @@ void AudioProcessorParameter::endChangeGesture() | |||||
processor->endParameterChangeGesture (parameterIndex); | processor->endParameterChangeGesture (parameterIndex); | ||||
} | } | ||||
bool AudioProcessorParameter::isOrientationInverted() const { return false; } | |||||
bool AudioProcessorParameter::isAutomatable() const { return true; } | |||||
bool AudioProcessorParameter::isMetaParameter() const { return false; } | |||||
AudioProcessorParameter::Category AudioProcessorParameter::getCategory() const { return genericParameter; } | |||||
int AudioProcessorParameter::getNumSteps() const { return AudioProcessor::getDefaultNumParameterSteps(); } | |||||
bool AudioProcessorParameter::isOrientationInverted() const { return false; } | |||||
bool AudioProcessorParameter::isAutomatable() const { return true; } | |||||
bool AudioProcessorParameter::isMetaParameter() const { return false; } | |||||
AudioProcessorParameter::Category AudioProcessorParameter::getCategory() const { return genericParameter; } | |||||
int AudioProcessorParameter::getNumSteps() const { return AudioProcessor::getDefaultNumParameterSteps(); } | |||||
bool AudioProcessorParameter::isDiscrete() const { return false; } | |||||
String AudioProcessorParameter::getText (float value, int /*maximumStringLength*/) const | String AudioProcessorParameter::getText (float value, int /*maximumStringLength*/) const | ||||
{ | { | ||||
@@ -1020,13 +1020,22 @@ public: | |||||
virtual String getParameterText (int parameterIndex, int maximumStringLength); | virtual String getParameterText (int parameterIndex, int maximumStringLength); | ||||
/** Returns the number of discrete steps that this parameter can represent. | /** Returns the number of discrete steps that this parameter can represent. | ||||
The default return value if you don't implement this method is | The default return value if you don't implement this method is | ||||
AudioProcessor::getDefaultNumParameterSteps(). | AudioProcessor::getDefaultNumParameterSteps(). | ||||
If your parameter is boolean, then you may want to make this return 2. | If your parameter is boolean, then you may want to make this return 2. | ||||
If you want the host to display stepped automation values, rather than a | |||||
continuous interpolation between successive values, you should ensure that | |||||
isParameterDiscrete returns true. | |||||
The value that is returned may or may not be used, depending on the host. | The value that is returned may or may not be used, depending on the host. | ||||
NOTE! This method will eventually be deprecated! It's recommended that you use | NOTE! This method will eventually be deprecated! It's recommended that you use | ||||
AudioProcessorParameter::getNumSteps() instead. | AudioProcessorParameter::getNumSteps() instead. | ||||
@see isParameterDiscrete | |||||
*/ | */ | ||||
virtual int getParameterNumSteps (int parameterIndex); | virtual int getParameterNumSteps (int parameterIndex); | ||||
@@ -1034,10 +1043,26 @@ public: | |||||
NOTE! This method will eventually be deprecated! It's recommended that you use | NOTE! This method will eventually be deprecated! It's recommended that you use | ||||
AudioProcessorParameter::getNumSteps() instead. | AudioProcessorParameter::getNumSteps() instead. | ||||
@see getParameterNumSteps | @see getParameterNumSteps | ||||
*/ | */ | ||||
static int getDefaultNumParameterSteps() noexcept; | static int getDefaultNumParameterSteps() noexcept; | ||||
/** Returns true if the parameter should take discrete, rather than continuous | |||||
values. | |||||
If the parameter is boolean, this should return true (with getParameterNumSteps | |||||
returning 2). | |||||
The value that is returned may or may not be used, depending on the host. | |||||
NOTE! This method will eventually be deprecated! It's recommended that you use | |||||
AudioProcessorParameter::isDiscrete() instead. | |||||
@see getParameterNumSteps | |||||
*/ | |||||
virtual bool isParameterDiscrete (int parameterIndex) const; | |||||
/** Returns the default value for the parameter. | /** Returns the default value for the parameter. | ||||
By default, this just returns 0. | By default, this just returns 0. | ||||
The value that is returned may or may not be used, depending on the host. | The value that is returned may or may not be used, depending on the host. | ||||
@@ -106,16 +106,32 @@ public: | |||||
*/ | */ | ||||
virtual String getLabel() const = 0; | virtual String getLabel() const = 0; | ||||
/** Returns the number of discrete interval steps that this parameter's range | |||||
should be quantised into. | |||||
/** Returns the number of steps that this parameter's range should be quantised into. | |||||
If you want a continuous range of values, don't override this method, and allow | If you want a continuous range of values, don't override this method, and allow | ||||
the default implementation to return AudioProcessor::getDefaultNumParameterSteps(). | the default implementation to return AudioProcessor::getDefaultNumParameterSteps(). | ||||
If your parameter is boolean, then you may want to make this return 2. | If your parameter is boolean, then you may want to make this return 2. | ||||
The value that is returned may or may not be used, depending on the host. | |||||
The value that is returned may or may not be used, depending on the host. If you | |||||
want the host to display stepped automation values, rather than a continuous | |||||
interpolation between successive values, you should override isDiscrete to return true. | |||||
@see isDiscrete | |||||
*/ | */ | ||||
virtual int getNumSteps() const; | virtual int getNumSteps() const; | ||||
/** Returns whether the parameter uses discrete values, based on the result of | |||||
getNumSteps, or allows the host to select values continuously. | |||||
This information may or may not be used, depending on the host. If you | |||||
want the host to display stepped automation values, rather than a continuous | |||||
interpolation between successive values, override this method to return true. | |||||
@see getNumSteps | |||||
*/ | |||||
virtual bool isDiscrete() const; | |||||
/** Returns a textual version of the supplied parameter value. | /** Returns a textual version of the supplied parameter value. | ||||
The default implementation just returns the floating point value | The default implementation just returns the floating point value | ||||
as a string, but this could do anything you need for a custom type | as a string, but this could do anything you need for a custom type | ||||
@@ -58,6 +58,7 @@ private: | |||||
void setValue (float newValue) override; | void setValue (float newValue) override; | ||||
float getDefaultValue() const override; | float getDefaultValue() const override; | ||||
int getNumSteps() const override; | int getNumSteps() const override; | ||||
bool isDiscrete() const override; | |||||
String getText (float, int) const override; | String getText (float, int) const override; | ||||
float getValueForText (const String&) const override; | float getValueForText (const String&) const override; | ||||
@@ -69,6 +69,7 @@ private: | |||||
void setValue (float newValue) override; | void setValue (float newValue) override; | ||||
float getDefaultValue() const override; | float getDefaultValue() const override; | ||||
int getNumSteps() const override; | int getNumSteps() const override; | ||||
bool isDiscrete() const override; | |||||
String getText (float, int) const override; | String getText (float, int) const override; | ||||
float getValueForText (const String&) const override; | float getValueForText (const String&) const override; | ||||
@@ -124,6 +124,7 @@ float AudioParameterBool::getValue() const { retur | |||||
void AudioParameterBool::setValue (float newValue) { value = newValue; } | void AudioParameterBool::setValue (float newValue) { value = newValue; } | ||||
float AudioParameterBool::getDefaultValue() const { return defaultValue; } | float AudioParameterBool::getDefaultValue() const { return defaultValue; } | ||||
int AudioParameterBool::getNumSteps() const { return 2; } | int AudioParameterBool::getNumSteps() const { return 2; } | ||||
bool AudioParameterBool::isDiscrete() const { return true; } | |||||
float AudioParameterBool::getValueForText (const String& text) const { return text.getIntValue() != 0 ? 1.0f : 0.0f; } | float AudioParameterBool::getValueForText (const String& text) const { return text.getIntValue() != 0 ? 1.0f : 0.0f; } | ||||
String AudioParameterBool::getText (float v, int /*length*/) const { return String ((int) (v > 0.5f ? 1 : 0)); } | String AudioParameterBool::getText (float v, int /*length*/) const { return String ((int) (v > 0.5f ? 1 : 0)); } | ||||
@@ -156,6 +157,7 @@ float AudioParameterChoice::getValue() const { retur | |||||
void AudioParameterChoice::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } | void AudioParameterChoice::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } | ||||
float AudioParameterChoice::getDefaultValue() const { return defaultValue; } | float AudioParameterChoice::getDefaultValue() const { return defaultValue; } | ||||
int AudioParameterChoice::getNumSteps() const { return choices.size(); } | int AudioParameterChoice::getNumSteps() const { return choices.size(); } | ||||
bool AudioParameterChoice::isDiscrete() const { return true; } | |||||
float AudioParameterChoice::getValueForText (const String& text) const { return convertTo0to1 (choices.indexOf (text)); } | float AudioParameterChoice::getValueForText (const String& text) const { return convertTo0to1 (choices.indexOf (text)); } | ||||
String AudioParameterChoice::getText (float v, int /*length*/) const { return choices [convertFrom0to1 (v)]; } | String AudioParameterChoice::getText (float v, int /*length*/) const { return choices [convertFrom0to1 (v)]; } | ||||
@@ -34,13 +34,15 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete | |||||
std::function<String (float)> valueToText, | std::function<String (float)> valueToText, | ||||
std::function<float (const String&)> textToValue, | std::function<float (const String&)> textToValue, | ||||
bool meta, | bool meta, | ||||
bool automatable) | |||||
bool automatable, | |||||
bool discrete) | |||||
: AudioProcessorParameterWithID (parameterID, paramName, labelText), | : AudioProcessorParameterWithID (parameterID, paramName, labelText), | ||||
owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue), | owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue), | ||||
range (r), value (defaultVal), defaultValue (defaultVal), | range (r), value (defaultVal), defaultValue (defaultVal), | ||||
listenersNeedCalling (true), | listenersNeedCalling (true), | ||||
isMetaParam (meta), | isMetaParam (meta), | ||||
isAutomatableParam (automatable) | |||||
isAutomatableParam (automatable), | |||||
isDiscreteParam (discrete) | |||||
{ | { | ||||
state.addListener (this); | state.addListener (this); | ||||
needsUpdate.set (1); | needsUpdate.set (1); | ||||
@@ -150,6 +152,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete | |||||
bool isMetaParameter() const override { return isMetaParam; } | bool isMetaParameter() const override { return isMetaParam; } | ||||
bool isAutomatable() const override { return isAutomatableParam; } | bool isAutomatable() const override { return isAutomatableParam; } | ||||
bool isDiscrete() const override { return isDiscreteParam; } | |||||
AudioProcessorValueTreeState& owner; | AudioProcessorValueTreeState& owner; | ||||
ValueTree state; | ValueTree state; | ||||
@@ -160,7 +163,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete | |||||
float value, defaultValue; | float value, defaultValue; | ||||
Atomic<int> needsUpdate; | Atomic<int> needsUpdate; | ||||
bool listenersNeedCalling; | bool listenersNeedCalling; | ||||
const bool isMetaParam, isAutomatableParam; | |||||
const bool isMetaParam, isAutomatableParam, isDiscreteParam; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) | ||||
}; | }; | ||||
@@ -185,7 +188,8 @@ AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParamet | |||||
float defaultVal, std::function<String (float)> valueToTextFunction, | float defaultVal, std::function<String (float)> valueToTextFunction, | ||||
std::function<float (const String&)> textToValueFunction, | std::function<float (const String&)> textToValueFunction, | ||||
bool isMetaParameter, | bool isMetaParameter, | ||||
bool isAutomatableParameter) | |||||
bool isAutomatableParameter, | |||||
bool isDiscreteParameter) | |||||
{ | { | ||||
// All parameters must be created before giving this manager a ValueTree state! | // All parameters must be created before giving this manager a ValueTree state! | ||||
jassert (! state.isValid()); | jassert (! state.isValid()); | ||||
@@ -195,7 +199,8 @@ AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParamet | |||||
Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, | Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, | ||||
defaultVal, valueToTextFunction, textToValueFunction, | defaultVal, valueToTextFunction, textToValueFunction, | ||||
isMetaParameter, isAutomatableParameter); | |||||
isMetaParameter, isAutomatableParameter, | |||||
isDiscreteParameter); | |||||
processor.addParameter (p); | processor.addParameter (p); | ||||
return p; | return p; | ||||
} | } | ||||
@@ -77,6 +77,8 @@ public: | |||||
@param textToValueFunction The inverse of valueToTextFunction | @param textToValueFunction The inverse of valueToTextFunction | ||||
@param isMetaParameter Set this value to true if this should be a meta parameter | @param isMetaParameter Set this value to true if this should be a meta parameter | ||||
@param isAutomatableParameter Set this value to false if this parameter should not be automatable | @param isAutomatableParameter Set this value to false if this parameter should not be automatable | ||||
@param isDiscrete Set this value to true to make this parameter take discrete values in a host. | |||||
@see AudioProcessorParameter::isDiscrete | |||||
@returns the parameter object that was created | @returns the parameter object that was created | ||||
*/ | */ | ||||
@@ -88,7 +90,8 @@ public: | |||||
std::function<String (float)> valueToTextFunction, | std::function<String (float)> valueToTextFunction, | ||||
std::function<float (const String&)> textToValueFunction, | std::function<float (const String&)> textToValueFunction, | ||||
bool isMetaParameter = false, | bool isMetaParameter = false, | ||||
bool isAutomatableParameter = true); | |||||
bool isAutomatableParameter = true, | |||||
bool isDiscrete = false); | |||||
/** Returns a parameter by its ID string. */ | /** Returns a parameter by its ID string. */ | ||||
AudioProcessorParameterWithID* getParameter (StringRef parameterID) const noexcept; | AudioProcessorParameterWithID* getParameter (StringRef parameterID) const noexcept; | ||||