AAX unexpected state save/recall

I haven’t read this thread in depth… but I just want to point out that Pro Tools does also read the chunk to get it’s Compare settings (which is usually a default setting) – so that would explain why it’s called twice.

Rail

1 Like

OK, now that you mention it, I actually hadn’t gotten to testing the Compare button yet. I’ll have to poke around with that a bit.

Addressing your comment about “why it’s called twice” - to be clear, I am not seeing getStateInformation called twice (I am not sure if that’s what you’re referring to, but I want to be sure what you mean by “it”.)

As I understand it, the AAX wrapper’s GetChunk and SetChunk methods do get called twice. The first time, they’re processing Pro Tools’ native parameter data format, aka the “Complete Controls State” as seen in .tfx files. The second time around (when chunkID == juceChunkType) they’re dealing with XML data generated from within JUCE (and that’s where getStateInformation / setStateInformation are called).

With the hope that this might be useful to someone else trying to do something similar, here’s a helper method I added to the Processor to return an XML element containing the default parameter values. This works for a plug-in using APVTS for parameter management, following the pattern laid out in this tutorial.

I invite comment about whether I’m doing anything stupid with this method. My understanding is that the unique_ptr used here, tempXml, is disposed of as soon as this method returns its result. But if I’m wrong, and this would be leaking memory, please let me know!

// use this helper method to return an XmlElement containing default parameter values
// in this example, we have named the Processor's AudioProcessorValueTreeState object as "parameters"
XmlElement Processor::getDefaultStateXml()
{
    ValueTree state = parameters.copyState();  // grab a copy of the current parameters Value Tree
    std::unique_ptr<XmlElement> tempXml (state.createXml());  // convert parameters Value Tree to an XML object
    jassert(tempXml);  // if the APVTS has no params, this is ok - but will fail if APVTS was not properly initialized

    // iterate through each "PARAM" element in XML, and overwrite values with their defaults
    forEachXmlChildElementWithTagName (*tempXml, child, "PARAM")
    {
        float defaultValue = parameters.getParameter(child->getStringAttribute("id"))->getDefaultValue();
        child->setAttribute("value", defaultValue);
    }
    DBG ("In the end, tempXml looks like:\n" + tempXml->createDocument(String(), false, false));
    return *tempXml;
}

It’s been a while (since the last time I dealt with this was outside the JUCE wrapper with a DSP plug-in with a JUCE GUI)… I just checked with my native synth and instantiated it in a new PT session and PT calls GetChunkSize() twice, then GetChunk() twice, then SetChunk() twice.

In the JUCE AAX wrapper…

GetChunkSize() calls the AudioProcessor’s getStateInformation()

 pluginInstance->getStateInformation (chunkMemoryBlock.data);

SetChunk() calls the AudioProcessor’s setStateInformation()

 pluginInstance->setStateInformation ((void*) chunk->fData, chunk->fSize);

My plug-in isn’t using APVTS… but the sequence of events should be the same.

Rail

I had to look twice at the code, since my first instinct was, the unique_ptr will be disposed right after leaving the scope, but then I realised, that your code will deep copy with the return statement into a stack object.

In this case, you might be able to save the copy step by having it on the stack from the beginning?

// use this helper method to return an XmlElement containing default parameter values
// in this example, we have named the Processor's AudioProcessorValueTreeState object as "parameters"
XmlElement Processor::getDefaultStateXml()
{
    auto state = parameters.copyState();  // grab a copy of the current parameters Value Tree
    XmlElement tempXml = state.createXml();  // convert parameters Value Tree to an XML object

    // iterate through each "PARAM" element in XML, and overwrite values with their defaults
    forEachXmlChildElementWithTagName (tempXml, child, "PARAM")
    {
        float defaultValue = parameters.getParameter(child->getStringAttribute("id"))->getDefaultValue();
        child->setAttribute("value", defaultValue);
        DBG("Now PARAM element " + child->getStringAttribute("id") + " = " + child->getStringAttribute("value") );
    }
    return tempXml;
}

But no objections about leaking, your version will work IMHO.

(only tangentially of interest, not related on AAX and the problem, but since you invited comments :wink: )

Oh, it’s quite appreciated, and very of interest to me. I’m still in a bit of a rough spot on the C++ learning curve, and trying to break out of cargo cult programming practices with it, especially when it comes to pointers.

That said, Xcode tells me that this line:
XmlElement tempXml = state.createXml();
needs to be this instead:
XmlElement tempXml = *state.createXml();

…since createXML() returns a pointer.

And, related, if the ValueTree wasn’t valid, createXml may return nullptr. So I do wonder if it would be better to grab the pointer, and guard it from being nullptr (or assert that it’s not), before dereferencing it.

Again, if I’ve bungled the concepts, don’t listen to me…see caveat above.

No, you are perfectly right, that was my bad. I forgot about the possibility of returning a nullptr. So it’s probably best to use the unique_ptr just like you did.

EDIT:
Alternatively you could return the std::unique_ptr, that would mean you need to use std::move() where you assign the result:

std::unique_ptr<XmlElement> Processor::getDefaultStateXml()
{
    std::unique_ptr<XmlElement> tempXml (parameters.copyState());
    // ...
    return tempXml;
}
// ...
std::unique_ptr<XmlElement> state = std::move (getDefaultStateXml());

…but I am a bit green on that territory as well…

Given this method’s role in the scheme of things (a helper function to generate a fairly lightweight XML data set), I wanted to have it return by value. Then I don’t have to remember any special needs that a returned pointer might have when invoking the method.

For example, in getStateInformation, this is all I need to do now to feed default parameter values to the host:
copyXmlToBinary (getDefaultStateXml(), destData);

But maybe if I was more comfortable with using pointers, I’d see a disadvantage to that approach… hence my invitation for comments on the code.