Where to put initial startup parameter values for AAX?

Hi, I have a plug-in that doesn’t have any saved presets. It’s just a simple 5 value processor.

How do I set up parameters values for startup? - I don’t mean a host saved state.
And I don’t mean ‘default’ values, which I use as useful reset positions.

I currently put it at the end of my AudioProcessor constructor, which works well for VST & AU, but AAX just ignores everything I do in the constructor and just uses the ‘default’ values from ‘createAndAddParameter’ which would normally be fine, but I want an initial state which is not a default state.

I need to do this in code. What’s the most elegant way of doing this please?

Thanks,
Dave H.
p.s. I found a way of putting it into the Editor startup, but that’s just a messy way of doing things, and I’d rather not put it there.
setCurrentProgram is not called when an AAX starts up it seems.

I just set the parameters to what I want in the constructor, but after creating and adding them (and after adding the processor as a listener to each).

Thanks for the reply.
Yeah, that’s what I’m doing. It doesn’t seem to work with an AAX though.
It’s a real shame that set and get current program is not called either.
edit I’m doing this last thing in the constructor.
I have a function that calls these:-

[error checking removed for clarity]
void setParameterNotifying(String name, float v)
{
	AudioProcessorParameterWithID* par = paraTree.getParameter(name);
	NormalisableRange<float> nor = paraTree.getParameterRange(name);
	v = nor.convertTo0to1(v);
	par->setValueNotifyingHost(v);
}

BTW I’m using the last Juce 5 commit.

Are you sure the parameters are not getting set? Maybe it’s just that your editor isn’t seeing those values? If you’re not using attachments, then maybe you need to query the parameters when the editor is opened and set your controls to the proper values (WITHOUT notifying the host)?

On a possibly related note, the second and subsequent instances of an AAX plugin get set to the values reported by the first instance to be created, because Pro Tools calls our getStateInformation() on the first instance, and then calls setStateInformation() on all subsequent instances. So, for second and subsequent instances, the initial state may get changed by Pro Tools.

I’m getting getStateInformation, then setStateInformation being called when I first insert the plug-in.
[ incidentally, if I delete the plug-in from the insert list, when I load it in again, getState is not called. ]

They are still set to the default values, even though I set them with OR without notifying the host.
BTW I’m using ProTools 12.4.0 on Windows.

The parameters are not being changed. Either in the processor OR the editor, they are just being set to default.
I am using attachments. As far as I know I’m doing everything correctly.

I’m not a pro but maybe the deprecated createAndAddParameter is the problem? I use the ParameterLayout way as explained in the tutorials and recall is working fine for me with AAX on macOS, haven’t tried in Windows though.

Thanks for the reply, I have the same problem with MacOSX. I can’t find any information on createAndAddParameter being deprecated in Juce 5? Where did you read that?
Cheers,
Dave H.

https://docs.juce.com/master/tutorial_audio_processor_value_tree_state.html#tutorial_audio_processor_value_tree_state_deprecated_methods

Scroll down to the bottom.

1 Like

OK, thanks for the information. I wish they would reflect the current state of things in the documentation:
https://docs.juce.com/master/classAudioProcessorValueTreeState.html#ac6313c4071fd2936cb1450d5e7b87c80
I doubt it will make any difference to my problem, but it’ll have to change it anyway.
I’ll do the change now and report back. :slight_smile:

Can anyone describe how I do this the ‘proper’ way? I’m getting little bit lost with all different approaches:

This is an example of my CPP file, as you can see, I’m using ‘paraTree’ which is an ‘AudioProcessorValueTreeState’:
I need to setup the NormalisedRange class before the parameter is added, as it’s used in other parameters as well.

I can’t find any examples of people using ‘apvts’ anywhere, the example demos just use ‘AddParameter’ which means they need a local parameter store, which I didn’t need before.

MyPlug_AudioProcessor::MyPlug_AudioProcessor()
     : AudioProcessor (BusesProperties()
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                       ),
	paraTree(*this, nullptr)
{
	NormalisableRange<float>  normPercent(0.0f, 100.0f, 1.0f);

	paraTree.createAndAddParameter(std::make_unique<AudioParameterFloat>("parStereoWidth", "Stereo Width", normPercent, 50.0f, "%"));
.
.
.
}

In the simple case, it should be sufficient to just declare some function like

