Melody strategy
This commit is contained in:
parent
89f9821f5a
commit
1e475eeaa5
47
ArpStrategy.h
Normal file
47
ArpStrategy.h
Normal 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
42
LuckyStrategy.h
Normal 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
14
MelodyStrategy.h
Normal 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
|
||||
@ -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
12
TrackerTypes.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user