display handler

This commit is contained in:
Dominic DiTaranto 2026-03-13 16:10:25 -04:00
parent 5c7fe5e88d
commit 83900d0711
4 changed files with 333 additions and 652 deletions

View file

@ -34,9 +34,11 @@ class DisplayHandler {
"CV2 INV",
"Mute"
};
bool onOutScreen = 0;
bool onSettingsScreen = 0;
bool onOutScreen = false;
bool onMainScreen = true;
bool onSettingsScreen = false;
std::array<std::string, 6> settings = {
"Save",
"Load",
@ -49,6 +51,11 @@ class DisplayHandler {
void renderMainPage();
void renderOutPage();
void renderSettingsPage();
void handleCursorOnOutputScreen(bool dir);
void handleClickOnOutputScreen();
void handleCursorOnMainScreen(bool dir);
void handleClickOnMainScreen();
void handleClickOnSettingsScreen();
public:
@ -61,6 +68,7 @@ class DisplayHandler {
int8_t currentOut;
bool cursorClick;
void setup();
uint8_t modifyParameter(uint8_t dir, int8_t target, uint8_t clampMax, uint8_t clampMin = 0, bool overflow = true);
void render();
void handleClick();
void exitSettingsScreen();

View file

@ -1,263 +0,0 @@
#include <SPI.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <TimerOne.h>
#include "globals.h"
#include "Out.h"
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define CLK 2
#define DT 3
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
byte play = 1;
byte BPM = 100;
unsigned long minute = 60000000L;
byte ppqn = 96;
unsigned long period = (minute / BPM) / ppqn;
volatile boolean beatToggle = false;
Out out1(4);
Out out2(5);
Out out3(6);
Out out4(7);
Out out5(8);
Out out6(9);
Out out7(10);
Out out8(11);
byte btnPin = 13;
byte btnState = 0;
volatile long rPotPos = 0;
unsigned long rPotLastUpdate = 0;
const unsigned long rPotThrottle = 50;
unsigned long screenLastUpdate = 0;
const unsigned long screenThrottle = 200;
byte forceScreenUpdate = 1;
byte currentScreen = 0;
byte editMode = 0;
int mainScreenCursorPos = 0;
byte maxMainScreenPos = 8;
void setup() {
Serial.begin(9600);
pinMode(btnPin, INPUT_PULLUP);
// rotary encoder set up
pinMode(CLK, INPUT_PULLUP);
pinMode(DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(CLK), updateEncoder, FALLING);
// screen setup
u8g2.begin();
u8g2.setFontMode(1);
// display.clearDisplay();
// display.setTextSize(2);
// display.setTextColor(WHITE);
// display.setCursor(0, 0);
//
// String bpmString = "BPM: ";
// String bpmCalculatedString = bpmString + BPM;
// display.println(bpmCalculatedString);
//
// display.display();
// init timer
Timer1.initialize(1000000);
Timer1.attachInterrupt(clock);
Timer1.setPeriod(period);
// init outputs
out1.setLen(period);
out2.setLen(period);
out3.setLen(period);
out4.setLen(period);
out5.setLen(period);
out6.setLen(period);
out7.setLen(period);
out8.setLen(period);
// manual setup
out1.setDiv(8, 0);
out1.setWidth(50);
out2.setDiv(4, 0);
out2.setWidth(50);
out3.setDiv(2, 0);
out3.setWidth(50);
out4.setDiv(1);
out4.setWidth(50);
out5.setDiv(2);
out5.setWidth(50);
out6.setDiv(4);
out6.setWidth(50);
out7.setDiv(8);
out7.setWidth(50);
out8.setDiv(16);
out8.setWidth(50);
}
void clock(void) {
if (play == 1) {
beatToggle = true;
}
}
void loop() {
if (beatToggle) {
out1.turnOn();
out2.turnOn();
out3.turnOn();
out4.turnOn();
out5.turnOn();
out6.turnOn();
out7.turnOn();
out8.turnOn();
beatToggle = false;
}
out1.turnOff();
out2.turnOff();
out3.turnOff();
out4.turnOff();
out5.turnOff();
out6.turnOff();
out7.turnOff();
out8.turnOff();
checkBtn();
checkRPot();
updateScreen();
}
void updateScreen() {
unsigned long currentTime = millis();
if (currentTime - screenLastUpdate >= screenThrottle && forceScreenUpdate == 1) {
screenLastUpdate = currentTime;
forceScreenUpdate = 0;
u8g2.firstPage();
do {
// 12px - nonbold
if (mainScreenCursorPos == 0) {
u8g2.setFont(u8g2_font_7x14B_mf);
} else {
u8g2.setFont(u8g2_font_7x14_mf);
}
u8g2.setCursor(0, 15);
u8g2.print(F("BPM: "));
u8g2.print(BPM);
u8g2.setFont(u8g2_font_7x14_mf);
u8g2.setCursor(0, 45);
char buffer[5];
for (byte i = 1; i < 9; i++) {
if (i == 5) {
u8g2.setCursor(0, 60);
}
if (mainScreenCursorPos == i) {
u8g2.setFont(u8g2_font_7x14B_mf);
}
sprintf(buffer, "[%d] ", i);
u8g2.print(buffer);
if (mainScreenCursorPos == i) {
u8g2.setFont(u8g2_font_7x14_mf);
}
};
} while (u8g2.nextPage());
}
}
void checkBtn() {
byte currentBtnState = digitalRead(btnPin);
if (currentBtnState != btnState) {
if (currentBtnState == 1) {
play = (play == 1) ? 0 : 1;
};
}
btnState = currentBtnState;
};
void checkRPot() {
if (millis() - rPotLastUpdate >= rPotThrottle) {
noInterrupts();
long currentPos = rPotPos;
interrupts();
// Print position only if it has changed
static long previousPos = -999;
if (currentPos != previousPos) {
Serial.print("Encoder Position: ");
Serial.println(currentPos);
// on home screen
if (currentScreen == 0) {
if (editMode == 0) {
if (currentPos > previousPos) {
mainScreenCursorPos++;
if (mainScreenCursorPos > maxMainScreenPos) {
mainScreenCursorPos = 0;
}
} else {
mainScreenCursorPos--;
if (mainScreenCursorPos < 0) {
mainScreenCursorPos = maxMainScreenPos;
}
}
}
}
forceScreenUpdate = 1;
previousPos = currentPos;
}
rPotLastUpdate = millis(); // Reset the timer for throttling the output
}
}
void handleBPMChange(byte increase) {
if (increase == 1) {
BPM++;
} else {
BPM--;
}
period = (minute / BPM) / ppqn;
Timer1.setPeriod(period);
}
void updateEncoder() {
// The ISR should be as short and fast as possible
// Check the state of the DT pin to determine direction
if (digitalRead(3) == LOW) {
rPotPos++; // Clockwise
} else {
rPotPos--; // Counter-clockwise
}
}

View file

@ -43,131 +43,75 @@ void DisplayHandler::setup() {
display->setOrientation(0);
}
void DisplayHandler::moveCursor(bool dir) {
if (onOutScreen) {
if (cursorClick == 0) {
uint8_t DisplayHandler::modifyParameter(uint8_t dir, int8_t target, uint8_t clampMax, uint8_t clampMin, bool overflow) {
if (dir == 1) {
cursorPosition++;
currentScreen++;
target++;
} else {
cursorPosition--;
currentScreen--;
target--;
}
if (cursorPosition > outMaxCursorPosition) {
cursorPosition = 0;
currentScreen = 0;
if (target > clampMax) {
if (overflow) {
target = clampMin;
} else {
target = clampMax;
}
}
if (cursorPosition < 0) {
cursorPosition = outMaxCursorPosition;
currentScreen = outMaxCursorPosition;
if (target < clampMin) {
if (overflow) {
target = clampMax;
} else {
target = clampMin;
}
} else { // click = 1
}
return target;
}
void DisplayHandler::handleCursorOnMainScreen(bool dir) {
if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen
update_BPM(dir);
} else {
cursorPosition = modifyParameter(dir, cursorPosition, mainMaxCursorPosition);
}
}
void DisplayHandler::handleCursorOnOutputScreen(bool dir) {
if (cursorClick == 0) { // No click, scroll through options
currentScreen = modifyParameter(dir, cursorPosition, outMaxCursorPosition, 0);
cursorPosition = currentScreen;
} else {
if (currentScreen == 1) { // mod screen
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->modifierSelectionIndex++;
} else {
outputs[currentOut]->modifierSelectionIndex--;
}
if (outputs[currentOut]->modifierSelectionIndex < 0) {
outputs[currentOut]->modifierSelectionIndex = 0;
}
if (outputs[currentOut]->modifierSelectionIndex >
std::size(MOD_TYPES) - 1) {
outputs[currentOut]->modifierSelectionIndex =
std::size(MOD_TYPES) - 1;
}
outputs[currentOut]->modifierSelectionIndex = modifyParameter(dir, outputs[currentOut]->modifierSelectionIndex, std::size(MOD_TYPES) - 1);
} else if (currentScreen == 2) { // shape control
int currentShape = (int)outputs[currentOut]->shape;
int8_t currentShape = (int8_t)outputs[currentOut]->shape;
if (dir == 1) {
currentShape++;
} else {
currentShape--;
}
if (currentShape >= SHAPE_COUNT) {
currentShape = 0;
}
if (currentShape < 0) {
currentShape = SHAPE_COUNT - 1;
}
currentShape = modifyParameter(dir, currentShape, SHAPE_COUNT -1, 0);
outputs[currentOut]->shape = (WaveShape)currentShape;
outputs[currentOut]->writeAnalog(0);
} else if (currentScreen == 3) { // level control
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->level++;
} else {
outputs[currentOut]->level--;
}
if (outputs[currentOut]->level > 100) {
outputs[currentOut]->level = 100;
}
if (outputs[currentOut]->level < 1) {
outputs[currentOut]->level = 1;
}
outputs[currentOut]->level = modifyParameter(dir, outputs[currentOut]->level, 100, 1, false);
} else if (currentScreen == 4) { // width control
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->width++;
} else {
outputs[currentOut]->width--;
}
if (outputs[currentOut]->width > 100) {
outputs[currentOut]->width = 100;
}
if (outputs[currentOut]->width < 1) {
outputs[currentOut]->width = 1;
}
outputs[currentOut]->width = modifyParameter(dir, outputs[currentOut]->width, 100, 1, false);
outputs[currentOut]->setWidth(outputs[currentOut]->width);
} else if (currentScreen == 5) { // SWING
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->swing++;
} else {
outputs[currentOut]->swing--;
}
if (outputs[currentOut]->swing > 100) {
outputs[currentOut]->swing = 100;
}
if (outputs[currentOut]->swing < 50) {
outputs[currentOut]->swing = 50;
}
outputs[currentOut]->swing = modifyParameter(dir, outputs[currentOut]->swing, 75, 50, false);
} else if (currentScreen == 6) { // PROBABILITY
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->p++;
} else {
outputs[currentOut]->p--;
}
if (outputs[currentOut]->p > 100) {
outputs[currentOut]->p = 100;
}
if (outputs[currentOut]->p < 0) {
outputs[currentOut]->p = 0;
}
outputs[currentOut]->p = modifyParameter(dir, outputs[currentOut]->p, 100, 0, false);
} else if (currentScreen == 7) { // STICKY
outputs[currentOut]->sticky ^= true;
@ -216,15 +160,10 @@ void DisplayHandler::moveCursor(bool dir) {
currentDest = DEST_COUNT - 1;
}
matrix.slots[outputs[currentOut]->slotIdx1].destParam =
(ModDest)currentDest;
matrix.slots[outputs[currentOut]->slotIdx1].destParam = (ModDest)currentDest;
} else if (currentScreen == 11) { // CV1 AMT
if (dir == 1) {
matrix.slots[outputs[currentOut]->slotIdx1].amount++;
} else {
matrix.slots[outputs[currentOut]->slotIdx1].amount--;
}
matrix.slots[outputs[currentOut]->slotIdx1].amount = modifyParameter(dir, matrix.slots[outputs[currentOut]->slotIdx1].amount, 100, 0, false);
} else if (currentScreen == 12) { // CV1 INV
matrix.slots[outputs[currentOut]->slotIdx1].inverted ^= true;
@ -271,92 +210,74 @@ void DisplayHandler::moveCursor(bool dir) {
(ModDest)currentDest;
} else if (currentScreen == 16) { // CV2 AMT
if (dir == 1) {
matrix.slots[outputs[currentOut]->slotIdx2].amount++;
} else {
matrix.slots[outputs[currentOut]->slotIdx2].amount--;
}
matrix.slots[outputs[currentOut]->slotIdx2].amount = modifyParameter(dir, matrix.slots[outputs[currentOut]->slotIdx2].amount, 100, 0, false);
} else if (currentScreen == 17) { // CV2 INV
matrix.slots[outputs[currentOut]->slotIdx2].inverted ^= true;
} else if (currentScreen == 18) { // MUTE
outputs[currentOut]->editing = 1;
outputs[currentOut]->isEnabled ^= true;
}
}
}
} else {
if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen
update_BPM(dir);
} else {
if (dir == 1) {
cursorPosition++;
} else {
cursorPosition--;
void DisplayHandler::moveCursor(bool dir) {
if (onOutScreen) {
handleCursorOnOutputScreen(dir);
}
if (!onSettingsScreen) {
if (cursorPosition > mainMaxCursorPosition) {
cursorPosition = 0;
if (onMainScreen) {
handleCursorOnMainScreen(dir);
}
if (cursorPosition < 0) {
cursorPosition = mainMaxCursorPosition;
if (onSettingsScreen) {
cursorPosition = modifyParameter(dir, cursorPosition, 5, 0);
}
} else if (onSettingsScreen) {
if (cursorPosition > 5) {
cursorPosition = 0;
}
if (cursorPosition < 0) {
cursorPosition = 5;
}
}
}
}
updateScreen = 1;
}
void DisplayHandler::handleClick() {
cursorClick ^= true;
if (onOutScreen && !onSettingsScreen) {
void DisplayHandler::handleClickOnOutputScreen() {
if (currentScreen == 0) { // exit screen
cursorPosition = currentOut + 1;
onMainScreen = true;
onOutScreen = false;
onSettingsScreen = false;
currentOut = -1;
currentScreen = 0;
onOutScreen = 0;
cursorClick = false;
}
// move this out to the other group
if (currentScreen == 1 && outputs[currentOut]->editing == 1) {
outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex);
outputs[currentOut]->editing = 0;
cursorClick = false;
}
} else {
updateScreen = 1;
}
if (currentScreen == 0 && !onSettingsScreen) { // on main screen
if (cursorPosition == 0) { // Change BPM
} else if (cursorPosition > 0 && cursorPosition < 9) { // go to out screen
void DisplayHandler::handleClickOnMainScreen() {
if (cursorPosition > 0 && cursorPosition < 9) { // GO TO OUTPUT
currentOut = cursorPosition - 1;
cursorPosition = 1;
currentScreen = 1;
onOutScreen = 1;
cursorClick = 0;
onOutScreen = true;
onMainScreen = false;
onSettingsScreen = false;;
} else if (cursorPosition == 9) { // PLAY/PAUSE BUTTON
PLAY ^= true;
} else if (cursorPosition == 10) { // GLOBAL SETTINGS
cursorPosition = 0;
onSettingsScreen = 1;
cursorClick = 0;
onSettingsScreen = true;
onMainScreen = false;
onOutScreen = false;
}
} else if (onSettingsScreen) {
cursorClick = 0;
}
void DisplayHandler::handleClickOnSettingsScreen() {
if (cursorPosition == 0) { // SAVE
flashMessage("Saving...");
@ -364,13 +285,6 @@ void DisplayHandler::handleClick() {
outputs[i]->pack(globalSettings.configs[i]);
}
globalSettings.bpm = BPM;
globalSettings.play = PLAY;
globalSettings.run = RUN;
globalSettings.ppqnidx = EXTPPQNIdx;
memcpy(globalSettings.slots, matrix.slots, sizeof(ModSlot) * 16);
save();
exitSettingsScreen();
@ -427,28 +341,40 @@ void DisplayHandler::handleClick() {
} else if (cursorPosition == 5) { // EXIT
exitSettingsScreen();
}
}
}
updateScreen = 1;
cursorClick = 0;
}
void DisplayHandler::exitSettingsScreen() {
onOutScreen = 0;
onSettingsScreen = 0;
onOutScreen = false;
onSettingsScreen = false;
onMainScreen = true;
cursorPosition = 10;
cursorClick = 0;
currentScreen = 0;
}
void DisplayHandler::handleClick() {
cursorClick ^= true;
if (onOutScreen) {
handleClickOnOutputScreen();
} else if (onMainScreen) { // on main screen
handleClickOnMainScreen();
} else if (onSettingsScreen) {
handleClickOnSettingsScreen();
}
updateScreen = 1;
}
void DisplayHandler::render() {
if (updateScreen) {
display->clear();
if (!onSettingsScreen && currentScreen == 0 &&
currentOut == -1) { // main screen
if (onMainScreen) { // main screen
renderMainPage();
} else if (!onSettingsScreen && currentOut != -1) {
} else if (onOutScreen) {
renderOutPage();
} else if (onSettingsScreen) {
renderSettingsPage();

View file

@ -10,6 +10,14 @@
DeviceSettings globalSettings;
void save() {
globalSettings.bpm = BPM;
globalSettings.play = PLAY;
globalSettings.run = RUN;
globalSettings.ppqnidx = EXTPPQNIdx;
memcpy(globalSettings.slots, matrix.slots, sizeof(ModSlot) * 16);
const uint32_t WRITE_SIZE = (sizeof(DeviceSettings) + 255) & ~255;
static uint8_t __attribute__((aligned(4))) write_buf[2048];
@ -30,6 +38,8 @@ void save() {
multicore_lockout_end_blocking();
}
void load_default() {
memset(&globalSettings.slots, 0, sizeof(globalSettings.slots));