juce::AudioProcessorValueTreeState::ParameterLayout createLayout()
{
    juce::AudioProcessorValueTreeState::ParameterLayout layout;

    // call layout.add() for each parameter

    return layout;
}

And then to call this function when constructing the APVTS:

: paraTree (*this, nullptr, "TREE", createLayout())

If you need to hold onto the individual parameter pointers (maybe for faster lookup), you will need to adjust this approach slightly to also return the pointers to the individual parameters. There’s an example of this slightly more advanced usage in the DSPModulePluginDemo.

OK, I’m now creating a ‘ParameterLayout’ and it still doesn’t init the parameter to a value I want in the constructor with AAX. It does still work for AU & VST.

IIRC, Pro Tools has a caching mechanism that basically stores the state of the plugin the very first time you load the plugin (via getStateInformation()) and then overwrites the default values via setStateInformation() the next times the plugin is loaded.
I don’t know if this is your case, but try to comment out the content of your setStateInformation implementation and see if it helps.
I’m not 100% sure, but if you remove the plugin, launch PT, then put the plugin back and re-launch PT, it should reset this cache.

Edit: here’s another thread about this: AAX / ProTools - plugin states persistent over multiple sessions?

2 Likes

If I empty setStateInformation then I still get the default values, not the one’s I set in the constructor.

I think my parameters are NOT being set by the time ‘getState’ and ‘setState’ are called at all.

PT Prefs is a useful (and free) tool for clearing out Pro Tools preferences, caches etc.

Thanks, I look at it soon. Though I’m pretty sure now that it’s not a caching problem.

Ok thing’s are getting weird. I just tested the Gain Demo and cleared getStateInformation and setStateInformation. You can see on the debug console that nothing is getting recalled from const void* data. But Pro Tools is still saving and recalling the parameter value. You can even save Plug-In Presets and recall them. When making the plug-in inactive and reactivate it, the value also gets recalled.

#pragma once


//==============================================================================
class GainProcessor  : public AudioProcessor
{
public:

    //==============================================================================
    GainProcessor()
        : AudioProcessor (BusesProperties().withInput  ("Input",  AudioChannelSet::stereo())
                                           .withOutput ("Output", AudioChannelSet::stereo()))
    {
        addParameter (gain = new AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.5f));
    }

    ~GainProcessor() {}

    //==============================================================================
    void prepareToPlay (double, int) override {}
    void releaseResources() override {}

    void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
    {
        buffer.applyGain (*gain);
    }

    //==============================================================================
    AudioProcessorEditor* createEditor() override          { return new GenericAudioProcessorEditor (*this); }
    bool hasEditor() const override                        { return true;   }

    //==============================================================================
    const String getName() const override                  { return "Gain PlugIn"; }
    bool acceptsMidi() const override                      { return false; }
    bool producesMidi() const override                     { return false; }
    double getTailLengthSeconds() const override           { return 0; }

    //==============================================================================
    int getNumPrograms() override                          { return 1; }
    int getCurrentProgram() override                       { return 0; }
    void setCurrentProgram (int) override                  {}
    const String getProgramName (int) override             { return {}; }
    void changeProgramName (int, const String&) override   {}

    //==============================================================================
    void getStateInformation (MemoryBlock& destData) override
    {
        DBG("GET STATE");
//        MemoryOutputStream (destData, true).writeFloat (*gain);
    }

    void setStateInformation (const void* data, int sizeInBytes) override
    {
        DBG("SET STATE");
        ValueTree fullValueTree = ValueTree::readFromData(data, sizeInBytes);
        DBG(fullValueTree.toXmlString());
//        gain->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat());
    }

    //==============================================================================
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override
    {
        const auto& mainInLayout  = layouts.getChannelSet (true,  0);
        const auto& mainOutLayout = layouts.getChannelSet (false, 0);

        return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled()));
    }

private:
    //==============================================================================
    AudioParameterFloat* gain;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor)
};

I think here is the answer!!

Pro Tools is reacting differently when you have automateable parameters. Just tried the same gain demo with my NonAutomatableAudioParameterFloat class.

The Generic Editor won’t even show you the slider but you can have a look at the console. Problem is, the gain value still gets overwritten somewhere between prepareToPlay() and processBlock()

#pragma once


class NonAutomatableAudioParameterFloat : public AudioParameterFloat  {
    
public:
    
