Storing Array<Point<float>> in ValueTree

I’m making a plugin which uses Array<Point> to contain the points for a custom LFO shape which I want to be able to save and recall like parameters in the ValueTree.

The user can add and remove Points from the Array and move them around to change the shape of the LFO, so I need to be able to store a variable size array of these points. Is it possible to store my Array in the ValueTree with my parameters or do I need to do something else to be able to save and recall the LFO shape. I am still quite new to C++ and learning as I go so any and all help is very much appreciated. Thank you!

1 Like

I would recommend storing the Points as children in a ValueTree (with properties ‘x’, ‘y’).
That has two advantages:

  • easy save and recall, just add it to your AudioProcessorValueTreeState before writing it to the MemoryBlock
  • you can use a undoManager to undo/redo changes made to those points.

Maybe a little bit messy, but I did this with storing a arbitrary number of loudspeaker objects:

The individual loudspeakers are added to ValueTree loudspeakers {"Loudspeakers"};
Everytime some changes were made (e.g. valueTreeChildAdded) all of the loudspeakers are converted to a simpler representation. You could convert them to Array to not break your code and have easier access to the information.

3 Likes

Thanks so much for the help so far! I’ve managed to completely replace my Array of Points with a ValueTree containing child trees which have x and y values as properties, and my code is all working exactly as it was with the Array.

I’m still having issues with saving and loading though, each time I load up a new instance of my plugin, I can move around my points, save it, then reload the project and it will recall the points as it was when it was saved. However any time after that, reloading the plugin will only reload the points as they were when it was saved for the first time. I must be doing something right for it to save the first time, but it only ever uses that first saved state from then on.

Here are my getStateInformation and setStateInformation functions, where parameters is my AudioProcessorValueTreeState, and pointTree is my ValueTree of points:

void TestProjectAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    if (parameters.state.getChildWithName("pointTree").isValid())
        parameters.state.removeChild(pointTree, nullptr);

    parameters.state.appendChild(pointTree, nullptr);

    ScopedPointer <XmlElement> xml (parameters.state.createXml()); 
    copyXmlToBinary(*xml, destData);
}

void TestProjectAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    ScopedPointer<XmlElement> xmlState (getXmlFromBinary(data, sizeInBytes));

    if (xmlState.get() != nullptr)
    {
        if (xmlState -> hasTagName(parameters.state.getType()))
            parameters.replaceState(ValueTree::fromXml(*xmlState));
    
        XmlElement* points (xmlState->getChildByName("pointTree"));
        if (points != nullptr)
        {
            pointTree.removeAllChildren(nullptr);

            forEachXmlChildElement(*points, each)
            {
                ValueTree point ("point");
                point.setProperty("x", each->getDoubleAttribute("x"), nullptr);
                point.setProperty("y", each->getDoubleAttribute("y"), nullptr);
                pointTree.appendChild(point, nullptr);
            }
        }
    }
}

I think your removeChild call doesn’t do anything as your pointTree is not the same ValueTree as the one in your AudioProcessorValueTreeState after loading. Consequently, you add a new child every time you close your session. And during loading, the first Child with name “pointTree” (the very first one) will be restored.

Try that little modification:

void TestProjectAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    if (parameters.state.getChildWithName("pointTree").isValid())
        parameters.state.removeChild(parameters.state.getChildWithName("pointTree"), nullptr);

    parameters.state.appendChild(pointTree, nullptr);

    ScopedPointer <XmlElement> xml (parameters.state.createXml()); 
    copyXmlToBinary(*xml, destData);
}
1 Like

Thank you again! This seems to have fixed it, I’ll keep testing to make sure there are no more issues but it’s looking good. I think I misunderstood something in the ValueTree documentation that made me think that when you add a ValueTree as a child it’s still the same ValueTree, obviously that was a big mistake to make! Thank you so much for your help!