Problem with storing and retrieving of juce::Value

In my audioplugin I use the AudioProcessorValueTree State. I use juce::AudioParamerFloat for Automation and I use some Properties.

To edit and read these int the frontend, I have a juce::Value for each parameter or property:
vts.getParameterAsValue(“paramID”)
vts.state.getPropertyAsValue(“propertyID”, NULL)

Is this the right way? Everything works fine with reading, writing, listeners and midi automation for the parameters.

But when I run the plugin as a standalone and I store the state and retrieve it again, the Values seem to have lost their connection to the parameters and properties. Other Parameters, connected to a Slider using a SliderAttachment have no problem with storing and retrieving.

Am I missing something, do I have to recall vts.getParameterAsValue(“paramID”) after retrieving a new state?

Btw, I followed the ValueTreeState-Tutorial

I partially solved the problem. Instead of using juce::Value, I use juce::RangedAudioParameter*.
vts.getParameter(“paramID”);
This RangedAudioParameter does not loose the connection to the parameter. After retrieving a stored state, everything works fine.

This does only work for juce::AudioParameterFloat, but not for Properties…

I checked in the AudioProcessor::setStateInformation(), that the Properties are retrieved correctly:
treeState.state.getPropertyAsValue(“propertyID”, NULL).getValue();
…return the expeceted Value.

But in my frontend, the juce::Value::Listener or juce::ValueTree::Listener do not respond at all…, when I do:
treeState.state.sendPropertyChangeMessage(“propertyID”);
or
treeState.state.getPropertyAsValue(“propertyId”, NULL).setValue(123.0f);

… the only solution that might work, is triggering to recall getPropertyAsValue() in frontend, after the setStateInformation was called… but that seems to be ugly

Am I missunderstanding the usage of properties?

How are you doing that?

How/when/where do you check this?

Why do you create a temporary Value instead of calling ValueTree::getProperty and setProperty directly?

Why do you create a temporary Value instead of calling ValueTree::getProperty and setProperty directly?

I worked with juce::Value in my frontend components to control tree’s properties and Value::Listener to receive callbacks whenever the property changes. If this is not the correct way, I probably missunderstood the documentation here? (I thought it is better/cleaner to have a juce::Value for each property in the frontend component instead of having a reference to the AudioProcessorValueTree state.)

If your question @kamedin does not refer to my general juce::Value usage, but just to that particular quoted line… you are right, I could have simply called getProperty / set Property there… thanks to point out!

I followed the Tutorial to store the state and retrieve it again:

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

void setStateInformation (const void* data, int sizeInBytes) override
    {
        std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
 
        if (xmlState.get() != nullptr)
            if (xmlState->hasTagName (parameters.state.getType()))
                parameters.replaceState (juce::ValueTree::fromXml (*xmlState));
    }

At the end of setStateInformation I have printOut:

void setStateInformation (const void* data, int sizeInBytes) override
    {
        std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
 
        if (xmlState.get() != nullptr)
            if (xmlState->hasTagName (parameters.state.getType()))
                parameters.replaceState (juce::ValueTree::fromXml (*xmlState));

        DBG(juce::String(parameters.state.getProperty("propertyID").toString()));
        parameters.state.sendPropertyChangeMessage("propertyID");
    }

In the DBG line it prints out the expected retrieved property value. But my Value::Listeners do not receive it. Doing sendPropertyChangeMessage() does not help either.

Oh at least, the juce::ValueTree::Listener receives a callback by sendPropertyChangeMessage() ! (Thats nice, at least something)

Why doesn’t it send the sendPropertyChangeMessage() for all properties by default, whenever the state is replaced and a new property Value is loaded…?

