How to use AudioParameterFloatAttributes().withAutomatable()?

Hello All,
I’m trying to set some parameters non-automatable in an audio plugin. The plugin uses APVTS and parameterLayout() to create and update parameters.

Parameters like drop down menus are required to be set/updated and retrieved but not needed to be automated. I’ve tried all kinds of notifyingHost functions available, but does not work on all DAWs.

On researching similar posts, bumped into this one, and looks like it will help solving my issue.

This is the first time I’m exploring this function, so unable to understand how can I use it in my processor code to make it work ?

If someone can help with example code/project that’d be great. TIA!

@reuk since I was following the other post (Showing a label e.g. Hz, dB using AudioParameterFloat? - #8 by reuk) and could see your responses there, so tagging you here if I could get any help :slight_smile:

When you create a parameter, pass an Attributes argument to the constructor:

using Parameter = AudioProcessorValueTreeState::Parameter;
using Attributes = AudioProcessorValueTreeStateParameterAttributes;

auto* param = new Parameter ({ "id", 1 }, "name", { 0.0f, 1.0f }, 0.5f, Attributes().withAutomatable (false));

Note that hosts are not guaranteed to respect the automatable flag. Some hosts may still allow users to automate the parameter.

@reuk Thank you, is there any way to use Attributes().withAutomatable (false) in add function of parameterLayour::add ? the current setup uses something like this,

 parameter.add(
         std::make_unique<juce::AudioParameterFloat>("abc",            // parameterID
                                                     "ABC",            // parameter name
                                                     NormalisableRange<float>(0.0f, 1.f, 1.f),  //normalisableRange
                                                     0.f,                // default value
                                                     String(),           // parameterLabel
                                                     AudioProcessorParameter::genericParameter, //parameterCategory
                                               
     );
    parameter.add(
        std::make_unique<juce::AudioParameterFloat>("xyz",            // parameterID
            "XYZ",            // parameter name
            -1,              // minimum value
            1,              // maximum value
            0)             // default value
    );

Thanks!

Yes:

using Parameter = AudioParameterFloat;
using Attributes = AudioParameterFloatAttributes;
parameters.add (std::make_unique<Parameter> ("xyz",
                                             "XYZ",
                                             NormalisableRange<float> (-1, 1),
                                             0,
                                             Attributes().withAutomatable (false)));

Hey, word of warning: Reaper ignores this and will let you automate these parameters anyway.

1 Like

Thank you @reuk and @jcomusic for the heads-up!

I’m aware that DAWs ignore this flag, but looking to implement it for some parameters which are not needed to be automated, but should be in APVTS so it retains it’s state when closing and reopening the plugin editor.
Majority like ComboBox, Text buttons etc.

Is there any other way to achieve this?

Yes. You can add them to the state of APVTS:

 parameters.state.addChild(
            {"uiState", {{"width", ZLInterface::WindowWidth}, {"height", ZLInterface::WindowHeight}}, {}}, -1, nullptr);

Another way is to manually store & read these parameters in getStateInformation & setStateInformation respectively. If these parameters are UI related, you also need to link them with UI in a proper way.

Another example (where I store the version number and something from controller):

void PluginProcessor::getStateInformation (juce::MemoryBlock& destData) {
    auto state = parameters.copyState();
    auto conState = state.getOrCreateChildWithName ("conState", nullptr);
    conState.setProperty ("diffs", controller.toString(), nullptr);
    auto version = state.getOrCreateChildWithName ("version", nullptr);
    version.setProperty ("string", JucePlugin_VersionString, nullptr);
    std::unique_ptr<juce::XmlElement> xml (state.createXml());
    copyXmlToBinary (*xml, destData);
}

void PluginProcessor::setStateInformation (const void* data, int sizeInBytes) {
    std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));

    if (xmlState != nullptr) {
        if (xmlState->hasTagName (parameters.state.getType())) {
            auto state = juce::ValueTree::fromXml (*xmlState);
            auto diffs = state.getChildWithName ("conState");
            auto version = state.getChildWithName ("version");
            if (diffs.isValid() && version.isValid()) {
                state.removeChild (diffs, nullptr);
                state.removeChild (version, nullptr);
                if (version.getProperty ("string", "") == JucePlugin_VersionString) {
                    controller.fromString (diffs.getProperty ("diffs", ""));
                }
            }
            parameters.replaceState (state);
        }
    }
}

