Where to put initial startup parameter values for AAX?

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?

1 Like

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.

Could all this be caused by some race condition considering that get/setState may happen on non-message threads?

There is a flag to tell Pro Tools to operate on the message thread, see here: [AAX][PULL REQUEST] Force Protools to do chunk calls in message thread option

Thanks for the heads up - I’ve inserted that code, but it doesn’t help this situation, unfortunately.

OK, I’ve found a way of hacking setState to use my initial setting, if it’s the first time the plug-in is loaded. It’s pretty ugly, but at least it’s out of the Editor now.

Why? If you make a distinction between “initial” and “default” then the user has no way to get back to the “initial” state other than de-instanciating and re-instanciating the plug-in.

I’m curious what your use case is.

I’d like to use ‘default’ as a neutral, or null position, so basically 0 for a knob as an example. When the plug-in first loads up it would be nice to set an example of what the plug is capable of. I would normally have presets but the plug-in is so basic that I don’t have any. You could always set default and starting value to be the same.
But never mind, I’ll work around it… :grinning:

edit: It also appears that get & ‘setCurrentProgram’ are not called at all when the number of programs is 1, which would help a little I think.

Setting that to 1 basically means there is only the “default” program, not that there is a set of programs which happens to only have 1 member. So there should be no need to load a program in that case. (And I believe that this is controlled by the host, so JUCE couldn’t enforce such a change, anyway.)

So I can’t set a preset on single program plug-ins, and I can’t set values in the constructor of the AAX processor. Never mind, I’ve worked around it now.