diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 20235c65ba..8881e6c93e 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -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 diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index 897b573075..e809d86c2a 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -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 (paramID, paramName, - audioProcessor.getParameterDefaultValue (parameterIndex), + processorParam->getDefaultValue(), AAX_CLinearTaperDelegate(), AAX_CNumberDisplayDelegate(), 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 diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index e3ee1d7d1f..ac92ab10a4 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -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 } //============================================================================== diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 415a9d1519..bea056cfd7 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -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; } diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client.h b/modules/juce_audio_plugin_client/juce_audio_plugin_client.h index f30ac91462..d5f034c2a2 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client.h +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client.h @@ -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 diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index fb3ae77b7f..fa379c3659 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -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 parameters; diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 938c6b736c..892e302eda 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -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 diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index fb8e13e0bf..6589718c02 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -56,6 +56,18 @@ #include #include +/** 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 diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index b19d9bb572..e33eee651b 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -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 { diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index e9e13618c1..a39b111373 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -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. diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h index 4d4be65814..d6ef2ddf5a 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h @@ -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 diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h index ff65cff8be..5c6f7153e3 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h @@ -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; diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h index c12432db07..784107bc02 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h +++ b/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h @@ -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; diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp index eecd0cc6d3..f0b2c0b99c 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp @@ -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)]; } diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index d5540cac7d..da4462f0d5 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -34,13 +34,15 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete std::function valueToText, std::function 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 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 valueToTextFunction, std::function 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; } diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h index aa276b470d..d1b3ec8c42 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h @@ -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 valueToTextFunction, std::function 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;