Another way is to create an extra APVTS that is not attached to the PluginProcessor :melting_face: someone mentioned this somewhere, but I haven’t tried it.

@zsliu98 Thank you for the suggestion, is “uiState” is separate state? and then adding all parameters as group? Also how to decide on ID if you have state already?

Current setup uses APVTS createLayout() function.

Not exactly. I first init the parameters with ParameterLayout and then add the child to parameters.state.

PluginProcessor::PluginProcessor()
        : AudioProcessor(BusesProperties()
                                 .withInput("Input", juce::AudioChannelSet::stereo(),
                                            true)
                                 .withOutput("Output", juce::AudioChannelSet::stereo(),
                                             true)
                                 .withInput("Aux", juce::AudioChannelSet::stereo(),
                                            true)),
          parameters(*this, nullptr, juce::Identifier("ZLLMakeupParameters"),
                     ZLDsp::getParameterLayout()),
          controller(this, parameters),
          controllerAttach(controller, parameters) {
    parameters.state.addChild (
            { "uiState", { { "width", ZLInterface::WindowWidth }, { "height", ZLInterface::WindowHeight } }, {} }, -1, nullptr);
}

Thanks @zsliu98 !

I’m adding it something like this

AudioParameterFloat* childParam = new AudioParameterFloat("ChildParam", "Child Parameter", 0.0f, 1.0f, 0.5f);
treeState.state.addChild(ValueTree("ChildNode"), -1, nullptr);
treeState.state.getChildWithName("ChildNode").setProperty("parameter", var(childParam->get()), nullptr);

But I don’t see this retaining when plugin editor is closed and reopened.
For instance, I’m setting this slider at 0.3 from UI, and when closing and reopening the UI, this setting is gone.

Taking this approach will sustain the set values for parameters on close/reopen plugin editor?

(I’m using ParameterLayout& ParameterLayout::add to add ‘Audio’ parameters to APVTS.)

PluginProcessor::PluginProcessor()
        : AudioProcessor(BusesProperties()
                                 .withInput("Input", juce::AudioChannelSet::stereo(),
                                            true)
                                 .withOutput("Output", juce::AudioChannelSet::stereo(),
                                             true)
                                 .withInput("Aux", juce::AudioChannelSet::stereo(),
                                            true)),
        parameters(*this, nullptr, Identifier("XYZ"), ParameterLayout()) ) {}

The AudioParameterFloat looks suspicious in your code. I am not sure how you store the values and link them to sliders.

Here is an example (form JUCE) how I store the window size of UI:

In here, what is this format being used?

parameters.state.addChild (
            { "uiState", { { "width", ZLInterface::WindowWidth }, { "height", ZLInterface::WindowHeight } }, {} }, -1, nullptr);

I am adding a child to the state of APVTS. The child (a value tree) is initialized by "uiState", { { "width", ZLInterface::WindowWidth }, { "height", ZLInterface::WindowHeight } }, {}.

are these UI parameters sliders/buttons etc?

    lastUIWidth.referTo (p.parameters.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr));
    lastUIHeight.referTo (p.parameters.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr));

These variables are the size of the UI window. You can view the original .cpp & .h for details.

right, but how to use it with actual UI parameter like a slider or button or menu?

I’ve a current setup that uses APVTS even for the parameters that are not supposed to be automated , but added to APVTS to retain it’s state when plugin editor is closed or reopened.

It is similar to how I deal with the lastUIWidth. The only difference is that now you have to

  • set-up the range of the slider properly
  • inherit juce::Slider::Listener in order to listen to the slider
  • maybe something more?

And it will definitely take you more effort compared to placing it in APVTS.