Pluginval can set an AudioParameterBools to odd values

I’m debugging a strange issue that sometimes leads to Pluginval’s state restoration test to fail. This is with an AU plugin, I’m not sure yet if it also happens with the VST3.

The test checks if the sum of parameter values is the same before and after a state restore. Under some circumstances, when the test starts, the two AudioParameterBools that the plugin uses have random values that are not 0.f or 1.f. After saving and restoring the state they are correctly set to either 0.f or 1.f, and thus the test fails because of the difference.

Whether the test fails depends mainly on which other tests have run before. There seem to be several tests that somehow manage to produce these odd parameter values.

Anyone ran into this problem before? I think it normally shouldn’t be possible at all to set AudioParameterBools to such odd values, especially from the outside.

This is on JUCE 6.1.6.

1 Like

Tracked the issue down further, and on develop it’s the same. AudioParameterBool can just be set to any float value. I’d propose the following change to fix it. That should somewhat guarantee non-surprising values for bool parameters.

diff --git a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp
index 4ba512ddd..9d42b6ef7 100644
--- a/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp
+++ b/modules/juce_audio_processors/utilities/juce_AudioParameterBool.cpp
@@ -77,7 +77,7 @@ AudioParameterBool::~AudioParameterBool()
 }
 
 float AudioParameterBool::getValue() const                               { return value; }
-void AudioParameterBool::setValue (float newValue)                       { value = newValue; valueChanged (get()); }
+void AudioParameterBool::setValue (float newValue)                       { value = newValue >= 0.5f; valueChanged (get()); }
 float AudioParameterBool::getDefaultValue() const                        { return defaultValue; }
 int AudioParameterBool::getNumSteps() const                              { return 2; }
 bool AudioParameterBool::isDiscrete() const                              { return true; }

Same issue here (JUCE 7.0.5), and a related topic as well: Basic AudioProcessorValueTreeState plugin won't pass PluginVal

As mentioned by @jacobweber, removing the guard clause from AudioProcessorValueTreeState::ParameterAdapter::setDenormalisedValue() also seems to do the trick (and perhaps is the better option?):

    void setDenormalisedValue (float value)
    {
        // if (value == unnormalisedValue)
        //    return;

        setNormalisedValue (normalise (value));
    }

I’ve yet to see a response from the JUCE team though :thinking:
Could there be any possible implications?

Thanks for reviving this. This has been lurking in my backlog for a while and is the only thing keeping me from enabling pluginval in my CI pipeline. Not sure though which fix would be the better option.

@dave96 mentions in the other thread that it might be required by auval that a parameter set to some odd value should return that odd value. But that feels like a weird requirement for any stepped parameter like bools or choices. For AudioParameterChoice I think there’s a similar logic like the one in my proposal to “snap” the normalized values to valid ones, IIRC.

it is unrealistic to assume that you can force the outside to put a parameter to integer values only, because parameters are just atomic floats on the inside, no matter what kinda wrapper juce invents over that. one of the major reasons why i’m a huge fan of not even using parameterBool and parameterChoice at all and instead using parameterFloat, but then come up with normalisableRanges that make it act steppy on the plugin gui

The AudioParameterBool and AudioParameterChoice are useful, and I would encourage anyone to use that consistent interface to access the parameters, so that normalising is never done through an independent code path that might create inconsistent behaviour.

The API has a consistent interface to set values and they are usually set to legitimate values. To allow random numbers was never the intention of the API creators, however it is sensible that the JUCE code should take care that the values are still valid in any case. Should be simple enough to be fixed, some possible fixes are already in this thread.

3 Likes

Bringing back this topic, as we are improving our automated testing game:

Is there a plan to solve this? And on which end should it be solved?

Option a) saving an odd value, like @dave96 e was quoted before?

Or shouldn’t a host as well as a test program respect the settings? The AudioProcessorBoolean has getNumSteps() = 2, isBoolean and isDiscrete set, so the host and pluginval would be perfectly able (and obliged IMHO) to check if a legal and correct value is returned.

1 Like

Can someone summarise this for me please so I can refresh my mind on the topic? I don’t have much bandwidth for pluginval these days so am a bit rusty…

Dave, thanks for chiming in. I’ll try to refresh, although it’s been a while since I investigated this.

IIRC, the current implementations in JUCE of AudioParameterBool and AudioParameterFloat allow setting them to any float via setValue(), which is not valid as they are stepped parameters. I think when saving and restoring the patch they are converted back and forth and in the process are snapped correctly to legal values (not sure right now if that happens during save or load, or both).

The pluginval test fails because the values before and after save/restore don’t match (as it sets random float values and compares the sum before/after). That happens only sporadically. I see this here with both AU and VST3 versions. Seems like in the “before sum” the unsnapped odd value is added, and in the “after sum” the snapped one.

Interestingly, for the AU version it can be fixed by fixing the AudioParameterBool in the plugin only, with the patch proposed above. The VST3 version however still fails sporadically, which can also be fixed by applying the same patch to pluginval’s underlying JUCE and rebuilding it.

I’m not sure though if my proposal is the “right” way to fix this behavior. I feel like it’s an ok idea to simply snap values when the host (?) tries to set it to odd values.

1 Like

practically speaking this problem was already solved a long time ago when it was fixed from only being true if the value is not 0 to being true if the value is above 0.5. there was a problem where the fl studio parameter randomizer kept silencing plugins because it was unlikely for the hidden bypass parameters to be true. I personally haven’t encountered a related problem ever since that was solved

This thread is about failing in pluginval, not about the user experience in fl studio.
Please don’t dilute the conversation.

1 Like

i was just reminding of the practical dimension of this problem.

Should I just exclude discrete parameters from this test? It seems like the wrong kind of test if it’s setting random float values?

That sounds like a sensible solution to me

1 Like

How about setting random values after applying snap to legal value?

That would be applicable for any kind of parameter, and the data on how to snap to legal value is available.

1 Like

Is the snapping algorithm consistent though? Isn’t it up the the plugin how it snaps values?
For example, for binary parameters, how do they snap a value of 0.5? Does it round up or down? Or do they just store the 0.5 and use it internally as a true or false?

Yes, I was thinking about that too.

So the test could not validate what the plugin would do for values at the snap boundaries. But the test would not send those values, like a sane host would not.

I think that situation would be better than those false positives and skipping the discrete tests.

I made a PR for setting snapped values for discrete parameters:

For that purpose I added a trivial method instead of setting r.nextFloat() directly:

static inline void randomizeParameter (juce::Random& r, juce::AudioProcessorParameter* parameter)
{
    auto newValue = r.nextFloat();

    // snap to legal value
    if (parameter->isDiscrete())
    {
        auto maxValue = static_cast<float>(parameter->getNumSteps() - 1);
        newValue = std::round(newValue * maxValue) / maxValue;
    }

    parameter->setValue (newValue);
}

It fixes the issues on our CI.

2 Likes

Are you sure that’s the only way to snap a normalised float value to a discrete num steps though?
This example shows you get different results if you truncate instead of rounding:

Then there are different rounding modes that round 0.5 up/down etc.

I’m just not sure if this fixes things in all situations?

Like I said before, this test does not validate what happens when you send 0.5, because as you describe, there is no unambiguous answer to that.

Instead my reasoning is to validate the values that are expected from a proper host.

This removes false positives and still tests a valid input.

But of course I am open for alternatives.

1 Like