Accents and ties with saving

This commit is contained in:
Dejvino 2026-02-16 21:04:25 +01:00
parent 8759d7f46a
commit 5a773d31a4

View File

@ -3,6 +3,7 @@
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
// --- HARDWARE CONFIGURATION ---
#define SCREEN_WIDTH 128
@ -30,6 +31,8 @@
struct Step {
int8_t note; // MIDI Note (0-127), -1 for OFF
bool accent;
bool tie;
};
Step sequence[NUM_STEPS];
@ -50,9 +53,9 @@ UIState currentState = UI_TRACKER;
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
const char* randomizeMenu[] = { "Back", "Gen Scale", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
const char* randomizeMenu[] = { "Back", "Scale", "Melody", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*);
const char* setupMenu[] = { "Back", "Channel" };
const char* setupMenu[] = { "Back", "Channel", "Save", "Load" };
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
int menuSelection = 0;
@ -61,7 +64,9 @@ int playbackStep = 0;
int midiChannel = 1;
int scaleNotes[12];
int numScaleNotes = 0;
int melodySeed = 0;
int queuedTheme = -1;
const uint32_t EEPROM_MAGIC = 0x42424244;
bool isEditing = false;
int scrollOffset = 0;
@ -113,6 +118,55 @@ void sortArray(int arr[], int size) {
}
}
void showMessage(const char* msg) {
display.clearDisplay();
display.setCursor(10, 25);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.print(msg);
display.display();
delay(500);
display.setTextSize(1);
}
void saveSequence() {
int addr = 0;
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
EEPROM.put(addr, midiChannel); addr += sizeof(midiChannel);
EEPROM.put(addr, melodySeed); addr += sizeof(melodySeed);
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i=0; i<12; i++) {
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
}
for (int i=0; i<NUM_STEPS; i++) {
EEPROM.put(addr, sequence[i]); addr += sizeof(Step);
}
EEPROM.commit();
showMessage("SAVED!");
}
bool loadSequence() {
int addr = 0;
uint32_t magic;
EEPROM.get(addr, magic); addr += sizeof(magic);
if (magic != EEPROM_MAGIC) return false;
EEPROM.get(addr, midiChannel); addr += sizeof(midiChannel);
EEPROM.get(addr, melodySeed); addr += sizeof(melodySeed);
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i=0; i<12; i++) {
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
}
for (int i=0; i<NUM_STEPS; i++) {
EEPROM.get(addr, sequence[i]); addr += sizeof(Step);
}
return true;
}
void setup() {
Serial.begin(115200);
// Use random ADC noise for seed
@ -143,31 +197,25 @@ void setup() {
pixels.clear();
pixels.show();
// 4. Init Sequence
for(int i=0; i<NUM_STEPS; i++) {
sequence[i].note = -1; // Default to empty
}
// Add a simple C-Major scale for testing
sequence[0].note = 60; // C4
sequence[2].note = 62; // D4
sequence[4].note = 64; // E4
sequence[6].note = 65; // F4
sequence[8].note = 65; // F4
sequence[9].note = 62; // D4
sequence[12].note = 64; // E4
// Init randomizer
randomSeed(micros());
generateRandomScale();
display.clearDisplay();
display.display();
// 5. Setup MIDI Serial
// 4. Setup MIDI Serial
Serial1.setTX(PIN_MIDI_TX);
Serial1.begin(31250);
Serial.println(F("MIDI Serial initialized on GP0/GP1"));
// 5. Init Sequence
randomSeed(micros());
generateRandomScale();
melodySeed = random(10000);
EEPROM.begin(512);
if (!loadSequence()) {
generateTheme(1);
}
isPlaying = false; // Don't start playing on boot
display.clearDisplay();
display.display();
Serial.println(F("Started."));
}
@ -195,11 +243,14 @@ void generateRandomScale() {
void generateTheme(int themeType) {
sendMidi(0xB0, 123, 0); // Panic / All Notes Off
randomSeed(themeType * 12345); // Deterministic seed for this theme
randomSeed(melodySeed + themeType * 12345); // Deterministic seed for this theme
if (numScaleNotes == 0) generateRandomScale();
for (int i = 0; i < NUM_STEPS; i++) {
sequence[i].note = (random(100) < 50) ? (12 * 4 + scaleNotes[random(numScaleNotes)]) : -1;
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);
}
randomSeed(micros()); // Restore randomness
isPlaying = true;
@ -293,18 +344,27 @@ void handleInput() {
break;
case UI_MENU_RANDOMIZE:
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; }
if (menuSelection == 1) generateRandomScale();
if (menuSelection >= 2) {
if (menuSelection == 1) generateRandomScale(); // Scale
if (menuSelection == 2) melodySeed = random(10000); // Melody
if (menuSelection >= 3) { // Themes
if (isPlaying) {
queuedTheme = menuSelection - 1;
queuedTheme = menuSelection - 2;
} else {
generateTheme(menuSelection - 1);
generateTheme(menuSelection - 2);
}
}
break;
case UI_MENU_SETUP:
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; }
if (menuSelection == 1) currentState = UI_SETUP_CHANNEL_EDIT;
if (menuSelection == 2) {
saveSequence();
currentState = UI_MENU_MAIN;
}
if (menuSelection == 3) {
if (loadSequence()) showMessage("LOADED!");
currentState = UI_MENU_MAIN;
}
break;
case UI_SETUP_CHANNEL_EDIT:
currentState = UI_MENU_SETUP;
@ -342,9 +402,16 @@ void handlePlayback() {
if (millis() - lastStepTime > interval) {
lastStepTime = millis();
// Note Off for previous step
if (sequence[playbackStep].note != -1) {
sendMidi(0x80, sequence[playbackStep].note, 0);
// Determine if we are tying to the next note
int nextStep = playbackStep + 1;
if (nextStep >= NUM_STEPS) nextStep = 0;
bool isTied = sequence[playbackStep].tie && (sequence[nextStep].note != -1);
int prevNote = sequence[playbackStep].note;
// Note Off for previous step (if NOT tied)
if (!isTied && prevNote != -1) {
sendMidi(0x80, prevNote, 0);
}
playbackStep++;
@ -358,7 +425,13 @@ void handlePlayback() {
// Note On for new step
if (sequence[playbackStep].note != -1) {
sendMidi(0x90, sequence[playbackStep].note, 100);
uint8_t velocity = sequence[playbackStep].accent ? 127 : 100;
sendMidi(0x90, sequence[playbackStep].note, velocity);
}
// Note Off for previous step (if tied - delayed Note Off)
if (isTied && prevNote != -1) {
sendMidi(0x80, prevNote, 0);
}
// Auto-scroll navigation cursor if not editing
@ -374,8 +447,16 @@ void drawMenu(const char* title, const char* items[], int count, int selection)
display.println(title);
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
// Simple scrolling: keep selection visible
int start = 0;
if (selection >= 5) {
start = selection - 4;
}
int y = 10;
for (int i = 0; i < count; i++) {
for (int i = start; i < count; i++) {
if (y > 55) break; // Stop if we run out of screen space
if (i == selection) {
display.fillRect(0, y, 128, 8, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
@ -392,29 +473,29 @@ void drawMenu(const char* title, const char* items[], int count, int selection)
}
// Special case for queued theme
if (currentState == UI_MENU_RANDOMIZE && i >= 2 && queuedTheme == (i - 1)) {
if (currentState == UI_MENU_RANDOMIZE && i >= 3 && queuedTheme == (i - 2)) {
display.print(F(" [NEXT]"));
}
y += 9;
// Special case for scale display
if (currentState == UI_MENU_RANDOMIZE && i == 1) { // After "Gen Scale"
display.setTextColor(SSD1306_WHITE); // Ensure it's not highlighted
display.setCursor(2, y);
// Special cases for Randomize Menu values
if (currentState == UI_MENU_RANDOMIZE) {
if (i == 1) { // Scale
display.print(F(": "));
if (numScaleNotes > 0) {
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
for (int j = 0; j < numScaleNotes; j++) {
for (int j = 0; j < min(numScaleNotes, 6); j++) {
display.print(noteNames[scaleNotes[j]]);
if (j < numScaleNotes - 1) display.print(F(" "));
if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
}
}
} else if (i == 2) { // Melody
display.print(F(": "));
display.print(melodySeed);
}
} else {
display.print(F("[No scale generated]"));
}
y += 9;
}
}
}
void drawTracker() {
display.clearDisplay();
@ -533,33 +614,45 @@ void updateLeds() {
pixels.clear(); // Clear buffer
for (int s = 0; s < NUM_STEPS; s++) {
uint32_t color = 0; // Default off
int stepNavIndex = navigationSelection - 1;
if (sequence[s].note != -1) {
color = getNoteColor(sequence[s].note);
}
if (currentState == UI_TRACKER && s == stepNavIndex && !isEditing) {
color = pixels.Color(40, 40, 40); // White for navigation
}
if (isPlaying && s == playbackStep) {
color = pixels.Color(0, 50, 0); // Green for playback (overwrites nav cursor)
}
if (currentState == UI_TRACKER && s == stepNavIndex && isEditing) {
color = pixels.Color(50, 0, 0); // Red for editing (highest precedence)
}
if (color != 0) {
int blockX = (s % 4) * 2;
int blockY = (s / 4) * 2;
pixels.setPixelColor(getPixelIndex(blockX, blockY), color);
pixels.setPixelColor(getPixelIndex(blockX + 1, blockY), color);
pixels.setPixelColor(getPixelIndex(blockX, blockY + 1), color);
pixels.setPixelColor(getPixelIndex(blockX + 1, blockY + 1), color);
// --- Top Row: Attributes ---
uint32_t colorTL = 0; // Octave
uint32_t colorTR = 0; // Accent/Tie
if (sequence[s].note != -1) {
int octave = sequence[s].note / 12;
// Base octave 4 (MIDI 48-59).
if (octave > 4) colorTL = pixels.Color(0, 50, 0); // Up (Green)
else if (octave < 4) colorTL = pixels.Color(50, 0, 0); // Down (Red)
if (sequence[s].accent && sequence[s].tie) colorTR = pixels.Color(50, 0, 50); // Both (Purple)
else if (sequence[s].accent) colorTR = pixels.Color(50, 50, 50); // Accent (White)
else if (sequence[s].tie) colorTR = pixels.Color(50, 50, 0); // Tie (Yellow)
}
// --- Bottom Row: Note / Cursor ---
uint32_t colorBL = 0;
uint32_t colorBR = 0;
if (sequence[s].note != -1) {
uint32_t noteColor = getNoteColor(sequence[s].note);
colorBL = noteColor;
colorBR = noteColor;
}
int stepNavIndex = navigationSelection - 1;
if (isPlaying && s == playbackStep) {
colorBL = colorBR = pixels.Color(0, 50, 0); // Green for playback
} else if (currentState == UI_TRACKER && s == stepNavIndex) {
colorBL = colorBR = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
}
pixels.setPixelColor(getPixelIndex(blockX, blockY), colorTL);
pixels.setPixelColor(getPixelIndex(blockX + 1, blockY), colorTR);
pixels.setPixelColor(getPixelIndex(blockX, blockY + 1), colorBL);
pixels.setPixelColor(getPixelIndex(blockX + 1, blockY + 1), colorBR);
}
pixels.show();