1
0
Fork 0
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:
Tom Poole 2018-09-12 16:27:51 +01:00
parent af041a02d7
commit 7e1db1aa4f
11 changed files with 1104 additions and 374 deletions

View file

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

View file

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

View file

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

View file

@ -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, &paramListSize);
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;

View file

@ -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] = &parameterGroups;
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();

View file

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

View file

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

View file

@ -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()
{

View file

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

View file

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

View file

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