Turn knobs from module code

I’m trying to solve following problem.
Module has a lot of knobs and one switch ‘Mode 1’ <-> ‘Mode 2’. On switch from ‘Mode 1’ to ‘Mode 2’ I would like to save all knob values for ‘Mode 1’, load values for ‘Mode 2’ and adjust knobs accordingly. Same backwards.

Adjusting param.value doesn’t work - knobs are not redrawn.
I assume I can make custom Widget child and override step() function, which will update widget value from param value and call redraw. But is there easier option?

This will work as expected in Rack v1.

1 Like

Bidoo.Dtroy uses this technique this might help, though it doesn’t really work well for knobs that have complex shapes in 0.6.x.

I am trying to change a knob value programatically (Rack v1.dev.46f9577)
If I call params[…].setValue(…) from the process() method the redraw works.
If I try to do the same from the onSampleRateChange the redraw doesn’t take place (if I drag the knob it will jump to the set value).
Is this expected?

A related question: calling configParam crashes if called in the onSampleRateChange. It didn’t a few days ago. Should we avoid using it outside the Module constructor?


I can’t reproduce the bug when you call Param::setValue from onSampleRateChange. My question though is why would you ever want to do that?! Regardless, I tested it and it works as expected.

Yes. Only call config* functions from the Module constructor.

Ok, what would be the proper way to modify properties such as the max/min values or the display Base/Multiplier/Offset?
There are no setter method so the only chance is directly accessing them.
I’m thinking of panels that can change some of their properties depending on e.g. a context menu option. Say, e.g. I leave the user an option to decide whether to use samples instead of millisecs for a knob.

1 Like

I have at least one use case: a value knob ranging 0-1 sec. The range should not be linear, it should be:

	int hold = floor(pow(args.sampleRate, params[PARAM_HOLD].getValue()));

and the displayed value should reflect this, so:

	configParam(PARAM_HOLD, 0.f, 1.f, 1.f, "Hold time", " s", APP->engine->getSampleRate(), 1.f/APP->engine->getSampleRate(), 0.f);

What if the sampling rate changes?
I can do this:

	void onSampleRateChange() override {
		paramQuantities[PARAM_HOLD]->displayBase = APP->engine->getSampleRate();
		paramQuantities[PARAM_HOLD]->displayMultiplier = 1.f / APP->engine->getSampleRate();

But I also want the new value to be consistent with the previous one, so I have to change the knob value in the onSampleRateChange method.
Does it make sense to you?

Don’t modify the parameter limits, just rescale in the DSP code. E.g.

float x = params[...].getValue();
if (mode)
    x = rescale(x, 0, 1, -1, 1);

But why would you want a knob to change its position based on the unit? The unit of measurement is a display setting. The DSP code should only care about the absolute measurement.

I don’t think your users want to care about samples. They think in terms of milliseconds, not samples.

Ok, but even keeping everything in ms, the problem of allowing a non-uniform mapping in the range 0-1s with varying sampling rate remains. The new display feature is very useful, but the values should be consistent with the DSP engine, otherwise it gets confusing for the user. I see the same issue with the clock of SEQ-3, for instance, where the BPM value ranges from 2^i where i ranges -2 to 6 but no hint is given to the user.

I’m sorry, but I really need to stress this detail: how are we going to handle display values in cases such as the SEQ-3 clock knob?
I’m wondering if other developers have found their own neat way to get this right, otherwise I believe that a solution should be found before v1 is out.
Just my 50 cents.

Do you mean discrete values? Set snap = true; in your Knob's constructor.

Ah, I misread that as “STEP” knob, not the “CLOCK” knob.

Maybe something like this?

configParam(CLOCK_PARAM, -2.f, 6.f, 2.f, "Clock tempo", " bpm", 2.f, 60.f);


The formula I just made above is 60 * 2^x.

I could have used Hz instead with ..., " Hz", 2.f);

Yes, I was fiddling with this and came to the same conclusion. Obviously in this case you don’t need the value of the engine sampling rate for displaying the value.

So I figured out that this is not exactly my case: in SEQ3 the upper limit is 3840BPM, i.e. one step lasts Fs/64 samples. In my case I’d like to get down to generating 1 random value per sample, keeping the same nonlinear mapping, and keeping the user informed with a meaningful display value.
The solution I figured out so far needs to know the sampling rate to init the knob display multiplier and base, so it’s not feasible if sampling rate changes.
Solution1: display value = Fs^(x-1) --> with x in [0,1] allows to range from 1/Fs to 1
Solution2: display value = x^2 --> with x in [0,1] allows a nonlinear map (not perfect though for musical stuff), but the APIs do not allow x to be the displayBase.

I guess there’s no way around this with the current APIs.
BTW: the intent is to make a random generator useful for BPM stuff but also to generate noise getting to a rate of Fs or close to it.

I’d just make the knob go really high, where the maximum is 100kHz or something. Anything higher than the sample rate would just be clamped.