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