Refactor: UIManager trimming

This commit is contained in:
Dejvino 2026-03-06 21:35:21 +01:00
parent b6150cbacd
commit 71583bdb4e
4 changed files with 227 additions and 200 deletions

168
LedMatrix.cpp Normal file
View File

@ -0,0 +1,168 @@
#include "LedMatrix.h"
#include <Arduino.h>
// --- HARDWARE CONFIGURATION ---
#define PIN_NEOPIXEL 6
#define NEOPIXELS_X 16
#define NEOPIXELS_Y 8
#define NUM_PIXELS 64*2 // 8x8 LEDs in 2 panels
LedMatrix::LedMatrix()
: pixels(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800)
{
}
void LedMatrix::begin() {
pixels.setPin(PIN_NEOPIXEL);
pixels.begin();
pixels.setBrightness(40);
pixels.clear();
pixels.show();
}
void LedMatrix::setBrightness(uint8_t b) {
pixels.setBrightness(b);
}
void LedMatrix::clear() {
pixels.clear();
}
void LedMatrix::show() {
pixels.show();
}
uint32_t LedMatrix::getNoteColor(int note, bool dim) {
if (note == -1) return 0;
uint16_t hue = 30000 + (note % 12) * 3628;
return Adafruit_NeoPixel::ColorHSV(hue, 255, dim ? 10 : 50);
}
int LedMatrix::getPixelIndex(int x, int y) {
return NEOPIXELS_Y * x + (NEOPIXELS_Y - 1 - y);
}
void LedMatrix::update(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode,
int selectedTrack, const int* numSteps, const bool* trackMute) {
pixels.clear();
const uint32_t COLOR_PLAYHEAD = pixels.Color(0, 255, 0);
const uint32_t COLOR_PLAYHEAD_DIM = pixels.Color(0, 32, 0);
const uint32_t COLOR_MUTED_PLAYHEAD = pixels.Color(0, 0, 255);
const uint32_t COLOR_CURSOR = pixels.Color(255, 100, 100);
const uint32_t COLOR_CURSOR_DIM = pixels.Color(32, 0, 0);
if(playMode == MODE_POLY) {
for(int t=0; t<NUM_TRACKS; t++) {
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) {
int col = s; // Each step is a column
// --- First row of track pair: Notes ---
int note_row = t * 2;
uint32_t note_color = 0;
if (s < currentTrackSteps) {
int note = sequence[t][s].note;
if (note != -1) {
note_color = getNoteColor(note, !sequence[t][s].accent);
}
}
pixels.setPixelColor(getPixelIndex(col, note_row), note_color);
// --- Second row of track pair: Steps & Playhead ---
int step_row = t * 2 + 1;
uint32_t step_color = 0; // Off by default for steps > currentTrackSteps
if (s < currentTrackSteps) {
step_color = COLOR_PLAYHEAD_DIM; // It's a valid step
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (trackMute[t]) {
step_color = COLOR_MUTED_PLAYHEAD;
} else {
step_color = COLOR_PLAYHEAD;
}
}
}
pixels.setPixelColor(getPixelIndex(col, step_row), step_color);
}
}
} else {
// --- Mono Mode (original) ---
const Step* trackSequence = sequence[selectedTrack];
for (int s = 0; s < NUM_STEPS; s++) {
if (s >= numSteps[selectedTrack]) continue;
int x = s % NUM_STEPS;
int yBase = (s / NUM_STEPS) * 2;
uint32_t color = 0, dimColor = 0;
bool isCursorHere = (isPlaying && s == (playbackStep % numSteps[selectedTrack]));
if (trackSequence[s].note != -1) {
color = getNoteColor(trackSequence[s].note, trackSequence[s].tie);
dimColor = getNoteColor(trackSequence[s].note, true);
}
uint32_t c[4] = {0};
if (trackSequence[s].note != -1) {
int octave = trackSequence[s].note / 12;
if (octave > 4) { c[0] = color; if (trackSequence[s].accent) c[1] = dimColor; }
else if (octave < 4) { c[2] = color; if (trackSequence[s].accent) c[1] = dimColor; }
else { c[1] = color; if (trackSequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } }
}
uint32_t cursorColor = pixels.Color(0, 0, 50);
if (isPlaying) {
cursorColor = pixels.Color(0, 50, 0);
if (songModeEnabled && s >= NUM_STEPS) {
int repeats = min(songRepeatsRemaining, NUM_STEPS);
if (x >= (NUM_STEPS - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
}
}
if (cursorColor != 0) {
if (isCursorHere) {
for(int i=0; i<4; i++) {
if (c[i] == 0) c[i] = cursorColor;
}
} else {
uint8_t r = (uint8_t)(cursorColor >> 16), g = (uint8_t)(cursorColor >> 8), b = (uint8_t)cursorColor;
c[3] = pixels.Color(r/5, g/5, b/5);
}
}
for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]);
}
// --- Overview of all tracks on bottom 4 rows ---
for(int t=0; t<NUM_TRACKS; t++) {
int row = 4 + t;
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) {
if (s >= currentTrackSteps) continue;
uint32_t pixelColor = 0;
// Background for active track
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR_DIM;
}
else if (sequence[t][s].note != -1) // Note
{
pixelColor = getNoteColor(sequence[t][s].note, !sequence[t][s].accent);
}
// Playhead
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR;
} else if (trackMute[t]) {
pixelColor = COLOR_MUTED_PLAYHEAD;
} else {
pixelColor = COLOR_PLAYHEAD;
}
}
pixels.setPixelColor(getPixelIndex(s, row), pixelColor);
}
}
}
if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
pixels.show();
}

