setStateInformation retrieves old value if set programmatically

I have a slight issue with getStateInformation / setStateInformation.

If I change a slider attached to a parameter, save the DAW file, and then reload it, my slider has the correct value. Same if I put an envelope / automation on the track, and change the value, save, then reload, and it parameter/slider is set to track envelope.

However if I change same slider by moving it, then use a reset/default function (Need this to quickly set synth modules to default) to programmatically reset parameters, and therefore it’s attached slider’s to default value, even though the slider’s change, and in debug mode I can see the parameter change, after saving and reloading DAW file, the value that is read is the older one, meaning the one that I changed via moving the slider, instead of the programmatically changed value.

Does this ring a bell to to anyone? If not I’ll post some code.

It sounds like you’re doing something on your side of the code to change the parameter, which isn’t being saved by the host. You could try calling yourAudioProcessor.updateHostDisplay() – this notifies the host that something has changed, and that may cause the host to call getStateInformation() again, thus saving the new state correctly.

How are you setting the parameter programmatically? I have a feeling you need to call setValueNotifyingHost, or wrap the parameter changes with beginChangeGesture/endChangeGesture in order for the host to know about them properly, but have never done this personally so I may be wrong.

2 Likes

Yes thanks and it is exactly what I thought about when trying to sleep last night, that during debugging I noted that getStateInformation was called every time I touched a slider.

Unfortunately using yourAudioProcessor.updateHostDisplay() immediately after updating my parameters manually did not do the trick.

I have tried changing a parameter two ways, in the processor like this;

*mainLevelParameter = 0.5f; // Default value

and in the editor like this;
mainLevelSlider.setValue (0.5f, dontSendNotification);

Since there is a slider attachment, changing either will change the other.

setValueNotifyingHost sounds like it is what I need, I did not see is as a method using my parameters as in AudioProcessorValueTreeState parameters;.

How do I use it?

I set up my main level parameter in processor.h like so; std::atomic<float>*mainLevelParameter = nullptr;

And in my processor’s constructor like this;

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
	),
	// DAW Automation Parameters
	parameters (*this, nullptr, Identifier ("Parameters"),
		{
			std::make_unique<AudioParameterFloat> ("MainLevelID", "Main Level",
                                                   0.0f, 1.0f, 0.5f),
            ...
            // More parameters
		})
#endif
{
	mainLevelParameter = parameters.getRawParameterValue ("MainLevelID");
    ...
}

Ok this may have done the trick!

    // Previous change attempt
    //*mainLevelParameter = 0.5f;

    // This seem to work, testing it now on more parameters
	parameters.getParameter ("MainLevelID")->setValueNotifyingHost (0.5f);

Why would you use dontSendNotification? That prevents the host being notified. You want to notify the host, don’t you?

Here is a function I use to set a parameter (whose pointer I have already):

void setParameterToGivenWorldValue( AudioProcessorParameterWithID* pParam, double worldValue, AudioProcessorValueTreeState& vts )
{
	if (pParam != NULL)
	{
		pParam->beginChangeGesture();
		NormalisableRange<float> range = vts.getParameterRange( pParam->paramID );
		float paramValue = range.convertTo0to1( worldValue );
		pParam->setValueNotifyingHost( paramValue );
		pParam->endChangeGesture();
	}
}

1 Like

It was a leftover, as I tried both, neither worked. But thanks a lot for the input. I figured out the solution as suggested my @asimilon ;

parameters.getParameter ("MainLevelID")->setValueNotifyingHost (0.5f);

But I can see that your complete solution is better, thanks!

Ok I think I am supposed to use your function like this;

setParameterToGivenWorldValue (mainLevelParameter, 0.5, parameters);

As mainLevelParameter was set in my PluginProcessor like this;
mainLevelParameter = parameters.getRawParameterValue ("MainLevelID");

However your function will not accept my parameter pointer, variable declared this way; std::atomic<float>* mainLevelParameter = nullptr; which I am using because of the JUCE “Saving and loading your plug-in state” tutorial. It gives error "argument of type “std::atomic " is incompatible with parameter of type "juce::AudioProcessorParameterWithID”.

That’s the wrong thing to pass. You need the same parameter that you were using to call setValueNotifyingHost with, e.g., parameters.getParameter (“MainLevelID”).

1 Like

Got it thanks!

Btw I get a compiler warning for this line of code in your function ‘’‘float paramValue = range.convertTo0to1 (worldValue);’’’

1>C:\JUCE\Mutineer\Source\PluginProcessor.cpp(473,43): warning C4244: ‘argument’: conversion from ‘double’ to ‘ValueType’, possible loss of data
1> with
1> [
1> ValueType=float
1> ]

No idea why I have double there. Should just be a float. (Thanks!)

I have in earlier posts touched on this, but as nothing has changed I am going to push a few buttons again!

I wonder if JUCE is ever going to realize the value of making some better tutorials. I mean the knowledge I just gathered in this post, I believe should have been in the tutorials. Also many of the tutorials are confusing and inconsistent. For example “Saving and loading your plug-in state” and “Adding plug-in parameters” each uses different approaches of declaring parameters, without explaining why.

I was a Scuba Instructor in Key Largo, Florida for several years and made instruction material for divers. Then I was a computer instructor for 9 years, travelling several times yearly to the company’s other locations, teaching staff how to use programs and made manuals documenting in-house software.

I can’t help to feel that several of the JUCE tutorials are seriously lacking, and judging from those, it seems they forget, or chose to ignore, that there are probably lots of people out there who want to make audio software, but give up early on because their C++ skills are lacking (like mine).

But hey I am a persistent “individual” and had it not been for this forum and many of it’s members who over and over again have given up their valuable time to help people like me, I would have given up long ago! But my point is, that these helpful people for whom I am forever grateful, should not have had to spend so much of their valuable time helping others, if only the JUCE tutorials would have been better.

@DKDiveDude out of curiosity, what DAW are you testing in? The pattern of calling getStateInformation on every parameter change but not when saving the project is, I guess, not standard.

About your remark on Juce tutorials, yes you’re probably right these could be more detailed. But the thing is that making audio software is challenging, even for experienced C++ developers. So in the end, they can make the tutorials as advanced as they want, people will still encounter issues of their own that are not directly documented. That is why being a software developer is a full time job :slightly_smiling_face:

I am using Reaper and must admit I have yet to test in another DAW.

I agree with you that there is room for improvment in the tutorials. There are a few problems.

First it is unknown what skill the learner has. The person writing the tutorials expected the reader to be fluent with C++. First that might not always be the case, but even if they are, the style of JUCE is quite different from the C++ people wrote 10-15 years ago. I learned a lot for C++ from using JUCE.

The other is the constant lack of manpower (like everywhere the day has just a fixed amount of hours). It is a question if you put priority on onboarding new users or making the framework better.