tl;dr How can I paginate via a CHOP that is in the range of 0…1 via an endless encoder?
I’m currently using an OSC controller in a custom VJ setup. I have a VJ setup that requires me to select a scene before displaying it on the performance monitor. Because there are multiple scenes, I need a way to scroll through all the scenes and select the next scene to render using a Switch TOP. This works perfectly fine for a limited number of scenes, but given that I have 25+ scenes, I’m realizing I need an endless encoder that will scroll through all the scenes until I choose one to select. However, I’m not sure how to page through using an endless encoder given that its values are still a float value in the interval 0 to 1
For example let’s say there are three scenes with the following animals:
I use the OSC controller surface that displays a group of radio buttons to pick the Dog scene to show next. Because there are only four scenes, it’s easy to select which one to render and it works perfectly fine. However, if I have many more scenes, then it quickly becomes unwieldy as I’d need tons of radio buttons.
If I use an endless encoder I theoretically can page through every rotation. Let’s say I can cycle through 10 scenes per full rotation. Then from OSC value 0…1 the first time, I can simply go from 0 to 9 to select any of 10 scenes. **I’d like in the second, third and nth rotation to be able to continue to go up. That is, on the second rotation I should be able to pick scene number 10 through 19, and if I cycle back I should be able to go back to scene number 9 through 0.
Are there any TD Ops to help do this? I’m assuming I may need to do some scripting to do this pagination technique. I did find that if I used a callback I should technically be able to compare
prev value with next
val of a CHOP and therefore be able to detect when it goes from 0.9xx to 0.0xx - meaning the encoder has completed a full rotation and 0.0xx to 0.9xx meaning a reverse rotation.
If I understand correctly, your hardware encoder is physically endless, but it always outputs it’s position as a value between 0-1 in TD? So if you rotate the encoder ever so slightly past it’s highest point, the value in td goes from ~1 to ~0 ?
If this is correct, you just need to take the value from this frame, and subtract the value from last frame and use the sign of that value as the direction to offset the current index.
The edge case you want to account for is looping back around to 0 from 1, or vice versa. you need to define a threshold that when the move is larger than that, it makes the value offset * -1.
Here’s the toe file. This may be buildable using chops if you wanted, maybe with clever use of a logic chop and some other things.
wraparound_encoder.1.toe (4.2 KB)
Worth noting in this example the magnitude of your drag does not change how much the id value is changes each frame. If you think of a mouse wheel, if you scroll down a web page really fast you actually move down the page much more quickly. There’s an accumulation of scrolls if you will.
This simpler implementation will not scroll your value faster if you twist the knob furiously fast to do that, you’d want to take into account the difference between this frame and last frame and not just take the sign.
How you might scale or remap that difference value into additional steps per frame is up to you, and largely would depend on the UX you’re going for and the feel of the notches in the rotary knob, etc.
This is actually amazing. The proof of concept via
chopexec and wraparound logic is enough for me to go and run with. Thank you!!
If I figure out some improvements on top, like the fast scroll, I’ll go ahead and update it here.
Since I’m going to bet many people will need this. I made some improvements here:
- change wraparound logic to comparing
- handles faster scrolling a bit better
- can adjust some of the code to adjust how many
grid_divisions an endless encoder will have (page size)
def onValueChange(channel, sampleIndex, val, prev):
op_index = op('index')
page = op_index.par.value0
sub_index = op_index.par.value1
index = op_index.par.value2
grid_divisions = 5
threshold = 0.05
# grid divisions TODO: make grid boundaries dynamic
if val >= 0 and val < 0.2:
sub_index.val = 0
elif val >= 0.2 and val < 0.4:
sub_index.val = 1
elif val >= 0.4 and val < 0.6:
sub_index.val = 2
elif val >= 0.6 and val < 0.8:
sub_index.val = 3
sub_index.val = 4
if val >= 0 and val <= threshold:
if prev <= 1 and prev >= (1 - threshold):
elif prev >= 0 and prev <= threshold:
if val <= 1 and val >= (1 - threshold):
index.val = page.val * grid_divisions + sub_index.val