Plug-in parameter groups


#1

Hello everybody.

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

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.

#2

-> 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() ?


#3

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.


#4

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


#5

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


#6

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.


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

#8

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?


#9

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


#10

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?


#11

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")
        ...

#12

I didn’t know that - thanks!