Adding parameters post launch to a plugin?

Yes, the parameter types now take a ParameterID { "unique_id", versionHint } as their first argument. Note that this type is also implicitly constructible from a single string argument, in which case the versionHint will be 0 (this is the case for old code that has not been updated).

For an example of how setting the version hint is intended to work in practice, search for ParameterID in the AudioPluginDemo or DSPModulePluginDemo sources.

As long as we’re discussing the parameter constructors, I would love to have a ParameterTraits struct, containing things like the name, range, etc, that can be passed to the constructor :slightly_smiling_face:

This already exists - although it’s only for the parameter attributes that might be considered ‘optional’. The name, range, and ID (for example) are used on practically every parameter, and we want to enforce that the user has set these to sensible values. For that reason, I thought it was best to keep these kinds of properties directly in the main constructor argument list.

Things like labels, parameter categories, and text conversion functions are a bit less integral to the functioning of the parameter, and it’s easier to make a case for reusing these sorts of arguments across multiple parameters. For these reasons, we’ve put the ‘optional’ properties into the Attributes structs.

Ahh OK… In my case I had to add it to my plugin description identifier.

PluginDescription desc;
desc.pluginFormatName = “AudioUnit”;
desc.fileOrIdentifier = {“AudioUnit:Synths/aumu,dls ,appl”, 1};

Oh, sorry – where can I find it in the docs? I must be missing it.

I’m not sure that will do what you expect. fileOrIdentifier is still a plain String. If the assertion is complaining about version hints, you must set the version hint on each of your plugin’s parameters.

Most AudioProcessorParameter subtypes now have a constructor taking an Attributes type, e.g.

https://docs.juce.com/develop/classAudioParameterFloatAttributes.html

https://docs.juce.com/develop/classAudioParameterIntAttributes.html

Most of these types are implemented in terms of RangedAudioParameterAttributes:

https://docs.juce.com/develop/classRangedAudioParameterAttributes.html

This class allows you to set the label, category, automatable state, isBoolean state, and text/value conversion functions. You can see the classes in action in the DSPModulePluginDemo:

using Attributes = AudioProcessorValueTreeStateParameterAttributes;

static String valueToTextFunction (float x, int) { return String (x, 2); }
static float textToValueFunction (const String& str) { return str.getFloatValue(); }

static auto getBasicAttributes()
{
    return Attributes().withStringFromValueFunction (valueToTextFunction)
                       .withValueFromStringFunction (textToValueFunction);
}

static auto getDbAttributes()           { return getBasicAttributes().withLabel ("dB"); }
static auto getMsAttributes()           { return getBasicAttributes().withLabel ("ms"); }
static auto getHzAttributes()           { return getBasicAttributes().withLabel ("Hz"); }
static auto getPercentageAttributes()   { return getBasicAttributes().withLabel ("%"); }
static auto getRatioAttributes()        { return getBasicAttributes().withLabel (":1"); }

Awesome, thank you!!

Thank you @reuk, how exactly could I do that?

I am only loading one plugin (Apple DLS Music Device) into my plugin. The assertion is hit when I try to create the plugin on this line:
plugin = formatManager.createPluginInstance(desc, 44100.0, 512, errorString);

Before this line my plugin instance is nullptr so I cannot set a version hint.

I think I see. Are you writing a plugin that can host other plugins? Perhaps the parameters for the internal AU need to be updated in JUCE to have a valid versionHint. If so, this would be a JUCE issue.

Yes, my plugin only hosts one plugin to use as an instrument for midi output. I think you may be right in saying that it is a JUCE issue. It looks like the process to create a plugin instance after calling formatManager.createPluginInstance() involves refreshing the parameter list and setting the host parameter tree in juce_AudioUnitPluginFormat.mm:

void refreshParameterList()
{

  ...
  setHostParameterTree (std::move (newParameterTree));

}

I just had a quick look into this issue, but I’m not sure of the best solution.

At the moment, we check the following in the AudioProcessor:

   #if JucePlugin_Build_AU
    jassert (wrapperType == wrapperType_Undefined || param->getVersionHint() != 0);
   #endif

The purpose of the wrapperType check is to avoid asserting if the AudioProcessor is not directly being used as a plugin or standalone, which should avoid the assertion in your situation too.

The problem is that the wrapperType is set using a global variable, which is checked and set during the AudioProcessor constructor. If you create inner AudioProcessors during the constructor of the outer editor, all of them will (incorrectly?) share the same wrapper type.

I don’t think that we should necessarily be setting the version hint for the parameters of hosted plugins. It would be better to pass the correct wrapper type into the AudioProcessor constructor, but that would be a disruptive and breaking change.

Perhaps you could do something like this when loading the inner plugin:

setTypeOfNextNewPlugin (wrapperType_Undefined);
plugin = formatManager.createPluginInstance (desc, 44100.0, 512, errorString);
setTypeOfNextNewPlugin (wrapperType);

It might be better for createPluginInstance to do this internally, but we don’t have access to the ‘previous’ wrapperType at that point, and I’m reluctant to make the global available in more places.

Thanks @reuk! Your solution works for me. Is there a purpose for the second setTypeOfNextNewPlugin() call if I am only loading the one plugin? Also, perhaps it would be possible to include a wrapperType as an optional parameter in createPluginInstance() which when not set, will default to the global wrapperType?

This puts the global value back to the previous (expected) value. It may not be necessary, but it ensures that the global state has the expected value after the outer AudioProcessor constructor completes.

There is a similar function for plug-in clients that’s called createPluginFilterOfType (wrapperType).

Perhaps in analogy to that, a similar createPluginInstanceOfType() can be added to the format manager, which takes the additional wrapperType argument in addition to those for createPluginInstance(), and wraps that in the appropriate setTypeOfNextNewPlugIn() calls.

The problem is the other way around. The wrapperType is intended purely for the AudioProcessor to detect how it has been loaded. Therefore, the AudioProcessor wrapper for a hosted plugin should always have a wrapper type of Undefined.

It might be more correct for createPluginInstance to unconditionally set a wrapper type of Undefined before creating the new instance, and then to set the global wrapper type back to its previous value afterwards. That still feels like a hack, though, and I’m not sure that the issue is severe enough to justify adding this additional workaround.

If we were to address this issue, I’d much prefer to do things properly and remove the global variable altogether.

1 Like

@mfritze

According to this thread, Logic Pro and GarageBand have a bug that causes them to use parameter indices instead of IDs to identify parameters. The effect of this is that adding parameters to a later version of a plugin can break automation saved with an earlier version of the plugin if the indices of existing parameters are changed.

This is a big problem for us developers, and it would be cool if the source of the problem (in Logic) would be fixed.