mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added plug-in parameter groups
This commit is contained in:
parent
af041a02d7
commit
7e1db1aa4f
11 changed files with 1104 additions and 374 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
std::unique_ptr<AUParameter, NSObjectDeleter> createParameter (AudioProcessorParameter* parameter)
|
||||
{
|
||||
const String name (parameter->getName (512));
|
||||
|
||||
AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic;
|
||||
AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable
|
||||
| kAudioUnitParameterFlag_IsReadable
|
||||
| kAudioUnitParameterFlag_HasCFNameString
|
||||
| kAudioUnitParameterFlag_ValuesHaveStrings);
|
||||
|
||||
if (! forceLegacyParamIDs)
|
||||
flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution;
|
||||
|
||||
// Set whether the param is automatable (unnamed parameters aren't allowed to be automated).
|
||||
if (name.isEmpty() || ! parameter->isAutomatable())
|
||||
flags |= kAudioUnitParameterFlag_NonRealTime;
|
||||
|
||||
const bool isParameterDiscrete = parameter->isDiscrete();
|
||||
|
||||
if (! isParameterDiscrete)
|
||||
flags |= kAudioUnitParameterFlag_CanRamp;
|
||||
|
||||
if (parameter->isMetaParameter())
|
||||
flags |= kAudioUnitParameterFlag_IsGlobalMeta;
|
||||
|
||||
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)
|
||||
{
|
||||
if (parameter->isDiscrete())
|
||||
{
|
||||
unit = parameter->isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed;
|
||||
auto maxValue = getMaximumParameterValue (parameter);
|
||||
auto numSteps = parameter->getNumSteps();
|
||||
|
||||
// Some hosts can't handle the huge numbers of discrete parameter values created when
|
||||
// using the default number of steps.
|
||||
jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps());
|
||||
|
||||
valueStrings.reset ([NSMutableArray new]);
|
||||
|
||||
for (int i = 0; i < numSteps; ++i)
|
||||
[valueStrings.get() addObject: juceStringToNS (parameter->getText ((float) i / maxValue, 0))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AUParameterAddress address = generateAUParameterAddress (parameter);
|
||||
|
||||
#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)));
|
||||
|
||||
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
|
||||
[children.get() addObject: createParameter (node->getParameter()).get()];
|
||||
}
|
||||
|
||||
std::unique_ptr<AUParameterGroup, NSObjectDeleter> result;
|
||||
|
||||
@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]);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void addParameters()
|
||||
{
|
||||
std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> params ([[NSMutableArray<AUParameterNode*> alloc] init]);
|
||||
|
||||
overviewParams.reset ([[NSMutableArray<NSNumber*> alloc] init]);
|
||||
|
||||
auto& processor = getAudioProcessor();
|
||||
juceParameters.update (processor, forceLegacyParamIDs);
|
||||
|
||||
const int n = juceParameters.getNumParameters();
|
||||
// This is updated when we build the tree.
|
||||
overviewParams.reset ([NSMutableArray<NSNumber*> new]);
|
||||
|
||||
for (int idx = 0; idx < n; ++idx)
|
||||
{
|
||||
auto* juceParam = juceParameters.getParamForIndex (idx);
|
||||
std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> topLevelNodes ([NSMutableArray<AUParameterNode*> new]);
|
||||
|
||||
const String identifier (idx);
|
||||
const String name = juceParam->getName (512);
|
||||
|
||||
AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic;
|
||||
AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable
|
||||
| kAudioUnitParameterFlag_IsReadable
|
||||
| kAudioUnitParameterFlag_HasCFNameString
|
||||
| kAudioUnitParameterFlag_ValuesHaveStrings);
|
||||
|
||||
if (! forceLegacyParamIDs)
|
||||
flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution;
|
||||
|
||||
// set whether the param is automatable (unnamed parameters aren't allowed to be automated)
|
||||
if (name.isEmpty() || ! juceParam->isAutomatable())
|
||||
flags |= kAudioUnitParameterFlag_NonRealTime;
|
||||
|
||||
const bool isParameterDiscrete = juceParam->isDiscrete();
|
||||
|
||||
if (! isParameterDiscrete)
|
||||
flags |= kAudioUnitParameterFlag_CanRamp;
|
||||
|
||||
if (juceParam->isMetaParameter())
|
||||
flags |= kAudioUnitParameterFlag_IsGlobalMeta;
|
||||
|
||||
std::unique_ptr<NSMutableArray, NSObjectDeleter> valueStrings;
|
||||
|
||||
// is this a meter?
|
||||
if (((juceParam->getCategory() & 0xffff0000) >> 16) == 2)
|
||||
{
|
||||
flags &= ~kAudioUnitParameterFlag_IsWritable;
|
||||
flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic;
|
||||
unit = kAudioUnitParameterUnit_LinearGain;
|
||||
}
|
||||
for (auto* node : processor.getParameterTree())
|
||||
if (auto* childGroup = node->getGroup())
|
||||
[topLevelNodes.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();
|
||||
[topLevelNodes.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());
|
||||
|
||||
valueStrings.reset ([NSMutableArray new]);
|
||||
|
||||
for (int i = 0; i < numSteps; ++i)
|
||||
[valueStrings.get() addObject: juceStringToNS (juceParam->getText ((float) i / maxValue, 0))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AUParameterAddress address = generateAUParameterAddress (juceParam);
|
||||
|
||||
#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)));
|
||||
|
||||
paramAddresses.add (address);
|
||||
paramMap.set (static_cast<int64> (address), idx);
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
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->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);
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue