Plug-in parameter groups

Hello everybody.

We’ve just added support for parameter groups to the develop branch.

https://github.com/WeAreROLI/JUCE/commit/7e1db1aa4f2e29776f31fa39df4fdfff5e13ca19

Hopefully the new functionality is relatively straightforward. Now, in addition to calling AudioProcessor::addParameter, you can also call AudioProcessor::addParameterGroup, which adds a nested tree of parameters and subgroups.

Parameters added in this way will be ordered by a depth-first group traversal.

However, there are some things to be aware of:


AU (Logic)

  • Each group has a integer ID (depth first ordering) and a string name.
  • Each parameter has integer group ID, a hashed integer ID and a string name.
  • Groups cannot contain subgroups.

GUI

  • Parameters are ordered by the parameter ID. Groups have no effect.

Automation list

  • Parameters are ordered alphabetically by name within a group and in the top level.
  • Groups are ordered by the lowest contained parameter ID.
  • Groups and parameters are interleaved by a scheme I cannot decipher.

AUv3 (Logic)

  • Each group has a string ID and a string name.
  • Each parameter has a hashed integer address, a string ID and a string name.
  • Groups can have subgroups and the entire tree structure is passed to the AUv3 backend.

GUI

  • Parameters are ordered by a depth-first traversal of the supplied tree.

Automation list

  • Parameters are ordered alphabetically by name within a group and in the top level.
  • Groups are ordered by their position within the supplied tree.
  • Groups and parameters are interleaved by a scheme I cannot decipher.

VST3 (Cubase)

  • Each group has an integer ID and a string name.
  • Each parameter has an integer group ID, an integer ID and a string name.
  • Groups can have subgroups and the tree structure is passed to the VST3 backend.

GUI

  • Parameters ordered by a depth-first traversal of the supplied tree.

Automation list

  • Parameters ordered by a depth-first traversal of the supplied tree.

Summary

AUs and AUv3s in Logic are problematic.

The only thing that makes sense is the parameter ordering in the GUI for AUv3s. The whole point of using hashes or strings for identifiers is that you can add and remove parameters without losing previously saved data associated with that identifier (note: JUCE does not support dynamic number of parameters at the moment) not to specify a certain ordering. And the fact that the automation list is sorted using a different procedure to the GUI controls is very frustrating, and that groups and parameters are mixed, apparently randomly, on the same level makes no sense at all.

Some broad guidance:

  • Do not mix parameters and groups in the same level of a group (including the top level of the group in the AudioProcessor). Apple avoids doing this in their AUs.
  • AUs will have the group structure flattened automatically by JUCE so there are no subgroups. Specify a separator string like " | " in the AudioProcessorParameterGroup constructor if you have nested subgroups. The full tree will be available to the host from AUv3 and VST3 plug-ins .
  • AU and AUv3 parameters will be sorted alphabetically in Logic’s automation list.
6 Likes

-> If i introduce groups afterwards, will plugin parameters restored correctly?

-> If i do mix parameters and groups on the same level accidentally, or use subgroups, will there be jasserts() ?

1 Like

The parameter IDs will not change, so hosts shouldn’t have any trouble recalling settings and automation.

If you are using the old system of indexing your parameters then you will have to make sure that a depth-first traversal of the tree produces parameters in the same order.

Not at the moment. You could make a case for a jassert if you mix groups and parameters since Logic is such a prominent host, but there’s nothing theoretically wrong with it. Subgroups are fine; AUv3s and VST3s handle nested groups without any problems. The AudioProcessorParameterGroup class requires a separatorString in its constructor so that groups can be flattened automatically.

Edit: Updated the original post so that this was clearer.

2 Likes

Hi,
How do we add these to AudioProcessorValueTreeState if we are using createAndAddParameter?
Thank you

1 Like

@lpb AudioProcessorValueTreeState::createAndAddParameter() returns a pointer to an AudioProcessorParameterWithID object which presumably you can then pass into AudioProcessorParameterGroup::addChild()

auto myParam = vts.createAndAddParameter("p1", "p1", "", ...);
myGroup->addChild(std::make_unique<AudioProcessorParameterFloat>(myParam));

Not tested but I think something along those lines should work?

1 Like

Nope, that’s going to cause trouble - you’ll have added the parameter twice. I’m currently investigating the best way to allow apvts parameters to be in groups.

2 Likes

https://github.com/WeAreROLI/JUCE/commit/b26495491b3ca03d193157c9f8ef52036d70e5a7

auto param = apvts.createParameter (...);
addParameterGroup (std::make_unique<AudioProcessorParameterGroup> ("gid", "GROUP A", " | ", std::move (param)));
2 Likes

Hi @t0m, really loving the parameter groups so far but can I suggest adding a method to AudioProcessorParameterGroup called getParameterById() that would allow us to get get a parameter based on its ID instead of using getParameters() and using the index of the parameter in the returned Array?

For example:

auto group = std::make_unique<AudioProcessorParameterGroup> ("g", "G", " - ");

group->addChild(std::make_unique_ptr<AudioParameterFloat>("p1", "P1", ...);
group->addChild(std::make_unique_ptr<AudioParameterFloat>("p2", "P2", ...);

// ...
// instead of this:
auto p1 = group.getParameters(false)[0]->getValue();
auto p2 = group.getParameters(false)[1]->getValue();

// use this:
auto p1 = group.getParameter(false, "p1")->getValue();
auto p2 = group.getParameter(false, "p2")->getValue();

If I later decided to add another parameter to the group before the existing two (p1, p2), I’d also have to remember to change the lines that access the parameters by index which, if I’m using the parameters in a lot of places, could be a big hassle. Accessing the parameters by ID removes the hassle and means the order in which the parameters are added to the group has no effect in the code?

1 Like

On the face of it that’s a good idea, but groups hold AudioProcessorParameters rather than AudioProcessorParameterWithIDs…

Yeah I noticed that after posting my comment - I’d always assumed all parameters had an ID! I ended up doing something like:

for (auto param : group.getParameters(false))
{
    try
    {
        auto paramFloat = dynamic_cast<AudioParameterFloat*>(param);
        if (paramFloat->paramID == "p1")
        {
            // use the parameter...
        }
    }
    catch(std::bad_cast&)
    {
        // not an AudioParameterFloat parameter...
    }
}

Does this look like a sensible way of checking a groups parameters by their ID’s?

std::bad_cast is only thrown when you’re attempting to cast to a reference type. Do this instead:

if (auto* paramFloat = dynamic_cast<AudioParameterFloat*> (param))
    if (paramFloat->paramID == "p1")
        ...
1 Like

I didn’t know that - thanks!

I don’t know if this is related but we just discovered that our host doesn’t get the list of parameters for the Roland D-50 VST3 since we updated from JUCE 5.2 to 5.4. Anyone know what we need to do differently?

Can you narrow it down to a specific commit where it stopped working? There’s a good chance it’s unrelated to parameter groups…

I’ll see what I can find out

For what it’s worth, I just tried reproducing the problem using Max 8.02 which of course also uses JUCE, presumably a recent version. As you’ll see from the image, if I try to generate the list of parameters for the VST3 version of the Arturia MiniV, I get a list. However, the same request produces nothing when I try it with the Roland D50.

While at first sight it might seem that the fault is with the D50 plugin, the reality is that in with our current officially released host using JUCE 5.2, we can see the D50 parameters with no problem. Hence our theory that the problem is with a more recent release of JUCE

Can you use JUCE’s AudioPluginHost to find out where the change happened?

This probably deserves its own thread, rather than hijacking this one.

We already did that - on another thread in which you have been interacting with us. I had only posted the problem on this thread because it showed up in a google search when we went hunting for information about this. We found the bug in JUCE code.

Hi there all!
I’m making a VST3 with some parameter groups now, and I’m sure I’m making the tree correctly (It’s true that I’m using a mix of params and groups at certain levels, but I’ve seen other VST3s that do that). Only one of my groups is appearing correctly, the rest are either flattened out, or not nested properly…
What could be the problem if I’ve checked that I’m passing the correctly formed tree to the JUCE backend?

Thanks all!

EDIT: I’ve managed to group everything in VST3 form as I wanted. I did this by assigning group names like “xyz1” “abc2” etc to the groups. In Logic, everything is out of order and appearing as the bottom level groups… Is there any way around this so that at least the groups and params are in order in Logic also?

Sorry for opening this old topic,
would be nice if the GenericAudioProcessorEditor would do basic support for group parameters.

meaning:

  • have them grouped :slight_smile:
  • use the separator/fallback mechanism used by unsupported plug-in formats.

I’d go with the fallback mechanism as the generic editor is mostly useful when developing.
But if you have let’s say a synth with 16 midi parts.
so now you got Osc1 Vol * 16, would be much nicer to have Ch {i} | Osc1 Vol

2 Likes