| @@ -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; | |||