How to manage presets in plugins

I’m just thinking out loud here. There are 2 aspects of using the state of apvts that I can’t decide on the best design to manage them.

One is when you don’t want a parameter to be automatable. Apparently if you create a new subclass for the parameter and override the isAutomatable() function to return false, some DAWs will ignore this flag. I don’t see this happening in any that I’ve tested, so is this actually the case, or just a remnant of some old bugs?

The other is when you straight up don’t want some parameters to change with the preset. This means swapping out the entire state is no longer an option. You could in theory cache the value, swap the state, then change the value back. Or you could loop through and only change the parameters you want one at a time.

For example, let’s say you have a parameter for oversampling, and you want that to stay the same while changing presets.

Has anyone given this aspect of things much thought? What did you land on?

Thanks!

Hi!
Some solutions I found fairly easy to implement with JUCE:
For non automatable parameters, you can not rely on isAutomatable, like you wrote. It’s fairly easy to use another ValueTree for all the internals you need. You can easily attach it as a childtree to the APVST.
For UI attachments, best write a set of your own that work similar to the parameter ones, that attach to particular children of your internal ValueTree. See ValueTree::Listener. There is some code around in the Forum somewhere if you don’t wanna start from scratch, iirc.
If it’s just a hand full of values, you could even skip this and attach the necessary UI components to the tree directly. ReplaceState will cause ValueTreeRedirected, so no additional complexity in your setState and getState overrides is needed.
For retaining values on a user preset switch, it may be a good idea to make a difference between the host setting the state, in which case you might want to include everything (initial load), and user preset switches. For the latter ones, a “post processing” step on a load, that copies an old subtree over from the old loaded tree to the newly loaded tree, may do what you want and it can be done with a few lines of code.

It really depends on the size of your project whether to write a full blown reusable data modelling framework with a type system and everything on top of value trees, personally I’ve done that and it works well, but it takes time. For a plugin with just 20-something UI controls, KISS wins I think.

Another thing to consider is that noone forces you to have a particular data layout in setState/getState. It’s just a binary blob. So you could alternatively have your internal parameters in an entirely different data structure, nested var arrays for example, and add these in setState/getState, but I find ValueTrees to be an awesome powerful data structure for this in general and one of the best “frameworky” parts of JUCE, so why not build on that.

Good luck! Would be nice to read about other approaches people here have come up with.

2 Likes

Forgot to mention: Mind that ValueTrees are reference counted internally. If you attach listeners, they are NOT attached to this internal object, but to the object holding the tree. This caused me some confusion a few years ago :slight_smile:

1 Like

I just have two apvts. One gets the real Audioprocessor to connect to and the other gets a dummy processor.
This has 2 advantages:

  1. The extra code is really short - for load/Save state you just add both to a parent XML element and supply/read from that.
  2. You keep all the great apvts attachment stuff etc. for non automatable Params.

I can post the code tomorrow.

I’ve seen this mentioned in places, have you found any downsides to this? There no weird edge cases like DAWs somehow recursively finding these nested processors?

Nope, works great

You do need to load some initial value for these parameters. And some DAWs will load a ‘default’ preset before loading the actual preset (that was saved by the DAW). So I use a timer. Any preset loaded in the first say 0.5 seconds will set ALL parameters, after that the special parameters (I call them “Ignore-Program-Change parameters”) will ignore any change in the preset.
This ensures that any session you save in the DAW will reload exactly the same as you left it.

Hi @moritzsur Checking in on the dummy processor apvts, I’d really appreciate an example. Thanks!

For what specifically do you need an example?
This is my dummy processor code, but since I have my own AudioProcessor subclass I use in all plugins (which also changes the processBlock callback etc), you will have to modify it:

    class DummyProcessor : public PluginProcessorBase
    {
    private:
        using AudioBlock = juce::dsp::AudioBlock<float>;

    public:
        DummyProcessor() : PluginProcessorBase (juce::AudioProcessor::BusesProperties()) {}
        void prepare (const juce::dsp::ProcessSpec& newSpec) override {}
        void process (AudioBlock block, const int numSamples, const int numCh, juce::MidiBuffer& midi, bool isBypassed) override {}
        juce::AudioProcessorEditor* createEditor() override { return nullptr; }
        void getStateInformation (juce::MemoryBlock& destData) override {}
        void setStateInformation (const void* data, int sizeInBytes) override {}
    };

you can then create members in your plugin AudioProcessor

using APVTS = juce::AudioProcessorValueTreeState;
APVTS m_hiddenAPVTS;
APVTS m_audioAPVTS;
DummyProcessor dummy;

and initialise them in the constructor of your AudioProcessor:

m_hiddenAPVTS (dummy, &undoManager, "HIDDEN", createHiddenParamLayout()),
m_audioAPVTS (*this, nullptr, "PARAMETERS", createAudioParamLayout()),
1 Like

Thanks moritzsur! I’ll start integrating this once I finish my current tasks, probably starting tomorrow.