Hello all. Trying to find the easiest way to capture user input on an encoder wheel. In theory, I would want to track the clockwise / counterclockwise mouse movement within the area representing the encoder. So instead of just moving a slider left to right or up and down, the user can actually rotate their mouse around a center point.
Create a PGraphic image. I used random circles but this could be your encoded wheel or anything.
Move the mouse over the canvas to detect colors.
Do something with the detected colors.
The rest is up to your creativity and imagination. I used the same concepts in the past to do something similar.
In my example the mouse location turned red or green depending on color it detected.
You can leave the mouse location and rotate image with key ‘4’ and ‘6’.
You can create a new image with key ‘5’.
Code is not commented so you can go through it.
Ask if you have questions.
Example Code < Expand this!
// Rotary Encoder
// v1.0.0
// GLV 2020-06-20
// Step one to creating a rotary encoder simulation
PGraphics pg;
color dot;
float angle = 0;
void setup()
{
size(400, 400);
pg = createGraphics(300, 300);
ranCircles();
}
void draw()
{
background(255);
push();
translate(width/2, height/2);
rotate(angle);
imageMode(CENTER);
image(pg, 0, 0);
pop();
int c = get(mouseX, mouseY);
if (c == color(0))
{
dot = color(0, 255, 0);
}
else
{
dot = color(255, 0, 0);
}
noStroke();
fill(dot);
circle(mouseX, mouseY, 10);
}
void keyPressed()
{
if (key == '5')
ranCircles();
if (key == '6')
angle += TAU/100;
if (key == '4')
angle -= TAU/100;
}
void ranCircles()
{
pg.beginDraw();
pg.imageMode(CENTER);
pg.background(color(255, 0));
pg.noStroke();
pg.fill(0);
for(int i = 0; i<= 50; i++)
{
float theta = random(0, TAU);
float r = random(0, 140);
float x = r*cos(theta) + 150;
float y = r*sin(theta) + 150;
pg.circle(x, y, 20);
}
pg.endDraw();
}
I took the code above, added a rotating arm and detected color at a point on the arm as it rotated.
screenX() and screenY() were used in place of mouseX and mouseY.
Just be careful to detect color before you draw over the point you are detecting.
Do you want an absolute encoder, which gives an angular location value in some range (0-360) – or do you want an incremental encoder which produces an offset value (e.g. 0.1 or -0.05) each frame depending on how fast the mouse moved – and optionally sums those (so you could spin the dial on and on? Or something else – for example, an absolute encoder which rolls over to higher and higher numbers?
Wow! Thanks all, Im glad I checked back up on this. I will look at the code above - but to answer your question… I think an Incremental Encoder would be preferred!
Note that it is still hybrid – the knob has an absolute indicator line on it, which is absolutely oriented with “head” based on the last mouse position. If you wanted to make it a pure rotary encoder, it would be non-oriented – drop the head value and indicator line, drop the oriented display of difference with arc, make update(angleOld, angleNew) public, and don’t worry that the sum will drift from the orientation, because there is no orientation.
revisiting this. This solution is my current implementation, however I am finding that it is a little difficult to stop on a specific number. I tried using modulus on the sum to mimic a “tick”, and only outputting at 0. However, this doesn’t really work well.
Is there another way you could go about only outputting a “tick” every “x” degrees rotated?
When you say “a specific number” what kind of range and sensitivity are you looking for? Are you trying to dial between 0 and 10, or -100 and +100 – are these numbers integers? Do you want the knob to turn a quarter turn for a “tick”, or a tenth of a turn?
That i am not quite sure yet. I was hoping to try a couple different settings and see how it feels. My first gut feeling says somewhere between 5 and 15 degrees.
To clarify, the way I have this working looks like this:
if (diff > 0) { //CLOCKWISE
encoderOutput = 1;
} else if (diff < 0) { //COUNTER-CLOCKWISE
encoderOutput = -1;
} else {
encoderOutput = 0;
}
//Listener event here
Where the listener event is simply sending which way the encoder is rotating for the OSC message.
Okay, so you are generating an OSC event anytime the wheel turns CW or CCW, no matter how little. But you want a minimum mount of turn before sending an event. Is that right?
Another way of approaching this problem is to create a Ticker class and feed the raw IncrementalEncoder values into it.
class Ticker {
float pos;
float lastTick;
float step;
Ticker(float start, float step) {
this.pos = start;
this.lastTick = start;
this.step = step;
}
int update(int newpos) {
// updates pos with newpos
// and updates lastTick if there has been a tick;
// returns 0, -1, or 1
}
}
Now add a Ticker to the example sketch above, and call myTicker.update(ie.sum); in draw. If the call returns a 1 or -1, send that via your Listener.
One subtle difference is that @glv’s suggestion to scale the output e.g. with map also needs a past comparison value in order to trigger events, but it will stay oriented – the click position will never drift. The Ticker class I suggested you is accumulating floats, so it will slowly drift over time – the knob indicator won’t always click at exactly the same angle after a long period of use. That may not matter to you, but if it does then you might want to extend the IncrementalEncoder class and add the mapping function directly to it.