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