From 1e475eeaa54803b83f814880a697a5aff425005b Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 17 Feb 2026 00:19:39 +0100 Subject: [PATCH] Melody strategy --- ArpStrategy.h | 47 ++++++++++++++++++ LuckyStrategy.h | 42 +++++++++++++++++ MelodyStrategy.h | 14 ++++++ RP2040_Tracker.ino | 115 +++++++++++++++++++++++++-------------------- TrackerTypes.h | 12 +++++ 5 files changed, 180 insertions(+), 50 deletions(-) create mode 100644 ArpStrategy.h create mode 100644 LuckyStrategy.h create mode 100644 MelodyStrategy.h create mode 100644 TrackerTypes.h diff --git a/ArpStrategy.h b/ArpStrategy.h new file mode 100644 index 0000000..cca1051 --- /dev/null +++ b/ArpStrategy.h @@ -0,0 +1,47 @@ +#ifndef ARP_STRATEGY_H +#define ARP_STRATEGY_H + +#include "MelodyStrategy.h" +#include + +class ArpStrategy : public MelodyStrategy { +public: + void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { + randomSeed(seed); + if (numScaleNotes == 0) return; + + int currentNoteIndex = 0; + int octave = 4; + + for (int i = 0; i < numSteps; i++) { + sequence[i].note = 12 * octave + scaleNotes[currentNoteIndex]; + sequence[i].accent = (i % 4 == 0); // Accent on beat + sequence[i].tie = false; + + currentNoteIndex++; + if (currentNoteIndex >= numScaleNotes) { + currentNoteIndex = 0; + octave++; + if (octave > 5) octave = 3; + } + } + randomSeed(micros()); + } + + void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override { + // Swap two notes + int s1 = random(numSteps); + int s2 = random(numSteps); + if (sequence[s1].note != -1 && sequence[s2].note != -1) { + int8_t temp = sequence[s1].note; + sequence[s1].note = sequence[s2].note; + sequence[s2].note = temp; + } + } + + const char* getName() override { + return "Arp"; + } +}; + +#endif \ No newline at end of file diff --git a/LuckyStrategy.h b/LuckyStrategy.h new file mode 100644 index 0000000..dd4f319 --- /dev/null +++ b/LuckyStrategy.h @@ -0,0 +1,42 @@ +#ifndef LUCKY_STRATEGY_H +#define LUCKY_STRATEGY_H + +#include "MelodyStrategy.h" +#include + +class LuckyStrategy : public MelodyStrategy { +public: + void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { + randomSeed(seed); + if (numScaleNotes == 0) return; + + for (int i = 0; i < numSteps; 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); + } + randomSeed(micros()); + } + + void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override { + // Mutate 1 or 2 steps + int count = random(1, 3); + for (int i = 0; i < count; i++) { + int s = random(numSteps); + if (sequence[s].note != -1) { + int r = random(100); + if (r < 30) sequence[s].accent = !sequence[s].accent; + else if (r < 60) sequence[s].tie = !sequence[s].tie; + else if (r < 80) sequence[s].note += 12; // Up octave + else sequence[s].note -= 12; // Down octave + } + } + } + + const char* getName() override { + return "Lucky"; + } +}; + +#endif \ No newline at end of file diff --git a/MelodyStrategy.h b/MelodyStrategy.h new file mode 100644 index 0000000..fb5c54f --- /dev/null +++ b/MelodyStrategy.h @@ -0,0 +1,14 @@ +#ifndef MELODY_STRATEGY_H +#define MELODY_STRATEGY_H + +#include "TrackerTypes.h" + +class MelodyStrategy { +public: + virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0; + virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0; + virtual const char* getName() = 0; + virtual ~MelodyStrategy() {} +}; + +#endif \ No newline at end of file diff --git a/RP2040_Tracker.ino b/RP2040_Tracker.ino index efb6208..a285ef8 100644 --- a/RP2040_Tracker.ino +++ b/RP2040_Tracker.ino @@ -5,6 +5,10 @@ #include #include #include +#include "TrackerTypes.h" +#include "MelodyStrategy.h" +#include "LuckyStrategy.h" +#include "ArpStrategy.h" // --- HARDWARE CONFIGURATION --- #define SCREEN_WIDTH 128 @@ -30,12 +34,6 @@ // --- TRACKER DATA --- #define NUM_STEPS 16 -struct Step { - int8_t note; // MIDI Note (0-127), -1 for OFF - bool accent; - bool tie; -}; - Step sequence[NUM_STEPS]; Step nextSequence[NUM_STEPS]; volatile bool sequenceChangeScheduled = false; @@ -53,17 +51,18 @@ enum UIState { UI_MENU_RANDOMIZE, UI_MENU_SETUP, UI_SETUP_CHANNEL_EDIT, - UI_SETUP_TEMPO_EDIT + UI_EDIT_TEMPO, + UI_EDIT_FLAVOUR }; UIState currentState = UI_MENU_MAIN; const char* mainMenu[] = { "Tracker", "Randomize", "Setup" }; const int mainMenuCount = sizeof(mainMenu) / sizeof(char*); -const char* randomizeMenu[] = { "Back", "Scale", "Melody", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" }; -const int THEME_1_INDEX = 5; +const char* randomizeMenu[] = { "Back", "Scale", "Melody", "Flavour", "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 int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*); -const char* setupMenu[] = { "Back", "Channel", "Tempo", "Save", "Load" }; +const char* setupMenu[] = { "Back", "Channel", "Save", "Load" }; const int setupMenuCount = sizeof(setupMenu) / sizeof(char*); int menuSelection = 0; @@ -76,7 +75,11 @@ int numScaleNotes = 0; int melodySeed = 0; volatile int queuedTheme = -1; volatile int currentThemeIndex = 1; -const uint32_t EEPROM_MAGIC = 0x42424244; +const uint32_t EEPROM_MAGIC = 0x42424246; + +MelodyStrategy* strategies[] = { new LuckyStrategy(), new ArpStrategy() }; +const int numStrategies = 2; +int currentStrategyIndex = 0; volatile bool mutationEnabled = false; volatile bool songModeEnabled = false; @@ -145,11 +148,13 @@ void showMessage(const char* msg) { display.setTextSize(1); } -void saveSequence() { +void saveSequence(bool quiet = false) { 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, currentStrategyIndex); addr += sizeof(currentStrategyIndex); + EEPROM.put(addr, (int)tempo); addr += sizeof(int); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); for (int i=0; i<12; i++) { @@ -162,7 +167,7 @@ void saveSequence() { } mutex_exit(&midiMutex); EEPROM.commit(); - showMessage("SAVED!"); + if (!quiet) showMessage("SAVED!"); } bool loadSequence() { @@ -174,6 +179,10 @@ bool loadSequence() { EEPROM.get(addr, midiChannel); addr += sizeof(midiChannel); shMidiChannel = midiChannel; EEPROM.get(addr, melodySeed); addr += sizeof(melodySeed); + EEPROM.get(addr, currentStrategyIndex); addr += sizeof(currentStrategyIndex); + int t; + EEPROM.get(addr, t); addr += sizeof(int); + tempo = t; EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); for (int i=0; i<12; i++) { @@ -272,15 +281,7 @@ void generateRandomScale() { 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) - 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 + strategies[currentStrategyIndex]->generate(target, NUM_STEPS, scaleNotes, numScaleNotes, melodySeed + themeType * 12345); } void generateTheme(int themeType) { @@ -298,18 +299,7 @@ void generateTheme(int themeType) { } void mutateSequence(Step* target) { - // Mutate 1 or 2 steps - int count = random(1, 3); - for (int i = 0; i < count; i++) { - int s = random(NUM_STEPS); - if (target[s].note != -1) { - int r = random(100); - if (r < 30) target[s].accent = !target[s].accent; - else if (r < 60) target[s].tie = !target[s].tie; - else if (r < 80) target[s].note += 12; // Up octave - else target[s].note -= 12; // Down octave - } - } + strategies[currentStrategyIndex]->mutate(target, NUM_STEPS, scaleNotes, numScaleNotes); } void handleInput() { @@ -364,11 +354,16 @@ void handleInput() { if (midiChannel > 16) midiChannel = 1; shMidiChannel = midiChannel; break; - case UI_SETUP_TEMPO_EDIT: + case UI_EDIT_TEMPO: tempo += delta; if (tempo < 40) tempo = 40; if (tempo > 240) tempo = 240; break; + case UI_EDIT_FLAVOUR: + currentStrategyIndex += (delta > 0 ? 1 : -1); + if (currentStrategyIndex < 0) currentStrategyIndex = numStrategies - 1; + if (currentStrategyIndex >= numStrategies) currentStrategyIndex = 0; + break; } } @@ -417,6 +412,7 @@ void handleInput() { sequenceChangeScheduled = true; mutex_exit(&midiMutex); } + saveSequence(true); } if (menuSelection == 2) { melodySeed = random(10000); // Melody @@ -427,9 +423,12 @@ void handleInput() { sequenceChangeScheduled = true; mutex_exit(&midiMutex); } + saveSequence(true); } - if (menuSelection == 3) mutationEnabled = !mutationEnabled; - if (menuSelection == 4) { + if (menuSelection == 3) currentState = UI_EDIT_FLAVOUR; + if (menuSelection == 4) currentState = UI_EDIT_TEMPO; + if (menuSelection == 5) mutationEnabled = !mutationEnabled; + if (menuSelection == 6) { songModeEnabled = !songModeEnabled; if (songModeEnabled) { songModeNeedsNext = true; @@ -451,21 +450,26 @@ void handleInput() { case UI_MENU_SETUP: if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; } if (menuSelection == 1) currentState = UI_SETUP_CHANNEL_EDIT; - if (menuSelection == 2) currentState = UI_SETUP_TEMPO_EDIT; - if (menuSelection == 3) { + if (menuSelection == 2) { saveSequence(); currentState = UI_MENU_MAIN; } - if (menuSelection == 4) { + if (menuSelection == 3) { if (loadSequence()) showMessage("LOADED!"); currentState = UI_MENU_MAIN; } break; case UI_SETUP_CHANNEL_EDIT: currentState = UI_MENU_SETUP; + saveSequence(true); break; - case UI_SETUP_TEMPO_EDIT: - currentState = UI_MENU_SETUP; + case UI_EDIT_TEMPO: + currentState = UI_MENU_RANDOMIZE; + saveSequence(true); + break; + case UI_EDIT_FLAVOUR: + currentState = UI_MENU_RANDOMIZE; + saveSequence(true); break; } } @@ -612,11 +616,6 @@ void drawMenu(const char* title, const char* items[], int count, int selection) display.print(F(": ")); display.print(midiChannel); } - // Special case for tempo display - if (currentState == UI_MENU_SETUP && i == 2) { - display.print(F(": ")); - display.print(tempo); - } // Special case for queued theme if (currentState == UI_MENU_RANDOMIZE && i >= THEME_1_INDEX && queuedTheme == (i - THEME_1_INDEX + 1)) { @@ -642,10 +641,16 @@ void drawMenu(const char* title, const char* items[], int count, int selection) } else if (i == 2) { // Melody display.print(F(": ")); display.print(melodySeed); - } else if (i == 3) { // Mutation + } else if (i == 3) { // Flavour + display.print(F(": ")); + display.print(strategies[currentStrategyIndex]->getName()); + } else if (i == 4) { // Tempo + display.print(F(": ")); + display.print(tempo); + } else if (i == 5) { // Mutation display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); - } else if (i == 4) { // Song Mode + } else if (i == 6) { // Song Mode display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); } @@ -759,7 +764,7 @@ void drawUI() { display.setCursor(0, 50); display.println(F(" (Press to confirm)")); break; - case UI_SETUP_TEMPO_EDIT: + case UI_EDIT_TEMPO: display.println(F("SET TEMPO")); display.drawLine(0, 8, 128, 8, SSD1306_WHITE); display.setCursor(20, 25); @@ -770,6 +775,16 @@ void drawUI() { display.setCursor(0, 50); display.println(F(" (Press to confirm)")); 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(strategies[currentStrategyIndex]->getName()); + display.setTextSize(1); + display.setCursor(0, 50); + display.println(F(" (Press to confirm)")); + break; } display.display(); diff --git a/TrackerTypes.h b/TrackerTypes.h new file mode 100644 index 0000000..70335e8 --- /dev/null +++ b/TrackerTypes.h @@ -0,0 +1,12 @@ +#ifndef TRACKER_TYPES_H +#define TRACKER_TYPES_H + +#include + +struct Step { + int8_t note; // MIDI Note (0-127), -1 for OFF + bool accent; + bool tie; +}; + +#endif \ No newline at end of file