When to use getStateInformation / setStateInformation

I seem to have missed some vital pieces of information about getStateInformation and setStateInformation, which is when and where to use it? The why I got, saving parameters.

For example I could imagine it being used to save plugin state when I save my DAW file or close my DAW, which contains one or more instances of my synth plugin, and use setStateInformation when I reopen the DAW file.

Or do I use it when closing or opening the PluginEditor?

Or something entirely different?

You wouldn’t usually yourself call those methods, the host calls the methods when needed.

Those methods are called by the host when it wants to get and set the plugin state. So, for example when saving/loading the host project or when undo/redo is used in the host.

1 Like

Ok so to clarify, even though I have still to work on a method to save my synth parameters as a patch, if I create a DAW project, insert my synth on a track, change some parameters also used in getStateInformation, when I save the DAW file those synth parameters will be saved as part of my DAW / host file, and thereby restored, if I implemented it right, when I reload the host file right?

Right, but you need to also implement setStateInformation, getStateInformation isn’t enough for the state save/recall.

Yes I did understand that part :slight_smile: Thanks a bunch!

One more clarification if you don’t mind.

The memory block works as a first in, last out right? Meaning if I store raw values with getStateInformation, then using setStateInformation there is no identifier on which data to retrieve, so I need to restore them in reverse order/

No need for that. Usually I set up a stream for reading and writing, and read the data in the same order I wrote it. For more flexibility, we actually create an XML doc and read/write from that, which lets us update to new versions and we can read whatever data we want, (in any order we want, for that matter). For our plugins that don’t have to save any custom data other than parameters, we have a base class that just cycles through the parameters, reading/writing them in the same order we defined them.

1 Like

Thanks!

Now that I am debugging my synth in the Reaper DAW, I am having a very confusing issue.

I put a breakpoint in getStateInformation which as a test now only has;

void MutineerAudioProcessor::getStateInformation (MemoryBlock & destData)
{
    juce::MemoryOutputStream (destData, true).writeFloat (*mainLevelParameter);
}

I also put a breakpoint in setStateInformation, which for now only have this;

void MutineerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
	*mainLevelParameter = juce::MemoryInputStream (data, static_cast<size_t> 
                          (sizeInBytes), false).readFloat ();
}

When I start my Visual Studio project in debug mode, my Reaper project, with only one track with my synth is loaded, getStateInformation is invoked twice. Here I would have assumed setStateInformation would be called. This causes my stored parameter to be overwritten.

Anyways after getStateInformatin has been called twice, setStateInformation is finally called, but now off course the data is incorrect!

Next in Reaper, on the track I open the “FX” which is my synth Plugin (PluginEditor). And now each and every time I via the parameter attached slider, change the parameter, getStateInformation is invoked! That does not seem right, as eventually when my 200+ parameters is added in this method, whenever I via a slider change any of these 200+ values, getStateInformation will be invoked storing all 200+ again!?!

getStateInformation is also invoked when I close my PluginEditor.

getStateInformation is invoked when I save my Reaper project.

This is how declare my parameter layout and single test parameter;

    juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout ();

    std::atomic<float>* mainLevelParameter = nullptr;

This is my PluginProcessor.cpp’s constructor;

MutineerAudioProcessor::MutineerAudioProcessor ()
#ifndef JucePlugin_PreferredChannelConfigurations
	: AudioProcessor (BusesProperties ()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
		.withInput ("Input", AudioChannelSet::stereo (), true)
#endif
		.withOutput ("Output", AudioChannelSet::stereo (), true)
#endif
	),
	parameters (*this, nullptr, "Parameters", createParameterLayout ())
