Strategies for altering parameters range and interval

Hi

I’ve been trying to find the best and cleanest way to change the range and interval (or snapped values, but I will keep using the word interval for simplicity here) on a slider and/or associated parameter, but I am struggling right now. Every approach I try seem to have its own drawbacks and complications.

The simplest approach would be to have different sliders/params for each range I want to address, and show/hide them when required, but I want to be able to have any combination of ranges and intervals, making this approach unpractical (eg frequency ranges, chromatic interval with any given A reference, etc.).

The second obvious approach is to keep the parameter on the largest possible range and continuous interval, and only change the slider interval and range, but this does not play too well with normalized ranges (eg frequency slider with a log progression), and also means that the parameter itself can still be internally (or through automation) be set to a value that does not correspond to the interval and/or range. This also means that the string representation has to be handled elsewhere (eg notes when a chromatic interval is chosen for a frequency slider) rather than taking it directly from the attached parameter (eg note for a chromatic frequency interval).

The third approach is to modify the parameter directly by changing the values returned by the from0-1/to0-1/snap lambdas from the normalized range constructor, as well as lambda returning the string representation. With this approach the initial range has to be set to something arbitrary (eg [0.0, 1.0]), meaning getMinimum()/getMaxium() cannot be used on the attached slider.
This seems like a clean approach and works appropriately, but I am afraid this could fail with some DAWs or wrappers.

I even tried another approach with a “proxy” parameter based on the real one but with a restricted range and interval and a specific string representation. The slider is re attached to a new proxy parameter each time the range/interval is changed.

I tried all these approaches (and then some even more obscure), each implying a large rewriting of my code, and I seem to have lost track (and faith) along the way: I am getting nowhere and need some guidance :slight_smile:

Ideally I would want to have the parameter reflect the current state of the range and interval, with no way of setting a parameter in an “illegal” value, but I can live without that.

The number of parameters a plugin has, and their ranges and types, form a kind of ‘contract’ with the DAW.
The DAW needs to be able to reliably and reproducibly save and restore the state of the plugin at any time. either through saving the session, performing automation, or undo/redo. However, if the meaning or behavior of any parameter keeps changing arbitrarily, the DAWs job becomes literally impossible.

unpopular suggestion: define a ‘clean’ simple set of parameters for your plugin, and don’t try to alter them at runtime.

3 Likes

I don’t know if this works for you but you can snap the values by overriding the snapValue function, for example this will do it in steps of 0.5

double snapValue(double attemptedValue, DragMode dragMode) override
{
    return round(attemptedValue/0.5) *0.5;
}
1 Like

Your requirements seem very challenging to implement in a single slider. Have you considered or tried having a mode slider that selects between frequency range, chromatic interval, and other modes?

When you change the mode slider, you can show/hide the appropriate control sliders. You could also presumably slave them to each other behind the scenes (so when the frequency slider changes, it calculates the appropriate value for the twinned chromatic interval slider) - or not, so that they retain their values when your switch back to the other mode.

Heck whether they are slaved or not could be another slider…!

This would not cause any confusion with the DAW as all parameters remain consistent throughout the program’s run time. You’re just changing which of the twin/triplet/quadruplet/… parameters is active.

1 Like

if the meaning or behavior of any parameter keeps changing arbitrarily, the DAWs job becomes literally impossible.

In the third approach described above I use the same strategy as @HowardAntares here, so the range itself is kept constant but its interpretation is changed.
I am still not sure this approach is safe and sane, and there could be some race conditions when loading presets or state, or some strange behavior with automation, etc.

unpopular suggestion: define a ‘clean’ simple set of parameters for your plugin, and don’t try to alter them at runtime.

That is a functionality I want to have in my plugin though…

Using this method did not work as intended, for some reason: it was not called when I needed it to be.

In my situation this would unfortunately require an unreasonable amount of hidden sliders/params.

The worst case is the frequency knob: I want to be able to change its range between five defined ranges (LF, MLF, full range, etc.) while in the same time also allow at least four frequency steps (continuous, 1/3 octave, chromatic 440, etc.). Representing all the combinations would already require a minimum of 20 sliders/params for that one frequency knob alone.
Ideally I would even want to be able to arbitrarily change the A tuning for the chromatic range, which would definitely render this approach impossible.

1 Like

you can delete the SliderAttachment and create another one with a different parameter, so you don’t need to have multiple sliders, but a list of assignable parameters.

https://docs.juce.com/master/classAudioProcessorValueTreeState_1_1SliderAttachment.html

(Disclaimer: I don’t know if this is a recommended practice or it can generate problems.)

1 Like

I feel this pain. My advice to myself in these parameters situations is “do the verbose easy thing” otherwise it gets quite painful. Like Jeff said, the parameters have to be pretty dumb and consistent, as we’re developing middleware for the lowest common denominator of DAWs.

