Trouble with new APVTS code

Hey all, will try to keep this short and sweet. Could really use some guidance here, intermediate level experience here.

My codebase is built on the old way of doing APVTS, i.e using createAndAddParameter(). Due to some bugs I can’t wrap my head around, I’m updating this to reflect the recommended implementation.

I ran into a confusing situation. My previous code has two parameters that depend on each other, guiFreqRange_min_Hz and guiFreqRange_max_Hz. These can be changed by the user to zoom a UI component in/out.

Here’s my existing & outdated relevant code, as confusing as it is. Basically, addFloatParameter has a call to createAndAddParameter here.

        const float minDiff = 0.001f;
        addFloatParameter(ids.guiFreqRange_min_Hz, "GUI Frequency Range Min",
            guiFreqRange_Hz.getStart(),
            NormalisableRange<float>(guiFreqRange_Hz.getStart(), guiFreqRange_Hz.getEnd() - 10.f,
                nullptr, nullptr,
                [&eq, minDiff](float rMin, float rMax, float v)
                {
                    // this check prevents a crash when first initializing parameter
                    if (eq.state->getParameter(ids.guiFreqRange_max_Hz) != nullptr)
                    {
                        auto max = eq.pm->getFloatParamValue(ids.guiFreqRange_max_Hz);
                        if (fabsf(v - max) < minDiff)
                            v = jmax(rMin, v - minDiff);

                        if (fabsf(v - max) < minDiff)
                            eq.pm->setFloatParamValue(ids.guiFreqRange_max_Hz, jmin(max + minDiff, rMax));
                    }

                    return v;
                }),
            nullptr, nullptr, false, true);

Given the new way of doing APVTS, I don’t see a way I can dynamically do the above mentioned crash-preventing check, given that “max” and “min” depend on each other.
If I’ve read correctly, ParameterLayout cannot call any apvts object to check its value, because it is called before the APVTS constructor.

Could someone please fill in the missing info I have here? Let me try to sum up my question here…

“How do I perform a safety check on a value using the newest APVTS methodology?”

Thanks in advance! Much love.
Zonk

EDIT: Realizing it would quite obviously make sense to do this check inside parameterChanged or some other Listener class…

I didn’t find a clean way to validate and change the value of audio value tree parameters. It’s not allowed to send a setValueAndNotify directly from inside the parameterChanged method when using the VST3 format. This could lead to subtle problems and can crash in the worst case.

I started to validate and handle such things directly in the UI or when loading a preset. Make sure that the code also works with invalid values for the case the user sets the parameters in a generic view or with automation. You may have to check the values also in the DSP code.

Try to keep things as simple as possible. There will be enough problems also without complicated code.

first of all i’d agree with kunz, that trying to keep it simple can really help. when i read this is about a frequency range defined by a lower and upper limit for example i immediatly wondered why not just swap the values in processBlock if the lower one is above the upper one, rather than making sure the parameters always behave right with all the different things that can influence a parameter, you know?

apart from that making extremely complicated parameter layouts can be made easier as well, by outsourcing it into a static function (or inline if part of a header’s namespace) because then you don’t have to do it all in the initializer list. that way you can make reasonable helper functions and temporary objects to have an easier time keeping a sane overview on your parameter creation process.

here a little bit of an offtopic anecdote for complicated parameter connections made simple. i wrote this function, that returns the lambda for pitch parameters string to value: Project/Param.cpp at master · Mrugalla/Project · GitHub
it draws both the pitch and the frequency to the screen, but it uses the “xenManager” for that, because in some plugins i let users change the xen scale, so pitch is interpreted differently, but the xenManager itself is controlled by a few parameters, so one could say this parameter’s output depends on other parameters. it’s a bit similiar to your problem and shows how these kinda things can be made easier to just make an entire namespace for that stuff, instead of just the initializer list of the audioProcessor

1 Like

Thanks for the advice. I’d love to keep it simple too. I will add this isn’t originally my code so it’s difficult to find the right Jenga pieces to pull on, so to speak.

Can you explain a little more about setting it directly in the UI? That could be a good solution.

You can attach listeners to the JUCE UI controls (slider, button, combobox). They will be called every time the user changes the value on the UI. You can then verify the new value inside the method that is called by the listener and set the new value with parameter.setValueNotifyHost().

Or you can use a juce::Timer, check the slider values periodically, and fix them when required.