I’m hitting a couple of issues with the Steinberg VST3 validator reporting failure when I define parameter groups.
To illustrate, if I build the JUCE AudioPluginExample_VST3 code and amend the constructor in examples\CMake\AudioPlugin\PluginProcessor.cpp as follows:
AudioPluginAudioProcessor::AudioPluginAudioProcessor()
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
{
juce::AudioProcessorParameterGroup rootGroup;
rootGroup.addChild (std::make_unique<juce::AudioParameterFloat> ("amount", "Amount", 0.0f, 1.0f, 0.5f));
auto subGroup = std::make_unique<juce::AudioProcessorParameterGroup> ("subgroup", "Sub Group", ">>");
subGroup->addChild (std::make_unique<juce::AudioParameterFloat> ("subamount", "Sub Amount", 0.0f, 1.0f, 0.5f));
rootGroup.addChild (std::move (subGroup));
setParameterTree (std::move (rootGroup));
}
The Steinberg validator complains thus:
[Scan Parameters]
Info: ===Scan Parameters ====================================
Info: This component exports 3 parameter(s)
Info: Parameter 000 (id=733630552): [title="Amount"] [unit=""] [type = F, default = 0.500000, unit = 0]
Info: Parameter 001 (id=8674968): [title="Sub Amount"] [unit=""] [type = F, default = 0.500000, unit = -2072240065]
ERROR: Parameter 001 (id=8674968): No appropriate unit ID!!!
[XXXXXXX Failed]
and
[Scan Units]
Info: ===Scan Units ====================================
Info: This component has 2 unit(s).
Info: Unit000 (ID = 0): "Root Unit" (parent ID = -1, programlist ID = -1)
ERROR: Unit 001: Invalid ID!
[XXXXXXX Failed]
As far as I can tell, the cause here is that the unit ID (created by hashing the sub group’s juce::String identifier
) is negative (in signed 32-bit). This falls into the parameters’ reserved ID range according to 3rd-Party Developers Support & SDKs | Steinberg
Up to 2^31 parameters can be exported with id range [0, 2147483648]
(the range [2147483649, 429496729] is reserved for host application).
(and presumably applies to unit IDs too as -1 is used to represent “no parent unit”). Changing the sub group ID from “subgroup” to “group” makes the hash fall back into positive 32-bit signed range, but this is just luck.
A related problem occurs if you give the top-level group a name, so the constructor code looks like:
AudioPluginAudioProcessor::AudioPluginAudioProcessor()
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
{
juce::AudioProcessorParameterGroup rootGroup ("rootgroup", "Root Group", "-");
rootGroup.addChild (std::make_unique<juce::AudioParameterFloat> ("amount", "Amount", 0.0f, 1.0f, 0.5f));
auto subGroup = std::make_unique<juce::AudioProcessorParameterGroup> ("group", "Sub Group", "-");
subGroup->addChild (std::make_unique<juce::AudioParameterFloat> ("subamount", "Sub Amount", 0.0f, 1.0f, 0.5f));
rootGroup.addChild (std::move (subGroup));
setParameterTree (std::move (rootGroup));
}
Though the parameters in the parent group report themselves with unit 0:
[Scan Parameters]
Info: ===Scan Parameters ====================================
Info: This component exports 3 parameter(s)
Info: Parameter 000 (id=733630552): [title="Amount"] [unit=""] [type = F, default = 0.500000, unit = 0]
Info: Parameter 001 (id=8674968): [title="Sub Amount"] [unit=""] [type = F, default = 0.500000, unit = 98629247]
Info: Parameter 002 (id=1652125811): [title="Bypass"] [unit=""] [type = T, default = 0.000000, unit = 0]
[Succeeded]
the sub group’s parent ID is reported as invalid:
[Scan Units]
Info: ===Scan Units ====================================
Info: This component has 2 unit(s).
Info: Unit000 (ID = 0): "Root Unit" (parent ID = -1, programlist ID = -1)
ERROR: Unit 001: Invalid parent ID!
[XXXXXXX Failed]
I believe this is because the sub group’s parent ID is the hash of the root group’s (now non-empty) identifier. This works when the root group has no identifier as (luckily) the hash of the empty string is 0 which maps to the Vst::kRootUnitId
used in the VST wrapper for the root unit. If you give the root group a non-empty identifier, the hash becomes non-zero and the sub group’s parent ID does not match the root unit.
In my application I can probably enforce that the root group has no ID (as long as other plugin formats are happy with that too) but it seems that the behaviour isn’t by design? In addition it seems like the hashing algorithms will regularly produce names out of the expected range for VST?
Could someone familiar with this confirm whether these are indeed bugs, or whether I’m just using the API incorrectly?