I have a basic AudioProcessorValueTreeState
with just an AudioParameterBool
and the PluginVal state restoration test fails with errors such as :
!!! Test 1 failed: Parameters not restored on setStateInformation -- Expected value within 0.1 of: 0.656967, Actual value: 0.963613
Below is a PIP to reproduce. Am I doing anything wrong?
PIP
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: GainPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Gain audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: GainProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class GainProcessor : public AudioProcessor
{
public:
//==============================================================================
GainProcessor()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{
}
//==============================================================================
void prepareToPlay (double, int) override {}
void releaseResources() override {}
void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
//==============================================================================
AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
//==============================================================================
const String getName() const override { return "Gain PlugIn"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
const auto& mainInLayout = layouts.getChannelSet (true, 0);
const auto& mainOutLayout = layouts.getChannelSet (false, 0);
return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled()));
}
//==============================================================================
AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
{
std::vector<std::unique_ptr<RangedAudioParameter>> params;
params.push_back (std::make_unique<AudioParameterBool> ("gain", "gain", false));
return { params.begin(), params.end() };
}
void getStateInformation (MemoryBlock& destData) override
{
auto xml = std::make_unique<XmlElement> ("GainPlugin");
xml->addChildElement (parameters.copyState().createXml().release());
copyXmlToBinary (*xml, destData);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
if (auto xml = getXmlFromBinary (data, sizeInBytes))
{
if (xml->hasTagName ("GainPlugin"))
{
if (auto stateXml = xml->getChildByName ("PARAMS"))
parameters.replaceState (ValueTree::fromXml (*stateXml));
}
}
}
AudioProcessorValueTreeState parameters { *this, nullptr, "PARAMS", createParameterLayout() };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor)
};
I have a code snippet online at juce-cookbook/working_with_juce/code_snippets .
It’s very similar, but my if condition is tree.isValid()
.
thanks!
But we have the same validation error with your code (PIP below).
PIP
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: GainPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Gain audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: GainProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class GainProcessor : public AudioProcessor
{
public:
//==============================================================================
GainProcessor()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{
}
//==============================================================================
void prepareToPlay (double, int) override {}
void releaseResources() override {}
void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
//==============================================================================
AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
//==============================================================================
const String getName() const override { return "Gain PlugIn"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return {}; }
void changeProgramName (int, const String&) override {}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
const auto& mainInLayout = layouts.getChannelSet (true, 0);
const auto& mainOutLayout = layouts.getChannelSet (false, 0);
return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled()));
}
//==============================================================================
AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
{
std::vector<std::unique_ptr<RangedAudioParameter>> params;
params.push_back (std::make_unique<AudioParameterBool> ("gain", "gain", false));
return { params.begin(), params.end() };
}
void getStateInformation (juce::MemoryBlock& destData) override
{
MemoryOutputStream stream (destData, false);
parameters.state.writeToStream (stream);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
ValueTree tree = ValueTree::readFromData (data, static_cast<size_t> (sizeInBytes));
jassert (tree.isValid());
if (tree.isValid())
parameters.state = tree;
}
AudioProcessorValueTreeState parameters { *this, nullptr, "PARAMS", createParameterLayout() };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor)
};
dave96
May 20, 2020, 10:10am
4
Presumably the problem is that this line is not actually updating the gain value? At least maybe not synchronously?
yes, it’s a normalised/unnormalised issue :
you can call AudioParameterBool::setValue()
with a normalised value (anything between 0 and 1), and the method AudioParameterBool::getValue()
returns that normalised value.
But AudioProcessorValueTreeState
set/recall the unnormalised value (0 OR 1), and doesn’t update the parameter value if the unnormalised value is the same.
So the issue is happening in such cases :
boolParam->setValue (0.6f); // value is at 0.6f
state = getValueTreeState();
boolParam->setValue (0.9f); // value is at 0.9f
setValueTreeState (state); // the value isn't changed back to 0.6f because the unnormalised value is already 1
As the state restoration test of PluginVal is based on the sum of the normalised values it may fails if there is a ‘discrete’ parameter.
So either we should save/recall the normalised value and cannot use AudioProcessorValueTreeState::replaceState()
as it is for now, either the PluginVal test should be relaxed a bit taking into account the discrete properties of the parameters.
Hmm, I’m not sure about this. If a param is set to a specific value, shouldn’t it return that value afterwards? I’m pretty sure auval
has a check for this.
I could ignore discrete params from this test but that feels like the wrong approach…
I think I’m running into this same issue.
It fails on certain random boolean parameters. If I print out the values after the instance.setStateInformation
call in PluginStateTestRestoration
, they still show the randomized float values instead of 1 or 0.
But if I remove the if
statement in AudioProcessorValueTreeState::ParameterAdapter::setDenormalisedValue
, that causes it to skip the setter as long as the denormalized values are equal, the test passes.
Is there a better approach?
My setStateInformation/getStateInformation are basically the same as the ones above.