AudioProcessorValueTreeState Improvements

Could your AnotherProcessorClass have a method that returns an array of AudioProcessorParameters (or perhaps a group of them) which you could then pass to the ParameterLayout constructor after you’ve initialised?

Something roughly along the lines of:

struct MainProcessor : public AudioProcessor
{
    MainProcessor()
        : state (*this, nullptr, "state", anotherProcessorClass1.getParameters(), 
                                          anotherProcessorClass2.getParameters()), 
    {
    }

    AudioProcessorValueTreeState state;
    AnotherProcessorClass anotherProcessorClass;
};

not really, because my “AnotherProcessorClass” constructor is expecting the AudioProcessorValueTreeState as parameter.

Of course I could change that, do things differently, but I wanted to stress the point that the new way can imply quite a few changes.

I just hope you will keep the old constructor around for a while… (I’m really not against the latest changes, which are most likely an improvement, but it’s quite some work to stay up to date with changes and depreciations lately)

Hi All,
I am playing around tutorial Cascading plug-in effects using processors which already use APVTS, similar like @lalala mentioned.

For now I had implementation more or less like @t0m proposed, but I wondering if each processor could have its own APVTS. Then in main processor methods getStateInformation and setStateInformation would be a little more complicated. But I think AudioPluginHost has such implementation already. There is static function createNodeXml in FilterGraph which calls
node->getProcessor()->getStateInformation (m);

Is there anything against using own APVTS object in all “anotherProcessorClass-es”?
Does it sounds reasonable?

Kindly regards,
Mateusz

I think it’s good that you’ve got the AudioParameterX classes back into the APVTS stuff. However, the best practice method seems to make it hard (impossible?) to use NormalisableRange::setSkewForCentre().

To grab an existing chunk of code as an example:

auto bwp_range = NormalisableRange<float>(MINUS_THIRTY_SIX_DB, 1.0f); bwp_range.setSkewForCentre(Decibels::decibelsToGain(-8.0f)); brick_wall_param = parameters.createAndAddParameter(std::make_unique<Parameter>("limit", "Target Max Signal", "dB", bwp_range, 1.0f, db_to_text, db_from_text));

Using parameters.getParameterRange("limit").setSkewForCentre(Decibels::decibelsToGain(-8.0f)); after the constructor block doesn’t work because getParameterRange returns a copy of the NormalisableRange.

parameters.getParameter("limit")->getNormalisableRange().setSkewForCentre(Decibels::decibelsToGain(-8.0f)); doesn’t work because this time it returns a const.

Any recommendations?

I haven’t tried it, but I would expect something like this to work:

juce::AudioProcessorValueTreeState::ParameterLayout makeParameters() {
  auto param = std::make_unique<juce::AudioParameterFloat>(...);
  param->range.setSkewForCentre(...);
  return { std::move(param), /* any extra parameters go here */ };
}

// When constructing your apvts
MyAudioProcessor()
  : apvts { *this, nullptr, "state", makeParameters() }
{
}
1 Like

Thanks, that seems so obvious now. :thinking:

thanks @t0m this post was super helpful for migrating to the new AudioProcessorValueTreeState management!
one question: let’s say I want to create an AudioParameterFloat in the createParameterLayout() method like above…I can use the other AudioParameterFloat constructor to insert almost all the old attributes…but isMeta and isAutomatable still seem to be missing (isChoice too but I assume that’s covered by the choice param…)
…is there a way to set those two in the new code??
thanks!

1 Like

I’m on board with this change, but I have a couple of questions:

  1. The suggested way to migrate legacy code is to replace:
createAndAddParameter (paramID1, paramName1, ...);

with

using Parameter = AudioProcessorValueTreeState::Parameter;
createAndAddParameter (std::make_unique&lt;Parameter&gt; (paramID1, paramName1, ...));

That works fine, but it still relies on the old constructor, I think?

apvts (*this, nullptr)

And since this constructor is deprecated, isn’t this new method of adding parameters just as fragile as the old method? i.e. it will stop working in future, due to the old constructor being removed.

  1. In the past parameters were created in my processor’s constructor, which gave me the freedom to interact with class members as I was adding each parameter. For example I might keep an array of bools that indicated whether changes in value to a particular parameter should be smoothed:
MyProcessor::MyProcessor()
    : state (*this, nullptr)
{
    state.createAndAddParameter ( ... )
    paramsShouldSmooth.add(false); // update a class member with info about this param

    state.createAndAddParameter ( ... )
    paramsShouldSmooth.add(true); // update a class member with info about this param
}

Presumably it is no longer possible to interact with my processor’s class members in “real-time” while I am adding parameters, and any such interaction would have to happen after all the members have been created?

1 Like
  1. You can use the new Parameter type with the new constructor and ParameterLayout, but you will have to migrate away from createAndAddParameter eventually.
  2. The new interface does mean we have to do things slightly differently, but the advantage of this approach is that we can avoid two-phase initialisation. A lot of beginners seem to get caught out by forgetting to initialise the state member… If you really really need to tweak data members while setting up parameters, you can add a function with the following signature to your class as a private member:
