@@ -6,14 +6,44 @@ Develop Branch | |||
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 | |||
--------------- | |||
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 | |||
---------- | |||
@@ -21,8 +51,8 @@ Add fps23976 to your switch statement and handle it appropriately. | |||
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 | |||
@@ -120,7 +150,7 @@ or | |||
2. Override the Look&Feel method | |||
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 | |||
Rationale | |||
@@ -1319,12 +1319,14 @@ namespace AAXClasses | |||
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) | |||
: String (parameterIndex)); | |||
AAX_CString paramName (audioProcessor.getParameterName (parameterIndex, 31).toRawUTF8()); | |||
AAX_CString paramName (processorParam->getName (31).toRawUTF8()); | |||
AAX_CParamID paramID = aaxParamIDs.getReference (parameterIndex).getCharPointer(); | |||
paramMap.set (AAXClasses::getAAXParamHash (paramID), parameterIndex); | |||
@@ -1339,19 +1341,25 @@ namespace AAXClasses | |||
AAX_IParameter* parameter | |||
= new AAX_CParameter<float> (paramID, | |||
paramName, | |||
audioProcessor.getParameterDefaultValue (parameterIndex), | |||
processorParam->getDefaultValue(), | |||
AAX_CLinearTaperDelegate<float, 0>(), | |||
AAX_CNumberDisplayDelegate<float, 3>(), | |||
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); | |||
#if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE | |||
parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous | |||
: 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_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) | |||
: (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax | |||
@@ -525,7 +525,7 @@ public: | |||
const String text (String::fromCFString (pv->inString)); | |||
if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | |||
pv->outValue = param->getValueForText (text); | |||
pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); | |||
else | |||
pv->outValue = text.getFloatValue(); | |||
@@ -546,11 +546,12 @@ public: | |||
String text; | |||
if (AudioProcessorParameter* param = juceFilter->getParameters() [paramID]) | |||
text = param->getText (value, 0); | |||
text = param->getText (value / getMaximumParameterValue (param), 0); | |||
else | |||
text = String (value); | |||
pv->outString = text.toCFString(); | |||
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, | |||
AudioUnitParameterID inParameterID, | |||
AudioUnitParameterInfo& outParameterInfo) override | |||
@@ -811,7 +823,7 @@ public: | |||
if (inScope == kAudioUnitScope_Global | |||
&& juceFilter != nullptr | |||
&& index < juceFilter->getNumParameters()) | |||
&& isPositiveAndBelow (index, juceFilter->getNumParameters())) | |||
{ | |||
outParameterInfo.unit = kAudioUnitParameterUnit_Generic; | |||
outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | |||
@@ -823,28 +835,40 @@ public: | |||
outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; | |||
#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; | |||
if (juceFilter->isMetaParameter (index)) | |||
if (! param->isDiscrete()) | |||
outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; | |||
if (param->isMetaParameter()) | |||
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_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; | |||
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); | |||
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 | |||
&& outParameterInfo.defaultValue <= outParameterInfo.maxValue); | |||
@@ -861,9 +885,9 @@ public: | |||
{ | |||
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; | |||
} | |||
@@ -878,9 +902,9 @@ public: | |||
{ | |||
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; | |||
} | |||
@@ -1727,6 +1751,14 @@ private: | |||
{ | |||
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) | |||
{ | |||
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); | |||
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; | |||
else | |||
info.flags = p.isParameterAutomatable (index) ? Vst::ParameterInfo::kCanAutomate : 0; | |||
info.flags = param->isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; | |||
valueNormalized = info.defaultNormalizedValue; | |||
} | |||
@@ -65,15 +65,28 @@ | |||
#define JUCE_FORCE_USE_LEGACY_PARAM_IDS 0 | |||
#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 | |||
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. | |||
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 | |||
incompatible to old automation data. | |||
*/ | |||
*/ | |||
#ifndef JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS | |||
#define JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS 1 | |||
#endif | |||
@@ -986,6 +986,22 @@ public: | |||
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 | |||
{ | |||
if (auto* p = parameters[index]) | |||
@@ -1178,6 +1194,8 @@ public: | |||
param->minValue = info.minValue; | |||
param->maxValue = info.maxValue; | |||
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) | |||
{ | |||
@@ -1263,7 +1281,8 @@ private: | |||
UInt32 paramID; | |||
String name; | |||
AudioUnitParameterValue minValue, maxValue; | |||
bool automatable; | |||
bool automatable, discrete; | |||
int numSteps; | |||
}; | |||
OwnedArray<ParamInfo> parameters; | |||
@@ -2247,30 +2247,54 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
return toString (getParameterInfoForIndex (parameterIndex).title); | |||
} | |||
float getParameter (int parameterIndex) override | |||
const String getParameterText (int parameterIndex) override | |||
{ | |||
if (editController != nullptr) | |||
{ | |||
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) | |||
{ | |||
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 | |||
@@ -56,6 +56,18 @@ | |||
#include <juce_gui_basics/juce_gui_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 | |||
@@ -625,6 +625,14 @@ int AudioProcessor::getDefaultNumParameterSteps() noexcept | |||
return 0x7fffffff; | |||
} | |||
bool AudioProcessor::isParameterDiscrete (int index) const | |||
{ | |||
if (auto* p = managedParameters[index]) | |||
return p->isDiscrete(); | |||
return false; | |||
} | |||
String AudioProcessor::getParameterLabel (int index) const | |||
{ | |||
if (auto* p = managedParameters[index]) | |||
@@ -1401,11 +1409,12 @@ void AudioProcessorParameter::endChangeGesture() | |||
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 | |||
{ | |||
@@ -1020,13 +1020,22 @@ public: | |||
virtual String getParameterText (int parameterIndex, int maximumStringLength); | |||
/** Returns the number of discrete steps that this parameter can represent. | |||
The default return value if you don't implement this method is | |||
AudioProcessor::getDefaultNumParameterSteps(). | |||
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. | |||
NOTE! This method will eventually be deprecated! It's recommended that you use | |||
AudioProcessorParameter::getNumSteps() instead. | |||
@see isParameterDiscrete | |||
*/ | |||
virtual int getParameterNumSteps (int parameterIndex); | |||
@@ -1034,10 +1043,26 @@ public: | |||
NOTE! This method will eventually be deprecated! It's recommended that you use | |||
AudioProcessorParameter::getNumSteps() instead. | |||
@see getParameterNumSteps | |||
*/ | |||
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. | |||
By default, this just returns 0. | |||
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; | |||
/** 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 | |||
the default implementation to return AudioProcessor::getDefaultNumParameterSteps(). | |||
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; | |||
/** 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. | |||
The default implementation just returns the floating point value | |||
as a string, but this could do anything you need for a custom type | |||
@@ -58,6 +58,7 @@ private: | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
bool isDiscrete() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
@@ -69,6 +69,7 @@ private: | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
bool isDiscrete() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
@@ -124,6 +124,7 @@ float AudioParameterBool::getValue() const { retur | |||
void AudioParameterBool::setValue (float newValue) { value = newValue; } | |||
float AudioParameterBool::getDefaultValue() const { return defaultValue; } | |||
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; } | |||
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); } | |||
float AudioParameterChoice::getDefaultValue() const { return defaultValue; } | |||
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)); } | |||
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<float (const String&)> textToValue, | |||
bool meta, | |||
bool automatable) | |||
bool automatable, | |||
bool discrete) | |||
: AudioProcessorParameterWithID (parameterID, paramName, labelText), | |||
owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue), | |||
range (r), value (defaultVal), defaultValue (defaultVal), | |||
listenersNeedCalling (true), | |||
isMetaParam (meta), | |||
isAutomatableParam (automatable) | |||
isAutomatableParam (automatable), | |||
isDiscreteParam (discrete) | |||
{ | |||
state.addListener (this); | |||
needsUpdate.set (1); | |||
@@ -150,6 +152,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete | |||
bool isMetaParameter() const override { return isMetaParam; } | |||
bool isAutomatable() const override { return isAutomatableParam; } | |||
bool isDiscrete() const override { return isDiscreteParam; } | |||
AudioProcessorValueTreeState& owner; | |||
ValueTree state; | |||
@@ -160,7 +163,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete | |||
float value, defaultValue; | |||
Atomic<int> needsUpdate; | |||
bool listenersNeedCalling; | |||
const bool isMetaParam, isAutomatableParam; | |||
const bool isMetaParam, isAutomatableParam, isDiscreteParam; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) | |||
}; | |||
@@ -185,7 +188,8 @@ AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParamet | |||
float defaultVal, std::function<String (float)> valueToTextFunction, | |||
std::function<float (const String&)> textToValueFunction, | |||
bool isMetaParameter, | |||
bool isAutomatableParameter) | |||
bool isAutomatableParameter, | |||
bool isDiscreteParameter) | |||
{ | |||
// All parameters must be created before giving this manager a ValueTree state! | |||
jassert (! state.isValid()); | |||
@@ -195,7 +199,8 @@ AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParamet | |||
Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, | |||
defaultVal, valueToTextFunction, textToValueFunction, | |||
isMetaParameter, isAutomatableParameter); | |||
isMetaParameter, isAutomatableParameter, | |||
isDiscreteParameter); | |||
processor.addParameter (p); | |||
return p; | |||
} | |||
@@ -77,6 +77,8 @@ public: | |||
@param textToValueFunction The inverse of valueToTextFunction | |||
@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 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 | |||
*/ | |||
@@ -88,7 +90,8 @@ public: | |||
std::function<String (float)> valueToTextFunction, | |||
std::function<float (const String&)> textToValueFunction, | |||
bool isMetaParameter = false, | |||
bool isAutomatableParameter = true); | |||
bool isAutomatableParameter = true, | |||
bool isDiscrete = false); | |||
/** Returns a parameter by its ID string. */ | |||
AudioProcessorParameterWithID* getParameter (StringRef parameterID) const noexcept; | |||