[SOLVED] How to store a non-APVTS parameter in the same XML document as APVTS parameters?

I have a plugin where I need to be able to save and recall private member variables of my processor class that are just ints and not APVTS parameters. I am able to save and recall all of my APVTS parameters using the typical code given in the Saving and loading your plugin state tutorial, i.e.

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

and

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));
    }

How can I add private member variables from my processor class (they are both ints) to the same XML document and recall them?

What we do is create a larger “parent” XmlElement that then adds the XmlElement called “xml” in your example in getStateInformation as a child. Then we add another child to that parent that contains our custom data, and we pass the parent to copyXmlToBinary. Then, in setStateInforrmation, we extract that “parent” XmlElement using getXmlFromBinary, extract the information we need from our custom XmlElement child, and pass the other child (the original XmlElement created by createXml in getStateInformation) on to replaceState.

What would the code look like for this? It seems that I can’t change xml in the original code to a pointer to an XmlElement instead of unique pointer and still keep the initialization as xml(state.createXml()), but it seems I also can’t add it as a child element to my parent XML if it is left as a unique pointer.

We use .release() on the unique_ptr, like this:

auto state = parameters.copyState();
std::unique_ptr<XmlElement> pParameterTreeXml = state.createXml();
pParametersXml->addChildElement(pParameterTreeXml.release());

I think I’ve got a solution here, for processor member variables juce::AudioProcessorValueTreeState tree and int nonAPVTSParam :

void AudioProcessor::getStateInformation(juce::MemoryBlock& destData)
{
    auto state = tree.copyState();
    juce::XmlElement* xmlParent = new juce::XmlElement("parent");
    juce::XmlElement* xmlNonAPVTSParam  = xmlParent->createNewChildElement("non_APVTS_int");
    std::unique_ptr<juce::XmlElement> xmlAPVTS(state.createXml());

    xmlParent->addChildElement(xmlAPVTS.release());
    xmlNonAPVTSParam  ->setAttribute(juce::Identifier(juce::String("myID")), NonAPVTSParam);
    
    copyXmlToBinary(*xmlParent, destData);
    
    delete xmlParent;
}

and for set state

void DigitalDelayAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
{

    std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
    juce::XmlElement* xmlAPVTS = xmlState->getChildByName(tree.state.getType());
    juce::XmlElement* xmlNonAPVTSParam = xmlState->getChildByName("non_APVTS_int");

    if (xmlState.get() != nullptr)
    {
        if (xmlTree->hasTagName(tree.state.getType()))
        {
            tree.replaceState(juce::ValueTree::fromXml(*xmlAPVTS));
        }
        if (xmlNonAPVTS->hasTagName(juce::StringRef("non_APVTS_int"))) //warning
        {
            nonAPVTSParam = xmlNonAPVTS->getIntAttribute(juce::String("myID"), 0);
        }
    }
}

although the line marked //warning is underlined (in VS2019) saying “Dereferencing NULL pointer ‘xmlNonAPVTS’”, though it sets the value correctly as desired and I have a DBG statement in my version of the code that explicitly checks that xmlNonAPVTS is not equal to nullptr and confirms that it is not; what’s going on there?

One note: in your getStateInformation, you don’t need to new and delete a pointer for xmlParent. You can just declare the local variable as:

juce::XmlElement xmlParent("parent");

(And of course use . instead of → when using it, and pass xmlParent instead of *xmlParent to copyXmlTo Binary.)