Poly mode

This commit is contained in:
Dejvino 2026-02-18 14:50:03 +01:00
parent 4180bb6a12
commit 5ea0070283
9 changed files with 496 additions and 219 deletions

View File

@ -6,7 +6,7 @@
class ArpStrategy : public MelodyStrategy { class ArpStrategy : public MelodyStrategy {
public: public:
void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -86,7 +86,7 @@ public:
// 3. Fill Sequence // 3. Fill Sequence
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
sequence[i] = arpPattern[i % arpLength]; sequence[track][i] = arpPattern[i % arpLength];
} }
randomSeed(micros()); randomSeed(micros());
} }
@ -106,14 +106,14 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override {
// Swap two notes // Swap two notes
int s1 = random(numSteps); int s1 = random(numSteps);
int s2 = random(numSteps); int s2 = random(numSteps);
if (sequence[s1].note != -1 && sequence[s2].note != -1) { if (sequence[track][s1].note != -1 && sequence[track][s2].note != -1) {
int8_t temp = sequence[s1].note; int8_t temp = sequence[track][s1].note;
sequence[s1].note = sequence[s2].note; sequence[track][s1].note = sequence[track][s2].note;
sequence[s2].note = temp; sequence[track][s2].note = temp;
} }
} }

View File

@ -6,7 +6,7 @@
class EuclideanStrategy : public MelodyStrategy { class EuclideanStrategy : public MelodyStrategy {
public: public:
void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -34,13 +34,13 @@ public:
if (pattern[i]) { if (pattern[i]) {
int octave = random(3) + 3; // 3, 4, 5 int octave = random(3) + 3; // 3, 4, 5
sequence[stepIndex].note = 12 * octave + scaleNotes[random(numScaleNotes)]; sequence[track][stepIndex].note = 12 * octave + scaleNotes[random(numScaleNotes)];
sequence[stepIndex].accent = (random(100) < 30); sequence[track][stepIndex].accent = (random(100) < 30);
sequence[stepIndex].tie = (random(100) < 10); sequence[track][stepIndex].tie = (random(100) < 10);
} else { } else {
sequence[stepIndex].note = -1; sequence[track][stepIndex].note = -1;
sequence[stepIndex].accent = false; sequence[track][stepIndex].accent = false;
sequence[stepIndex].tie = false; sequence[track][stepIndex].tie = false;
} }
} }
randomSeed(micros()); randomSeed(micros());
@ -61,20 +61,20 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override {
// Rotate sequence // Rotate sequence
if (random(2) == 0) { if (random(2) == 0) {
Step last = sequence[numSteps - 1]; Step last = sequence[track][numSteps - 1];
for (int i = numSteps - 1; i > 0; i--) { for (int i = numSteps - 1; i > 0; i--) {
sequence[i] = sequence[i - 1]; sequence[track][i] = sequence[track][i - 1];
} }
sequence[0] = last; sequence[track][0] = last;
} else { } else {
// Randomize a note // Randomize a note
int s = random(numSteps); int s = random(numSteps);
if (sequence[s].note != -1) { if (sequence[track][s].note != -1) {
int octave = random(3) + 3; int octave = random(3) + 3;
sequence[s].note = 12 * octave + scaleNotes[random(numScaleNotes)]; sequence[track][s].note = 12 * octave + scaleNotes[random(numScaleNotes)];
} }
} }
} }

View File

@ -6,15 +6,15 @@
class LuckyStrategy : public MelodyStrategy { class LuckyStrategy : public MelodyStrategy {
public: public:
void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; 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; sequence[track][i].note = (random(100) < 50) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1;
sequence[i].accent = (random(100) < 30); sequence[track][i].accent = (random(100) < 30);
sequence[i].tie = (random(100) < 20); sequence[track][i].tie = (random(100) < 20);
} }
randomSeed(micros()); randomSeed(micros());
} }
@ -34,17 +34,17 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override {
// Mutate 1 or 2 steps // Mutate 1 or 2 steps
int count = random(1, 3); int count = random(1, 3);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int s = random(numSteps); int s = random(numSteps);
if (sequence[s].note != -1) { if (sequence[track][s].note != -1) {
int r = random(100); int r = random(100);
if (r < 30) sequence[s].accent = !sequence[s].accent; if (r < 30) sequence[track][s].accent = !sequence[track][s].accent;
else if (r < 60) sequence[s].tie = !sequence[s].tie; else if (r < 60) sequence[track][s].tie = !sequence[track][s].tie;
else if (r < 80) sequence[s].note += 12; // Up octave else if (r < 80) sequence[track][s].note += 12; // Up octave
else sequence[s].note -= 12; // Down octave else sequence[track][s].note -= 12; // Down octave
} }
} }
} }

View File

@ -1,13 +1,14 @@
#ifndef MELODY_STRATEGY_H #ifndef MELODY_STRATEGY_H
#define MELODY_STRATEGY_H #define MELODY_STRATEGY_H
#include "config.h"
#include "TrackerTypes.h" #include "TrackerTypes.h"
class MelodyStrategy { class MelodyStrategy {
public: public:
virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0; virtual void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0;
virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0; virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0;
virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0; virtual void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) = 0;
virtual const char* getName() = 0; virtual const char* getName() = 0;
virtual ~MelodyStrategy() {} virtual ~MelodyStrategy() {}
}; };

View File