#endif
{
    ...

My createParameterLayout;

juce::AudioProcessorValueTreeState::ParameterLayout 
                                    MutineerAudioProcessor::createParameterLayout ()
{
    juce::AudioProcessorValueTreeState::ParameterLayout params;

	params.add (std::make_unique<AudioParameterFloat> ("MainLevelID", "Main Level", 
                                                       0.0f, 1.0f, 0.5f));

	return params;
}

I declare the parameter slider attachment in PluginEditor.h;

typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;

std::unique_ptr<SliderAttachment> mainLevelAttachment;

Finally in my PluginEditor’s constructor I attach the parameter to a slider;

mainLevelAttachment = std::make_unique<SliderAttachment> (processor.parameters, 
                                       "MainLevelID", mainLevelSlider);

So obviously I messed up somewhere, but where?

200 parameters doesn’t sound that much to handle even if the get/setStateinformation calls happen a bit more often than one might expect.

Your setStateInformation implementation seems wrong. You should restore the state of the tree in that instead of just assigning into the atomic variable. (The atomic should be used only to read the parameter value, not for attempting to write into it.)

Regarding the setStateInformation. I followed the example in this JUCE tutorial; https://docs.juce.com/master/tutorial_audio_processor_value_tree_state.html

Which says;
The AudioProcessor::setStateInformation() function needs to do the opposite: it should read data from a memory location and restore the state of our plug-in:

void setStateInformation (const void* data, int sizeInBytes) override
{
   *gain = juce::MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).
                                    readFloat();

}

Ok I am missing the “override”, but I don’t think that is going to make a difference.

Either way however ridicules I think it is that getStateInformation is invoked whenever one of a few hundreds parameters is changed, either via PluginEditor or DAW automation, my main immediate issue is that getStateInformation is invoked when I load the DAW file, and it thereby overwrites the setStateInformation.

Your getStateInformation should not change the state of your plugin in any way. This method is the host asking “What’s your state, in case I need it later…”.

Later when restoring from a session, or some hosts have a “compare” feature, it calls setStateInformation to tell your plugin to set itself accordingly to the memory blob that was acquired previously in one of the getStateInformation calls.

As I understand it, getStateInformation stores my parameters in a memory block, when I save my DAW file.

I then thought setStateInformation should restore those same parameters when I load my DAW file.

If that is correct, then as I wrote, because getStateInformation is invoked when I load my DAW file, it write default values to the memory block. Then setStateInformation is invoked reading default values just stored, instead of the values saved with my DAW file.

Yes I know I am messing up somewhere, I am just trying to find out where.

Ok my problem seems to have been fixed by changing getStateInformation and setStateInformation. I must have mixed up information from different tutorials.

void MutineerAudioProcessor::getStateInformation (MemoryBlock & destData)
{
	auto state = parameters.copyState ();
	std::unique_ptr<juce::XmlElement> xml (state.createXml ());
	copyXmlToBinary (*xml, destData);
}
void MutineerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
	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));
}

So the change I did in the above post, fixed so AudioParameter’s state is get and set right.

But can somebody please provide a simple example how to add non-AudioParameter, information not needing DAW automation, in get/setStateInformation. For example I want to save the current synth module that is selected.

I tried the following addition to the above, which does not work.

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

    MemoryOutputStream (destData, true).writeInt (currentModule);
}

void MutineerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
	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));

    currentModule = MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).
                                       readInt ();
}

Seems like you may want to save that in your state as well, perhaps as an AudioParameterChoice. Alternatively, you can add things to state in your set function and expect them in your set function.

Thanks, but if I use “AudioParameterChoice” won’t it show up in a DAW as an automation parameter? If so, that is not how I want it.

Regarding your alternative, yes this is what I am trying to figure out, however the JUCE tutorial “Saving and loading your plug-in state” does not answer this issue!

I need to save and restore some GUI settings, such as which module and section user was last working in, plus plugin dimensions (width and height in pixels). Sure it could be fun to automate my plugin size via automation :slight_smile:

What we do is use an output stream to write to that destData buffer. Then, after writing the parameter state data to it, we just make our own xml document (not using ValueTreeState) and add whatever non-parameter data we want to it, and write() that to the stream after the parameter xml’s data.

Thank you very much for that information.

In-case it helps, this is an example of including height/width parameters, which should not be automated.

void PluginProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    auto s = state.copyState();
    std::unique_ptr<juce::XmlElement> xml (s.createXml());
    xml->setAttribute ("uiWidth", lastUIWidth);
    xml->setAttribute ("uiHeight", lastUIHeight);
    copyXmlToBinary (*xml, destData);
}

void PluginProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    std::unique_ptr<juce::XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
    if (xmlState != nullptr) {
        if (xmlState->hasTagName (state.state.getType())) {
            lastUIWidth  = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400);
            lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 200);
            state.replaceState (ValueTree::fromXml (*xmlState));
        }
    }
}
1 Like