Turn knobs from module code

(MicMusic) #1

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?

(Andrew Belt) #2

This will work as expected in Rack v1.

1 Like
(Phil Golden) #3

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.

(Leodardo) #4

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?


(Andrew Belt) #5

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.

(Leodardo) #6

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.

(Leodardo) #7

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?

(Andrew Belt) #8

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.

(Leodardo) #9

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.

(Leodardo) #10

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.

(Andrew Belt) #11

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

(Andrew Belt) #12

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);

(Leodardo) #13

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.

(Andrew Belt) #14

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.