tracker QOL improvements

This commit is contained in:
Dejvino 2026-02-16 21:14:16 +01:00
parent 5a773d31a4
commit 98fc15cef2

View File

@ -4,6 +4,7 @@
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include <pico/mutex.h>
// --- HARDWARE CONFIGURATION ---
#define SCREEN_WIDTH 128
@ -36,6 +37,11 @@ struct Step {
};
Step sequence[NUM_STEPS];
Step nextSequence[NUM_STEPS];
volatile bool nextSequenceReady = false;
volatile bool needsPanic = false;
mutex_t midiMutex;
// --- STATE ---
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
@ -59,20 +65,20 @@ const char* setupMenu[] = { "Back", "Channel", "Save", "Load" };
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
int menuSelection = 0;
int navigationSelection = 1;
int playbackStep = 0;
int midiChannel = 1;
volatile int navigationSelection = 1;
volatile int playbackStep = 0;
volatile int midiChannel = 1;
int scaleNotes[12];
int numScaleNotes = 0;
int melodySeed = 0;
int queuedTheme = -1;
volatile int queuedTheme = -1;
const uint32_t EEPROM_MAGIC = 0x42424244;
bool isEditing = false;
int scrollOffset = 0;
bool isPlaying = false;
volatile bool isEditing = false;
volatile int scrollOffset = 0;
volatile bool isPlaying = false;
unsigned long lastStepTime = 0;
int tempo = 120; // BPM
volatile int tempo = 120; // BPM
// Encoder State
@ -140,9 +146,11 @@ void saveSequence() {
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
}
mutex_enter_blocking(&midiMutex);
for (int i=0; i<NUM_STEPS; i++) {
EEPROM.put(addr, sequence[i]); addr += sizeof(Step);
}
mutex_exit(&midiMutex);
EEPROM.commit();
showMessage("SAVED!");
}
@ -161,14 +169,18 @@ bool loadSequence() {
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
}
mutex_enter_blocking(&midiMutex);
for (int i=0; i<NUM_STEPS; i++) {
EEPROM.get(addr, sequence[i]); addr += sizeof(Step);
}
mutex_exit(&midiMutex);
return true;
}
void setup() {
Serial.begin(115200);
mutex_init(&midiMutex);
// Use random ADC noise for seed
delay(5000);
Serial.println(F("Starting."));
@ -241,18 +253,24 @@ void generateRandomScale() {
sortArray(scaleNotes, numScaleNotes);
}
void generateTheme(int themeType) {
sendMidi(0xB0, 123, 0); // Panic / All Notes Off
void generateSequenceData(int themeType, Step* target) {
randomSeed(melodySeed + themeType * 12345); // Deterministic seed for this theme
if (numScaleNotes == 0) generateRandomScale();
for (int i = 0; i < NUM_STEPS; i++) {
int octave = random(3) + 3; // 3, 4, 5 (Base is 4)
sequence[i].note = (random(100) < 50) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1;
sequence[i].accent = (random(100) < 30);
sequence[i].tie = (random(100) < 20);
target[i].note = (random(100) < 50) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1;
target[i].accent = (random(100) < 30);
target[i].tie = (random(100) < 20);
}
randomSeed(micros()); // Restore randomness
}
void generateTheme(int themeType) {
needsPanic = true;
mutex_enter_blocking(&midiMutex);
generateSequenceData(themeType, sequence);
mutex_exit(&midiMutex);
isPlaying = true;
}
@ -273,7 +291,9 @@ void handleInput() {
int newNote = sequence[stepIndex].note + delta;
if (newNote < -1) newNote = -1;
if (newNote > 127) newNote = 127;
mutex_enter_blocking(&midiMutex);
sequence[stepIndex].note = newNote;
mutex_exit(&midiMutex);
} else {
// Move Cursor
navigationSelection += (delta > 0 ? 1 : -1);
@ -349,6 +369,8 @@ void handleInput() {
if (menuSelection >= 3) { // Themes
if (isPlaying) {
queuedTheme = menuSelection - 2;
generateSequenceData(queuedTheme, nextSequence);
nextSequenceReady = true;
} else {
generateTheme(menuSelection - 2);
}
@ -386,7 +408,7 @@ void handleInput() {
lastStepTime = millis(); // Reset timer to start immediately
} else {
// Send All Notes Off on stop (CC 123)
sendMidi(0xB0, 123, 0);
needsPanic = true;
queuedTheme = -1;
}
}
@ -402,6 +424,8 @@ void handlePlayback() {
if (millis() - lastStepTime > interval) {
lastStepTime = millis();
mutex_enter_blocking(&midiMutex);
// Determine if we are tying to the next note
int nextStep = playbackStep + 1;
if (nextStep >= NUM_STEPS) nextStep = 0;
@ -417,8 +441,10 @@ void handlePlayback() {
playbackStep++;
if (playbackStep >= NUM_STEPS) {
playbackStep = 0;
if (queuedTheme != -1) {
generateTheme(queuedTheme);
if (nextSequenceReady) {
sendMidi(0xB0, 123, 0); // Panic / All Notes Off
memcpy(sequence, nextSequence, sizeof(sequence));
nextSequenceReady = false;
queuedTheme = -1;
}
}
@ -434,12 +460,7 @@ void handlePlayback() {
sendMidi(0x80, prevNote, 0);
}
// Auto-scroll navigation cursor if not editing
if (!isEditing) {
navigationSelection = playbackStep + 1; // +1 because 0 is menu
if (navigationSelection < scrollOffset) scrollOffset = navigationSelection;
if (navigationSelection >= scrollOffset + 6) scrollOffset = navigationSelection - 5;
}
mutex_exit(&midiMutex);
}
}
@ -518,6 +539,7 @@ void drawTracker() {
// Steps
int y = 10;
mutex_enter_blocking(&midiMutex);
for (int i = 0; i < 6; i++) {
int itemIndex = i + scrollOffset;
if (itemIndex > NUM_STEPS) break;
@ -535,9 +557,22 @@ void drawTracker() {
display.print(F(">> MENU"));
} else {
int stepIndex = itemIndex - 1;
bool isPlayback = isPlaying && (stepIndex == playbackStep);
if (isPlayback) {
if (itemIndex == navigationSelection) display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
else display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
}
// Step Number
if (stepIndex < 10) display.print(F("0"));
display.print(stepIndex);
if (isPlayback) {
if (itemIndex == navigationSelection) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
}
display.print(F(" | "));
// Note Value
@ -554,6 +589,7 @@ void drawTracker() {
y += 9;
}
mutex_exit(&midiMutex);
}
void drawUI() {
@ -613,6 +649,7 @@ uint32_t getNoteColor(int note) {
void updateLeds() {
pixels.clear(); // Clear buffer
mutex_enter_blocking(&midiMutex);
for (int s = 0; s < NUM_STEPS; s++) {
int blockX = (s % 4) * 2;
int blockY = (s / 4) * 2;
@ -654,13 +691,21 @@ void updateLeds() {
pixels.setPixelColor(getPixelIndex(blockX, blockY + 1), colorBL);
pixels.setPixelColor(getPixelIndex(blockX + 1, blockY + 1), colorBR);
}
mutex_exit(&midiMutex);
pixels.show();
}
void loop1() {
if (needsPanic) {
sendMidi(0xB0, 123, 0);
needsPanic = false;
}
handlePlayback();
}
void loop() {
handleInput();
handlePlayback();
drawUI();
updateLeds();
delay(10); // Small delay to prevent screen tearing/excessive refresh