static ParameterLayout setUpParameters (MyProcessor&);

Then, in your initialiser list you would

: apvts { *this, nullptr, "tree", setUpParameters (*this) }

I stress that this should be a last-resort technique though.

Also, due to the order of data member initialisation in C++, you should ensure that the apvts member is declared after any members you modify in setUpParameters in the processor class definition.

3 Likes

Thanks reuk. For future plugins I will follow the recommended approach, but I think your suggestions will help me to refactor one of my old plugins. I hadn’t considered passing a reference to processor to ParameterLayout.

Jan 12

thanks @t0m this post was super helpful for migrating to the new AudioProcessorValueTreeState management!
one question: let’s say I want to create an AudioParameterFloat in the createParameterLayout() method like above…I can use the other AudioParameterFloat constructor to insert almost all the old attributes…but isMeta and isAutomatable still seem to be missing ( isChoice too but I assume that’s covered by the choice param…)
…is there a way to set those two in the new code??
thanks!

This is an important part of dynamic parameters? Any news. Would be great to have an alternate dummy system, where isAutomatable and other attributes can be edited later. The number of required dummy parameters and their ids alone could be set on init.

The reasoning so far was, that most hosts don’t support that.
Maybe it is time for a thorough investigation, for which hosts that is still the case.
So what we would need:

  • an experimental branch, that allows adding parameters later
  • people testing as many hosts as possible
  • if enough hosts support that in a stable fashion, it should go into the production version (aka master)

If people have already information about hosts, that support adding and removing parameters at runtime, we could start collecting data.

Sounds like a plan?

1 Like

oh yeah

Are you sure this example works? I’m getting an EXC_BAD_ACCESS violation when trying to dereference yourIntParam to use its int value.

Nevermind, figured it out: I was declaring things in the wrong order: AudioProcessorValueTreeState parameters should come last.

Hi all, I’ve used to initialize / setup the AudioProcessorValueTreeState in an other class or method. Something like this:

MyAudioProcessor:: MyAudioProcessor() :
apvts(*this, &undoManager_) {
  setup();
}

void MyAudioProcessor::setup() {
  AudioProcessorValueTreeStateBuilder::initialize(apvts, this);
}

However with the new initialization I need to do something like this:

MyAudioProcessor:: MyAudioProcessor() :
apvts(*this, &undoManager_, "PARAMETERS", ParameterLayoutBuilder::Build()) {
}

Where ParameterLayoutBuilder::Build() create a vector of std::vector<std::unique_ptr<RangedAudioParameter>>.

But now I was wondering if there will be an elegant solution to add a listener to all the parameters of the ParameterLayout. Indeed from what I understand I cannot add the listener into the ParameterLayoutBuilder::Build() because the AudioProcessorValueTreeState is not instanciate at this moment. I cannot do something like this:

MyAudioProcessor:: MyAudioProcessor() :
apvts(*this, &undoManager_, "PARAMETERS", ParameterLayoutBuilder::Build(apvts, this)) {
}

The only solution I found is to do something like:

MyAudioProcessor:: MyAudioProcessor() :
parameterLayoutBuilder,
apvts(*this, &undoManager_, "PARAMETERS", parameterLayoutBuilder.build()) {
    for (auto& parameterId : parameterLayoutBuilder.getParameterIds() ) {
        parameters.addParameterListener(parametersId, this);
    }
}

I’ve now a member for the parameterLayoutBuilder object and use it to build the parameters and to get the list of the parameter IDs. But I need to solve some pitfalls when I create the vector of unique_ptr in the parameterLayoutBuilder to store the parameter IDs.

Wouldn’t be an other better and more elegant solution to create an AudioProcessorValueTreeState and listen to its parameters?

Simple question:

How do I make a parameter non-automatable?

AudioParameterFloat doesn’t seem to accept args for that
(when AudioProcessorValueTreeState::Parameter did, or does, but when we use it, all hell breaks loose… I made another thread for that)

One way to add non-automatable parameter is to create a custom parameter instance that is not connected to AudioProcessorValueTreeState at all. Restoring/storing value for that parameter must have an custom implementation (easily done in get/setStateInformation).

Another way is to add custom parameter with isAutomatable function overridden into the APVTS. The issue with this solution is that hosts may ignore the isAutomatable value so the parameter may get automated anyway.

Just wanted to mention that those codes are all using pointers that will be invalid after the std::move (param) calls..

Moving a unique_ptr doesn’t change the address of the pointed-to object. Taking the address of the pointed-to object with auto ptr = param.get() and then doing std::move (param) just transfers ownership of the object, it doesn’t affect the address of the object itself, so after the move, ptr will still point to the parameter.

It’s true that the raw pointer might ‘dangle’ if the pointed-to object gets deleted elsewhere, but in the examples above, the parameter lifetime is known, and referring to the parameter via a raw pointer is reasonably safe.