28
LedMatrix.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef LED_MATRIX_H
#define LED_MATRIX_H
#include <Adafruit_NeoPixel.h>
#include "TrackerTypes.h"
#include "config.h"
class LedMatrix {
public:
LedMatrix();
void begin();
void update(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode,
int selectedTrack, const int* numSteps, const bool* trackMute);
void setBrightness(uint8_t b);
void clear();
void show();
private:
Adafruit_NeoPixel pixels;
uint32_t getNoteColor(int note, bool dim);
int getPixelIndex(int x, int y);
};
#endif

View File

@ -7,16 +7,11 @@
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define PIN_NEOPIXEL 6
#define NEOPIXELS_X 16
#define NEOPIXELS_Y 8
#define NUM_PIXELS 64*2 // 8x8 LEDs in 2 panels
UIManager ui;
UIManager::UIManager()
: display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET),
pixels(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800)
: display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET)
{
}
@ -31,11 +26,7 @@ void UIManager::begin() {
display.display();
// Setup NeoPixel Matrix
pixels.setPin(PIN_NEOPIXEL);
pixels.begin();
pixels.setBrightness(40);
pixels.clear();
pixels.show();
ledMatrix.begin();
}
void UIManager::showMessage(const char* msg) {
@ -67,61 +58,22 @@ void UIManager::draw(UIState currentState, int menuSelection,
drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, currentTrackNumSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities);
break;
case UI_SETUP_CHANNEL_EDIT:
display.println(F("SET MIDI CHANNEL"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(F("CH: "));
if (midiChannel < 10) display.print(F(" "));
display.print(midiChannel);
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
drawEditScreen("SET MIDI CHANNEL", "CH: ", midiChannel);
break;
case UI_EDIT_TEMPO:
display.println(F("SET TEMPO"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(F("BPM: "));
display.print(tempo);
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
drawEditScreen("SET TEMPO", "BPM: ", tempo);
break;
case UI_EDIT_STEPS:
display.println(F("SET STEPS"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(F("LEN: "));
display.print(currentTrackNumSteps);
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
drawEditScreen("SET STEPS", "LEN: ", currentTrackNumSteps);
break;
case UI_EDIT_FLAVOUR:
display.println(F("SET FLAVOUR"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(currentStrategy->getName());
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
drawEditScreen("SET FLAVOUR", "", currentStrategy->getName());
break;
case UI_EDIT_ROOT:
display.println(F("SET ROOT NOTE"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
{
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
display.print(noteNames[currentRoot % 12]);
drawEditScreen("SET ROOT NOTE", "", noteNames[currentRoot % 12]);
}
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
break;
case UI_EDIT_SCALE_TYPE:
display.println(F("ENABLED SCALES"));
@ -147,15 +99,7 @@ void UIManager::draw(UIState currentState, int menuSelection,
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
break;
case UI_RANDOMIZE_TRACK_EDIT:
display.println(F("SET TRACK"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(F("TRK: "));
display.print(randomizeTrack + 1);
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
drawEditScreen("SET TRACK", "TRK: ", randomizeTrack + 1);
break;
case UI_SCALE_EDIT:
case UI_SCALE_NOTE_EDIT:
@ -265,6 +209,24 @@ void UIManager::drawNumberEditor(const char* title, int value, int minVal, int m
display.println(F(" (Press to confirm)"));
}
void UIManager::drawEditScreen(const char* title, const char* label, const char* valueStr) {
display.println(title);
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
if (label && *label) display.print(label);
display.print(valueStr);
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
}
void UIManager::drawEditScreen(const char* title, const char* label, int value) {
char buf[16];
itoa(value, buf, 10);
drawEditScreen(title, label, buf);
}
void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int numScaleNotes,
const int* scaleNotes, int melodySeed, int currentTrackNumSteps, bool mutationEnabled,
@ -368,139 +330,9 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
}
}
uint32_t UIManager::getNoteColor(int note, bool dim) {
if (note == -1) return 0;
uint16_t hue = 30000 + (note % 12) * 3628;
return Adafruit_NeoPixel::ColorHSV(hue, 255, dim ? 10 : 50);
}
int UIManager::getPixelIndex(int x, int y) {
// [i] Here you can adjust the mapping from "logical" pixel coordinates
// to your physical NeoPixel layout. It depends on how you connected it.
return NEOPIXELS_Y * x + (NEOPIXELS_Y - 1 - y);
}
void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode,
int selectedTrack, const int* numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute) {
pixels.clear();
const uint32_t COLOR_PLAYHEAD = pixels.Color(0, 255, 0);
const uint32_t COLOR_PLAYHEAD_DIM = pixels.Color(0, 32, 0);
const uint32_t COLOR_MUTED_PLAYHEAD = pixels.Color(0, 0, 255);
const uint32_t COLOR_CURSOR = pixels.Color(255, 100, 100);
const uint32_t COLOR_CURSOR_DIM = pixels.Color(32, 0, 0);
if(playMode == MODE_POLY) {
for(int t=0; t<NUM_TRACKS; t++) {
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) {
int col = s; // Each step is a column
// --- First row of track pair: Notes ---
int note_row = t * 2;
uint32_t note_color = 0;
if (s < currentTrackSteps) {
int note = sequence[t][s].note;
if (note != -1) {
note_color = getNoteColor(note, !sequence[t][s].accent);
}
}
pixels.setPixelColor(getPixelIndex(col, note_row), note_color);
// --- Second row of track pair: Steps & Playhead ---
int step_row = t * 2 + 1;
uint32_t step_color = 0; // Off by default for steps > currentTrackSteps
if (s < currentTrackSteps) {
step_color = COLOR_PLAYHEAD_DIM; // It's a valid step
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (trackMute[t]) {
step_color = COLOR_MUTED_PLAYHEAD;
} else {
step_color = COLOR_PLAYHEAD;
}
}
}
pixels.setPixelColor(getPixelIndex(col, step_row), step_color);
}
}
} else {
// --- Mono Mode (original) ---
const Step* trackSequence = sequence[selectedTrack];
for (int s = 0; s < NUM_STEPS; s++) {
if (s >= numSteps[selectedTrack]) continue;
int x = s % NUM_STEPS;
int yBase = (s / NUM_STEPS) * 2;
uint32_t color = 0, dimColor = 0;
bool isCursorHere = (isPlaying && s == (playbackStep % numSteps[selectedTrack]));
if (trackSequence[s].note != -1) {
color = getNoteColor(trackSequence[s].note, trackSequence[s].tie);
dimColor = getNoteColor(trackSequence[s].note, true);
}
uint32_t c[4] = {0};
if (trackSequence[s].note != -1) {
int octave = trackSequence[s].note / 12;
if (octave > 4) { c[0] = color; if (trackSequence[s].accent) c[1] = dimColor; }
else if (octave < 4) { c[2] = color; if (trackSequence[s].accent) c[1] = dimColor; }
else { c[1] = color; if (trackSequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } }
}
uint32_t cursorColor = pixels.Color(0, 0, 50);
if (isPlaying) {
cursorColor = pixels.Color(0, 50, 0);
if (songModeEnabled && s >= NUM_STEPS) {
int repeats = min(songRepeatsRemaining, NUM_STEPS);
if (x >= (NUM_STEPS - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
}
}
if (cursorColor != 0) {
if (isCursorHere) {
for(int i=0; i<4; i++) {
if (c[i] == 0) c[i] = cursorColor;
}
} else {
uint8_t r = (uint8_t)(cursorColor >> 16), g = (uint8_t)(cursorColor >> 8), b = (uint8_t)cursorColor;
c[3] = pixels.Color(r/5, g/5, b/5);
}
}
for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]);
}
// --- Overview of all tracks on bottom 4 rows ---
for(int t=0; t<NUM_TRACKS; t++) {
int row = 4 + t;
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) {
if (s >= currentTrackSteps) continue;
uint32_t pixelColor = 0;
// Background for active track
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR_DIM;
}
else if (sequence[t][s].note != -1) // Note
{
pixelColor = getNoteColor(sequence[t][s].note, !sequence[t][s].accent);
}
// Playhead
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR;
} else if (trackMute[t]) {
pixelColor = COLOR_MUTED_PLAYHEAD;
} else {
pixelColor = COLOR_PLAYHEAD;
}
}
pixels.setPixelColor(getPixelIndex(s, row), pixelColor);
}
}
}
if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
pixels.show();
ledMatrix.update(sequence, playbackStep, isPlaying, currentState, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled, playMode, selectedTrack, numSteps, trackMute);
}

View File

@ -3,9 +3,9 @@
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>
#include "TrackerTypes.h"
#include "MelodyStrategy.h"
#include "LedMatrix.h"
class UIManager {
public:
@ -29,7 +29,7 @@ public:
private:
Adafruit_SSD1306 display;
Adafruit_NeoPixel pixels;
LedMatrix ledMatrix;
void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex,
@ -37,9 +37,8 @@ private:
bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities);
void drawNumberEditor(const char* title, int value, int minVal, int maxVal);
uint32_t getNoteColor(int note, bool dim);
int getPixelIndex(int x, int y);
void drawEditScreen(const char* title, const char* label, const char* valueStr);
void drawEditScreen(const char* title, const char* label, int value);
};
extern UIManager ui;