| @@ -474,6 +474,31 @@ public: | |||
| { | |||
| switch (inID) | |||
| { | |||
| case kAudioUnitProperty_ParameterClumpName: | |||
| if (auto* clumpNameInfo = (AudioUnitParameterNameInfo*) outData) | |||
| { | |||
| if (juceFilter != nullptr) | |||
| { | |||
| auto clumpIndex = clumpNameInfo->inID - 1; | |||
| const auto* group = parameterGroups[(int) clumpIndex]; | |||
| auto name = group->getName(); | |||
| while (group->getParent() != &juceFilter->getParameterTree()) | |||
| { | |||
| group = group->getParent(); | |||
| name = group->getName() + group->getSeparator() + name; | |||
| } | |||
| clumpNameInfo->outName = name.toCFString(); | |||
| return noErr; | |||
| } | |||
| } | |||
| // Failed to find a group corresponding to the clump ID. | |||
| jassertfalse; | |||
| break; | |||
| case juceFilterObjectPropertyID: | |||
| ((void**) outData)[0] = (void*) static_cast<AudioProcessor*> (juceFilter.get()); | |||
| ((void**) outData)[1] = (void*) this; | |||
| @@ -879,6 +904,14 @@ public: | |||
| if (param->isMetaParameter()) | |||
| outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; | |||
| auto parameterGroupHierarchy = juceFilter->getParameterTree().getGroupsForParameter (param); | |||
| if (! parameterGroupHierarchy.isEmpty()) | |||
| { | |||
| outParameterInfo.flags |= kAudioUnitParameterFlag_HasClump; | |||
| outParameterInfo.clumpID = (UInt32) parameterGroups.indexOf (parameterGroupHierarchy.getLast()) + 1; | |||
| } | |||
| // Is this a meter? | |||
| if (((param->getCategory() & 0xffff0000) >> 16) == 2) | |||
| { | |||
| @@ -1687,6 +1720,7 @@ private: | |||
| LegacyAudioParametersWrapper juceParameters; | |||
| HashMap<int32, AudioProcessorParameter*> paramMap; | |||
| Array<AudioUnitParameterID> auParamIDs; | |||
| Array<const AudioProcessorParameterGroup*> parameterGroups; | |||
| //============================================================================== | |||
| AudioUnitEvent auEvent; | |||
| @@ -1839,6 +1873,8 @@ private: | |||
| //============================================================================== | |||
| void addParameters() | |||
| { | |||
| parameterGroups = juceFilter->getParameterTree().getSubgroups (true); | |||
| juceParameters.update (*juceFilter, forceUseLegacyParamIDs); | |||
| const int numParams = juceParameters.getNumParameters(); | |||
| @@ -1157,107 +1157,175 @@ private: | |||
| #endif | |||
| } | |||
| void addParameters() | |||
| std::unique_ptr<AUParameter, NSObjectDeleter> createParameter (AudioProcessorParameter* parameter) | |||
| { | |||
| std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> params ([[NSMutableArray<AUParameterNode*> alloc] init]); | |||
| const String name (parameter->getName (512)); | |||
| overviewParams.reset ([[NSMutableArray<NSNumber*> alloc] init]); | |||
| AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; | |||
| AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | |||
| | kAudioUnitParameterFlag_IsReadable | |||
| | kAudioUnitParameterFlag_HasCFNameString | |||
| | kAudioUnitParameterFlag_ValuesHaveStrings); | |||
| auto& processor = getAudioProcessor(); | |||
| juceParameters.update (processor, forceLegacyParamIDs); | |||
| if (! forceLegacyParamIDs) | |||
| flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; | |||
| const int n = juceParameters.getNumParameters(); | |||
| // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). | |||
| if (name.isEmpty() || ! parameter->isAutomatable()) | |||
| flags |= kAudioUnitParameterFlag_NonRealTime; | |||
| for (int idx = 0; idx < n; ++idx) | |||
| { | |||
| auto* juceParam = juceParameters.getParamForIndex (idx); | |||
| const bool isParameterDiscrete = parameter->isDiscrete(); | |||
| if (! isParameterDiscrete) | |||
| flags |= kAudioUnitParameterFlag_CanRamp; | |||
| const String identifier (idx); | |||
| const String name = juceParam->getName (512); | |||
| if (parameter->isMetaParameter()) | |||
| flags |= kAudioUnitParameterFlag_IsGlobalMeta; | |||
| AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; | |||
| AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | |||
| | kAudioUnitParameterFlag_IsReadable | |||
| | kAudioUnitParameterFlag_HasCFNameString | |||
| | kAudioUnitParameterFlag_ValuesHaveStrings); | |||
| std::unique_ptr<NSMutableArray, NSObjectDeleter> valueStrings; | |||
| // Is this a meter? | |||
| if (((parameter->getCategory() & 0xffff0000) >> 16) == 2) | |||
| { | |||
| flags &= ~kAudioUnitParameterFlag_IsWritable; | |||
| flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; | |||
| unit = kAudioUnitParameterUnit_LinearGain; | |||
| } | |||
| else | |||
| { | |||
| if (! forceLegacyParamIDs) | |||
| flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; | |||
| { | |||
| if (parameter->isDiscrete()) | |||
| { | |||
| unit = parameter->isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; | |||
| auto maxValue = getMaximumParameterValue (parameter); | |||
| auto numSteps = parameter->getNumSteps(); | |||
| // set whether the param is automatable (unnamed parameters aren't allowed to be automated) | |||
| if (name.isEmpty() || ! juceParam->isAutomatable()) | |||
| flags |= kAudioUnitParameterFlag_NonRealTime; | |||
| // Some hosts can't handle the huge numbers of discrete parameter values created when | |||
| // using the default number of steps. | |||
| jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); | |||
| const bool isParameterDiscrete = juceParam->isDiscrete(); | |||
| valueStrings.reset ([NSMutableArray new]); | |||
| if (! isParameterDiscrete) | |||
| flags |= kAudioUnitParameterFlag_CanRamp; | |||
| for (int i = 0; i < numSteps; ++i) | |||
| [valueStrings.get() addObject: juceStringToNS (parameter->getText ((float) i / maxValue, 0))]; | |||
| } | |||
| } | |||
| } | |||
| if (juceParam->isMetaParameter()) | |||
| flags |= kAudioUnitParameterFlag_IsGlobalMeta; | |||
| AUParameterAddress address = generateAUParameterAddress (parameter); | |||
| std::unique_ptr<NSMutableArray, NSObjectDeleter> valueStrings; | |||
| #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS | |||
| // If you hit this assertion then you have either put a parameter in two groups or you are | |||
| // very unlucky and the hash codes of your parameter ids are not unique. | |||
| jassert (! paramMap.contains (static_cast<int64> (address))); | |||
| // is this a meter? | |||
| if (((juceParam->getCategory() & 0xffff0000) >> 16) == 2) | |||
| { | |||
| flags &= ~kAudioUnitParameterFlag_IsWritable; | |||
| flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; | |||
| unit = kAudioUnitParameterUnit_LinearGain; | |||
| } | |||
| paramAddresses.add (address); | |||
| paramMap.set (static_cast<int64> (address), parameter->getParameterIndex()); | |||
| #endif | |||
| auto getParameterIdentifier = [parameter] | |||
| { | |||
| if (auto* paramWithID = dynamic_cast<AudioProcessorParameterWithID*> (parameter)) | |||
| return paramWithID->paramID; | |||
| // This could clash if any groups have been given integer IDs! | |||
| return String (parameter->getParameterIndex()); | |||
| }; | |||
| std::unique_ptr<AUParameter, NSObjectDeleter> param; | |||
| @try | |||
| { | |||
| // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | |||
| param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) | |||
| name: juceStringToNS (name) | |||
| address: address | |||
| min: 0.0f | |||
| max: getMaximumParameterValue (parameter) | |||
| unit: unit | |||
| unitName: nullptr | |||
| flags: flags | |||
| valueStrings: valueStrings.get() | |||
| dependentParameters: nullptr] | |||
| retain]); | |||
| } | |||
| @catch (NSException* exception) | |||
| { | |||
| // Do you have duplicate identifiers in any of your groups or parameters, | |||
| // or do your identifiers have unusual characters in them? | |||
| jassertfalse; | |||
| } | |||
| [param.get() setValue: parameter->getDefaultValue()]; | |||
| [overviewParams.get() addObject: [NSNumber numberWithUnsignedLongLong: address]]; | |||
| return param; | |||
| } | |||
| std::unique_ptr<AUParameterGroup, NSObjectDeleter> createParameterGroup (AudioProcessorParameterGroup* group) | |||
| { | |||
| std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> children ([NSMutableArray<AUParameterNode*> new]); | |||
| for (auto* node : *group) | |||
| { | |||
| if (auto* childGroup = node->getGroup()) | |||
| [children.get() addObject: createParameterGroup (childGroup).get()]; | |||
| else | |||
| { | |||
| if (! forceLegacyParamIDs) | |||
| { | |||
| if (juceParam->isDiscrete()) | |||
| { | |||
| unit = juceParam->isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; | |||
| auto maxValue = getMaximumParameterValue (juceParam); | |||
| auto numSteps = juceParam->getNumSteps(); | |||
| [children.get() addObject: createParameter (node->getParameter()).get()]; | |||
| } | |||
| // Some hosts can't handle the huge numbers of discrete parameter values created when | |||
| // using the default number of steps. | |||
| jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); | |||
| std::unique_ptr<AUParameterGroup, NSObjectDeleter> result; | |||
| valueStrings.reset ([NSMutableArray new]); | |||
| @try | |||
| { | |||
| // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | |||
| result.reset ([[AUParameterTree createGroupWithIdentifier: juceStringToNS (group->getID()) | |||
| name: juceStringToNS (group->getName()) | |||
| children: children.get()] | |||
| retain]); | |||
| } | |||
| for (int i = 0; i < numSteps; ++i) | |||
| [valueStrings.get() addObject: juceStringToNS (juceParam->getText ((float) i / maxValue, 0))]; | |||
| } | |||
| } | |||
| } | |||
| @catch (NSException* exception) | |||
| { | |||
| // Do you have duplicate identifiers in any of your groups or parameters, | |||
| // or do your identifiers have unusual characters in them? | |||
| jassertfalse; | |||
| } | |||
| AUParameterAddress address = generateAUParameterAddress (juceParam); | |||
| return result; | |||
| } | |||
| #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS | |||
| // Consider yourself very unlucky if you hit this assertion. The hash codes of your | |||
| // parameter ids are not unique. | |||
| jassert (! paramMap.contains (static_cast<int64> (address))); | |||
| void addParameters() | |||
| { | |||
| auto& processor = getAudioProcessor(); | |||
| juceParameters.update (processor, forceLegacyParamIDs); | |||
| paramAddresses.add (address); | |||
| paramMap.set (static_cast<int64> (address), idx); | |||
| #endif | |||
| // This is updated when we build the tree. | |||
| overviewParams.reset ([NSMutableArray<NSNumber*> new]); | |||
| std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> topLevelNodes ([NSMutableArray<AUParameterNode*> new]); | |||
| for (auto* node : processor.getParameterTree()) | |||
| if (auto* childGroup = node->getGroup()) | |||
| [topLevelNodes.get() addObject: createParameterGroup (childGroup).get()]; | |||
| else | |||
| [topLevelNodes.get() addObject: createParameter (node->getParameter()).get()]; | |||
| // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | |||
| std::unique_ptr<AUParameter, NSObjectDeleter> param ([[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier) | |||
| name: juceStringToNS (name) | |||
| address: address | |||
| min: 0.0f | |||
| max: getMaximumParameterValue (juceParam) | |||
| unit: unit | |||
| unitName: nullptr | |||
| flags: flags | |||
| valueStrings: valueStrings.get() | |||
| dependentParameters: nullptr] retain]); | |||
| [param.get() setValue: juceParam->getDefaultValue()]; | |||
| [params.get() addObject: param.get()]; | |||
| [overviewParams.get() addObject: [NSNumber numberWithUnsignedLongLong:address]]; | |||
| @try | |||
| { | |||
| // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | |||
| paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); | |||
| } | |||
| // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h | |||
| paramTree.reset ([[AUParameterTree createTreeWithChildren: params.get()] retain]); | |||
| @catch (NSException* exception) | |||
| { | |||
| // Do you have duplicate identifiers in any of your groups or parameters, | |||
| // or do your identifiers have unusual characters in them? | |||
| jassertfalse; | |||
| } | |||
| paramObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedFromHost); | |||
| paramProvider = CreateObjCBlock (this, &JuceAudioUnitv3::getValue); | |||
| @@ -1582,13 +1650,8 @@ private: | |||
| { | |||
| const String& juceParamID = LegacyAudioParameter::getParamID (param, forceLegacyParamIDs); | |||
| #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | |||
| auto result = juceParamID.getIntValue(); | |||
| #else | |||
| auto result = juceParamID.hashCode64(); | |||
| #endif | |||
| return static_cast<AUParameterAddress> (result); | |||
| return static_cast<AUParameterAddress> (forceLegacyParamIDs ? juceParamID.getIntValue() | |||
| : juceParamID.hashCode64()); | |||
| } | |||
| AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept | |||
| @@ -126,6 +126,11 @@ public: | |||
| return getParamForVSTParamID (bypassParamID); | |||
| } | |||
| static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) | |||
| { | |||
| return group == nullptr ? Vst::kRootUnitId : group->getID().hashCode(); | |||
| } | |||
| int getNumParameters() const noexcept { return vstParamIDs.size(); } | |||
| bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } | |||
| @@ -330,10 +335,12 @@ public: | |||
| struct Param : public Vst::Parameter | |||
| { | |||
| Param (JuceVST3EditController& editController, AudioProcessorParameter& p, | |||
| Vst::ParamID vstParamID, bool isBypassParameter, bool forceLegacyParamIDs) | |||
| Vst::ParamID vstParamID, Vst::UnitID vstUnitID, | |||
| bool isBypassParameter, bool forceLegacyParamIDs) | |||
| : owner (editController), param (p) | |||
| { | |||
| info.id = vstParamID; | |||
| info.unitId = vstUnitID; | |||
| toString128 (info.title, param.getName (128)); | |||
| toString128 (info.shortTitle, param.getName (8)); | |||
| @@ -349,7 +356,6 @@ public: | |||
| info.defaultNormalizedValue = param.getDefaultValue(); | |||
| jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); | |||
| info.unitId = Vst::kRootUnitId; | |||
| // Is this a meter? | |||
| if (((param.getCategory() & 0xffff0000) >> 16) == 2) | |||
| @@ -740,8 +746,10 @@ private: | |||
| { | |||
| auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | |||
| auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); | |||
| auto* parameterGroup = pluginInstance->parameterTree.getGroupsForParameter (juceParam).getLast(); | |||
| auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); | |||
| parameters.addParameter (new Param (*this, *juceParam, vstParamID, | |||
| parameters.addParameter (new Param (*this, *juceParam, vstParamID, unitID, | |||
| (vstParamID == audioProcessor->bypassParamID), forceLegacyParamIDs)); | |||
| } | |||
| @@ -1271,6 +1279,8 @@ public: | |||
| // and not AudioChannelSet::discreteChannels (2) etc. | |||
| jassert (checkBusFormatsAreNotDiscrete()); | |||
| parameterGroups = pluginInstance->parameterTree.getSubgroups (true); | |||
| comPluginInstance = new JuceAudioProcessor (pluginInstance); | |||
| zerostruct (processContext); | |||
| @@ -1763,7 +1773,7 @@ public: | |||
| //============================================================================== | |||
| Steinberg::int32 PLUGIN_API getUnitCount() override | |||
| { | |||
| return 1; | |||
| return parameterGroups.size() + 1; | |||
| } | |||
| tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override | |||
| @@ -1779,7 +1789,17 @@ public: | |||
| return kResultTrue; | |||
| } | |||
| zerostruct (info); | |||
| if (auto* group = parameterGroups[unitIndex - 1]) | |||
| { | |||
| info.id = JuceAudioProcessor::getUnitID (group); | |||
| info.parentUnitId = JuceAudioProcessor::getUnitID (group->getParent()); | |||
| info.programListId = Vst::kNoProgramListId; | |||
| toString128 (info.name, group->getName()); | |||
| return kResultTrue; | |||
| } | |||
| return kResultFalse; | |||
| } | |||
| @@ -2263,43 +2283,6 @@ public: | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| Atomic<int> refCount { 1 }; | |||
| AudioProcessor* pluginInstance; | |||
| ComSmartPtr<Vst::IHostApplication> host; | |||
| ComSmartPtr<JuceAudioProcessor> comPluginInstance; | |||
| ComSmartPtr<JuceVST3EditController> juceVST3EditController; | |||
| /** | |||
| Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, | |||
| this object needs to be copied on every call to process() to be up-to-date... | |||
| */ | |||
| Vst::ProcessContext processContext; | |||
| Vst::ProcessSetup processSetup; | |||
| MidiBuffer midiBuffer; | |||
| Array<float*> channelListFloat; | |||
| Array<double*> channelListDouble; | |||
| AudioBuffer<float> emptyBufferFloat; | |||
| AudioBuffer<double> emptyBufferDouble; | |||
| #if JucePlugin_WantsMidiInput | |||
| bool isMidiInputBusEnabled = true; | |||
| #else | |||
| bool isMidiInputBusEnabled = false; | |||
| #endif | |||
| #if JucePlugin_ProducesMidiOutput | |||
| bool isMidiOutputBusEnabled = true; | |||
| #else | |||
| bool isMidiOutputBusEnabled = false; | |||
| #endif | |||
| ScopedJuceInitialiser_GUI libraryInitialiser; | |||
| static const char* kJucePrivateDataIdentifier; | |||
| //============================================================================== | |||
| template <typename FloatType> | |||
| void processAudio (Vst::ProcessData& data, Array<FloatType*>& channelList) | |||
| @@ -2517,6 +2500,45 @@ private: | |||
| } | |||
| //============================================================================== | |||
| Atomic<int> refCount { 1 }; | |||
| AudioProcessor* pluginInstance; | |||
| ComSmartPtr<Vst::IHostApplication> host; | |||
| ComSmartPtr<JuceAudioProcessor> comPluginInstance; | |||
| ComSmartPtr<JuceVST3EditController> juceVST3EditController; | |||
| /** | |||
| Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, | |||
| this object needs to be copied on every call to process() to be up-to-date... | |||
| */ | |||
| Vst::ProcessContext processContext; | |||
| Vst::ProcessSetup processSetup; | |||
| MidiBuffer midiBuffer; | |||
| Array<float*> channelListFloat; | |||
| Array<double*> channelListDouble; | |||
| AudioBuffer<float> emptyBufferFloat; | |||
| AudioBuffer<double> emptyBufferDouble; | |||
| #if JucePlugin_WantsMidiInput | |||
| bool isMidiInputBusEnabled = true; | |||
| #else | |||
| bool isMidiInputBusEnabled = false; | |||
| #endif | |||
| #if JucePlugin_ProducesMidiOutput | |||
| bool isMidiOutputBusEnabled = true; | |||
| #else | |||
| bool isMidiOutputBusEnabled = false; | |||
| #endif | |||
| ScopedJuceInitialiser_GUI libraryInitialiser; | |||
| static const char* kJucePrivateDataIdentifier; | |||
| Array<const AudioProcessorParameterGroup*> parameterGroups; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) | |||
| }; | |||
| @@ -1331,6 +1331,7 @@ public: | |||
| { | |||
| managedParameters.clear(); | |||
| paramIDToIndex.clear(); | |||
| AudioProcessorParameterGroup parameterGroups ({}, {}, {}); | |||
| if (audioUnit != nullptr) | |||
| { | |||
| @@ -1348,6 +1349,8 @@ public: | |||
| AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, | |||
| 0, ids, ¶mListSize); | |||
| std::map<UInt32, AudioProcessorParameterGroup*> groupIDMap; | |||
| for (size_t i = 0; i < numParams; ++i) | |||
| { | |||
| AudioUnitParameterInfo info; | |||
| @@ -1400,23 +1403,68 @@ public: | |||
| break; | |||
| } | |||
| addParameter (new AUInstanceParameter (*this, | |||
| ids[i], | |||
| paramName, | |||
| info.minValue, | |||
| info.maxValue, | |||
| info.defaultValue, | |||
| (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0, | |||
| isDiscrete, | |||
| isDiscrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), | |||
| isBoolean, | |||
| label, | |||
| (info.flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0)); | |||
| auto* parameter = new AUInstanceParameter (*this, | |||
| ids[i], | |||
| paramName, | |||
| info.minValue, | |||
| info.maxValue, | |||
| info.defaultValue, | |||
| (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0, | |||
| isDiscrete, | |||
| isDiscrete ? (int) (info.maxValue + 1.0f) : AudioProcessor::getDefaultNumParameterSteps(), | |||
| isBoolean, | |||
| label, | |||
| (info.flags & kAudioUnitParameterFlag_ValuesHaveStrings) != 0); | |||
| if (info.flags & kAudioUnitParameterFlag_HasClump) | |||
| { | |||
| auto groupInfo = groupIDMap.find (info.clumpID); | |||
| if (groupInfo == groupIDMap.end()) | |||
| { | |||
| auto getClumpName = [this, info] | |||
| { | |||
| AudioUnitParameterNameInfo clumpNameInfo; | |||
| UInt32 sz = sizeof (clumpNameInfo); | |||
| zerostruct (clumpNameInfo); | |||
| clumpNameInfo.inID = info.clumpID; | |||
| clumpNameInfo.inDesiredLength = (SInt32) 256; | |||
| if (AudioUnitGetProperty (audioUnit, | |||
| kAudioUnitProperty_ParameterClumpName, | |||
| kAudioUnitScope_Global, | |||
| 0, | |||
| &clumpNameInfo, | |||
| &sz) == noErr) | |||
| return String::fromCFString (clumpNameInfo.outName); | |||
| return String (info.clumpID); | |||
| }; | |||
| auto group = std::make_unique<AudioProcessorParameterGroup> (String (info.clumpID), | |||
| getClumpName(), String()); | |||
| group->addChild (std::unique_ptr<AudioProcessorParameter> (parameter)); | |||
| groupIDMap[info.clumpID] = group.get(); | |||
| parameterGroups.addChild (std::move (group)); | |||
| } | |||
| else | |||
| { | |||
| groupInfo->second->addChild (std::unique_ptr<AudioProcessorParameter> (parameter)); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| parameterGroups.addChild (std::unique_ptr<AudioProcessorParameter> (parameter)); | |||
| } | |||
| addParameter (parameter); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| parameterTree.swapWith (parameterGroups); | |||
| UInt32 propertySize = 0; | |||
| Boolean writable = false; | |||
| @@ -36,9 +36,6 @@ namespace juce | |||
| using namespace Steinberg; | |||
| //============================================================================== | |||
| struct VST3Classes | |||
| { | |||
| #ifndef JUCE_VST3_DEBUGGING | |||
| #define JUCE_VST3_DEBUGGING 0 | |||
| #endif | |||
| @@ -168,15 +165,6 @@ static void setStateForAllBusesOfType (Vst::IComponent* component, | |||
| warnOnFailure (component->activateBus (mediaType, direction, i, state)); | |||
| } | |||
| //============================================================================== | |||
| /** Assigns a complete AudioBuffer's channels to an AudioBusBuffers' */ | |||
| static void associateWholeBufferTo (Vst::AudioBusBuffers& vstBuffers, AudioBuffer<float>& buffer) noexcept | |||
| { | |||
| vstBuffers.channelBuffers32 = buffer.getArrayOfWritePointers(); | |||
| vstBuffers.numChannels = buffer.getNumChannels(); | |||
| vstBuffers.silenceFlags = 0; | |||
| } | |||
| //============================================================================== | |||
| static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, double sampleRate) | |||
| { | |||
| @@ -247,7 +235,7 @@ static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playH | |||
| } | |||
| //============================================================================== | |||
| struct VST3PluginInstance; | |||
| class VST3PluginInstance; | |||
| struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
| public Vst::IComponentHandler2, // From VST V3.1.0 (a very well named class, of course!) | |||
| @@ -274,96 +262,11 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
| } | |||
| //============================================================================== | |||
| tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->beginChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->sendValueChangedMessageToListeners ((float) valueNormalized); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| { | |||
| Steinberg::int32 eventIndex; | |||
| plugin->inputParameterChanges->addParameterData (paramID, eventIndex)->addPoint (0, valueNormalized, eventIndex); | |||
| } | |||
| // did the plug-in already update the parameter internally | |||
| if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalized) | |||
| return plugin->editController->setParamNormalized (paramID, valueNormalized); | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API endEdit (Vst::ParamID paramID) override | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override; | |||
| tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override; | |||
| tresult PLUGIN_API endEdit (Vst::ParamID paramID) override; | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->endChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| if (hasFlag (flags, Vst::kReloadComponent)) | |||
| plugin->reset(); | |||
| if (hasFlag (flags, Vst::kIoChanged)) | |||
| { | |||
| auto sampleRate = plugin->getSampleRate(); | |||
| auto blockSize = plugin->getBlockSize(); | |||
| plugin->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0, | |||
| blockSize > 0 ? blockSize : 1024); | |||
| } | |||
| if (hasFlag (flags, Vst::kLatencyChanged)) | |||
| if (plugin->processor != nullptr) | |||
| plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); | |||
| plugin->updateHostDisplay(); | |||
| return kResultTrue; | |||
| } | |||
| jassertfalse; | |||
| return kResultFalse; | |||
| } | |||
| tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override; | |||
| //============================================================================== | |||
| tresult PLUGIN_API setDirty (TBool) override | |||
| @@ -453,63 +356,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
| return kResultFalse; | |||
| } | |||
| tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override | |||
| { | |||
| Array<const Item*> subItemStack; | |||
| OwnedArray<PopupMenu> menuStack; | |||
| PopupMenu* topLevelMenu = menuStack.add (new PopupMenu()); | |||
| for (int i = 0; i < items.size(); ++i) | |||
| { | |||
| auto& item = items.getReference (i).item; | |||
| auto* menuToUse = menuStack.getLast(); | |||
| if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled)) | |||
| { | |||
| subItemStack.add (&item); | |||
| menuStack.add (new PopupMenu()); | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsGroupEnd)) | |||
| { | |||
| if (auto* subItem = subItemStack.getLast()) | |||
| { | |||
| if (auto* m = menuStack [menuStack.size() - 2]) | |||
| m->addSubMenu (toString (subItem->name), *menuToUse, | |||
| ! hasFlag (subItem->flags, Item::kIsDisabled), | |||
| nullptr, | |||
| hasFlag (subItem->flags, Item::kIsChecked)); | |||
| menuStack.removeLast (1); | |||
| subItemStack.removeLast (1); | |||
| } | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsSeparator)) | |||
| { | |||
| menuToUse->addSeparator(); | |||
| } | |||
| else | |||
| { | |||
| menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement, | |||
| toString (item.name), | |||
| ! hasFlag (item.flags, Item::kIsDisabled), | |||
| hasFlag (item.flags, Item::kIsChecked)); | |||
| } | |||
| } | |||
| PopupMenu::Options options; | |||
| if (auto* ed = owner.getActiveEditor()) | |||
| options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1)); | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| // Unfortunately, Steinberg's docs explicitly say this should be modal.. | |||
| handleResult (topLevelMenu->showMenu (options)); | |||
| #else | |||
| topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr<ContextMenu> (this))); | |||
| #endif | |||
| return kResultOk; | |||
| } | |||
| tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override; | |||
| #if ! JUCE_MODAL_LOOPS_PERMITTED | |||
| static void menuFinished (int modalResult, ComSmartPtr<ContextMenu> menu) { menu->handleResult (modalResult); } | |||
| @@ -613,13 +460,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
| return kResultFalse; | |||
| } | |||
| tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override | |||
| { | |||
| if (plugin != nullptr) | |||
| plugin->syncProgramNames(); | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override; | |||
| //============================================================================== | |||
| tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override | |||
| @@ -651,29 +492,7 @@ private: | |||
| using ParamMapType = std::map<Vst::ParamID, int>; | |||
| ParamMapType paramToIndexMap; | |||
| int getIndexOfParamID (Vst::ParamID paramID) | |||
| { | |||
| if (plugin == nullptr || plugin->editController == nullptr) | |||
| return -1; | |||
| auto result = getMappedParamID (paramID); | |||
| if (result < 0) | |||
| { | |||
| auto numParams = plugin->editController->getParameterCount(); | |||
| for (int i = 0; i < numParams; ++i) | |||
| { | |||
| Vst::ParameterInfo paramInfo; | |||
| plugin->editController->getParameterInfo (i, paramInfo); | |||
| paramToIndexMap[paramInfo.id] = i; | |||
| } | |||
| result = getMappedParamID (paramID); | |||
| } | |||
| return result; | |||
| } | |||
| int getIndexOfParamID (Vst::ParamID paramID); | |||
| int getMappedParamID (Vst::ParamID paramID) | |||
| { | |||
| @@ -1777,8 +1596,9 @@ struct VST3ComponentHolder | |||
| }; | |||
| //============================================================================== | |||
| struct VST3PluginInstance : public AudioPluginInstance | |||
| class VST3PluginInstance : public AudioPluginInstance | |||
| { | |||
| public: | |||
| struct VST3Parameter final : public Parameter | |||
| { | |||
| VST3Parameter (VST3PluginInstance& parent, | |||
| @@ -1881,10 +1701,10 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
| }; | |||
| VST3PluginInstance (VST3ComponentHolder* componentHolder) | |||
| : AudioPluginInstance (getBusProperties (componentHolder->component)), | |||
| holder (componentHolder), | |||
| inputParameterChanges (new ParamValueQueueList()), | |||
| outputParameterChanges (new ParamValueQueueList()), | |||
| : AudioPluginInstance (getBusProperties (componentHolder->component)), | |||
| holder (componentHolder), | |||
| inputParameterChanges (new ParamValueQueueList()), | |||
| outputParameterChanges (new ParamValueQueueList()), | |||
| midiInputs (new MidiEventList()), | |||
| midiOutputs (new MidiEventList()) | |||
| { | |||
| @@ -1948,19 +1768,7 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
| editController->setComponentHandler (holder->host); | |||
| grabInformationObjects(); | |||
| interconnectComponentAndController(); | |||
| for (int i = 0; i < editController->getParameterCount(); ++i) | |||
| { | |||
| auto paramInfo = getParameterInfoForIndex (i); | |||
| VST3Parameter* p = new VST3Parameter (*this, | |||
| paramInfo.id, | |||
| (paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0); | |||
| addParameter (p); | |||
| if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0) | |||
| bypassParam = p; | |||
| } | |||
| addParameters(); | |||
| synchroniseStates(); | |||
| syncProgramNames(); | |||
| setupIO(); | |||
| @@ -2718,6 +2526,70 @@ private: | |||
| } | |||
| } | |||
| void addParameters() | |||
| { | |||
| AudioProcessorParameterGroup parameterGroups ({}, {}, {}); | |||
| // We're going to add parameter groups to the tree recursively in the same order as the | |||
| // first parameters contained within them. | |||
| std::map<Vst::UnitID, Vst::UnitInfo> infoMap; | |||
| std::map<Vst::UnitID, AudioProcessorParameterGroup*> groupMap; | |||
| groupMap[Vst::kRootUnitId] = ¶meterGroups; | |||
| if (unitInfo != nullptr) | |||
| { | |||
| const auto numUnits = unitInfo->getUnitCount(); | |||
| for (int i = 1; i < numUnits; ++i) | |||
| { | |||
| Vst::UnitInfo ui = { 0 }; | |||
| unitInfo->getUnitInfo (i, ui); | |||
| infoMap[ui.id] = std::move (ui); | |||
| } | |||
| } | |||
| for (int i = 0; i < editController->getParameterCount(); ++i) | |||
| { | |||
| auto paramInfo = getParameterInfoForIndex (i); | |||
| auto* param = new VST3Parameter (*this, | |||
| paramInfo.id, | |||
| (paramInfo.flags & Vst::ParameterInfo::kCanAutomate) != 0); | |||
| addParameter (param); | |||
| if ((paramInfo.flags & Vst::ParameterInfo::kIsBypass) != 0) | |||
| bypassParam = param; | |||
| std::function<AudioProcessorParameterGroup*(Vst::UnitID)> findOrCreateGroup; | |||
| findOrCreateGroup = [&groupMap, &infoMap, &findOrCreateGroup](Vst::UnitID groupID) | |||
| { | |||
| auto existingGoup = groupMap.find (groupID); | |||
| if (existingGoup != groupMap.end()) | |||
| return existingGoup->second; | |||
| auto groupInfo = infoMap.find (groupID); | |||
| if (groupInfo == infoMap.end()) | |||
| return groupMap[Vst::kRootUnitId]; | |||
| auto* group = new AudioProcessorParameterGroup (String (groupInfo->first), | |||
| toString (groupInfo->second.name), | |||
| {}); | |||
| groupMap[groupInfo->first] = group; | |||
| auto* parentGroup = findOrCreateGroup (groupInfo->second.parentUnitId); | |||
| parentGroup->addChild (std::unique_ptr<AudioProcessorParameterGroup> (group)); | |||
| return group; | |||
| }; | |||
| auto* group = findOrCreateGroup (paramInfo.unitId); | |||
| group->addChild (std::unique_ptr<AudioProcessorParameter> (param)); | |||
| } | |||
| parameterTree.swapWith (parameterGroups); | |||
| } | |||
| void synchroniseStates() | |||
| { | |||
| Steinberg::MemoryStream stream; | |||
| @@ -2984,10 +2856,8 @@ private: | |||
| #pragma warning (pop) | |||
| #endif | |||
| }; | |||
| //============================================================================== | |||
| AudioPluginInstance* VST3Classes::VST3ComponentHolder::createPluginInstance() | |||
| AudioPluginInstance* VST3ComponentHolder::createPluginInstance() | |||
| { | |||
| if (! initialise()) | |||
| return nullptr; | |||
| @@ -2997,6 +2867,190 @@ AudioPluginInstance* VST3Classes::VST3ComponentHolder::createPluginInstance() | |||
| return plugin; | |||
| } | |||
| //============================================================================== | |||
| tresult VST3HostContext::beginEdit (Vst::ParamID paramID) | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->beginChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult VST3HostContext::performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->sendValueChangedMessageToListeners ((float) valueNormalized); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| { | |||
| Steinberg::int32 eventIndex; | |||
| plugin->inputParameterChanges->addParameterData (paramID, eventIndex)->addPoint (0, valueNormalized, eventIndex); | |||
| } | |||
| // did the plug-in already update the parameter internally | |||
| if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalized) | |||
| return plugin->editController->setParamNormalized (paramID, valueNormalized); | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult VST3HostContext::endEdit (Vst::ParamID paramID) | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| auto index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| if (auto* param = plugin->getParameters()[index]) | |||
| param->endChangeGesture(); | |||
| else | |||
| jassertfalse; // Invalid parameter index! | |||
| } | |||
| return kResultTrue; | |||
| } | |||
| tresult VST3HostContext::restartComponent (Steinberg::int32 flags) | |||
| { | |||
| if (plugin != nullptr) | |||
| { | |||
| if (hasFlag (flags, Vst::kReloadComponent)) | |||
| plugin->reset(); | |||
| if (hasFlag (flags, Vst::kIoChanged)) | |||
| { | |||
| auto sampleRate = plugin->getSampleRate(); | |||
| auto blockSize = plugin->getBlockSize(); | |||
| plugin->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0, | |||
| blockSize > 0 ? blockSize : 1024); | |||
| } | |||
| if (hasFlag (flags, Vst::kLatencyChanged)) | |||
| if (plugin->processor != nullptr) | |||
| plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); | |||
| plugin->updateHostDisplay(); | |||
| return kResultTrue; | |||
| } | |||
| jassertfalse; | |||
| return kResultFalse; | |||
| } | |||
| //============================================================================== | |||
| tresult VST3HostContext::ContextMenu::popup (Steinberg::UCoord x, Steinberg::UCoord y) | |||
| { | |||
| Array<const Item*> subItemStack; | |||
| OwnedArray<PopupMenu> menuStack; | |||
| PopupMenu* topLevelMenu = menuStack.add (new PopupMenu()); | |||
| for (int i = 0; i < items.size(); ++i) | |||
| { | |||
| auto& item = items.getReference (i).item; | |||
| auto* menuToUse = menuStack.getLast(); | |||
| if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled)) | |||
| { | |||
| subItemStack.add (&item); | |||
| menuStack.add (new PopupMenu()); | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsGroupEnd)) | |||
| { | |||
| if (auto* subItem = subItemStack.getLast()) | |||
| { | |||
| if (auto* m = menuStack [menuStack.size() - 2]) | |||
| m->addSubMenu (toString (subItem->name), *menuToUse, | |||
| ! hasFlag (subItem->flags, Item::kIsDisabled), | |||
| nullptr, | |||
| hasFlag (subItem->flags, Item::kIsChecked)); | |||
| menuStack.removeLast (1); | |||
| subItemStack.removeLast (1); | |||
| } | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsSeparator)) | |||
| { | |||
| menuToUse->addSeparator(); | |||
| } | |||
| else | |||
| { | |||
| menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement, | |||
| toString (item.name), | |||
| ! hasFlag (item.flags, Item::kIsDisabled), | |||
| hasFlag (item.flags, Item::kIsChecked)); | |||
| } | |||
| } | |||
| PopupMenu::Options options; | |||
| if (auto* ed = owner.getActiveEditor()) | |||
| options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1)); | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| // Unfortunately, Steinberg's docs explicitly say this should be modal.. | |||
| handleResult (topLevelMenu->showMenu (options)); | |||
| #else | |||
| topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr<ContextMenu> (this))); | |||
| #endif | |||
| return kResultOk; | |||
| } | |||
| //============================================================================== | |||
| tresult VST3HostContext::notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) | |||
| { | |||
| if (plugin != nullptr) | |||
| plugin->syncProgramNames(); | |||
| return kResultTrue; | |||
| } | |||
| //============================================================================== | |||
| int VST3HostContext::getIndexOfParamID (Vst::ParamID paramID) | |||
| { | |||
| if (plugin == nullptr || plugin->editController == nullptr) | |||
| return -1; | |||
| auto result = getMappedParamID (paramID); | |||
| if (result < 0) | |||
| { | |||
| auto numParams = plugin->editController->getParameterCount(); | |||
| for (int i = 0; i < numParams; ++i) | |||
| { | |||
| Vst::ParameterInfo paramInfo; | |||
| plugin->editController->getParameterInfo (i, paramInfo); | |||
| paramToIndexMap[paramInfo.id] = i; | |||
| } | |||
| result = getMappedParamID (paramID); | |||
| } | |||
| return result; | |||
| } | |||
| //============================================================================== | |||
| VST3PluginFormat::VST3PluginFormat() {} | |||
| @@ -3004,7 +3058,7 @@ VST3PluginFormat::~VST3PluginFormat() {} | |||
| bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, const MemoryBlock& rawData) | |||
| { | |||
| if (auto vst3 = dynamic_cast<VST3Classes::VST3PluginInstance*> (api)) | |||
| if (auto vst3 = dynamic_cast<VST3PluginInstance*> (api)) | |||
| return vst3->setStateFromPresetFile (rawData); | |||
| return false; | |||
| @@ -3013,13 +3067,13 @@ bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, cons | |||
| void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) | |||
| { | |||
| if (fileMightContainThisPluginType (fileOrIdentifier)) | |||
| VST3Classes::VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); | |||
| VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); | |||
| } | |||
| void VST3PluginFormat::createPluginInstance (const PluginDescription& description, double, int, | |||
| void* userData, PluginCreationCallback callback) | |||
| { | |||
| std::unique_ptr<VST3Classes::VST3PluginInstance> result; | |||
| std::unique_ptr<VST3PluginInstance> result; | |||
| if (fileMightContainThisPluginType (description.fileOrIdentifier)) | |||
| { | |||
| @@ -3028,13 +3082,13 @@ void VST3PluginFormat::createPluginInstance (const PluginDescription& descriptio | |||
| auto previousWorkingDirectory = File::getCurrentWorkingDirectory(); | |||
| file.getParentDirectory().setAsCurrentWorkingDirectory(); | |||
| if (const VST3Classes::VST3ModuleHandle::Ptr module = VST3Classes::VST3ModuleHandle::findOrCreateModule (file, description)) | |||
| if (const VST3ModuleHandle::Ptr module = VST3ModuleHandle::findOrCreateModule (file, description)) | |||
| { | |||
| std::unique_ptr<VST3Classes::VST3ComponentHolder> holder (new VST3Classes::VST3ComponentHolder (module)); | |||
| std::unique_ptr<VST3ComponentHolder> holder (new VST3ComponentHolder (module)); | |||
| if (holder->initialise()) | |||
| { | |||
| result.reset (new VST3Classes::VST3PluginInstance (holder.release())); | |||
| result.reset (new VST3PluginInstance (holder.release())); | |||
| if (! result->initialise()) | |||
| result.reset(); | |||
| @@ -167,4 +167,5 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
| #include "scanning/juce_PluginDirectoryScanner.cpp" | |||
| #include "scanning/juce_PluginListComponent.cpp" | |||
| #include "utilities/juce_AudioProcessorParameters.cpp" | |||
| #include "processors/juce_AudioProcessorParameterGroup.cpp" | |||
| #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
| @@ -110,6 +110,7 @@ | |||
| #include "processors/juce_AudioProcessorEditor.h" | |||
| #include "processors/juce_AudioProcessorListener.h" | |||
| #include "processors/juce_AudioProcessorParameter.h" | |||
| #include "processors/juce_AudioProcessorParameterGroup.h" | |||
| #include "processors/juce_AudioProcessor.h" | |||
| #include "processors/juce_PluginDescription.h" | |||
| #include "processors/juce_AudioPluginInstance.h" | |||
| @@ -60,6 +60,11 @@ AudioProcessor::~AudioProcessor() | |||
| // or more parameters without having made a corresponding call to endParameterChangeGesture... | |||
| jassert (changingParams.countNumberOfSetBits() == 0); | |||
| #endif | |||
| // The parameters are owned by an AudioProcessorParameterGroup, but we need | |||
| // to keep the managedParameters array populated to maintain backwards | |||
| // compatibility. | |||
| managedParameters.clearQuick (false); | |||
| } | |||
| //============================================================================== | |||
| @@ -691,17 +696,36 @@ AudioProcessorParameter* AudioProcessor::getParamChecked (int index) const noexc | |||
| return p; | |||
| } | |||
| void AudioProcessor::addParameter (AudioProcessorParameter* p) | |||
| void AudioProcessor::addParameterInternal (AudioProcessorParameter* param) | |||
| { | |||
| p->processor = this; | |||
| p->parameterIndex = managedParameters.size(); | |||
| managedParameters.add (p); | |||
| param->processor = this; | |||
| param->parameterIndex = managedParameters.size(); | |||
| managedParameters.add (param); | |||
| #ifdef JUCE_DEBUG | |||
| shouldCheckParamsForDupeIDs = true; | |||
| #endif | |||
| } | |||
| void AudioProcessor::addParameter (AudioProcessorParameter* param) | |||
| { | |||
| addParameterInternal (param); | |||
| parameterTree.addChild (std::unique_ptr<AudioProcessorParameter> (param)); | |||
| } | |||
| void AudioProcessor::addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup> group) | |||
| { | |||
| for (auto* param : group->getParameters (true)) | |||
| addParameterInternal (param); | |||
| parameterTree.addChild (std::move (group)); | |||
| } | |||
| const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() | |||
| { | |||
| return parameterTree; | |||
| } | |||
| #ifdef JUCE_DEBUG | |||
| void AudioProcessor::checkForDupedParamIDs() | |||
| { | |||
| @@ -1205,12 +1205,25 @@ public: | |||
| void updateHostDisplay(); | |||
| //============================================================================== | |||
| /** Adds a parameter to the list. | |||
| The parameter object will be managed and deleted automatically by the list | |||
| when no longer needed. | |||
| /** Adds a parameter to the AudioProcessor. | |||
| The parameter object will be managed and deleted automatically by the | |||
| AudioProcessor when no longer needed. | |||
| */ | |||
| void addParameter (AudioProcessorParameter*); | |||
| /** Adds a group of parameters to the AudioProcessor. | |||
| All the parameter objects contained within the group will be managed and | |||
| deleted automatically by the AudioProcessor when no longer needed. | |||
| @see addParameter | |||
| */ | |||
| void addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup>); | |||
| /** Returns the group of parameters managed by this AudioProcessor. */ | |||
| const AudioProcessorParameterGroup& getParameterTree(); | |||
| /** Returns the current list of parameters. */ | |||
| const OwnedArray<AudioProcessorParameter>& getParameters() const noexcept; | |||
| @@ -1584,6 +1597,9 @@ protected: | |||
| void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | |||
| private: | |||
| //============================================================================== | |||
| void addParameterInternal (AudioProcessorParameter*); | |||
| //============================================================================== | |||
| struct InOutChannelPair | |||
| { | |||
| @@ -1648,6 +1664,8 @@ private: | |||
| OwnedArray<AudioProcessorParameter> managedParameters; | |||
| AudioProcessorParameter* getParamChecked (int) const noexcept; | |||
| AudioProcessorParameterGroup parameterTree { {}, {}, {} }; | |||
| #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | |||
| BigInteger changingParams; | |||
| #endif | |||
| @@ -1671,6 +1689,7 @@ private: | |||
| friend class JuceVST3EditController; | |||
| friend class JuceVST3Component; | |||
| friend class VST3PluginInstance; | |||
| friend class AudioUnitPluginInstance; | |||
| friend class LADSPAPluginInstance; | |||
| @@ -0,0 +1,163 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| #if JUCE_UNIT_TESTS | |||
| class ParameterGroupTests : public UnitTest | |||
| { | |||
| public: | |||
| ParameterGroupTests() : UnitTest ("ParameterGroups", "Parameters") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("ParameterGroups"); | |||
| auto g1 = std::make_unique<AudioProcessorParameterGroup> ("g1", "g1", " - "); | |||
| auto* p1 = new AudioParameterFloat ("p1", "p1", { 0.0f, 2.0f }, 0.5f); | |||
| auto* p2 = new AudioParameterFloat ("p2", "p2", { 0.0f, 2.0f }, 0.5f); | |||
| auto* p3 = new AudioParameterFloat ("p3", "p3", { 0.0f, 2.0f }, 0.5f); | |||
| g1->addChild (std::unique_ptr<AudioParameterFloat> (p1)); | |||
| g1->addChild (std::unique_ptr<AudioParameterFloat> (p2), | |||
| std::unique_ptr<AudioParameterFloat> (p3)); | |||
| auto p4 = std::make_unique<AudioParameterFloat> ("p4", "p4", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| auto p5 = std::make_unique<AudioParameterFloat> ("p5", "p5", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| auto p6 = std::make_unique<AudioParameterFloat> ("p6", "p6", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| g1->addChild (std::move (p4)); | |||
| g1->addChild (std::move (p5), | |||
| std::move (p6)); | |||
| { | |||
| auto topLevelParams = g1->getParameters (false); | |||
| auto params = g1->getParameters (true); | |||
| expect (topLevelParams == params); | |||
| expectEquals (params.size(), 6); | |||
| expect (params[0] == (AudioProcessorParameter*) p1); | |||
| expect (params[1] == (AudioProcessorParameter*) p2); | |||
| expect (params[2] == (AudioProcessorParameter*) p3); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[3])->name == "p4"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[4])->name == "p5"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[5])->name == "p6"); | |||
| } | |||
| auto* p7 = new AudioParameterFloat ("p7", "p7", { 0.0f, 2.0f }, 0.5f); | |||
| auto* p8 = new AudioParameterFloat ("p8", "p8", { 0.0f, 2.0f }, 0.5f); | |||
| auto* p9 = new AudioParameterFloat ("p9", "p9", { 0.0f, 2.0f }, 0.5f); | |||
| auto p10 = std::make_unique<AudioParameterFloat> ("p10", "p10", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| auto p11 = std::make_unique<AudioParameterFloat> ("p11", "p11", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| auto p12 = std::make_unique<AudioParameterFloat> ("p12", "p12", NormalisableRange<float> (0.0f, 2.0f), 0.5f); | |||
| auto g2 = std::make_unique<AudioProcessorParameterGroup> ("g2", "g2", " | ", std::unique_ptr<AudioParameterFloat> (p7)); | |||
| auto g3 = std::make_unique<AudioProcessorParameterGroup> ("g3", "g3", " | ", std::unique_ptr<AudioParameterFloat> (p8), std::unique_ptr<AudioParameterFloat> (p9)); | |||
| auto g4 = std::make_unique<AudioProcessorParameterGroup> ("g4", "g4", " | ", std::move (p10)); | |||
| auto g5 = std::make_unique<AudioProcessorParameterGroup> ("g5", "g5", " | ", std::move (p11), std::move (p12)); | |||
| g1->addChild (std::move (g2)); | |||
| g4->addChild (std::move (g5)); | |||
| g1->addChild (std::move (g3), std::move (g4)); | |||
| { | |||
| auto topLevelParams = g1->getParameters (false); | |||
| auto params = g1->getParameters (true); | |||
| expectEquals (topLevelParams.size(), 6); | |||
| expectEquals (params.size(), 12); | |||
| expect (params[0] == (AudioProcessorParameter*) p1); | |||
| expect (params[1] == (AudioProcessorParameter*) p2); | |||
| expect (params[2] == (AudioProcessorParameter*) p3); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[3])->name == "p4"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[4])->name == "p5"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[5])->name == "p6"); | |||
| expect (params[6] == (AudioProcessorParameter*) p7); | |||
| expect (params[7] == (AudioProcessorParameter*) p8); | |||
| expect (params[8] == (AudioProcessorParameter*) p9); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[9]) ->name == "p10"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[10])->name == "p11"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (params[11])->name == "p12"); | |||
| } | |||
| g1->addChild (std::make_unique<AudioProcessorParameterGroup> ("g6", "g6", " | ", | |||
| std::make_unique<AudioParameterFloat> ("p11", "p11", NormalisableRange<float> (0.0f, 2.0f), 0.5f), | |||
| std::make_unique<AudioProcessorParameterGroup> ("g7", "g7", " | ", | |||
| std::make_unique<AudioParameterFloat> ("p12", "p12", NormalisableRange<float> (0.0f, 2.0f), 0.5f)), | |||
| std::make_unique<AudioParameterFloat> ("p13", "p13", NormalisableRange<float> (0.0f, 2.0f), 0.5f))); | |||
| TestAudioProcessor processor; | |||
| processor.addParameter (new AudioParameterFloat ("pstart", "pstart", NormalisableRange<float> (0.0f, 2.0f), 0.5f)); | |||
| auto groupParams = g1->getParameters (true); | |||
| processor.addParameterGroup (std::move (g1)); | |||
| processor.addParameter (new AudioParameterFloat ("pend", "pend", NormalisableRange<float> (0.0f, 2.0f), 0.5f)); | |||
| auto& processorParams = processor.getParameters(); | |||
| expect (dynamic_cast<AudioParameterFloat*> (processorParams.getFirst())->name == "pstart"); | |||
| expect (dynamic_cast<AudioParameterFloat*> (processorParams.getLast()) ->name == "pend"); | |||
| auto numParams = processorParams.size(); | |||
| for (int i = 1; i < numParams - 1; ++i) | |||
| expect (processorParams[i] == groupParams[i - 1]); | |||
| } | |||
| private: | |||
| struct TestAudioProcessor : public AudioProcessor | |||
| { | |||
| const String getName() const override { return "ap"; } | |||
| void prepareToPlay (double, int) override {} | |||
| void releaseResources() override {} | |||
| void processBlock (AudioBuffer<float>&, MidiBuffer&) override {} | |||
| double getTailLengthSeconds() const override { return 0.0; } | |||
| bool acceptsMidi() const override { return false; } | |||
| bool producesMidi() const override { return false; } | |||
| AudioProcessorEditor* createEditor() override { return nullptr; } | |||
| bool hasEditor() const override { return false; } | |||
| int getNumPrograms() override { return 0; } | |||
| int getCurrentProgram() override { return 0; } | |||
| void setCurrentProgram (int) override {} | |||
| const String getProgramName (int) override { return {}; } | |||
| void changeProgramName (int, const String&) override {} | |||
| void getStateInformation (MemoryBlock&) override {} | |||
| void setStateInformation (const void*, int) override {} | |||
| }; | |||
| }; | |||
| static ParameterGroupTests parameterGroupTests; | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,299 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** A class encapsulating a group of AudioProcessorParameters and nested | |||
| AudioProcessorParameterGroups. | |||
| This class is predominantly write-only; there are methods for adding group | |||
| members but none for removing them. Ultimately you will probably want to | |||
| add a fully constructed group to an AudioProcessor. | |||
| @see AudioProcessor::addParameterGroup | |||
| @tags{Audio} | |||
| */ | |||
| class AudioProcessorParameterGroup | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** A child of an AudioProcessorParameterGroup. | |||
| This can contain either an AudioProcessorParameter or an | |||
| AudioProcessorParameterGroup. You can query which using the | |||
| getParameter and getGroup methods. | |||
| @code | |||
| for (auto* child : group) | |||
| if (auto* parameter = node.getParameter()) | |||
| parameter->setValueNotifyingHost (0.5f); | |||
| else | |||
| node.getGroup()->AddChild (new Parameter()); | |||
| @endcode | |||
| */ | |||
| class AudioProcessorParameterNode | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns the parent group or nullptr if this is a top-level group. */ | |||
| AudioProcessorParameterGroup* getParent() const { return parent; } | |||
| /** Returns a pointer to a parameter if this node contains a paramater, nullptr otherwise. */ | |||
| AudioProcessorParameter* getParameter() const { return parameter.get(); } | |||
| /** Returns a pointer to a group if this node contains a group, nullptr otherwise. */ | |||
| AudioProcessorParameterGroup* getGroup() const { return group.get(); } | |||
| private: | |||
| //============================================================================== | |||
| AudioProcessorParameterNode (std::unique_ptr<AudioProcessorParameter> param, | |||
| AudioProcessorParameterGroup* parentGroup) | |||
| : parameter (std::move (param)), parent (parentGroup) | |||
| {} | |||
| AudioProcessorParameterNode (std::unique_ptr<AudioProcessorParameterGroup> grp, | |||
| AudioProcessorParameterGroup* parentGroup) | |||
| : group (std::move (grp)), parent (parentGroup) | |||
| { | |||
| group->parent = parent; | |||
| } | |||
| std::unique_ptr<AudioProcessorParameterGroup> group; | |||
| std::unique_ptr<AudioProcessorParameter> parameter; | |||
| AudioProcessorParameterGroup* parent = nullptr; | |||
| friend class AudioProcessorParameterGroup; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameterNode) | |||
| }; | |||
| //============================================================================== | |||
| /** Creates an empty AudioProcessorParameterGroup. | |||
| @param groupID A unique identifier for the group. Keep it basic; don't use any special | |||
| characters like "." and avoid pure integer strings which could collide with | |||
| legacy parameter IDs. | |||
| @param groupName The group's name, which will be displayed in the host. | |||
| @param subgroupSeparator A separator string to use between the name of this group and the name of any | |||
| subgroups if this group is flattened. AUv3 and VST3 plug-ins can have multiple | |||
| layers of nested subgroups, but AU plug-ins cannot have any subgroups. | |||
| */ | |||
| AudioProcessorParameterGroup (const String& groupID, const String& groupName, const String& subgroupSeparator) | |||
| : identifier (groupID), name (groupName), separator (subgroupSeparator) | |||
| { | |||
| } | |||
| /** Creates an AudioProcessorParameterGroup with a single child. | |||
| @param groupID A unique identifier for the group. Keep it basic; don't use any special | |||
| characters like "." and avoid pure integer strings which could collide with | |||
| legacy parameter IDs. | |||
| @param groupName The group's name, which will be displayed in the host. | |||
| @param subgroupSeparator A separator string to use between the name of this group and the name of any | |||
| subgroups if this group is flattened. AUv3 and VST3 plug-ins can have multiple | |||
| layers of nested subgroups, but AU plug-ins cannot have any subgroups. | |||
| @param child An AudioProcessorParameter or an AudioProcessorParameterGroup to add to the group. | |||
| */ | |||
| template <typename ChildType> | |||
| AudioProcessorParameterGroup (const String& groupID, const String& groupName, const String& subgroupSeparator, | |||
| std::unique_ptr<ChildType> child) | |||
| : AudioProcessorParameterGroup (groupID, groupName, subgroupSeparator) | |||
| { | |||
| addChild (std::move (child)); | |||
| } | |||
| /** Creates an AudioProcessorParameterGroup with multiple children. | |||
| @param groupID A unique identifier for the group. Keep it basic; don't use any special | |||
| characters like "." and avoid pure integer strings which could collide with | |||
| legacy parameter IDs. | |||
| @param groupName The group's name, which will be displayed in the host. | |||
| @param subgroupSeparator A separator string to use between the name of this group and the name of any | |||
| subgroups if this group is flattened. AUv3 and VST3 plug-ins can have multiple | |||
| layers of nested subgroups, but AU plug-ins cannot have any subgroups. | |||
| @param firstChild An AudioProcessorParameter or an AudioProcessorParameterGroup to add to the group. | |||
| @param remainingChildren A list of more AudioProcessorParameters or AudioProcessorParameterGroups to add to the group. | |||
| */ | |||
| template <typename ChildType, typename... Args> | |||
| AudioProcessorParameterGroup (const String& groupID, const String& groupName, const String& subgroupSeparator, | |||
| std::unique_ptr<ChildType> firstChild, Args&&... remainingChildren) | |||
| : AudioProcessorParameterGroup (groupID, groupName, subgroupSeparator, std::move (firstChild)) | |||
| { | |||
| addChild (std::forward<Args> (remainingChildren)...); | |||
| } | |||
| //============================================================================== | |||
| /** Returns the group's ID. */ | |||
| String getID() const { return identifier; } | |||
| /** Returns the group's name. */ | |||
| String getName() const { return name; } | |||
| /** Returns the group's separator string. */ | |||
| String getSeparator() const { return separator; } | |||
| /** Returns the parent of the group, or nullptr if this is a top-levle group. */ | |||
| const AudioProcessorParameterGroup* getParent() const noexcept { return parent; } | |||
| //============================================================================== | |||
| const AudioProcessorParameterNode** begin() const noexcept { return children.begin(); } | |||
| const AudioProcessorParameterNode** end() const noexcept { return children.end(); } | |||
| //============================================================================== | |||
| /** Swaps the content of this group with another. */ | |||
| void swapWith (AudioProcessorParameterGroup& other) noexcept | |||
| { | |||
| children.swapWith (other.children); | |||
| auto refreshParentPtr = [] (AudioProcessorParameterGroup& parentGroup) | |||
| { | |||
| for (auto* child : parentGroup) | |||
| if (auto* group = child->getGroup()) | |||
| group->parent = &parentGroup; | |||
| }; | |||
| refreshParentPtr (*this); | |||
| refreshParentPtr (other); | |||
| } | |||
| //============================================================================== | |||
| /** Returns all subgroups of this group. | |||
| @param recursive If this is true then this method will fetch all nested | |||
| subgroups using a depth first search. | |||
| */ | |||
| Array<const AudioProcessorParameterGroup*> getSubgroups (bool recursive) const | |||
| { | |||
| Array<const AudioProcessorParameterGroup*> groups; | |||
| getSubgroups (groups, recursive); | |||
| return groups; | |||
| } | |||
| /** Returns all the parameters in this group. | |||
| @param recursive If this is true then this method will fetch all nested | |||
| parameters using a depth first search. | |||
| */ | |||
| Array<AudioProcessorParameter*> getParameters (bool recursive) const | |||
| { | |||
| Array<AudioProcessorParameter*> parameters; | |||
| getParameters (parameters, recursive); | |||
| return parameters; | |||
| } | |||
| /** Searches this group recursively for a parameter and returns a depth ordered | |||
| list of the groups it belongs to. | |||
| */ | |||
| Array<const AudioProcessorParameterGroup*> getGroupsForParameter (AudioProcessorParameter* parameter) const | |||
| { | |||
| Array<const AudioProcessorParameterGroup*> groups; | |||
| if (auto* group = getGroupForParameter (parameter)) | |||
| { | |||
| while (group != this) | |||
| { | |||
| groups.insert (0, group); | |||
| group = group->getParent(); | |||
| } | |||
| } | |||
| return groups; | |||
| } | |||
| //============================================================================== | |||
| /** Adds a child to the group. */ | |||
| template <typename ChildType> | |||
| void addChild (std::unique_ptr<ChildType> child) | |||
| { | |||
| // If you hit a compiler error here then you are attempting to add a | |||
| // child that is neither a pointer to an AudioProcessorParameterGroup | |||
| // nor a pointer to an AudioProcessorParameter. | |||
| children.add (new AudioProcessorParameterNode (std::move (child), this)); | |||
| } | |||
| /** Adds multiple children to the group. */ | |||
| template <typename ChildType, typename... Args> | |||
| void addChild (std::unique_ptr<ChildType> firstChild, Args&&... remainingChildren) | |||
| { | |||
| addChild (std::move (firstChild)); | |||
| addChild (std::forward<Args> (remainingChildren)...); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| void getSubgroups (Array<const AudioProcessorParameterGroup*>& previousGroups, bool recursive) const | |||
| { | |||
| for (auto* child : children) | |||
| { | |||
| if (auto* group = child->getGroup()) | |||
| { | |||
| previousGroups.add (group); | |||
| if (recursive) | |||
| group->getSubgroups (previousGroups, true); | |||
| } | |||
| } | |||
| } | |||
| void getParameters (Array<AudioProcessorParameter*>& previousParameters, bool recursive) const | |||
| { | |||
| for (auto* child : children) | |||
| { | |||
| if (auto* parameter = child->getParameter()) | |||
| previousParameters.add (parameter); | |||
| else if (recursive) | |||
| child->getGroup()->getParameters (previousParameters, true); | |||
| } | |||
| } | |||
| const AudioProcessorParameterGroup* getGroupForParameter (AudioProcessorParameter* parameter) const | |||
| { | |||
| for (auto* child : children) | |||
| { | |||
| if (child->getParameter() == parameter) | |||
| return this; | |||
| if (auto* group = child->getGroup()) | |||
| if (auto* foundGroup = group->getGroupForParameter (parameter)) | |||
| return foundGroup; | |||
| } | |||
| return nullptr; | |||
| } | |||
| //============================================================================== | |||
| const String identifier, name, separator; | |||
| OwnedArray<const AudioProcessorParameterNode> children; | |||
| AudioProcessorParameterGroup* parent = nullptr; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameterGroup) | |||
| }; | |||
| } // namespace juce | |||