    NonAutomatableAudioParameterFloat(const String& parameterID,
                                    const String& parameterName,
                                    NormalisableRange<float> normalisableRange,
                                    float defaultValue,
                                    const String& parameterLabel = String(),
                                    Category parameterCategory = AudioProcessorParameter::genericParameter,
                                    std::function<String(float value, int maximumStringLength)> stringFromValue = nullptr,
                                    std::function<float(const String& text)> valueFromString = nullptr)
    : AudioParameterFloat(parameterID, parameterName, normalisableRange, defaultValue)
    {}
    
    NonAutomatableAudioParameterFloat(String parameterID,
                                      String parameterName,
                                      float minValue,
                                      float maxValue,
                                      float defaultValue)
    : AudioParameterFloat(parameterID, parameterName, minValue, maxValue, defaultValue)
    {}
    
    
    bool isAutomatable() const override {
        return false;
    }
    
};


//==============================================================================
class GainProcessor  : public AudioProcessor
{
public:

    //==============================================================================
    GainProcessor()
        : AudioProcessor (BusesProperties().withInput  ("Input",  AudioChannelSet::stereo())
                                           .withOutput ("Output", AudioChannelSet::stereo()))
    {
        addParameter (gain = new NonAutomatableAudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.5f));
        
    }

    ~GainProcessor() {}

    //==============================================================================
    void prepareToPlay (double, int) override {
        float randomValue = Random::getSystemRandom().nextFloat();
        gain->setValueNotifyingHost(randomValue);
        DBG("PREPARE TO PLAY - Random Val: " << randomValue << " Gain Value: " << *gain);
    }
    void releaseResources() override {}

    void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
    {
        DBG("PROCESSBLOCK - Gain: "  << *gain);
        buffer.applyGain (*gain);
    }

    //==============================================================================
    AudioProcessorEditor* createEditor() override          { return new GenericAudioProcessorEditor (*this); }
    bool hasEditor() const override                        { return true;   }

    //==============================================================================
    const String getName() const override                  { return "Gain PlugIn"; }
    bool acceptsMidi() const override                      { return false; }
    bool producesMidi() const override                     { return false; }
    double getTailLengthSeconds() const override           { return 0; }

    //==============================================================================
    int getNumPrograms() override                          { return 1; }
    int getCurrentProgram() override                       { return 0; }
    void setCurrentProgram (int) override                  {}
    const String getProgramName (int) override             { return {}; }
    void changeProgramName (int, const String&) override   {}

    //==============================================================================
    void getStateInformation (MemoryBlock& destData) override
    {
        DBG("GET STATE - Value: " << *gain);
        
        MemoryOutputStream (destData, true).writeFloat (*gain);
    }

    void setStateInformation (const void* data, int sizeInBytes) override
    {
        MemoryInputStream mis {data, static_cast<size_t>(sizeInBytes), true};
        float value = mis.readFloat();
        gain->setValueNotifyingHost(value);
        DBG("SET STATE - Value: " << value);
    }

    //==============================================================================
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override
    {
        const auto& mainInLayout  = layouts.getChannelSet (true,  0);
        const auto& mainOutLayout = layouts.getChannelSet (false, 0);

        return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled()));
    }

private:
    //==============================================================================
    NonAutomatableAudioParameterFloat* gain;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor)
};

and lastly, in one of the threads was a good hint on the cached parameters from plug-ins binaries:

you should check the DigiOptionsFile in the AAX manual or simply trash your plug-ins from your plug-ins folder, open Pro Tools, close Pro Tools and then recompile your AAX binaries. This will erase the plug-in cache. That helped me with an assertion error when loading the factory default settings while instantiating a plug-in. Actually it recalled trash from an older version…

Yeah, I just re-installed ProTools to version 2020.9 and I still can’t get it off the default values. It doesn’t matter what values I set them to when I quit the plug-in last.

When ‘getState’ is called the values are always already set to the default parameter values, not the values I set in the constructor.
(edit: so the values are being set to default before any state information is called, which means I have no chance of changing the values, unless I do it in the Editor constructor as first stated above)

I wonder if there’s a way to force the values in getState the first time it is run? It seems like a really dodgy thing to do, but it’s kind of logical, being in a place that handles state information.
I’m at my wits end right now. I wish there was an official way of doing this properly on AAX.

It would be nice if the parameters would have a default value AND a starting value.