@ -8,26 +8,10 @@
#include "EuclideanStrategy.h" #include "EuclideanStrategy.h"
#include "MidiDriver.h" #include "MidiDriver.h"
#include "UIManager.h" #include "UIManager.h"
#include "config.h"
// --- HARDWARE CONFIGURATION --- Step sequence[NUM_TRACKS][NUM_STEPS];
#define SCREEN_WIDTH 128 Step nextSequence[NUM_TRACKS][NUM_STEPS];
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
// Pin Definitions for Raspberry Pi Pico (RP2040)
#define PIN_SDA 4
#define PIN_SCL 5
#define ENC_CLK 12
#define ENC_DT 13
#define ENC_SW 14
// --- TRACKER DATA ---
#define NUM_STEPS 16
Step sequence[NUM_STEPS];
Step nextSequence[NUM_STEPS];
volatile bool sequenceChangeScheduled = false; volatile bool sequenceChangeScheduled = false;
volatile bool needsPanic = false; volatile bool needsPanic = false;
@ -35,34 +19,43 @@ UIState currentState = UI_MENU_MAIN;
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" }; const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*); const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
const char* randomizeMenu[] = { "Back", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
const int THEME_1_INDEX = 7; const char* randomizeMenuMono[] = { "Back", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*); const int randomizeMenuMonoCount = sizeof(randomizeMenuMono) / sizeof(char*);
const char* setupMenu[] = { "Back", "Channel", "Factory Reset" }; const int THEME_1_INDEX_MONO = 7;
const char* randomizeMenuPoly[] = { "Back", "Track", "Mute", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
const int randomizeMenuPolyCount = sizeof(randomizeMenuPoly) / sizeof(char*);
const int THEME_1_INDEX_POLY = 9;
const char* setupMenu[] = { "Back", "Play Mode", "Channel", "Factory Reset" };
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*); const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
int menuSelection = 0; int menuSelection = 0;
volatile int navigationSelection = 1; volatile int navigationSelection = 1;
volatile int currentTrack = 0;
volatile bool trackMute[NUM_TRACKS];
int randomizeTrack = 0;
volatile int playbackStep = 0; volatile int playbackStep = 0;
int midiChannel = 1; volatile int midiChannels[NUM_TRACKS];
volatile int shMidiChannel = midiChannel;
int scaleNotes[12]; int scaleNotes[12];
int numScaleNotes = 0; int numScaleNotes = 0;
int melodySeed = 0; int melodySeeds[NUM_TRACKS];
volatile int queuedTheme = -1; volatile int queuedTheme = -1;
volatile int currentThemeIndex = 1; volatile int currentThemeIndex = 1;
const uint32_t EEPROM_MAGIC = 0x42424247; const uint32_t EEPROM_MAGIC = 0x4242424A;
MelodyStrategy* strategies[] = { new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy() }; MelodyStrategy* strategies[] = { new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy() };
const int numStrategies = 3; const int numStrategies = 3;
int currentStrategyIndex = 0; int currentStrategyIndices[NUM_TRACKS];
volatile PlayMode playMode = MODE_MONO;
volatile bool mutationEnabled = false; volatile bool mutationEnabled = false;
volatile bool songModeEnabled = false; volatile bool songModeEnabled = false;
volatile int songRepeatsRemaining = 0; volatile int songRepeatsRemaining = 0;
volatile int nextSongRepeats = 0; volatile int nextSongRepeats = 0;
volatile bool songModeNeedsNext = false; volatile bool songModeNeedsNext = false;
volatile bool isEditing = false; volatile EditMode editMode = NAV_STEP;
volatile int scrollOffset = 0; volatile int scrollOffset = 0;
volatile bool isPlaying = false; volatile bool isPlaying = false;
volatile int tempo = 120; // BPM volatile int tempo = 120; // BPM
@ -70,6 +63,11 @@ volatile unsigned long lastClockTime = 0;
volatile int clockCount = 0; volatile int clockCount = 0;
// Watchdog
volatile unsigned long lastLoop0Time = 0;
volatile unsigned long lastLoop1Time = 0;
volatile bool watchdogActive = false;
// Encoder State // Encoder State
volatile int encoderDelta = 0; volatile int encoderDelta = 0;
static uint8_t prevNextCode = 0; static uint8_t prevNextCode = 0;
@ -104,10 +102,16 @@ void readEncoder() {
void saveSequence(bool quiet = false) { void saveSequence(bool quiet = false) {
int addr = 0; int addr = 0;
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC); EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
EEPROM.put(addr, midiChannel); addr += sizeof(midiChannel); int channels[NUM_TRACKS];
EEPROM.put(addr, melodySeed); addr += sizeof(melodySeed); for(int i=0; i<NUM_TRACKS; i++) channels[i] = midiChannels[i];
EEPROM.put(addr, currentStrategyIndex); addr += sizeof(currentStrategyIndex); EEPROM.put(addr, channels); addr += sizeof(channels);
EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds);
EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
bool mutes[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes);
EEPROM.put(addr, (int)tempo); addr += sizeof(int); EEPROM.put(addr, (int)tempo); addr += sizeof(int);
EEPROM.put(addr, (int)playMode); addr += sizeof(int);
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i=0; i<12; i++) { for (int i=0; i<12; i++) {
@ -115,9 +119,7 @@ void saveSequence(bool quiet = false) {
} }
midi.lock(); midi.lock();
for (int i=0; i<NUM_STEPS; i++) { EEPROM.put(addr, sequence); addr += sizeof(sequence);
EEPROM.put(addr, sequence[i]); addr += sizeof(Step);
}
midi.unlock(); midi.unlock();
EEPROM.commit(); EEPROM.commit();
if (!quiet) ui.showMessage("SAVED!"); if (!quiet) ui.showMessage("SAVED!");
@ -129,13 +131,19 @@ bool loadSequence() {
EEPROM.get(addr, magic); addr += sizeof(magic); EEPROM.get(addr, magic); addr += sizeof(magic);
if (magic != EEPROM_MAGIC) return false; if (magic != EEPROM_MAGIC) return false;
EEPROM.get(addr, midiChannel); addr += sizeof(midiChannel); int channels[NUM_TRACKS];
shMidiChannel = midiChannel; EEPROM.get(addr, channels); addr += sizeof(channels);
EEPROM.get(addr, melodySeed); addr += sizeof(melodySeed); for(int i=0; i<NUM_TRACKS; i++) midiChannels[i] = channels[i];
EEPROM.get(addr, currentStrategyIndex); addr += sizeof(currentStrategyIndex); EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes);
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
int t; int t;
EEPROM.get(addr, t); addr += sizeof(int); EEPROM.get(addr, t); addr += sizeof(int);
tempo = t; tempo = t;
EEPROM.get(addr, t); addr += sizeof(int);
playMode = (PlayMode)t;
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i=0; i<12; i++) { for (int i=0; i<12; i++) {
@ -143,9 +151,7 @@ bool loadSequence() {
} }
midi.lock(); midi.lock();
for (int i=0; i<NUM_STEPS; i++) { EEPROM.get(addr, sequence); addr += sizeof(sequence);
EEPROM.get(addr, sequence[i]); addr += sizeof(Step);
}
midi.unlock(); midi.unlock();
return true; return true;
} }
@ -179,25 +185,42 @@ void setup() {
// 5. Init Sequence // 5. Init Sequence
randomSeed(micros()); randomSeed(micros());
generateRandomScale();
melodySeed = random(10000);
EEPROM.begin(512); EEPROM.begin(512);
if (!loadSequence()) { if (!loadSequence()) {
generateRandomScale();
for(int i=0; i<NUM_TRACKS; i++) {
midiChannels[i] = i + 1;
melodySeeds[i] = random(10000);
currentStrategyIndices[i] = 0;
trackMute[i] = false;
}
generateTheme(1); generateTheme(1);
} }
isPlaying = false; // Don't start playing on boot isPlaying = false; // Don't start playing on boot
Serial.println(F("Started.")); Serial.println(F("Started."));
// Enable Watchdog
lastLoop0Time = millis();
lastLoop1Time = millis();
watchdogActive = true;
}
void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
randomSeed(melodySeeds[track] + themeType * 12345);
strategies[currentStrategyIndices[track]]->generate(target, track, NUM_STEPS, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345);
} }
void generateRandomScale() { void generateRandomScale() {
strategies[currentStrategyIndex]->generateScale(scaleNotes, numScaleNotes); // All tracks share the same scale for now
strategies[currentStrategyIndices[0]]->generateScale(scaleNotes, numScaleNotes);
} }
void generateSequenceData(int themeType, Step* target) { void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
randomSeed(melodySeed + themeType * 12345); // Deterministic seed for this theme for(int i=0; i<NUM_TRACKS; i++) {
strategies[currentStrategyIndex]->generate(target, NUM_STEPS, scaleNotes, numScaleNotes, melodySeed + themeType * 12345); generateTrackData(i, themeType, target);
}
} }
void generateTheme(int themeType) { void generateTheme(int themeType) {
@ -214,8 +237,12 @@ void generateTheme(int themeType) {
isPlaying = true; isPlaying = true;
} }
void mutateSequence(Step* target) { void mutateSequence(Step (*target)[NUM_STEPS]) {
strategies[currentStrategyIndex]->mutate(target, NUM_STEPS, scaleNotes, numScaleNotes); if (playMode == MODE_POLY) {
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, NUM_STEPS, scaleNotes, numScaleNotes);
} else {
strategies[currentStrategyIndices[0]]->mutate(target, 0, NUM_STEPS, scaleNotes, numScaleNotes);
}
} }
void handleInput() { void handleInput() {
@ -229,14 +256,20 @@ void handleInput() {
if (delta != 0) { if (delta != 0) {
switch(currentState) { switch(currentState) {
case UI_TRACKER: case UI_TRACKER:
if (isEditing && navigationSelection > 0) { if (editMode == EDIT_NOTE && navigationSelection > 0) {
// Change Note // Change Note
int stepIndex = navigationSelection - 1; int stepIndex = navigationSelection - 1;
int newNote = sequence[stepIndex].note + delta; int newNote = sequence[currentTrack][stepIndex].note + delta;
if (newNote < -1) newNote = -1; if (newNote < -1) newNote = -1;
if (newNote > 127) newNote = 127; if (newNote > 127) newNote = 127;
midi.lock(); midi.lock();
sequence[stepIndex].note = newNote; sequence[currentTrack][stepIndex].note = newNote;
midi.unlock();
} else if (editMode == NAV_TRACK) {
midi.lock();
currentTrack += (delta > 0 ? 1 : -1);
if(currentTrack < 0) currentTrack = NUM_TRACKS - 1;
if(currentTrack >= NUM_TRACKS) currentTrack = 0;
midi.unlock(); midi.unlock();
} else { } else {
// Move Cursor // Move Cursor
@ -255,9 +288,12 @@ void handleInput() {
if (menuSelection >= mainMenuCount) menuSelection = 0; if (menuSelection >= mainMenuCount) menuSelection = 0;
break; break;
case UI_MENU_RANDOMIZE: case UI_MENU_RANDOMIZE:
{
menuSelection += (delta > 0 ? 1 : -1); menuSelection += (delta > 0 ? 1 : -1);
if (menuSelection < 0) menuSelection = randomizeMenuCount - 1; int count = (playMode == MODE_POLY) ? randomizeMenuPolyCount : randomizeMenuMonoCount;
if (menuSelection >= randomizeMenuCount) menuSelection = 0; if (menuSelection < 0) menuSelection = count - 1;
if (menuSelection >= count) menuSelection = 0;
}
break; break;
case UI_MENU_SETUP: case UI_MENU_SETUP:
menuSelection += (delta > 0 ? 1 : -1); menuSelection += (delta > 0 ? 1 : -1);
@ -265,10 +301,12 @@ void handleInput() {
if (menuSelection >= setupMenuCount) menuSelection = 0; if (menuSelection >= setupMenuCount) menuSelection = 0;
break; break;
case UI_SETUP_CHANNEL_EDIT: case UI_SETUP_CHANNEL_EDIT:
midiChannel += (delta > 0 ? 1 : -1); {
if (midiChannel < 1) midiChannel = 16; int trackToEdit = (playMode == MODE_POLY) ? currentTrack : 0;
if (midiChannel > 16) midiChannel = 1; midiChannels[trackToEdit] += (delta > 0 ? 1 : -1);
shMidiChannel = midiChannel; if (midiChannels[trackToEdit] < 1) midiChannels[trackToEdit] = 16;
if (midiChannels[trackToEdit] > 16) midiChannels[trackToEdit] = 1;
}
break; break;
case UI_EDIT_TEMPO: case UI_EDIT_TEMPO:
tempo += delta; tempo += delta;
@ -276,11 +314,25 @@ void handleInput() {
if (tempo > 240) tempo = 240; if (tempo > 240) tempo = 240;
break; break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
currentStrategyIndex += (delta > 0 ? 1 : -1); {
if (currentStrategyIndex < 0) currentStrategyIndex = numStrategies - 1; int trackToEdit = playMode == MODE_POLY ? randomizeTrack : 0;
if (currentStrategyIndex >= numStrategies) currentStrategyIndex = 0; currentStrategyIndices[trackToEdit] += (delta > 0 ? 1 : -1);
if (currentStrategyIndices[trackToEdit] < 0) currentStrategyIndices[trackToEdit] = numStrategies - 1;
if (currentStrategyIndices[trackToEdit] >= numStrategies) currentStrategyIndices[trackToEdit] = 0;
}
break;
case UI_SETUP_PLAYMODE_EDIT:
playMode = (playMode == MODE_MONO) ? MODE_POLY : MODE_MONO;
// Reset edit mode when switching
editMode = NAV_STEP;
break; break;
} }
if (currentState == UI_RANDOMIZE_TRACK_EDIT) {
randomizeTrack += (delta > 0 ? 1 : -1);
if (randomizeTrack < 0) randomizeTrack = NUM_TRACKS - 1;
if (randomizeTrack >= NUM_TRACKS) randomizeTrack = 0;
}
} }
// Handle Button // Handle Button
@ -309,49 +361,72 @@ void handleInput() {
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
menuSelection = 0; menuSelection = 0;
} else { // A step is selected } else { // A step is selected
isEditing = !isEditing; editMode = (EditMode)((editMode + 1) % 3);
// In mono mode, skip track navigation
if(playMode == MODE_MONO && editMode == NAV_TRACK) {
editMode = EDIT_NOTE;
}
if(editMode == NAV_STEP) { // Cycled back to start
// No action needed
}
} }
break; break;
case UI_MENU_MAIN: case UI_MENU_MAIN:
if (menuSelection == 0) currentState = UI_TRACKER; if (menuSelection == 0) { currentState = UI_TRACKER; break; }
if (menuSelection == 1) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; } if (menuSelection == 1) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; break; }
if (menuSelection == 2) { currentState = UI_MENU_SETUP; menuSelection = 0; } if (menuSelection == 2) { currentState = UI_MENU_SETUP; menuSelection = 0; break; }
break; break;
case UI_MENU_RANDOMIZE: case UI_MENU_RANDOMIZE:
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; } {
if (menuSelection == 1) { int track_offset = (playMode == MODE_POLY) ? 2 : 0;
melodySeed = random(10000); // Melody int theme_1_index = (playMode == MODE_POLY) ? THEME_1_INDEX_POLY : THEME_1_INDEX_MONO;
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; break; }
if (playMode == MODE_POLY) {
if (menuSelection == 1) { currentState = UI_RANDOMIZE_TRACK_EDIT; break; }
if (menuSelection == 2) { trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break; }
}
if (menuSelection == 1 + track_offset) { // Melody
int track = playMode == MODE_POLY ? randomizeTrack : 0;
midi.lock();
melodySeeds[track] = random(10000);
if (isPlaying) { if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock(); if (!sequenceChangeScheduled) {
generateSequenceData(theme, nextSequence); memcpy(nextSequence, sequence, sizeof(sequence));
}
generateTrackData(track, theme, nextSequence);
sequenceChangeScheduled = true; sequenceChangeScheduled = true;
}
midi.unlock(); midi.unlock();
}
saveSequence(true); saveSequence(true);
break;
} }
if (menuSelection == 2) currentState = UI_EDIT_FLAVOUR; // Flavour if (menuSelection == 2 + track_offset) { currentState = UI_EDIT_FLAVOUR; break; } // Flavour
if (menuSelection == 3) { // Scale if (menuSelection == 3 + track_offset) { // Scale
generateRandomScale(); generateRandomScale();
if (isPlaying) { if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock(); midi.lock();
// Regenerate all tracks with new scale
generateSequenceData(theme, nextSequence); generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true; sequenceChangeScheduled = true;
midi.unlock(); midi.unlock();
} }
saveSequence(true); saveSequence(true);
break;
} }
if (menuSelection == 4) currentState = UI_EDIT_TEMPO; if (menuSelection == 4 + track_offset) { currentState = UI_EDIT_TEMPO; break; }
if (menuSelection == 5) mutationEnabled = !mutationEnabled; if (menuSelection == 5 + track_offset) { mutationEnabled = !mutationEnabled; break; }
if (menuSelection == 6) { if (menuSelection == 6 + track_offset) {
songModeEnabled = !songModeEnabled; songModeEnabled = !songModeEnabled;
if (songModeEnabled) { if (songModeEnabled) {
songModeNeedsNext = true; songModeNeedsNext = true;
} }
break;
} }
if (menuSelection >= THEME_1_INDEX) { // Themes if (menuSelection >= theme_1_index) { // Themes
const int selectedTheme = menuSelection - THEME_1_INDEX + 1; const int selectedTheme = menuSelection - theme_1_index + 1;
if (isPlaying) { if (isPlaying) {
queuedTheme = selectedTheme; queuedTheme = selectedTheme;
midi.lock(); midi.lock();
@ -361,12 +436,15 @@ void handleInput() {
} else { } else {
generateTheme(selectedTheme); generateTheme(selectedTheme);
} }
break;
}
} }
break; break;
case UI_MENU_SETUP: case UI_MENU_SETUP:
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; } if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; break; }
if (menuSelection == 1) currentState = UI_SETUP_CHANNEL_EDIT; if (menuSelection == 1) { currentState = UI_SETUP_PLAYMODE_EDIT; break; }
if (menuSelection == 2) factoryReset(); if (menuSelection == 2) { currentState = UI_SETUP_CHANNEL_EDIT; break; }
if (menuSelection == 3) { factoryReset(); break; }
break; break;
case UI_SETUP_CHANNEL_EDIT: case UI_SETUP_CHANNEL_EDIT:
currentState = UI_MENU_SETUP; currentState = UI_MENU_SETUP;
@ -379,14 +457,26 @@ void handleInput() {
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
currentState = UI_MENU_RANDOMIZE; currentState = UI_MENU_RANDOMIZE;
if (isPlaying) { if (isPlaying) {
queuedTheme = currentThemeIndex; int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
int track = playMode == MODE_POLY ? randomizeTrack : 0;
midi.lock(); midi.lock();
generateSequenceData(currentThemeIndex, nextSequence); if (!sequenceChangeScheduled) {
memcpy(nextSequence, sequence, sizeof(sequence));
}
generateTrackData(track, theme, nextSequence);
sequenceChangeScheduled = true; sequenceChangeScheduled = true;
midi.unlock(); midi.unlock();
} }
saveSequence(true); saveSequence(true);
break; break;
case UI_SETUP_PLAYMODE_EDIT:
currentState = UI_MENU_SETUP;
saveSequence(true);
break;
case UI_RANDOMIZE_TRACK_EDIT:
currentState = UI_MENU_RANDOMIZE;
saveSequence(true);
break;
} }
} }
} }
@ -421,6 +511,7 @@ void handlePlayback() {
unsigned long currentMicros = micros(); unsigned long currentMicros = micros();
unsigned long clockInterval = 2500000 / tempo; // 60s * 1000000us / (tempo * 24ppqn) unsigned long clockInterval = 2500000 / tempo; // 60s * 1000000us / (tempo * 24ppqn)
int tracksToPlay = (playMode == MODE_POLY) ? NUM_TRACKS : 1;
if (currentMicros - lastClockTime >= clockInterval) { if (currentMicros - lastClockTime >= clockInterval) {
lastClockTime += clockInterval; lastClockTime += clockInterval;
@ -433,16 +524,21 @@ void handlePlayback() {
midi.lock(); midi.lock();
// Determine if we are tying to the next note
for(int t=0; t<tracksToPlay; t++) {
int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
int nextStep = playbackStep + 1; int nextStep = playbackStep + 1;
if (nextStep >= NUM_STEPS) nextStep = 0; if (nextStep >= NUM_STEPS) nextStep = 0;
bool isTied = sequence[playbackStep].tie && (sequence[nextStep].note != -1); // Determine if we are tying to the next note
int prevNote = sequence[playbackStep].note; bool isTied = sequence[t][playbackStep].tie && (sequence[t][nextStep].note != -1);
int prevNote = sequence[t][playbackStep].note;
// Note Off for previous step (if NOT tied) // Note Off for previous step (if NOT tied)
if (!isTied && prevNote != -1) { if (!isTied && prevNote != -1) {
midi.sendNoteOff(prevNote, shMidiChannel); midi.sendNoteOff(prevNote, trackChannel);
}
} }
playbackStep++; playbackStep++;
@ -465,7 +561,7 @@ void handlePlayback() {
sequenceChangeScheduled = true; sequenceChangeScheduled = true;
} }
midi.panic(shMidiChannel); // Panic / All Notes Off for (int i=0; i<tracksToPlay; i++) midi.panic(midiChannels[i]);
if (sequenceChangeScheduled) { if (sequenceChangeScheduled) {
memcpy(sequence, nextSequence, sizeof(sequence)); memcpy(sequence, nextSequence, sizeof(sequence));
@ -490,14 +586,20 @@ void handlePlayback() {
} }
// Note On for new step // Note On for new step
if (sequence[playbackStep].note != -1) { for(int t=0; t<tracksToPlay; t++) {
uint8_t velocity = sequence[playbackStep].accent ? 127 : 100; int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
midi.sendNoteOn(sequence[playbackStep].note, velocity, shMidiChannel); if (!trackMute[t] && sequence[t][playbackStep].note != -1) {
uint8_t velocity = sequence[t][playbackStep].accent ? 127 : 100;
midi.sendNoteOn(sequence[t][playbackStep].note, velocity, trackChannel);
} }
int prevStep = (playbackStep == 0) ? NUM_STEPS - 1 : playbackStep - 1;
bool wasTied = sequence[t][prevStep].tie && (sequence[t][playbackStep].note != -1);
int prevNote = sequence[t][prevStep].note;
// Note Off for previous step (if tied - delayed Note Off) // Note Off for previous step (if tied - delayed Note Off)
if (isTied && prevNote != -1) { if (wasTied && prevNote != -1) {
midi.sendNoteOff(prevNote, shMidiChannel); midi.sendNoteOff(prevNote, trackChannel);
}
} }
midi.unlock(); midi.unlock();
@ -506,20 +608,58 @@ void handlePlayback() {
void drawUI() { void drawUI() {
midi.lock(); midi.lock();
ui.draw(currentState, menuSelection, navigationSelection, isEditing, midiChannel, tempo, strategies[currentStrategyIndex], queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, sequence, scrollOffset, playbackStep, isPlaying, mainMenu, mainMenuCount, randomizeMenu, randomizeMenuCount, setupMenu, setupMenuCount, THEME_1_INDEX); const char **randMenu;
int randMenuCount;
int themeIndex;
if (playMode == MODE_POLY) {
randMenu = randomizeMenuPoly;
randMenuCount = randomizeMenuPolyCount;
themeIndex = THEME_1_INDEX_POLY;
} else {
randMenu = randomizeMenuMono;
randMenuCount = randomizeMenuMonoCount;
themeIndex = THEME_1_INDEX_MONO;
}
int ui_track = 0;
if (playMode == MODE_POLY) {
if (currentState == UI_MENU_RANDOMIZE || currentState == UI_EDIT_FLAVOUR || currentState == UI_EDIT_TEMPO || currentState == UI_RANDOMIZE_TRACK_EDIT) {
ui_track = randomizeTrack;
} else { // UI_TRACKER, UI_MENU_SETUP, UI_SETUP_CHANNEL_EDIT etc.
ui_track = currentTrack;
}
}
ui.draw(currentState, menuSelection, navigationSelection, editMode,
midiChannels[ui_track], tempo, strategies[currentStrategyIndices[ui_track]],
queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeeds[ui_track],
mutationEnabled, songModeEnabled, sequence, scrollOffset, playbackStep, isPlaying,
mainMenu, mainMenuCount, randMenu, randMenuCount, setupMenu, setupMenuCount,
themeIndex, playMode, currentTrack, randomizeTrack, (const bool*)trackMute);
midi.unlock(); midi.unlock();
} }
void updateLeds() { void updateLeds() {
midi.lock(); midi.lock();
ui.updateLeds(sequence, navigationSelection, playbackStep, isPlaying, currentState, isEditing, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled); ui.updateLeds(sequence, navigationSelection, playbackStep, isPlaying, currentState, editMode, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled, playMode, currentTrack, numScaleNotes, scaleNotes, (const bool*)trackMute);
midi.unlock(); midi.unlock();
} }
void loop1() { void loop1() {
unsigned long now = millis();
lastLoop1Time = now;
if (watchdogActive && (now - lastLoop0Time > 1000)) {
Serial.println("Core 0 Freeze detected");
rp2040.reboot();
}
if (needsPanic) { if (needsPanic) {
midi.lock(); midi.lock();
midi.panic(shMidiChannel); if (playMode == MODE_POLY) {
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
} else {
midi.panic(midiChannels[0]);
}
midi.unlock(); midi.unlock();
needsPanic = false; needsPanic = false;
} }
@ -527,6 +667,13 @@ void loop1() {
} }
void loop() { void loop() {
unsigned long now = millis();
lastLoop0Time = now;
if (watchdogActive && (now - lastLoop1Time > 1000)) {
Serial.println("Core 1 Freeze detected");
rp2040.reboot();
}
// Handle Song Mode Generation in UI Thread // Handle Song Mode Generation in UI Thread
if (songModeNeedsNext) { if (songModeNeedsNext) {
int nextTheme = random(1, 8); // Themes 1-7 int nextTheme = random(1, 8); // Themes 1-7

View File

@ -2,6 +2,7 @@
#define TRACKER_TYPES_H #define TRACKER_TYPES_H
#include <stdint.h> #include <stdint.h>
#include "config.h"
struct Step { struct Step {
int8_t note; // MIDI Note (0-127), -1 for OFF int8_t note; // MIDI Note (0-127), -1 for OFF
@ -9,6 +10,17 @@ struct Step {
bool tie; bool tie;
}; };
enum PlayMode {
MODE_MONO,
MODE_POLY
};
enum EditMode {
NAV_STEP,
NAV_TRACK,
EDIT_NOTE
};
enum UIState { enum UIState {
UI_TRACKER, UI_TRACKER,
UI_MENU_MAIN, UI_MENU_MAIN,
@ -16,7 +28,9 @@ enum UIState {
UI_MENU_SETUP, UI_MENU_SETUP,
UI_SETUP_CHANNEL_EDIT, UI_SETUP_CHANNEL_EDIT,
UI_EDIT_TEMPO, UI_EDIT_TEMPO,
UI_EDIT_FLAVOUR UI_EDIT_FLAVOUR,
UI_SETUP_PLAYMODE_EDIT,
UI_RANDOMIZE_TRACK_EDIT
}; };
inline void sortArray(int arr[], int size) { inline void sortArray(int arr[], int size) {

View File

@ -1,14 +1,13 @@
#include "UIManager.h" #include "UIManager.h"
#include "config.h"
// --- HARDWARE CONFIGURATION --- // --- HARDWARE CONFIGURATION ---
#define SCREEN_WIDTH 128 #define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64 #define SCREEN_HEIGHT 64
#define OLED_RESET -1 #define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C #define SCREEN_ADDRESS 0x3C
#define PIN_NEOPIXEL 16 #define PIN_NEOPIXEL 16
#define NUM_PIXELS 64 #define NUM_PIXELS 64
#define NUM_STEPS 16
UIManager ui; UIManager ui;
@ -46,16 +45,16 @@ void UIManager::showMessage(const char* msg) {
display.setTextSize(1); display.setTextSize(1);
} }
void UIManager::draw(UIState currentState, int menuSelection, int navSelection, bool isEditing, void UIManager::draw(UIState currentState, int menuSelection, int navSelection, EditMode editMode,
int midiChannel, int tempo, MelodyStrategy* currentStrategy, int midiChannel, int tempo, MelodyStrategy* currentStrategy,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numScaleNotes, const int* scaleNotes, int melodySeed,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int scrollOffset, int playbackStep, bool isPlaying,
const char* mainMenu[], int mainMenuCount, const char* mainMenu[], int mainMenuCount,
const char* randomizeMenu[], int randomizeMenuCount, const char* randomizeMenu[], int randomizeMenuCount,
const char* setupMenu[], int setupMenuCount, const char* setupMenu[], int setupMenuCount, int theme1Index,
int theme1Index) { PlayMode playMode, int currentTrack, int randomizeTrack, const bool* trackMute) {
display.clearDisplay(); display.clearDisplay();
display.setTextSize(1); display.setTextSize(1);
@ -64,16 +63,16 @@ void UIManager::draw(UIState currentState, int menuSelection, int navSelection,
switch(currentState) { switch(currentState) {
case UI_TRACKER: case UI_TRACKER:
drawTracker(navSelection, isEditing, midiChannel, sequence, scrollOffset, playbackStep, isPlaying); drawTracker(navSelection, editMode, midiChannel, sequence, scrollOffset, playbackStep, isPlaying, playMode, currentTrack);
break; break;
case UI_MENU_MAIN: case UI_MENU_MAIN:
drawMenu("MAIN MENU", mainMenu, mainMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index); drawMenu("MAIN MENU", mainMenu, mainMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index, playMode, randomizeTrack, trackMute);
break; break;
case UI_MENU_RANDOMIZE: case UI_MENU_RANDOMIZE:
drawMenu("RANDOMIZE", randomizeMenu, randomizeMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index); drawMenu("RANDOMIZE", randomizeMenu, randomizeMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index, playMode, randomizeTrack, trackMute);
break; break;
case UI_MENU_SETUP: case UI_MENU_SETUP:
drawMenu("SETUP", setupMenu, setupMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index); drawMenu("SETUP", setupMenu, setupMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index, playMode, randomizeTrack, trackMute);
break; break;
case UI_SETUP_CHANNEL_EDIT: case UI_SETUP_CHANNEL_EDIT:
display.println(F("SET MIDI CHANNEL")); display.println(F("SET MIDI CHANNEL"));
@ -108,15 +107,36 @@ void UIManager::draw(UIState currentState, int menuSelection, int navSelection,
display.setCursor(0, 50); display.setCursor(0, 50);
display.println(F(" (Press to confirm)")); display.println(F(" (Press to confirm)"));
break; break;
case UI_SETUP_PLAYMODE_EDIT:
display.println(F("SET PLAY MODE"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
display.print(playMode == MODE_MONO ? "Mono" : "Poly");
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
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)"));
break;
} }
display.display(); display.display();
} }
void UIManager::drawMenu(const char* title, const char* items[], int count, int selection, void UIManager::drawMenu(const char* title, const char* items[], int count, int selection,
UIState currentState, int midiChannel, int tempo, const char* flavourName, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex, int numScaleNotes,
int numScaleNotes, const int* scaleNotes, int melodySeed, const int* scaleNotes, int melodySeed, bool mutationEnabled,
bool mutationEnabled, bool songModeEnabled, int theme1Index) { bool songModeEnabled, int theme1Index, PlayMode playMode, int randomizeTrack, const bool* trackMute) {
display.println(title); display.println(title);
display.drawLine(0, 8, 128, 8, SSD1306_WHITE); display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
@ -137,8 +157,17 @@ void UIManager::drawMenu(const char* title, const char* items[], int count, int
display.print(items[i]); display.print(items[i]);
if (currentState == UI_MENU_SETUP && i == 1) { if (currentState == UI_MENU_SETUP && i == 1) {
display.print(F(": ")); display.print(playMode == MODE_MONO ? "Mono" : "Poly");
}
if (currentState == UI_MENU_SETUP && i == 2) {
display.print(F(": ")); display.print(midiChannel); display.print(F(": ")); display.print(midiChannel);
} }
if (currentState == UI_MENU_RANDOMIZE && playMode == MODE_POLY && i == 1) {
display.print(F(": ")); display.print(randomizeTrack + 1);
}
if (currentState == UI_MENU_RANDOMIZE && playMode == MODE_POLY && i == 2) {
display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO"));
}
if (currentState == UI_MENU_RANDOMIZE && i >= theme1Index && queuedTheme == (i - theme1Index + 1)) { if (currentState == UI_MENU_RANDOMIZE && i >= theme1Index && queuedTheme == (i - theme1Index + 1)) {
display.print(F(" [NEXT]")); display.print(F(" [NEXT]"));
} }
@ -146,11 +175,12 @@ void UIManager::drawMenu(const char* title, const char* items[], int count, int
display.print(F(" *")); display.print(F(" *"));
} }
if (currentState == UI_MENU_RANDOMIZE) { if (currentState == UI_MENU_RANDOMIZE) {
if (i == 1) { // Melody int track_offset = (playMode == MODE_POLY) ? 2 : 0;
if (i == 1 + track_offset) { // Melody
display.print(F(": ")); display.print(melodySeed); display.print(F(": ")); display.print(melodySeed);
} else if (i == 2) { // Flavour } else if (i == 2 + track_offset) { // Flavour
display.print(F(": ")); display.print(flavourName); display.print(F(": ")); display.print(flavourName);
} else if (i == 3) { // Scale } else if (i == 3 + track_offset) { // Scale
display.print(F(": ")); display.print(F(": "));
if (numScaleNotes > 0) { if (numScaleNotes > 0) {
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
@ -159,19 +189,29 @@ void UIManager::drawMenu(const char* title, const char* items[], int count, int
if (j < min(numScaleNotes, 6) - 1) display.print(F(" ")); if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
} }
} }
} else if (i == 4) { display.print(F(": ")); display.print(tempo); } } else if (i == 4 + track_offset) { display.print(F(": ")); display.print(tempo); }
else if (i == 5) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); } else if (i == 5 + track_offset) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
else if (i == 6) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); } else if (i == 6 + track_offset) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
} }
y += 9; y += 9;
} }
} }
void UIManager::drawTracker(int navSelection, bool isEditing, int midiChannel, void UIManager::drawTracker(int navSelection, EditMode editMode, int midiChannel,
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying) { const Step sequence[][NUM_STEPS], int scrollOffset, int playbackStep, bool isPlaying,
PlayMode playMode, int currentTrack) {
display.print(F("SEQ ")); display.print(F("SEQ "));
if (navSelection > 0 && isEditing) display.print(F("[EDIT]")); if (playMode == MODE_POLY) {
else display.print(F("[NAV] ")); switch(editMode) {
case NAV_STEP: display.print(F("[STP]")); break;
case NAV_TRACK: display.print(F("[TRK]")); break;
case EDIT_NOTE: display.print(F("[EDT]")); break;
}
} else {
if (navSelection > 0 && editMode == EDIT_NOTE) display.print(F("[EDT]"));
else display.print(F("[NAV]"));
}
display.print(F(" CH:")); display.print(midiChannel); display.print(F(" CH:")); display.print(midiChannel);
display.println(); display.println();
display.drawLine(0, 8, 128, 8, SSD1306_WHITE); display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
@ -181,7 +221,7 @@ void UIManager::drawTracker(int navSelection, bool isEditing, int midiChannel,
int itemIndex = i + scrollOffset; int itemIndex = i + scrollOffset;
if (itemIndex > NUM_STEPS) break; if (itemIndex > NUM_STEPS) break;
if (itemIndex == navSelection) { if (itemIndex == navSelection && editMode != NAV_TRACK) {
display.fillRect(0, y, 128, 8, SSD1306_WHITE); display.fillRect(0, y, 128, 8, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} else { } else {
@ -194,24 +234,39 @@ void UIManager::drawTracker(int navSelection, bool isEditing, int midiChannel,
} else { } else {
int stepIndex = itemIndex - 1; int stepIndex = itemIndex - 1;
bool isPlayback = isPlaying && (stepIndex == playbackStep); bool isPlayback = isPlaying && (stepIndex == playbackStep);
if (isPlayback) { if (isPlayback && editMode != NAV_TRACK) {
if (itemIndex == navSelection) display.setTextColor(SSD1306_WHITE, SSD1306_BLACK); if (itemIndex == navSelection) display.setTextColor(SSD1306_WHITE, SSD1306_BLACK); // Cursor on playback row
else display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); else display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} }
if (stepIndex < 10) display.print(F("0")); if (stepIndex < 10) display.print(F("0"));
display.print(stepIndex); display.print(stepIndex);
if (isPlayback) { if (isPlayback && editMode != NAV_TRACK) {
if (itemIndex == navSelection) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); if (itemIndex == navSelection) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE); else display.setTextColor(SSD1306_WHITE);
} }
display.print(F(" | ")); display.print(F(" | "));
int n = sequence[stepIndex].note;
if (n == -1) display.print(F("---")); int tracksToShow = (playMode == MODE_POLY) ? NUM_TRACKS : 1;
else { for(int t=0; t<tracksToShow; t++) {
if (playMode == MODE_POLY && t == currentTrack && (editMode == NAV_TRACK || (editMode == EDIT_NOTE && itemIndex == navSelection))) {
display.fillRect(display.getCursorX(), y, 28, 8, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} else if (itemIndex == navSelection && editMode != NAV_TRACK) {
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} else {
display.setTextColor(SSD1306_WHITE);
}
int n = sequence[t][stepIndex].note;
if (n == -1) {
display.print(F(" ---"));
} else {
const char* noteNames[] = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"}; const char* noteNames[] = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"};
display.print(noteNames[n % 12]); display.print(noteNames[n % 12]);
display.print(n / 12 - 1); display.print(n / 12 - 1);
} }
display.print(" ");
}
} }
y += 9; y += 9;
} }
@ -227,27 +282,62 @@ int UIManager::getPixelIndex(int x, int y) {
return y * 8 + x; return y * 8 + x;
} }
void UIManager::updateLeds(const Step* sequence, int navSelection, int playbackStep, bool isPlaying, void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int navSelection, int playbackStep, bool isPlaying,
UIState currentState, bool isEditing, bool songModeEnabled, UIState currentState, EditMode editMode, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled) { int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode, int currentTrack,
int numScaleNotes, const int* scaleNotes, const bool* trackMute) {
pixels.clear(); 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, 255, 255);
const uint32_t COLOR_CURSOR_DIM = pixels.Color(32, 0, 0);
if(playMode == MODE_POLY) {
for(int t=0; t<NUM_TRACKS; t++) {
for(int s=0; s<NUM_STEPS; s++) {
int col = t * 2 + (s / 8);
int row = s % 8;
uint32_t color = 0;
int note = sequence[t][s].note;
if (note != -1) {
color = getNoteColor(note, !sequence[t][s].accent);
}
if (isPlaying && s == playbackStep) {
if (trackMute[t]) {
color = COLOR_MUTED_PLAYHEAD;
} else {
color = (note != -1) ? COLOR_PLAYHEAD : COLOR_PLAYHEAD_DIM;
}
} else if (!isPlaying && currentState == UI_TRACKER && (navSelection - 1) == s && currentTrack == t) {
color = (note != -1) ? COLOR_CURSOR : COLOR_CURSOR_DIM;
}
pixels.setPixelColor(getPixelIndex(col, row), color);
}
}
} else {
// --- Mono Mode (original) ---
for (int s = 0; s < NUM_STEPS; s++) { for (int s = 0; s < NUM_STEPS; s++) {
int x = s % 8; int x = s % 8;
int yBase = (s / 8) * 4; int yBase = (s / 8) * 4;
uint32_t color = 0, dimColor = 0; uint32_t color = 0, dimColor = 0;
if (sequence[s].note != -1) { if (sequence[0][s].note != -1) {
color = getNoteColor(sequence[s].note, sequence[s].tie); color = getNoteColor(sequence[0][s].note, sequence[0][s].tie);
dimColor = getNoteColor(sequence[s].note, true); dimColor = getNoteColor(sequence[0][s].note, true);
} }
uint32_t c[4] = {0}; uint32_t c[4] = {0};
if (sequence[s].note != -1) { if (sequence[0][s].note != -1) {
int octave = sequence[s].note / 12; int octave = sequence[0][s].note / 12;
if (octave > 4) { c[0] = color; if (sequence[s].accent) c[1] = dimColor; } if (octave > 4) { c[0] = color; if (sequence[0][s].accent) c[1] = dimColor; }
else if (octave < 4) { c[2] = color; if (sequence[s].accent) c[1] = dimColor; } else if (octave < 4) { c[2] = color; if (sequence[0][s].accent) c[1] = dimColor; }
else { c[1] = color; if (sequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } } else { c[1] = color; if (sequence[0][s].accent) { c[0] = dimColor; c[2] = dimColor; } }
} }
int stepNavIndex = navSelection - 1; int stepNavIndex = navSelection - 1;
// Apply cursor color logic from original code more strictly
uint32_t cursorColor = 0; uint32_t cursorColor = 0;
if (isPlaying) { if (isPlaying) {
cursorColor = pixels.Color(0, 50, 0); cursorColor = pixels.Color(0, 50, 0);
@ -256,7 +346,7 @@ void UIManager::updateLeds(const Step* sequence, int navSelection, int playbackS
if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40); if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
} }
} else if (currentState == UI_TRACKER) { } else if (currentState == UI_TRACKER) {
cursorColor = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40); cursorColor = (editMode == EDIT_NOTE) ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
} }
bool isCursorHere = (isPlaying && s == playbackStep) || (!isPlaying && currentState == UI_TRACKER && s == stepNavIndex); bool isCursorHere = (isPlaying && s == playbackStep) || (!isPlaying && currentState == UI_TRACKER && s == stepNavIndex);
@ -267,9 +357,9 @@ void UIManager::updateLeds(const Step* sequence, int navSelection, int playbackS
c[3] = pixels.Color(r/5, g/5, b/5); 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]); for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]);
} }
}
if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0)); if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
pixels.show(); pixels.show();
} }

