How to create a stepped knob

I am trying to create a 5 way switch using Knob and I have two problems.

Slider responds to .step:

View().layout_(HLayout(Slider().minHeight_(100).step_(0.2).action_{|n| n.().postln })).front

But Knob does not:

View().layout_(HLayout(Knob().step_(0.2).action_{|n| n.().postln })).front

Slider and Knob share the same description of .step in the helpfiles so why are they behaving differently?

I could change the behaviour of Knob with something like:

v = View().layout_(HLayout(Knob().action_{|n| v.children[0].value_(n.().round(0.25)); n.().postln })).front

In all cases above the post window is showing activity in between steps, meaning .action is called for every in-between/undefined position of the slider/knob.

So my question is: is it possible to create a 5 way knob with only 5 states - like a 5 state button in the form of a rotary knob?

If you store the rounded knob value then you can run your action only when the rounded val changes, rather than when the mouse is clicked or dragged. e.g.:

(
x = 0;
v = View().layout_(HLayout(Knob().action_{|n|
	n.value_(n.value.round(0.25));
	if (x != n.value, {
		x = n.value;
		n.value.postln;
	});
})).front;
)

Best,
Paul

1 Like

Thanks TXMod, I thought about solutions like this myself but it just feels wrong to have to do 20+ boolean tests to change one value, I wish there was a more direct way.

To save effort, why not make a helper function which you can call whenever needed:

(
var knobSwitchFunc = {arg parent, bounds, numSteps = 5, action;
	var roundedVal;
	numSteps = numSteps.max(2);
	Knob(parent, bounds).action_{|n|
		n.value_(n.value.round((numSteps - 1).reciprocal));
		if (roundedVal != n.value, {
			roundedVal = n.value;
			action.value(n);
		});
	};
};
View().layout_(HLayout(
	knobSwitchFunc.value(nil, nil, 5, {arg knob; knob.value.postln;}),
	knobSwitchFunc.value(nil, nil, 7, {arg knob; knob.value.postln;})
)).front;
)

Best,
Paul

1 Like

It seems simply to have been left out of the C++ implementation.

Slider:

void QcSlider::setValue(double val) {
    double step = _step;
    modifyStep(&step);
    if (step)
        val = qRound(val / step) * step;

    _value = qBound(0.0, val, 1.0);

    update();
}

Knob:

void QcKnob::setValue(double val) {
    _value = qBound(0.0, val, 1.0);
    update();
}

That is – likely a bug.

The three big questions of programming are: 1/ What information do I have? 2/ What information do I want? 3/ What steps will get from 1 to 2?

You have a stream of values corresponding to every movement. With step, this includes duplicate values.

You want a stream of values without the duplicates.

And the way to do that is to test each value in the source stream to see if it’s a duplicate or not.

One might request a feature so that the widget would de-dupe the stream internally – but the Boolean tests will still be done, just in C++, not sclang. I don’t see a way around that.

hjh

2 Likes