My first reaction is “20 params isn’t bad?” That’s the most straightforward route to get everything working consistently in a DAW today. It’s also a pretty common pattern (for example a “rate” switching between hz/bpm scales) because of the features you care about are setup perfectly: strings, ranges, snapping, automation, etc. Just swap the knobs out. The only real concern (to the user) with having 100+ parameters is it makes automation more annoying (meaning, if the range/steps are changed “where the frequency is being automated” jumps).

It sounds like you’ve done all the homework, so IMO the only thing you have left is a product decision. This might sound silly, but in these tougher situations, I find it helps to pretend I’m not the programmer anymore and put on the product/owner hat. It’s now my job to weigh how I expect people to use the plugin against the constraints the programmer has bubbled up. And then make the tough annoying call. For example, if range/steps are just UI conveniences, and it’s not critical to the product to prioritize snapping/limits in automation you could decide to have 1 frequency parameter and everything else is just UI (or Meta Parameters) that modify and constrain frequency.

Good luck!

2 Likes

Yes but then I still need a large number of parameters.
That is the approach I tried with the “proxy” parameter: I keep my main parameter (the one in the apvts) untouched, and with a large enough range to accommodate all intended ranges, then I generate a new “proxy” parameter when modifying the range or interval, and reattach it to the slider.
The problem is that I cannot really “extract” all the needed info from the main parameter. This means I have to keep them in a different object, together with the stringier lambdas, and generate the main parameter from this.

I don’t find this approach very clean…

Yes, that is probably what I am going to do: keep all these refinements for the UI only.

Still, I need to decide how to do it, and the proxy parameter approach seems to be the best I have found so far in terms of flexibility and genericity, as this would also potentially allow me to use the generated parameter for the DSP code directly (and hopefully “force” the range and interval there even when the parameters are modified from outside the UI).
I will need to synchronize in both directions (from main to proxy and back) for each process block, not sure this will go seamlessly…

I wanted to ask for advice and opinions before committing to this X-th rewrite, hopping there was a simplest and cleanest way I might have overlooked, but I guess now is the time to get my hands dirty with this :smiley:

1 Like

You could try just proxy sliders (not attached to a parameter) with custom ranges, set snapValue (I could be wrong but this was overridden if you connected to a param with normalizable ranges) and connect them to the freq parameter manually via ParameterAttachment.

I think it would be relatively clean without the need to create a “master” parameter. Create a list of parameters in which each parameter is a group (you are basically creating with them the “RangeAudioParameters” desired), and attach to each slider one of them.

Now assign a listener to all of them. In the callback method update all related parameters according to the value of the parameter that caused the call, which will usually be the attached parameter to the slider, using the appropriate conversion.

1 Like

Although I recommended the master-mode parameter approach, I think this is actually better as it keeps the UI and user experience clean and consistent. It will be crowded, but lay it out elegantly and it won’t feel cluttered.

This is great advice!

1 Like

Hey OP, did you land on a good solution to your problem?

I am facing a similar challenge. I think it’s less complex because I’m not changing units, step sizes, etc. but essentially I need to tune the comb filters in the built-in reverb module. So there should be one slider that controls the comb filter number, and then three more that control - for that specific comb filter - the index and left/right damping (we are trying to make the sound more organic and lively). It’s best if they are all parameters so, after tuning, we can save the AudioProcessorValueTreeState to easily extract the tuned parameters for hardcoding.

Not sure how it could translate to your particular situation, but here is what I ended up doing.
I kept the parameters untouched, and set them to a range that exceeds any possible range that could be chosen in the interface. Same for the string representation, I just used a generic representation in the parameters.
I did all the modifications in a derived Slider class, redefining getTextFromValue() for the string representation and calling setNormalisableRange for the range and snapping.

This approach means that the user can still set unintended values (both out of range and unsnapped) by modifying the parameter directly, but this is his responsibility then, and it will not break anything either…

Hope this helps.

1 Like

typically when you find yourself using range based slider functions for stuff like getMaximum to solve a problem, it’s better to step back and rather catch the problem at its root, in the processor, where you init the parameters. when it comes to parameters that are supposed to be able to represent multiple kinds of units at the same time it’s best to decide which one of them has the upper hand on the range. for example you could make a pitch parameter from 0 to 127, but it can also display the values in frequency hz, including the lambdas for converting values and strings and snapping to legal steps. or you could think of it the other way around and have a range of 20 to 20k hz but also display it in pitches. another popular kind of parameter where people often wanna combine ranges are ones where you wanna switch between some time unit like ms or hz and temposync values like 1/16. but those are usually better off being individual parameters, cause it allows the user to automate them individually while switching between them

1 Like