AudioProcessorValueTreeState::copyState() not behaving as expected

If I create a test VST3 plugin with an AudioProcessor constructor like this:

TestPluginAudioProcessor::TestPluginAudioProcessor()
     : AudioProcessor (BusesProperties()
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                       )
, parameters_( *this, nullptr, "PARAMETERS", {
   std::make_unique< juce::AudioParameterFloat >( "FooID", "Foo", 0.0, 30.0, 15.0),
})
{
}

Where parameters_ is an AudioProcessorValueTreeState.

And I implement getStateInformation() as follows:

void TestPluginAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
   auto param = getParameters()[0];
   
   param->setValue(0.0f);
   const auto value = param->getValue();
   
   DBG("FooID VALUE IS: " + String(value));
   
   auto state = parameters_.copyState();
   std::unique_ptr<juce::XmlElement> xml (state.createXml());
   
   DBG( "STATE: " + xml->toString() );
}

Then when calling getStateInformation(), I get the following output:

FooID VALUE IS: 0
STATE: <?xml version="1.0" encoding="UTF-8"?>

<PARAMETERS>
  <PARAM id="FooID" value="15.0"/>
</PARAMETERS>

In other words, the internal ValueTree state seems to be inconsistent with the actual parameter state. My understanding was that calling copyState() is supposed to bring the two into sync.

If I call setValueNotifyingHost() instead of setValue() then it works as expected, but this isn’t going to work if hosts are calling setValue().

JUCE 6.1.6. macOS.

I think this is working as intended. The ValueTree only updates the tree when it gets a change callback from a parameter, which is why the NotifyingHost version works, but normal setValue doesn’t.

I’m not sure what you mean here. Hosts don’t call setValue - when the host changes a parameter, it will always trigger a parameter change callback. Maybe you’re thinking of race conditions, if the audio callback is running while the state is being saved, However, I think such problems would equally affect setValueNotifyingHost and setValue.

I don’t really understand why you’d want to set a parameter value from getStateInformation. The host might call regularly this to auto-save the project and keep track of undo state, and most hosts will expect that saving the state won’t change any parameter values. The result might be frustrating for users, if a parameter changes value each time the host gets the current state. Finally, the plugin may fail in some validators, as those expect that applying a state previously obtained from getState will set all parameters to whatever values they had immediately before the getState call.

If you want to save a version of the state where a certain parameter always has a specific value, I think it would probably be better to make this change to the ValueTree after fetching it. That way, the state of the plugin isn’t affected during save. This might still cause problems in validators, though.

@reuk

I’m not sure what you mean here. Hosts don’t call setValue - when the host changes a parameter, it will always trigger a parameter change callback.

Ah… I was confused because when a host calls AudioProcessorParameter::setValue() for a VST3 plugin it does trigger an update like you mention.

However if AudioProcessorParameter::setValue() is called for a non-plugin AudioProcessorParameter e.g. AudioParameterFloat then the behaviour is as I describe above.

That is, the implementation of AudioProcessorParameter::setValue() works differently depending on whether the processor is a plugin or not.

I don’t really understand why you’d want to set a parameter value from getStateInformation

I don’t. It was just a minimal example to illustrate the problem.

I think this is working as intended

OK, so let me explain my use case…

Our product is a host, and I’m trying to make it generic so that it can work with any AudioProcessor subclass. So, AudioPluginInstance for VST3 etc or other AudioProcessor subclasses for internal effects.

This all works fine except for AudioProcessorParameter::setValue() because the behaviour is inconsistent between AudioParamterFloat and VST3Parameter.

I thought from the documentation of AudioProcessorValueTreeState::copyState(), that calling this method would cause the value tree to synchronize with the underlying parameter state. i.e.

This method flushes all pending audio parameter value updates and returns a copy of the state in a thread safe way

But as per my example, this seems not the case. The reason seems that this method only flushes if a parameter has been flagged as needsUpdate from a listener callback (which doesn’t happen if AudioParameterFloat::setValue() is called).

Anyway, what I would like to do is:

  1. Set parameter values without notifying all listeners (AudioParameter*::setValue() already does this)

  2. Have a way to cause the AVPTS value tree to get updated to reflect the state of the underlying parameters, such that when getStateInformation() is called the VT is consistent with the parameter values

If I’m understanding correctly, the recommended way to do this would be to override valueChanged() for AudioParameter* and manually update the VT from there?