Cross Platform presets - any ideas?

No worries Sean. This is still just the skeleton of the plug, so there’s not a lot of state information - but you should get the idea OK:

void HyperdriveAudioProcessor::deSerialiseStateInformation(ScopedPointer<XmlElement> xmlState) { // make sure that it's actually our type of XML object.. if (xmlState->hasTagName ("MYPLUGINSETTINGS")) { // ok, now pull out our parameters.. lastUIWidth = xmlState->getIntAttribute ("uiWidth", lastUIWidth); lastUIHeight = xmlState->getIntAttribute ("uiHeight", lastUIHeight); XmlElement* xmlChild[HYPERDRIVE_numPrograms]; for (int i = 0; i<HYPERDRIVE_numPrograms; ++i) { xmlChild[i] = xmlState->getChildByName ("ProgramState_" + String(i+1)); if (xmlChild[i]!=0) { programName[i] = xmlChild[i]->getStringAttribute ("ProgramName", programName[i]); drive[i] = (float) xmlChild[i]->getDoubleAttribute ("Drive", drive[i]); attack[i] = (float) xmlChild[i]->getDoubleAttribute ("Attack", attack[i]); release[i] = (float) xmlChild[i]->getDoubleAttribute ("Release", release[i]); } } } } void HyperdriveAudioProcessor::deSerialiseStateInformationAndNotify(ScopedPointer<XmlElement> xmlState) { deSerialiseStateInformation(xmlState); // Notify host of changes to parameters sendParamChangeMessageToListeners (driveParam, param2Float(driveParam, drive[currentProgram])); sendParamChangeMessageToListeners (attackParam, param2Float(attackParam, attack[currentProgram])); sendParamChangeMessageToListeners (releaseParam, param2Float(releaseParam, release[currentProgram])); }

Here’s my current preset code on the AudioProcessor side of things, as used in my upcoming plugin. I call this from a TextButton in my Editor code. Feel free to make suggestions about C++ correctness (seriously, I’m crappy at the ++ of C++).

[code]//==============================================================================
void ValhallaRoom::getStateInformation (MemoryBlock& destData)
{
// create an outer XML element…
XmlElement xmlState (T(“ValhallaRoom”));
saveStateToXML(xmlState);

// then use this helper function to stuff it into the binary blob and return it..
copyXmlToBinary (xmlState, destData);

}

void ValhallaRoom::setStateInformation (const void* data, int sizeInBytes)
{
// use this helper function to get the XML from this binary blob…
XmlElement* const xmlState = getXmlFromBinary (data, sizeInBytes);

if (xmlState != 0)
{	
	loadStateFromXML(xmlState);
    delete xmlState;
}

}

void ValhallaRoom::setCurrentPresetName(String& newName)
{
presetName = newName;
}

String ValhallaRoom::getCurrentPresetName()
{
return presetName;
}

void ValhallaRoom::saveStateToXML(XmlElement& xmlState)
{
// add some attributes to it…
xmlState.setAttribute (T(“pluginVersion”), 1);
xmlState.setAttribute(T(“presetName”), presetName);
for(int j=0;j<kNumberOfParameters;j++)
xmlState.setAttribute(getParameterName(j), getParameter(j));
}

void ValhallaRoom::loadStateFromXML(XmlElement* xmlState)
{
// check that it’s the right type of xml…
if (xmlState->hasTagName (T(“ValhallaRoom”)))
{
presetName = xmlState->getStringAttribute(T(“presetName”));
// ok, now pull out our parameters…
for(int j=0;j<kNumberOfParameters;j++)
setParameter(j, (float) xmlState->getDoubleAttribute (getParameterName(j), getParameter(j)));

	sendChangeMessage();
}

}

void ValhallaRoom::getStateAsText (String& destStr)
{
// create an outer XML element.
XmlElement xml (T(“ValhallaRoom”));
saveStateToXML(xml);
destStr = xml.createDocument (String::empty, true, false);
}

void ValhallaRoom::setStateFromText (const String& stateStr)
{
XmlDocument doc(stateStr);
XmlElement* xmlState = doc.getDocumentElement();
if (xmlState)
{
loadStateFromXML(xmlState);
delete xmlState;
}
}

bool ValhallaRoom::saveStateToFile(File newFile)
{
// create an outer XML element…
XmlElement xmlState (T(“ValhallaRoom”));
saveStateToXML(xmlState);
return (xmlState.writeToFile(newFile, T(""), “UTF-8”, 60));
}

void ValhallaRoom::setStateFromFile(File newFile)
{
XmlElement* xmlState = XmlDocument::parse (newFile);

if (xmlState != 0 && xmlState->hasTagName ("ValhallaRoom"))
{
	loadStateFromXML(xmlState);
	delete xmlState;
}

}
[/code]

Bringing this thread back to life! devil

 

Would storing preset information as XML still be the best approach for cross-platform presets using the JUCE4 framework? 

I'm working on a VST/VST3/AU Audio plug-in and moving into the preset mechanism phase.

Any insight regarding this topic would be most appreciated.

 

Additionally, thank you Andrew J for your snippets of code (four years later) and other contributors to this thread; I'll be experimenting with xml techniques tomorrow and I have found this thread most helpful.

 

Cheers,
B

 

No worries, but 4 years later and I still never finished that plugin! (have built up a decent library of other code though)

Andrew,

the code that you have completed within the past four years: have any of these programs included a preset system?

If so, was the approach XML-based similar to this thread?

 

Cheers,
B

No I haven't.

Did you know that JUCE 4 has new audio parameter classes and something to hold a plugin state in a ValueTree (which has XML methods)? Take a look at AudioProcessorValueTreeState...

I did not know. After reading the documentation, it seems very useful and simplifies parameter callback for sure! For my current project, I do have a system in place -- however I will keep the AudioProcessorValueTreeState in mind for future projects. 

The AudioProcessorValueTreeState doesn't seem to address importing and exporting parameter settings as xml either, which is my current hurdle (particularly importing). I will make a seperate post in that regard.

 

Cheers,
B


 


Attached below is a super-shortened version of Andrew J's deSerialiseStateInformation function. The 'If' statement above is when my plug-in seems to crash. The above functions are called in the stateStateInformation and getStateInformation respectively. 

Perhaps I am not addressing the XML document correctly? Perhaps I am not saving the XML document correctly? Perhaps there's a bug?
Any thoughts would be very helpful. 

Cheers,
B


void HyperdriveAudioProcessor::deSerialiseStateInformation(ScopedPointer<XmlElement> xmlState) 
{ 
     // make sure that it's actually our type of XML object.. 
     if (xmlState->hasTagName ("DSPMODULESETTINGS")) /* The program is crashing when it reaches this line. */ 
     { 
          // something happens here 
     } 
}

XmlElement HyperdriveAudioProcessor::serialiseStateInformation()
{
    XmlElement xml ("DSPMODULESETTINGS");
    
    for (int param = 0; param < NumParams; ++param)
    {
        String thisAttr = parameterMap.GetParameterName (param);
        double value    = getLinearParameter (param);
        xml.setAttribute (thisAttr, value);
    }
    
    return xml;
}


When my 'Preset Load' button is pressed, this is the code block that executes:


        String fyleSpec = ("/Users/bDawson/presets.xml");
        File fyle(fyleSpec);
        
        if (fyle.existsAsFile())
        {
            XmlDocument xmlDoc(fyle);
            deSerialiseStateInformation(xmlDoc.getDocumentElement());   // CRASHING
        }
     
        uiDirty = true;


My saved xml file, which I am attempting to import to the program to set my parameters.


<?xml version="1.0" encoding="UTF-8"?>
<DSPMODULESETTINGS Depth="0" Dry/Wet="0" ComboBox="0" Preset Save="1" Preset Load="0"/>

You can get the state as a ValueTree object and thus has methods to convert to XML

I can't remember the rules for valid XML attribute names off the top of my head, but the slash in dry/wet and the space in preset load & preset save might be a problem. 

If a pointer might be nullptr (like here due to the failing xml parsing) you should check before using it :

 if (xmlState != nullptr && xmlState->hasTagName ("DSPMODULESETTINGS"))

fellow jucers,

Your insight has helped solve my current crashing issue. Yesterday's frusteration solved this morning in an instance (naturally). 

 

So here's what changed:


<?xml version="1.0" encoding="UTF-8"?> <DSPMODULESETTINGS Depth="0" Dry/Wet="0" ComboBox="0" Preset Save="1" Preset Load="0"/>

Now looks like this:


<?xml version="1.0" encoding="UTF-8"?> <DSPMODULESETTINGS Depth="0" DryWet="0" ComboBox="0" PresetSave="1" PresetLoad="0"/>

 

The '/' character and spaces that crept their way into my xml document was causing the crashes. Now everything parses as expected.

 

Thank you!
B