Got it, thank you both so much!
I thought it would be simple to add mapping to the extended class, but it turns out there are some very odd corner cases on counting ticks on encoder wheels --in particular encoders with either two tick marks or one tick mark on the full circumference. Small mouse movements that linger around the zero rollover are causing the encoder to drop or add ticks, so I need to take another look at the logic or whether those need to be special-cased.
I got inspired by this thread, so thanks for that!
@jeremydouglass, hope you donât take this âcopycatâ the wrong way I rolled my own class though, even if it was initially based on yours (if only to remind me of syntax and using PVectors). But itâs a bit messy and not as elegant. Could probably (definitively!) be improved.
I figured Iâd base it on how a real encoder works, with quadruature encoding. Thereâs probably better methods, but I wanted to test it.
One difference is that I count each âquadrature stepâ, so it will be 4 counts pr. step. For a hardware solution, one step is plenty (and also often detented. Well I guess thatâs also read by software in some microcontroller). Anyway in this case it would be a bit slow or error prone if increasing nr. of steps.
Added mouse scroll wheel for fun (bit primitive, and might break if you use the scroll wheel other places in the sketch). Thereâs probably a better way of doing that, but I just donât know how atm. Which kind of made the whole quadrature thing moot. Itâs an alternative at least.
Depending on the size of the encoder, display resolution, computer speed etc, donât overdo the nr. of steps (which is multiplied by 4 because of the quadrature encoding), or else it will be easy to skip a few and mess up the sequence, especially with fast mouse movements. Suggest keeping nr. of steps in low-mid single-digit range.
Demo sketch (looks somewhat based on jeremydouglasâs dials):
Click to (un)expand
/* Rotary Encoder demo
Based on HW rotary encoder mechanics (quadrature encoding)
Class method getDirection() returns -1, 0, or 1. Erased when read.
2020.08.21 raron
*/
HwRotaryEncoder enc[];
int encoders = 5;
int [] counter = new int[encoders];
float scrollWheel = 0.0;
void setup()
{
size(700, 200);
//smooth(4);
surface.setTitle( "HW based rotary encoder demo" );
ellipseMode(RADIUS);
enc = new HwRotaryEncoder[encoders];
for (int i=0; i<encoders; i++) {
enc[i] = new HwRotaryEncoder(115*(encoders-i-1)+115, 80, 39, i+1);
counter[i] = 0;
}
}
void draw()
{
background(192);
fill(0);
for (int i=0; i<encoders; i++) {
counter[i] += enc[i].getDirection();
text(counter[i], 115*(encoders-i-1)+112, 150);
enc[i].update();
enc[i].display();
}
}
void mouseWheel(MouseEvent event)
{
scrollWheel = event.getCount();
}
class HwRotaryEncoder
{
PVector center;
PVector startDragPos;
PVector currentPos;
float encSize, slice;
float rotation, oldRotation;
int quadsteps, quadIndex, oldQuadIndex;
int direction;
boolean encA, encB, oldEncA, oldEncB;
boolean active, mouseButtonWasPressed;
color knobColor;
HwRotaryEncoder(float w, float h, float encSize, int stepsPerRev)
{
this.center = new PVector(w, h);
this.startDragPos = new PVector(0, w);
this.currentPos = new PVector(0, w);
this.encSize = encSize;
this.quadsteps = stepsPerRev*4; // internal counts per step (quadrature encoder)
this.slice = TWO_PI/this.quadsteps;
this.direction = 0;
this.rotation = 0;
this.oldRotation = 0;
this.oldEncA = false;
this.oldEncB = false;
this.active = false;
}
boolean mouseInside()
{
if(currentPos.mag()<encSize) return true;
return false;
}
void update()
{
currentPos = new PVector(mouseX, mouseY).sub(center);
if (mousePressed == false) {
// a mouse button is not pressed
if (active) {
active = false;
oldRotation = rotation;
}
mouseButtonWasPressed = false;
} else {
// a mouse button is pressed
if (mouseButtonWasPressed == false) {
mouseButtonWasPressed = true;
if (mouseInside()) {
active = true;
startDragPos = new PVector(mouseX, mouseY).sub(center);
oldEncA = false;
oldEncB = false;
direction = 0;
}
}
}
// Simple scrollwheel rotation
// scrollWheel MIGHT be more than +/- 1.0, but usually not (not taken into account)
if (mouseInside()) {
if (scrollWheel == 0.0 && !active) oldRotation = rotation;
if (scrollWheel != 0.0) {
rotation += slice * scrollWheel;
direction = int(scrollWheel);
if (rotation<0) rotation += TWO_PI;
if (rotation>TWO_PI) rotation -= TWO_PI;
scrollWheel = 0;
}
}
// Mouse drag rotation
// If moving mouse fast, will probably skip steps or count backwards
if (active) {
rotation = currentPos.heading() - startDragPos.heading() + oldRotation;
if (rotation<0) rotation += TWO_PI;
if (rotation>TWO_PI) rotation -= TWO_PI;
int quadIndex = (int)((float)quadsteps * (rotation/TWO_PI));
if (oldQuadIndex != quadIndex) {
// Make rotary encoder quadrature pattern
int pa = (quadIndex/2)%2;
int pb = ((quadIndex+1)/2)%2;
if (pa == 1) encA = true; else encA = false;
if (pb == 1) encB = true; else encB = false;
// Count when transitioning pattern edges
if (encB & (encA ^ oldEncA)) {
if (encA) direction = 1; else direction = -1;
}
if (!encB & (encA ^ oldEncA)) {
if (encA) direction = -1; else direction = 1;
}
if (encA & (encB ^ oldEncB)) {
if (encB) direction = -1; else direction = 1;
}
if (!encA & (encB ^ oldEncB)) {
if (encB) direction = 1; else direction = -1;
}
oldEncA = encA;
oldEncB = encB;
oldQuadIndex = quadIndex;
}
}
}
int getDirection()
{
int temp = direction;
direction = 0;
return temp;
}
void display()
{
if(mouseInside() || active) knobColor = color(210); else knobColor = color(192);
pushMatrix();
pushStyle();
translate(center.x, center.y);
stroke(0);
for (int i=0; i<quadsteps; i++) {
if (i == 0) strokeWeight(3); else strokeWeight(1.5);
line( encSize*0.9, 0, encSize, 0 );
rotate(slice);
}
fill(knobColor);
circle(0, 0, encSize*6/8);
rotate(rotation);
line( 0, 0, encSize*6/8, 0 );
popMatrix();
popStyle();
}
}
EDIT: Very minor edit, forgot resetting oldEncB (not really important, just a bit of OCD I guess)