Melody strategy

This commit is contained in:
Dejvino 2026-02-17 00:19:39 +01:00
parent 89f9821f5a
commit 1e475eeaa5
5 changed files with 180 additions and 50 deletions

47
ArpStrategy.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef ARP_STRATEGY_H
#define ARP_STRATEGY_H
#include "MelodyStrategy.h"
#include <Arduino.h>
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

42
LuckyStrategy.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef LUCKY_STRATEGY_H
#define LUCKY_STRATEGY_H
#include "MelodyStrategy.h"
#include <Arduino.h>
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

14
MelodyStrategy.h Normal file
View File

@ -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

View File

@ -5,6 +5,10 @@
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include <pico/mutex.h>
#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();

12
TrackerTypes.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef TRACKER_TYPES_H
#define TRACKER_TYPES_H
#include <stdint.h>
struct Step {
int8_t note; // MIDI Note (0-127), -1 for OFF
bool accent;
bool tie;
};
#endif