Saving and loading plugin state using a raw Value Tree?

Hi, I’m making a plugin and I’m using a ValueTree in order to maintain the state of the plugin. However I’m not sure how to get saving/loading to work as it does in this tutorial:
https://docs.juce.com/master/tutorial_audio_processor_value_tree_state.html

here is the code I have in getStateInformation and setStateInformation:

void ProteusAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    std::unique_ptr<juce::XmlElement> xml(state.createXml());
    copyXmlToBinary(*xml, destData);
}

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

    if (xmlState.get() != nullptr)
    {
        if (xmlState->hasTagName(state.getType()))
        {
            juce::ValueTree& newState = juce::ValueTree::fromXml(*xmlState);
            state.copyPropertiesAndChildrenFrom(newState, nullptr);
        }
    }

}

This approach is not working however. When I load the state then the UI does not update and and I get no output when I press on my midi keyboard(its a synth btw) even if the state I saved did have output.

In order to connect my value tree to UI elements I have done this sort of thing:

juce::Value& valueToControl = state.getChild(<index>).getPropertyAsValue(<property>);
slider->getValueObject().referTo(valueToControl);

I would assume that when I call copyPropertiesAndChildrenFrom() then it would update all my UI elements but apparently my assumption was wrong.

Anyone know what I can do to get this to work?

Thanks in advance

Why not use APVTS? copyPropertiesAndChildrenFrom removes all children, breaking all connections to them. APVTS::replaceState uses operator=, which triggers valueTreeRedirected, which calls updateParameterConnectionsToChildTrees precisely to fix that. Are these properties that shouldn’t be visible from the host? Otherwise APVTS does a lot of work for you.

Yeah I’m using a raw value tree because it gives me a bit finer control and because I will have like 50~odd parameters by the end, which will be very cluttered if the host is able to see them all.
*sidenote: I tried myself to use operator= like this:

juce::ValueTree& newState = juce::ValueTree::fromXml(*xmlState);
state = newState;

but that did not work

I have well over 80 parameters and APVTS works just fine for me.

I assume you didn’t mean that to be a reference.

Take a look at the implementations and see what they do. There’s no facility in ValueTree for setting the whole tree to the contents of another one without replacing the children themselves. To do that, you need to traverse the tree manually. replaceState lets operator= replace the nodes, but because APVTS is a ValueTree::Listener, it receives a valueTreeRedirected callback, which uses to recreate its connections to the ValueTree.

The main issue is that get/setStateInformation can be called from any thread, and ValueTree is not thread-safe by itself. If you’re using it both from the UI and processBlock, that’s not safe either. APVTS solves these problems for you (mostly). There are lots of plugins with more than a hundred parameters, I don’t think anyone would complain about it. It’s more or less manageable to have some UI parameters saved with state and not exposed, but once they’re needed by the audio process, you’d have to recreate a good part of APVTS to make it work.

Are all of your parameteres set to “isAutomatable”? Because I don’t want them to be visible to the host.
In ableton, my daw of choice, it is possible to view these parameters by pressing an arrow on the plugin:
image

if I have 50+ parameters then I don’t want to be able to see them all in this window.
If your parameters aren’t visible to the host, how did you make this change? I couldn’t a super easy way to do this.

are all of them set to isAutomatable = true?

All my parameters are automatable, and some hosts actually ignore the tag, but Live won’t show you all of them if there’s a lot. You can pick which parameters you want to have there.
live

ok thanks, I will try to implement an apvts into my synth. Secondly, in order to communicate from the ValueTreeState to the dsp, do you think its a good idea to use an AudioProcessorValueTreeState::Listener? Using getRawParameter() doesnt seem like a good idea to me as I have so many paramaters it would seem weird to update them every time a block is generated.
To me, at least, it makes more sense to implement the listener call back and then update member variables when a parameter is changed like so:

void AudioProcessorValueTreeState::Listener::parameterChanged(const String& parameterID, float newValue)
{
    if(parameterID == "attack") //or something of that nature
        osc[i].setAttack(newValue); 
}

but I am open to ideas, I am fairly new to juce so I am still figureing out the best way to structure my project.
thanks

The intuition is right -it’s probably not a great idea to use raw parameter values directly if there’s a lot of them, especially if you have derived parameters. This is a good case for a mpsc fifo queue. You need a key-value type like

struct Message { juce::Identifier id; float value; }

Then somewhere in your AudioProcessor, a member

YourFifoType<Message> messages;

with a reasonable size. You make your AudioProcessor listen to its APVTS for all parameters, then

void YourAudioProcessor::parameterChanged (const juce::String& id, float value)
{
    messages.push ({ id, value });
}

Then once per block in processBlock

Message msg;

while (messages.pop (msg))
{
    if      (msg.id == param1id) // ...
    else if (msg.id == param2id) // ...
    else if (msg.id == param3id) // ...
}

and you assign the incoming values to wherever they need to live for processBlock. These locations don’t have to be atomic -the queue synchronizes the whole thing. To replace the if-else-if chain with a switch, I actually use enum ids and have a mapping from Strings to the enum. There are a number of fifo implementations out there, I use Fabian Renn’s farbot::fifo.

3 Likes

Thanks for your reply, thats something I’ve never thought of doing.
However one question immediately pops into my mind:
Why not implement the update directly into the parameterChanged() function.

i.e.

void APVTSListener::parameterChanged (const juce::String& id, float value)
{
    if      (msg.id == param1id) // ...
    else if (msg.id == param2id) // ...
    else if (msg.id == param3id) // ...
}

is there some reason why you need to go out of your way to create this data

Because parameterChanged can be called from any thread. Specifically, setStateInformation can, and also automation. And the UI would call it from the message thread. Take a look at these videos.

Ah i see thank you.
One last question (I have done some googling but can’t get it to work): I am unable to get my project to recognise farbot i.e. I can’t say
#include "fifo.hpp"
I have never used libraries/external files with juce.
Thans

The path should be relative to the file where the include is. For example, if you have fifo.hpp in a “farbot” folder which is in the same folder that the current file, it’s #include "farbot/fifo.hpp".

ah i see. How do you think you would go about choosing the correct length for the fifo. I have created it but when I call the constructor I need to pass a size. It seems a bit arbitrary to just choose a size for this application.

I’m not sure about that. The idea is to consider how many changes may arrive between two blocks in the limiting case of all parameters being automated. I chose a very conservative 16384, but when I’ve tested it in Cubase with heavy automation, it stayed well below 1000. That may change from one host to another though.