-
Notifications
You must be signed in to change notification settings - Fork 12
Persistence
As it is now, our plugin has all the parameters we need. But one thing it lacks is parameter persistence. This is crucial when working on real life projects in your DAW: you want the plugin to remember its state between sessions. Luckily JUCE provides two functions to handle this: the getStateInformation
and setStateInformation
functions. These functions are called whenever the user saves or loads a project in his DAW.
What we want to do here is on a call to getStateInformation
iterate through all the plugin parameters and store them to a xml file. When setStateInformation
is called we want to parse that file and restore the values of our parameters to these values.
Let's look at the implementation of these two functions:
void GrrnlrrAudioProcessor::getStateInformation (MemoryBlock& destData)
{
// [1]
std::cout << "Save Settings" << std::endl;
// [2]
// Create an outer XML element..
XmlElement xml ("GRRNLRRPLUGINSETTINGS");
// [3.1]
// Store the values of all our parameters, using their param ID as the XML attribute
for (int i = 0; i < getNumParameters(); ++i)
{
// [3.2]
if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
{
// [3.3]
xml.setAttribute (p->paramID, p->getValue());
std::cout << p->paramID << " " << p->getValue() << std::endl;
}
}
// [4]
// then use this helper function to stuff it into the binary blob and return it..
copyXmlToBinary (xml, destData);
}
- First we post a message to the console for debugging purposes.
- We create an object representing the xml data that we want to write to the disk later. We also give the object a name to identify it later when loading the data.
- Here we parse the names and values of our parameters into the
XmlElement
object - We iterate through the number of parameters.
- We create a pointer
p
to a parameter ifp
points to a valid object we continue. - We write the parameter ID and the parameter value into
xml
. We also post the value for debugging. - Finally we write the
XmlElement
object to disk. The location of this file is provided by the arguement of thegetStateInformation
function.
setStateInformation
looks similar:
void GrrnlrrAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
// You should use this method to restore your parameters from this memory block,
// whose contents will have been created by the getStateInformation() call.
std::cout << "Load Settings" << std::endl;
// [1]
// This getXmlFromBinary() helper function retrieves our XML from the binary blob..
ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
// [2.1]
if (xmlState != nullptr)
{
// [2.2]
// make sure that it's actually our type of XML object..
if (xmlState->hasTagName ("GRRNLRRPLUGINSETTINGS"))
{
// [2.3]
// Now reload our parameters..
for (int i = 0; i < getNumParameters(); ++i)
{
if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
{
// [2.4]
p->setValueNotifyingHost ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
std::cout << p->paramID << " " << p->getValue() << std::endl;
}
}
}
}
}
- Here we load the binary data provided by the function's first argument into a
XmlElement
objectxmlState
. We don't access the data directly but instead create a pointer to it that is local to this function, this way we don't have to worry about deleting the object later. - Here we set our parameters to the values in the
xmlState
object similar to how we wrote them: - We check if
xmlState
points to something. - We also check if the data is actually meant for our plugin.
- If so we iterate through our parameters and set the value notifying the host application to the value in the
xmlState
object. We also post all the values again.
In addition to the parameters we need one special value to persist: the path of our sample. Since the path isn't a parameter (and shouldn't be visible to the host application) we have to make it persistent manually. Also we need to modify the mechanism for loading samples a bit so it loads a sample from a restored path.
First we declare two variables in the header of our PluginAudioProcessor
:
// Sample Path
String filePath;
String restoredPath;
The first variable filePath
will hold the path to our file. It will be permanent for the session once the file is loaded and we will use it to write the path to the xml file. The second variable restoredPath
will hold the path of a sample restored from the setStateInformation
function. It will hold that value until the sample is loaded.
Now we will need to modify our openPath
function in the PluginAudioProcessorEditor
to set filePath
once a file has been chosen:
void GrrnlrrAudioProcessorEditor::openButtonClicked()
{
FileChooser chooser ("Select a File to open...",
File::nonexistent,
"*.wav", "*.aif", "*.aiff");
if(chooser.browseForFileToOpen()){
const File file (chooser.getResult());
String path (file.getFullPathName());
swapVariables (chosenPath, path);
processor.filePath = chosenPath;
notify();
}
}
Then we will add a statement for writing the path of the file to the xml file in getStateInformation
:
// [...]
xml.setAttribute("FilePath", filePath);
std::cout << "Save Path: " << filePath << std::endl;
// then use this helper function to stuff it into the binary blob and return it..
copyXmlToBinary (xml, destData);
// [...]
We also modify setStateInformation
:
// [...]
if (xmlState != nullptr)
{
// make sure that it's actually our type of XML object..
if (xmlState->hasTagName ("GRRNLRRPLUGINSETTINGS"))
{
// Now reload our parameters..
for (int i = 0; i < getNumParameters(); ++i)
{
if (AudioProcessorParameterWithID* p = dynamic_cast<AudioProcessorParameterWithID*> (getParameters().getUnchecked(i)))
{
p->setValueNotifyingHost ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
std::cout << p->paramID << " " << p->getValue() << std::endl;
}
}
restoredPath = xmlState->getStringAttribute("FilePath");
std::cout << "Load Path: " << filePath << std::endl;
}
}
// [...]
After setting restoredPath
we have to to get the sample to actually load. For this we declare another function in the header of our PluginAudioProcessorEditor
: void checkForRestoredPath();
We define the function in PluginEditor.cpp
:
void GrrnlrrAudioProcessorEditor::checkForRestoredPath()
{
String path;
path = processor.restoredPath;
if(path.isNotEmpty()){
swapVariables(chosenPath, path);
processor.restoredPath = "";
}
}
This function is very similar to checkForPathToOpen
we define a local variable path
to which we copy the contents of the restoredPath
member variable of our PluginAudioProcessor
. We then check if that string is empty. If it is not we swap the value of chosenPath
and path
. We then assign restoredPath
to be an empty string.
Finally we have to add a call to checkForRestoredPath
to the run
function of our PluginAudioProcessorEditor
Object:
void GrrnlrrAudioProcessorEditor::run()
{
while(! threadShouldExit()){
checkForRestoredPath();
checkForPathToOpen();
checkForBuffersToFree();
wait(500);
}
}
When you compile now the path to your plugin should be restored once you open the plugins GUI for the first time. With that the sample should also start to play.