Select Git revision
potentiometers.h

David Huss authored
The Arduino IDE wants things to be in a folder that has the same name as the ino. Lets do that then.
potentiometers.h 8.04 KiB
#include "WCharacter.h"
#include "wiring_analog.h"
#include <stdint.h>
#include <limits>
#ifndef Potentiometers_h
#define Potentiometers_h
#include "Arduino.h"
#include "luts.h"
#include "helpers.h"
#include "Adafruit_SH110X.h"
#include "Adafruit_GFX.h"
extern Adafruit_SH1106G display;
// The length of the moving average filter that smooths the
// controls. Higher number is smoother, but less responsive
// and needs more memory.
#define POT_MOVING_AVERAGE_SIZE 2
// Length of the Textbuffer for floats in the UI
#define UI_TEXTBUFFER_LENGTH 6
// Modes
enum PotMode {
POT_MODE_LIN,
POT_MODE_BIP,
POT_MODE_PITCH,
POT_MODE_SWITCH,
POT_MODE_LAST
};
// Display Modes
enum PotDisplayMode {
POT_DISPLAY_MODE_DEFAULT,
POT_DISPLAY_MODE_PITCH,
POT_DISPLAY_MODE_PERCENT,
POT_DISPLAY_MODE_SWITCH,
POT_DISPLAY_MODE_SWITCH_NUMBERS,
POT_DISPLAY_MODE_LAST
};
typedef void (*callback_function)(void);
class Potentiometer {
int pin;
int readings[POT_MOVING_AVERAGE_SIZE];
PotMode mode = POT_MODE_LIN;
PotDisplayMode display_mode = POT_DISPLAY_MODE_DEFAULT;
float last_reading, last_normalized_reading = 0.0f;
float display_scale = 1.0f;
callback_function onChangeFunction;
Easer easer;
public:
Potentiometer(int pin);
void init();
void setLinear();
void setPitch();
void setSwitch();
float read();
void setOnChange(callback_function f);
void renderUi();
void setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode);
const char *name;
double last_displayed = 0.0;
bool should_display = false;
bool display_value_changes = false;
bool last_was_nan = false;
uint8_t switch_positions;
uint8_t switch_offset = 0;
const char* const switch_labels[4] = {"TRI", "SQR", "RAND", "JUMP"};
};
Potentiometer::Potentiometer(int pin) {
this->pin = pin;
}
void Potentiometer::init() {
analogReadResolution(12);
easer.setFactor(0.001);
}
void Potentiometer::setLinear() {
this->mode = POT_MODE_LIN;
}
void Potentiometer::setPitch() {
this->mode = POT_MODE_PITCH;
}
void Potentiometer::setSwitch() {
this->mode = POT_MODE_SWITCH;
}
float Potentiometer::read() {
int reading = analogRead(this->pin);
// Shift all readings in the buffer over by one position, deleting the oldest
// and adding the newest
for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
int next = i+1;
if (next < POT_MOVING_AVERAGE_SIZE) {
(this->readings)[i] = (this->readings)[next];
}
}
(this->readings)[POT_MOVING_AVERAGE_SIZE-1] = reading;
// Get the average of the last readings
reading = 0;
for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
reading += (this->readings)[i];
}
reading = reading / POT_MOVING_AVERAGE_SIZE;
// Convert the last reading to a float and return
float current_reading = easer.Process(reading / 4096.0f);
float normalized_reading = current_reading;
// Depending on the Mode
switch (this->mode) {
case POT_MODE_PITCH:
current_reading = get_from_xy_table(pitch_knob_lookup_x, pitch_knob_lookup_y, current_reading, pitch_knob_lookup_length);
break;
case POT_MODE_SWITCH:
current_reading = int(current_reading * switch_positions);
break;
}
bool changed = abs(normalized_reading - this->last_normalized_reading) > 0.002;
// If the difference to the last reading is big enough assume the knob has been touched
if (this->last_normalized_reading && changed) {
if (display_value_changes) {
last_displayed = millis();
should_display = true;
}
if (this->onChangeFunction) { this->onChangeFunction(); }
}
if (this->last_normalized_reading && !changed) {
// if (!last_was_nan) {
// Serial.print(this->name);
// Serial.println(" returned NaN");
// }
last_was_nan = true;
return std::numeric_limits<float>::quiet_NaN();
}
last_was_nan = false;
this->last_reading = current_reading;
this->last_normalized_reading = normalized_reading;
return current_reading;
}
void Potentiometer::setOnChange(callback_function f) {
this->onChangeFunction = f;
}
void Potentiometer::setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode) {
this->display_value_changes = true;
this->name = name;
this->display_scale = display_scale;
this->display_mode = display_mode;
}
void Potentiometer::renderUi() {
double now = millis();
if (this->should_display) {
int x_margin = 28;
int y_margin = 13;
int x_center = display.width()/2;
int y_center = display.height()/2;
// Render a rectangle Backdrop for the text
display.fillRect(3+x_margin, 3+y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
display.fillRect(x_margin, y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
display.fillRect(x_margin+1, y_margin+1, display.width()-(x_margin+1)*2, display.height()-(y_margin+1)*2, SH110X_BLACK);
// Render the name of the parameter (e.g. "Pitch")
centeredText(this->name, x_center, y_center-4, SH110X_WHITE);
// Choose how many digits to display depending on the mode
int digits = 2;
if (this->display_mode == POT_DISPLAY_MODE_PERCENT) { digits = 0; }
// Allocate a buffer for the float and convert it into characters
char value_buffer[UI_TEXTBUFFER_LENGTH]; // Buffer big enough for 7-character float
dtostrf(this->last_reading*display_scale, 6, digits, value_buffer); // Leave room for too large numbers!
// If we are on a bipolar pot display an indicator if we are in the center
if (this->mode == POT_MODE_BIP && this->last_reading > -0.0001 && this->last_reading < 0.0001) {
display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
}
// If we are on a pitch pot display an indicator if we are in the the right steps
if (this->mode == POT_MODE_PITCH) {
float reading_mod = fmod(abs(this->last_reading), 0.05f);
if (reading_mod > 0.999f || reading_mod < 0.001f) {
display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
}
}
// The float value may contain some empty whitespace characters, remove them by
// first figuring out which the first actual character is
int nonwhite = 0;
for (int i=0; i<UI_TEXTBUFFER_LENGTH; i++) {
if (value_buffer[i] == ' ') {
nonwhite++;
}
}
// Create a new buffer that can hold everything
char text_buffer[UI_TEXTBUFFER_LENGTH+6];
// Copy all non-white characters over
for (int i = 0; i<UI_TEXTBUFFER_LENGTH; i++) {
text_buffer[i] = value_buffer[i+int(nonwhite)];
}
// Figure out where the last character (\0) in our new buffer is
int last = UI_TEXTBUFFER_LENGTH+6;
for (int i=16; i>0; i--) {
if (text_buffer[i] == '\0') {
last = i;
}
}
// Add units depending on the display mode : )
if (this->display_mode == POT_DISPLAY_MODE_PERCENT) {
text_buffer[last] = ' ';
text_buffer[last+1] = '%';
text_buffer[last+2] = '\0';
} else if (this->display_mode == POT_DISPLAY_MODE_PITCH) {
text_buffer[last] = ' ';
text_buffer[last+1] = 'S';
text_buffer[last+2] = 'e';
text_buffer[last+3] = 'm';
text_buffer[last+4] = 'i';
text_buffer[last+5] = '\0';
}
// Render that new text
if (this->display_mode == POT_DISPLAY_MODE_SWITCH) {
centeredText(switch_labels[int(last_reading+switch_offset)], x_center, y_center+4, SH110X_WHITE);
} else if (this->display_mode == POT_DISPLAY_MODE_SWITCH_NUMBERS) {
sprintf(text_buffer, "%d", int(last_reading));
centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
} else {
centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
}
}
// Show this for 700 ms after it has been last touched
if ((now - this->last_displayed) > 700.0) {
this->should_display = false;
}
}
#endif