View File

@ -14,33 +14,36 @@ public:
void showMessage(const char* msg); void showMessage(const char* msg);
void draw(UIState currentState, int menuSelection, int navSelection, bool isEditing, void draw(UIState currentState, int menuSelection, int navSelection, EditMode editMode,
int midiChannel, int tempo, MelodyStrategy* currentStrategy, int midiChannel, int tempo, MelodyStrategy* currentStrategy,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numScaleNotes, const int* scaleNotes, int melodySeed,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int scrollOffset, int playbackStep, bool isPlaying,
const char* mainMenu[], int mainMenuCount, const char* mainMenu[], int mainMenuCount,
const char* randomizeMenu[], int randomizeMenuCount, const char* randomizeMenu[], int randomizeMenuCount,
const char* setupMenu[], int setupMenuCount, const char* setupMenu[], int setupMenuCount, int theme1Index,
int theme1Index); PlayMode playMode, int currentTrack, int randomizeTrack, const bool* trackMute);
void updateLeds(const Step* sequence, int navSelection, int playbackStep, bool isPlaying, void updateLeds(const Step sequence[][NUM_STEPS], int navSelection, int playbackStep, bool isPlaying,
UIState currentState, bool isEditing, bool songModeEnabled, UIState currentState, EditMode editMode, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled); int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode, int currentTrack,
int numScaleNotes, const int* scaleNotes, const bool* trackMute);
private: private:
Adafruit_SSD1306 display; Adafruit_SSD1306 display;
Adafruit_NeoPixel pixels; Adafruit_NeoPixel pixels;
uint32_t leds_buffer[8][8]; // For piano roll
void drawMenu(const char* title, const char* items[], int count, int selection, void drawMenu(const char* title, const char* items[], int count, int selection,
UIState currentState, int midiChannel, int tempo, const char* flavourName, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numScaleNotes, const int* scaleNotes, int melodySeed,
bool mutationEnabled, bool songModeEnabled, int theme1Index); bool mutationEnabled, bool songModeEnabled, int theme1Index, PlayMode playMode, int randomizeTrack, const bool* trackMute);
void drawTracker(int navSelection, bool isEditing, int midiChannel, void drawTracker(int navSelection, EditMode editMode, int midiChannel,
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying); const Step sequence[][NUM_STEPS], int scrollOffset, int playbackStep, bool isPlaying,
PlayMode playMode, int currentTrack);
uint32_t getNoteColor(int note, bool dim); uint32_t getNoteColor(int note, bool dim);
int getPixelIndex(int x, int y); int getPixelIndex(int x, int y);

22
config.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef CONFIG_H
#define CONFIG_H
// --- HARDWARE CONFIGURATION ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
// Pin Definitions for Raspberry Pi Pico (RP2040)
#define PIN_SDA 4
#define PIN_SCL 5
#define ENC_CLK 12
#define ENC_DT 13
#define ENC_SW 14
// --- TRACKER DATA ---
#define NUM_STEPS 16
#define NUM_TRACKS 4
#endif