Endless Knob

Endless Knob

Issues from output “k” continuous values, increasing or decreasing by 1 each time around. As for regular knob nodes, clockwise rotation is a positive change. Unlike regular knob nodes, these are adjusted by rotation about their centers. Small changes can be made by dragging the knob anywhere along or near its edge.

The value of k and the knob position are optionally saved and will be restored at next startup. A sample/hold node allows this memory. To make the memory feature easily optional, two S/H nodes are used, one with save turned on and one off, so that you can set the option without plowing through the guts of the module.

This knob can be truly “endless” or it can be set to work over a limited range by applying a 1 at “Limits,” using the values at Lim1 and Lim2, making the higher of the two the upper limit, etc. (Don’t forget that an open input is interpreted as one of the limits)

In addition to the k output, the “∂k” output provides the stream of distance samples and the “Adj” issues a 1 while the knob is being touched.

Reset the knob to the value at input “Reset Val” by taking the “Reset” input high.

Set the home position (pointer position when output is zero) by applying a value to input “Home Pos.”

This module is not yet assignable, as I don’t have a continuous controller knob at this point for testing.

Operation: This module uses point-to-point wiring, which proved far more CPU cycle friendly than DSP node, but the operation is similar.

1: waiting: Capture and verify initial touch, capture initial x and y and make sure it’s close enough to the knob. Compute first angle using arc-tangent (atan2) function.

To allow closer positioning to adjacent knobs such that their xy touchpads overlap somewhat, a touch is accepted only if it’s close to the knob. This is calculated by comparing the knob-center-to-touch distance with sum of knob radius, “kradius,” and an arbitrary finger width which is intended to allow adjustment of values by flicking at or near the edge of the knob. To keep the active region circular for large knobs, the valid touch radius can not grow larger than half the width of the Canvas node (and matching touch pad.)

2: collecting - if still touching, collect subsequent x and y, compute new angle via atan2 function, find the difference between samples, then compute tangent on the difference, which represents how far you’ve moved along the unit circle (analogous to the derivative.) Add this distance to the previous knob position (analogous to integration). If not still touching, go back to a “waiting” state.

“Reset” high will in both operating states set the output to the value applied to the “Reset Val” input and set the state machine to “waiting.” Note that it’s possible to reset to a value outside of the limits (if the knob has limits set, of course). This won’t break it, but with any subsequent adjustment the output will pop to one of the limits.


Input Signal Range Notes
kRadius 10-40 Knob radius (upper value limited by xy touch pad size. Defaults to standard knob radius.
Reset 0/1 Held high, resets knob output to “Reset Value,” below.
Reset Value +/- any value
Home Pos +/- any value the fractional part sets position where k = 0
Save@close 0/1 If 1, saves knob position and value at close
Limits 0/1 If 1, applies limits at Lim1 and Lim2
lim1/2 +/- any value Used as lower and upper limits. Order is not important for these inputs; the lower of the two is used as the lower limit.

Output Signal Range Notes
k +/- any value knob output, in revolutions, one rev per turn
∂k +/- small values angular velocity stream, revolutions/sample.
Adj 0/1 1 when the knob is being adjusted.

Version History

Revision | File
Endless Knob v2-8.audulus4 (16.5 KB)

2| Original, DSP node-based. Original features, no save.
1 | Endless Knob 2024-02-06-01.audulus4 (8.3 KB) | 2/6/2024


Endless Knob v2-8 Demos.audulus4 (41.1 KB)


Love this.

One comment – chances are that with a knob you do not need per sample processing. If so, you can save on CPU by processing at control rate (block rate instead of per sample) using fill(). It’s easy to convert – instead of using the for i=1,frames loop, use the first sample at each buffer for inputs, for ex., touch[1] instead of touch[i], then output as fill(output, knobValue) ).

1 Like

Thanks. I’ve learned a lot from this, and have experienced some strange behavior due I think to my naive assumptions on the characteristics of the xy pad and not accounting for the fact that the xy pad updates at far below audio rate. I’m working on a version that will properly address it. Building with expression nodes might be more appropriate for this module, but right now it’s fun implementing mostly in Lua.

On my iPad the update rate of the xy pad node is about 5.3ms

I’m definitely going to try your suggestion. Lots of time spent waiting at sample rate for a new value out of the xy pad!

Yeah, if your audio buffer size is set to 256, update rate would be about the same anyway. Curious, how did you measure the update rate?

I used my o scope!

Way better and no mysteries, but it still seems like quite the processor hog. Here’s what I have:
Endless Knob v2-5.audulus4 (12.0 KB)

It adds memory and optional limits.

Will rework and post properly in a day or two, hopefully!
BTW, I made this knob because I wanted a fairly unlimited trigger delay for my o-scope and needed a control that allowed that in a small space. I have always enjoyed the control I have over the volume of my stereo receiver, which I imagine uses a shaft encoder with a flywheel and impressively big knob. It took a zillion turns of the knob to go from -80 db to zero.

All useless if I don’t get the %CPU under control.

New version, now with point to point innards. Lighter on the CPU!