Ok, I dived a bit into this -the internals of ValueTree stress tf out of me, mostly because they’re impossible to inspect in debug. You may already know that Value and ValueTree are reference counted pointers to underlying shared objects, so when you copy construct a Value or a ValueTree, only the pointer is copied and the count increased. The problem with replaceState is that it just does state = newState, which effectively replaces the underlying object. So if you had taken ValueTree references to the previous state, they’ll now be pointing to expired objects, so to say. This also applies to Values taken with ValueTree::getPropertyAsValue or APVTS::getParameterAsValue, because the ValueSource for these is just a wrapper around a ValueTree reference. So yes, replaceState breaks these connections. This doesn’t happen with parameter attachments because they use a different mechanism -they connect to AudioProcessorParameters directly, or through APVTS. You could try catching ValueTree::Listener::valueTreeRedirected to update your connections but I don’t think that would work either, because APVTS will also catch it and you can’t be sure that your code will be called last. I never connect to APVTS::state, and I store my non automatable stuff in separate ValueTrees whose persistence I can rely on.

Yep, I just meant that.

1 Like

Ok, thank you, I have a workaround for now!

As far as I understand… setStateInformation makes the previous juce::Values point to expired objects. In other words, the connection is lost.

But the juce::ValueTree connection seems to be perfectly fine!

 void    valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property);
 void    valueTreeRedirected(juce::ValueTree& treeWhoseParentHasChanged) override;

Thank you for pointing me at valueTreeRedirected(), it is perfect for reconnecting the expired Values:

At the callback of ValueTree::Listener::valueTreeRedirected() I remove the old Value::Listeners, which refer to the expired Values. Than I get the new Values and add new Listeners and everything seems to work fine with storing and retrieving!

But maybe I should also use seperate ValueTrees for my non automatable stuff in future, like you. How do you achive persistence during storing/retrieving with seperate ValueTrees and frontend components? (do you have an example or could you link a repository?)

Anyway thank you very much!

Not setStateInformation itself but APVTS::replaceState (and ValueTree’s copy assignment operator, which replaceState calls).

I can’t verify that, at least for elements other than root -after replaceState, my ValueTree listeners are not called. Maybe it works for properties added manually to the root element, I didn’t test that.

I think the only reasonable “solution” is to make the reconnections in setStateInformation itself, after replaceState. It’s safer than relying on your valueTreeRedirected being called after APVTS’s. But even better is not using APVTS::state to connect with the UI -that’s what ParameterAttachment and its relatives are for.

I actually don’t use replaceState, or assigment of ValueTrees, also because it’s not undoable. copyPropertiesAndChildrenFrom is undoable, but it breaks connections for children. Both copyPropertiesAndChildrenFrom and copyPropertiesFrom are problematic for versioning. If you’ve added a parameter in v1.2, and you load a host chunk saved with v1.1, the new parameter gets removed. So I load my states manually, with something like

void MyAudioProcessor::setStateFromXml (const juce::XmlElement& xml, bool msgThread)
{
    if (msgThread) undoManager.beginNewTransaction();
    forEachXmlChildElementWithTagName (xml, x, "PARAM")
    {
        auto& id{ x->getStringAttribute ("id") };
        auto value{ x->getDoubleAttribute ("value", -65536.) };
        if (id.isNotEmpty() && value != -65536.)
        {
            if (auto param{ apvts.getParameter (id) })
            {
                auto normalised{ param->convertTo0to1 (float (value)) };
                if (normalised != param->getValue())
                {
                    if (msgThread) param->beginChangeGesture();
                    param->setValueNotifyingHost (normalised);
                    if (msgThread) param->endChangeGesture();
                }
            }
        }
    }
}

I use separate ValueTrees for non automatable parameters to avoid messing with APVTS. If you never replace them (as in tree = anotherTree), connections shouldn’t break. In getStateInformation I prepend them like

if (auto xml{ apvts.copyState().createXml() })
{
    xml->prependChildElement (anotherTree.createXml().release());
    copyXmlToBinary (*xml, destData);
}

In setStateInformation, because it can be called from any thread, I defer the changes for non automatable parameters to the message thread (basically save the xml pointer and check it from a Timer callback), then I apply them manually. It’s a bit of a hassle, but it’s what I need.

1 Like