Compare commits

...

2 Commits

Author SHA1 Message Date
Dejvino
f34f960358 Chance setting 2026-03-07 21:31:17 +01:00
Dejvino
99add4e2ac Song progression 2026-03-07 21:27:08 +01:00
8 changed files with 337 additions and 69 deletions

View File

@ -22,13 +22,18 @@ void Persistence::saveSequence(bool quiet) {
int intensities[NUM_TRACKS]; int intensities[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) intensities[i] = (int)trackIntensity[i]; for(int i=0; i<NUM_TRACKS; i++) intensities[i] = (int)trackIntensity[i];
EEPROM.put(addr, intensities); addr += sizeof(intensities); EEPROM.put(addr, intensities); addr += sizeof(intensities);
int chances[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) chances[i] = (int)trackChance[i];
EEPROM.put(addr, chances); addr += sizeof(chances);
int steps[NUM_TRACKS]; int steps[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i]; for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i];
EEPROM.put(addr, steps); addr += sizeof(steps); EEPROM.put(addr, steps); addr += sizeof(steps);
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.put(addr, globalRoot); addr += sizeof(globalRoot);
EEPROM.put(addr, currentProgression); addr += sizeof(currentProgression);
EEPROM.put(addr, progressionOrder); addr += sizeof(progressionOrder);
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType); EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
EEPROM.put(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
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++) {
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int); EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
@ -77,6 +82,13 @@ bool Persistence::loadSequence() {
if (trackIntensity[i] < 1) trackIntensity[i] = 1; if (trackIntensity[i] < 1) trackIntensity[i] = 1;
if (trackIntensity[i] > 10) trackIntensity[i] = 10; if (trackIntensity[i] > 10) trackIntensity[i] = 10;
} }
int chances[NUM_TRACKS];
EEPROM.get(addr, chances); addr += sizeof(chances);
for(int i=0; i<NUM_TRACKS; i++) {
trackChance[i] = chances[i];
if (trackChance[i] < 0) trackChance[i] = 0;
if (trackChance[i] > 100) trackChance[i] = 100;
}
int steps[NUM_TRACKS]; int steps[NUM_TRACKS];
EEPROM.get(addr, steps); addr += sizeof(steps); EEPROM.get(addr, steps); addr += sizeof(steps);
for(int i=0; i<NUM_TRACKS; i++) { for(int i=0; i<NUM_TRACKS; i++) {
@ -86,9 +98,12 @@ bool Persistence::loadSequence() {
} }
} }
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.get(addr, globalRoot); addr += sizeof(globalRoot);
currentRoot = globalRoot; // Sync
EEPROM.get(addr, currentProgression); addr += sizeof(currentProgression);
EEPROM.get(addr, progressionOrder); addr += sizeof(progressionOrder);
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType); EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
EEPROM.get(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0; if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i<12; i++) { for (int i = 0; i<12; i++) {
@ -109,7 +124,6 @@ void Persistence::savePatch(int bank, int slot) {
midi.lock(); midi.lock();
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot);
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType); EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
EEPROM.put(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
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++) {
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int); EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
@ -125,6 +139,9 @@ void Persistence::savePatch(int bank, int slot) {
int intensities[NUM_TRACKS]; int intensities[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) intensities[i] = trackIntensity[i]; for(int i=0; i<NUM_TRACKS; i++) intensities[i] = trackIntensity[i];
EEPROM.put(addr, intensities); addr += sizeof(intensities); EEPROM.put(addr, intensities); addr += sizeof(intensities);
int chances[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) chances[i] = trackChance[i];
EEPROM.put(addr, chances); addr += sizeof(chances);
midi.unlock(); midi.unlock();
EEPROM.commit(); EEPROM.commit();
ui.showMessage("SAVED!"); ui.showMessage("SAVED!");
@ -137,7 +154,6 @@ void Persistence::loadPatch(int bank, int slot, Step (*scratchBuffer)[NUM_STEPS]
midi.lock(); midi.lock();
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType); EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
EEPROM.get(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0; if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
@ -170,6 +186,13 @@ void Persistence::loadPatch(int bank, int slot, Step (*scratchBuffer)[NUM_STEPS]
if (trackIntensity[i] < 1) trackIntensity[i] = 1; if (trackIntensity[i] < 1) trackIntensity[i] = 1;
if (trackIntensity[i] > 10) trackIntensity[i] = 10; if (trackIntensity[i] > 10) trackIntensity[i] = 10;
} }
int chances[NUM_TRACKS];
EEPROM.get(addr, chances); addr += sizeof(chances);
for(int i=0; i<NUM_TRACKS; i++) {
trackChance[i] = chances[i];
if (trackChance[i] < 0) trackChance[i] = 0;
if (trackChance[i] > 100) trackChance[i] = 100;
}
if (isPlaying) { if (isPlaying) {
SequenceGenerator::generateSequenceData(currentThemeIndex, nextSequence); SequenceGenerator::generateSequenceData(currentThemeIndex, nextSequence);
@ -186,6 +209,7 @@ void Persistence::factoryReset() {
ui.showMessage("RESETTING..."); ui.showMessage("RESETTING...");
for(int i=0; i<NUM_TRACKS; i++) { for(int i=0; i<NUM_TRACKS; i++) {
trackIntensity[i] = 10; trackIntensity[i] = 10;
trackChance[i] = 100;
} }
uint32_t magic = 0; uint32_t magic = 0;
EEPROM.put(0, magic); EEPROM.put(0, magic);

View File

@ -97,6 +97,7 @@ static void handlePlayback() {
if (sequenceChangeScheduled) { if (sequenceChangeScheduled) {
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence)); memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
midi.lock(); midi.lock();
visualProgressionStep = queuedProgressionStep;
memcpy(sequence, local_sequence, sizeof(local_sequence)); memcpy(sequence, local_sequence, sizeof(local_sequence));
sequenceChangeScheduled = false; sequenceChangeScheduled = false;
midi.unlock(); midi.unlock();
@ -132,7 +133,10 @@ static void handlePlayback() {
// If tied to the SAME note, do not retrigger (sustain) // If tied to the SAME note, do not retrigger (sustain)
bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked; bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked;
if (!trackMute[t] && currentNote != -1 && !isContinuing) { // Check chance
bool shouldPlay = (random(100) < trackChance[t]);
if (!trackMute[t] && currentNote != -1 && !isContinuing && shouldPlay) {
uint8_t velocity = local_sequence[t][current_step].accent ? 127 : 100; uint8_t velocity = local_sequence[t][current_step].accent ? 127 : 100;
midi.sendNoteOn(currentNote, velocity, trackChannel); midi.sendNoteOn(currentNote, velocity, trackChannel);
} }

View File

@ -80,15 +80,6 @@ void SequenceGenerator::pickRandomScaleType(int themeType) {
for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i]; for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i];
randomSeed(seed); randomSeed(seed);
int candidates[10]; currentScaleType = random(10);
int count = 0;
for (int i = 0; i < 10; i++) {
if (enabledScaleTypes & (1 << i)) {
candidates[count++] = i;
}
}
if (count > 0) {
currentScaleType = candidates[random(count)];
SequenceGenerator::updateScale(); SequenceGenerator::updateScale();
} }
}

View File

@ -20,23 +20,36 @@ UIState currentState = UI_MENU_MAIN;
bool protectedMode = false; bool protectedMode = false;
volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10}; volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10};
volatile int trackChance[NUM_TRACKS] = {100, 100, 100, 100};
// Menus // Menus
#define PROG_STEP_ITEMS(n) \
{ "[" #n "] Mod", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3), false, false, 3 }, \
{ "[" #n "] Type", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3 + 1), false, false, 3 }, \
{ "[" #n "] Reps", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3 + 2), false, false, 3 }
MenuItem menuItems[] = { MenuItem menuItems[] = {
{ "Main", MENU_ID_GROUP_MAIN, true, true, 0 }, { "Main", MENU_ID_GROUP_MAIN, true, true, 0 },
{ "Playback", MENU_ID_PLAYBACK, false, false, 1 }, { "Playback", MENU_ID_PLAYBACK, false, false, 1 },
{ "Melody", MENU_ID_MELODY, false, false, 1 }, { "Melody", MENU_ID_MELODY, false, false, 1 },
{ "Root", MENU_ID_ROOT, false, false, 1 }, { "Root", MENU_ID_ROOT, false, false, 1 },
{ "Scale Type", MENU_ID_SCALE_TYPE, true, false, 1 }, { "Progression", MENU_ID_GROUP_PROGRESSION, true, false, 1 },
{ "Chromatic", MENU_ID_SCALE_TYPE_CHROMATIC, false, false, 2 }, { "Order", MENU_ID_PROG_ORDER, false, false, 2 },
{ "Major", MENU_ID_SCALE_TYPE_MAJOR, false, false, 2 }, { "Apply Template", MENU_ID_PROG_APPLY_TEMPLATE, false, false, 2 },
{ "Minor", MENU_ID_SCALE_TYPE_MINOR, false, false, 2 }, { "Sequence", MENU_ID_GROUP_PROG_SEQUENCE, true, false, 2 },
{ "Harm. Min", MENU_ID_SCALE_TYPE_HARM_MIN, false, false, 2 }, { "Length", MENU_ID_PROG_LENGTH, false, false, 3 },
{ "Pent. Maj", MENU_ID_SCALE_TYPE_PENT_MAJ, false, false, 2 }, PROG_STEP_ITEMS(1),
{ "Pent. Min", MENU_ID_SCALE_TYPE_PENT_MIN, false, false, 2 }, PROG_STEP_ITEMS(2),
{ "Chord Maj", MENU_ID_SCALE_TYPE_CHORD_MAJ, false, false, 2 }, PROG_STEP_ITEMS(3),
{ "Chord Min", MENU_ID_SCALE_TYPE_CHORD_MIN, false, false, 2 }, PROG_STEP_ITEMS(4),
{ "Chord Dim", MENU_ID_SCALE_TYPE_CHORD_DIM, false, false, 2 }, PROG_STEP_ITEMS(5),
{ "Chord 7", MENU_ID_SCALE_TYPE_CHORD_7, false, false, 2 }, PROG_STEP_ITEMS(6),
PROG_STEP_ITEMS(7),
PROG_STEP_ITEMS(8),
PROG_STEP_ITEMS(9),
PROG_STEP_ITEMS(10),
PROG_STEP_ITEMS(11),
PROG_STEP_ITEMS(12),
{ "Tempo", MENU_ID_TEMPO, false, false, 1 }, { "Tempo", MENU_ID_TEMPO, false, false, 1 },
{ "Song Mode", MENU_ID_SONG_MODE, false, false, 1 }, { "Song Mode", MENU_ID_SONG_MODE, false, false, 1 },
{ "Track", MENU_ID_GROUP_TRACK, true, true, 0 }, { "Track", MENU_ID_GROUP_TRACK, true, true, 0 },
@ -45,6 +58,7 @@ MenuItem menuItems[] = {
{ "Steps", MENU_ID_STEPS, false, false, 1 }, { "Steps", MENU_ID_STEPS, false, false, 1 },
{ "Flavour", MENU_ID_FLAVOUR, false, false, 1 }, { "Flavour", MENU_ID_FLAVOUR, false, false, 1 },
{ "Intensity", MENU_ID_INTENSITY, false, false, 1 }, { "Intensity", MENU_ID_INTENSITY, false, false, 1 },
{ "Chance", MENU_ID_CHANCE, false, false, 1 },
{ "Mutation", MENU_ID_MUTATION, false, false, 1 }, { "Mutation", MENU_ID_MUTATION, false, false, 1 },
{ "Theme 1", MENU_ID_THEME_1, false, false, 1 }, { "Theme 1", MENU_ID_THEME_1, false, false, 1 },
{ "Theme 2", MENU_ID_THEME_2, false, false, 1 }, { "Theme 2", MENU_ID_THEME_2, false, false, 1 },
@ -106,6 +120,13 @@ bool isItemVisible(int index) {
} }
} }
// Hide progression steps beyond current length
MenuItemID id = menuItems[index].id;
if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
if (stepIdx >= currentProgression.length) return false;
}
if (menuItems[index].indentLevel == 0) return true; if (menuItems[index].indentLevel == 0) return true;
for (int i = index - 1; i >= 0; i--) { for (int i = index - 1; i >= 0; i--) {
if (menuItems[i].indentLevel < menuItems[index].indentLevel) { if (menuItems[i].indentLevel < menuItems[index].indentLevel) {
@ -126,10 +147,27 @@ int numScaleNotes = 0;
int melodySeeds[NUM_TRACKS]; int melodySeeds[NUM_TRACKS];
volatile int queuedTheme = -1; volatile int queuedTheme = -1;
volatile int currentThemeIndex = 1; volatile int currentThemeIndex = 1;
int globalRoot = 0; // The "Key" of the song
int currentRoot = 0; // C int currentRoot = 0; // C
int currentScaleType = 1; // Major int currentScaleType = 1; // Major
int enabledScaleTypes = 2; // Major (1<<1) extern const uint32_t EEPROM_MAGIC = 0x42424254;
extern const uint32_t EEPROM_MAGIC = 0x42424250;
const ChordProgression progressions[] = {
{ "Off", 0, { {0,0,1} } }, // Dummy
{ "I-IV-V-I", 4, { {0, 1, 1}, {5, 1, 1}, {7, 1, 1}, {0, 1, 1} } }, // Major
{ "ii-V-I", 3, { {2, 2, 1}, {7, 1, 1}, {0, 1, 1} } }, // min, Maj, Maj
{ "I-vi-IV-V", 4, { {0, 1, 1}, {9, 2, 1}, {5, 1, 1}, {7, 1, 1} } }, // 50s progression
{ "i-bVI-bIII-bVII", 4, { {0, 2, 1}, {8, 1, 1}, {3, 1, 1}, {10, 1, 1} } }, // Axis of Awesome (Am, F, C, G relative to Am)
{ "Blues 12", 12, { {0,9,1}, {0,9,1}, {0,9,1}, {0,9,1}, {5,9,1}, {5,9,1}, {0,9,1}, {0,9,1}, {7,9,1}, {5,9,1}, {0,9,1}, {7,9,1} } } // Dom7 chords
};
ChordProgression currentProgression = progressions[0]; // Start with Off/Empty
ProgressionOrder progressionOrder = PROG_ORDER_SEQUENCE;
int progressionStep = 0;
volatile int queuedProgressionStep = 0;
volatile int visualProgressionStep = 0;
const int numProgressions = 6;
int templateSelectionIndex = 0;
int stepEditIndex = 0;
MelodyStrategy* strategies[] = { MelodyStrategy* strategies[] = {
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),

View File

@ -19,17 +19,15 @@ enum MenuItemID {
MENU_ID_PLAYBACK, MENU_ID_PLAYBACK,
MENU_ID_MELODY, MENU_ID_MELODY,
MENU_ID_ROOT, MENU_ID_ROOT,
MENU_ID_SCALE_TYPE,
MENU_ID_SCALE_TYPE_CHROMATIC, MENU_ID_GROUP_PROGRESSION,
MENU_ID_SCALE_TYPE_MAJOR, MENU_ID_PROG_ORDER,
MENU_ID_SCALE_TYPE_MINOR, MENU_ID_PROG_APPLY_TEMPLATE,
MENU_ID_SCALE_TYPE_HARM_MIN, MENU_ID_GROUP_PROG_SEQUENCE,
MENU_ID_SCALE_TYPE_PENT_MAJ, MENU_ID_PROG_LENGTH,
MENU_ID_SCALE_TYPE_PENT_MIN, MENU_ID_PROG_STEP_START,
MENU_ID_SCALE_TYPE_CHORD_MAJ, MENU_ID_PROG_STEP_END = MENU_ID_PROG_STEP_START + (MAX_PROG_STEPS * 3),
MENU_ID_SCALE_TYPE_CHORD_MIN,
MENU_ID_SCALE_TYPE_CHORD_DIM,
MENU_ID_SCALE_TYPE_CHORD_7,
MENU_ID_TEMPO, MENU_ID_TEMPO,
MENU_ID_SONG_MODE, MENU_ID_SONG_MODE,
@ -39,6 +37,7 @@ enum MenuItemID {
MENU_ID_STEPS, MENU_ID_STEPS,
MENU_ID_FLAVOUR, MENU_ID_FLAVOUR,
MENU_ID_INTENSITY, MENU_ID_INTENSITY,
MENU_ID_CHANCE,
MENU_ID_MUTATION, MENU_ID_MUTATION,
MENU_ID_THEME_1, MENU_ID_THEME_1,
MENU_ID_THEME_2, MENU_ID_THEME_2,
@ -84,10 +83,20 @@ extern int numScaleNotes;
extern int melodySeeds[NUM_TRACKS]; extern int melodySeeds[NUM_TRACKS];
extern volatile int queuedTheme; extern volatile int queuedTheme;
extern volatile int currentThemeIndex; extern volatile int currentThemeIndex;
extern int globalRoot;
extern int currentRoot; extern int currentRoot;
extern volatile int trackIntensity[NUM_TRACKS]; extern volatile int trackIntensity[NUM_TRACKS];
extern volatile int trackChance[NUM_TRACKS];
extern int currentScaleType; extern int currentScaleType;
extern int enabledScaleTypes; extern ChordProgression currentProgression;
extern ProgressionOrder progressionOrder;
extern int progressionStep;
extern volatile int queuedProgressionStep;
extern volatile int visualProgressionStep;
extern const ChordProgression progressions[];
extern const int numProgressions;
extern int templateSelectionIndex;
extern int stepEditIndex;
extern const uint32_t EEPROM_MAGIC; extern const uint32_t EEPROM_MAGIC;
extern MelodyStrategy* strategies[]; extern MelodyStrategy* strategies[];

View File

@ -10,6 +10,25 @@ struct Step {
bool tie; bool tie;
}; };
struct ChordStep {
int8_t rootOffset; // Semitones relative to key
int8_t scaleType; // Scale type index
uint8_t repeats; // Number of repetitions
};
#define MAX_PROG_STEPS 12
struct ChordProgression {
const char* name;
int length;
ChordStep steps[MAX_PROG_STEPS]; // Max 12 steps
};
enum ProgressionOrder {
PROG_ORDER_SEQUENCE,
PROG_ORDER_RANDOM
};
enum PlayMode { enum PlayMode {
MODE_MONO, MODE_MONO,
MODE_POLY MODE_POLY
@ -22,9 +41,16 @@ enum UIState {
UI_EDIT_STEPS, UI_EDIT_STEPS,
UI_EDIT_FLAVOUR, UI_EDIT_FLAVOUR,
UI_EDIT_INTENSITY, UI_EDIT_INTENSITY,
UI_EDIT_CHANCE,
UI_SETUP_PLAYMODE_EDIT, UI_SETUP_PLAYMODE_EDIT,
UI_RANDOMIZE_TRACK_EDIT, UI_RANDOMIZE_TRACK_EDIT,
UI_EDIT_ROOT UI_EDIT_ROOT,
UI_EDIT_PROG_ORDER,
UI_EDIT_PROG_TEMPLATE,
UI_EDIT_PROG_LENGTH,
UI_EDIT_PROG_STEP_ROOT,
UI_EDIT_PROG_STEP_TYPE,
UI_EDIT_PROG_STEP_REPS
}; };
inline void sortArray(int arr[], int size) { inline void sortArray(int arr[], int size) {

View File

@ -72,12 +72,40 @@ void UIManager::draw(UIState currentState, int menuSelection,
case UI_EDIT_ROOT: case UI_EDIT_ROOT:
{ {
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"};
drawEditScreen("SET ROOT NOTE", "", noteNames[currentRoot % 12]); drawEditScreen("SET KEY", "", noteNames[globalRoot % 12]);
} }
break; break;
case UI_EDIT_PROG_ORDER:
drawEditScreen("PROG ORDER", "", (progressionOrder == PROG_ORDER_SEQUENCE) ? "Sequence" : "Random");
break;
case UI_EDIT_PROG_TEMPLATE:
drawEditScreen("APPLY TEMPLATE", "", progressions[templateSelectionIndex].name);
break;
case UI_EDIT_PROG_LENGTH:
drawEditScreen("PROG LENGTH", "Steps: ", currentProgression.length);
break;
case UI_EDIT_PROG_STEP_ROOT:
drawEditScreen("STEP MODULATION", "Semi: ", currentProgression.steps[stepEditIndex/3].rootOffset);
break;
case UI_EDIT_PROG_STEP_TYPE:
{
const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"};
int type = currentProgression.steps[stepEditIndex/3].scaleType;
if (type >= 0 && type < 10)
drawEditScreen("STEP CHORD", "", typeNames[type]);
else
drawEditScreen("STEP CHORD", "", "?");
}
break;
case UI_EDIT_PROG_STEP_REPS:
drawEditScreen("STEP REPEATS", "Reps: ", currentProgression.steps[stepEditIndex/3].repeats);
break;
case UI_EDIT_INTENSITY: case UI_EDIT_INTENSITY:
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10); drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
break; break;
case UI_EDIT_CHANCE:
drawNumberEditor("SET CHANCE %", trackChance[randomizeTrack], 0, 100);
break;
case UI_RANDOMIZE_TRACK_EDIT: case UI_RANDOMIZE_TRACK_EDIT:
drawEditScreen("SET TRACK", "TRK: ", randomizeTrack + 1); drawEditScreen("SET TRACK", "TRK: ", randomizeTrack + 1);
break; break;
@ -154,6 +182,8 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
if (currentVisualIndex >= startVisualIndex) { if (currentVisualIndex >= startVisualIndex) {
if (y > 55) break; if (y > 55) break;
MenuItemID id = menuItems[i].id;
if (i == selection) { if (i == selection) {
display.fillRect(0, y, 128, 9, SSD1306_WHITE); display.fillRect(0, y, 128, 9, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
@ -168,22 +198,18 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
display.print(menuItems[i].expanded ? F("v ") : F("> ")); display.print(menuItems[i].expanded ? F("v ") : F("> "));
} }
if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
if (stepIdx == visualProgressionStep) {
display.setCursor(x - 6 * 2, y + 1);
display.print(F("| "));
} else if (stepIdx == queuedProgressionStep) {
display.setCursor(x - 6 * 2, y + 1);
display.print(F(", "));
}
}
display.print(menuItems[i].label); display.print(menuItems[i].label);
// Render checkbox for scale types
if (menuItems[i].id >= MENU_ID_SCALE_TYPE_CHROMATIC && menuItems[i].id <= MENU_ID_SCALE_TYPE_CHORD_7) {
int typeIndex = menuItems[i].id - MENU_ID_SCALE_TYPE_CHROMATIC;
bool enabled = (enabledScaleTypes & (1 << typeIndex));
int boxX = x - 10;
display.drawRect(boxX, y + 1, 7, 7, (i == selection) ? SSD1306_BLACK : SSD1306_WHITE);
if (enabled) display.fillRect(boxX + 2, y + 3, 3, 3, (i == selection) ? SSD1306_BLACK : SSD1306_WHITE);
if (typeIndex == currentScaleType) {
display.print(F(" *"));
}
}
MenuItemID id = menuItems[i].id;
if (id == MENU_ID_CHANNEL) { if (id == MENU_ID_CHANNEL) {
display.print(F(": ")); display.print(midiChannel); display.print(F(": ")); display.print(midiChannel);
} }
@ -195,13 +221,37 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
} else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); } } else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); }
else if (id == MENU_ID_ROOT) { else if (id == MENU_ID_ROOT) {
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(F(": ")); display.print(noteNames[currentRoot % 12]); display.print(F(": ")); display.print(noteNames[globalRoot % 12]);
} }
else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(currentTrackNumSteps); } else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(currentTrackNumSteps); }
else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); } else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); } else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); }
else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); } else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); }
else if (id == MENU_ID_FLAVOUR) { display.print(F(": ")); display.print(flavourName); } else if (id == MENU_ID_FLAVOUR) { display.print(F(": ")); display.print(flavourName); }
else if (id == MENU_ID_PROG_ORDER) { display.print(F(": ")); display.print((progressionOrder == PROG_ORDER_SEQUENCE) ? F("Seq") : F("Rnd")); }
else if (id == MENU_ID_PROG_LENGTH) { display.print(F(": ")); display.print(currentProgression.length); }
else if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
// Only show steps that are within the current length
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
if (stepIdx >= currentProgression.length) {
continue; // Skip rendering this item
}
int idx = id - MENU_ID_PROG_STEP_START;
int step = idx / 3;
int type = idx % 3; // 0=Root, 1=Type, 2=Reps
display.print(F(": "));
if (type == 1) {
const char* typeNames[] = {"Chrom", "Maj", "Min", "HMin", "PMaj", "PMin", "CMaj", "CMin", "CDim", "C7"};
int t = currentProgression.steps[step].scaleType;
if (t >= 0 && t < 10) display.print(typeNames[t]);
} else if (type == 2) {
display.print(currentProgression.steps[step].repeats);
} else {
int root = currentProgression.steps[step].rootOffset;
if (root > 0) display.print(F("+"));
display.print(root);
}
}
else if (id == MENU_ID_INTENSITY) { else if (id == MENU_ID_INTENSITY) {
display.print(F(": ")); display.print(F(": "));
display.print(trackIntensities[randomizeTrack]); display.print(trackIntensities[randomizeTrack]);
@ -214,6 +264,18 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
display.drawRect(barX, barY, maxW + 2, h, color); display.drawRect(barX, barY, maxW + 2, h, color);
display.fillRect(barX + 1, barY + 1, (val * maxW) / 10, h - 2, color); display.fillRect(barX + 1, barY + 1, (val * maxW) / 10, h - 2, color);
} }
else if (id == MENU_ID_CHANCE) {
display.print(F(": "));
display.print(trackChance[randomizeTrack]);
int val = trackChance[randomizeTrack];
int barX = display.getCursorX() + 3;
int barY = y + 2;
int maxW = 20;
int h = 5;
uint16_t color = (i == selection) ? SSD1306_BLACK : SSD1306_WHITE;
display.drawRect(barX, barY, maxW + 2, h, color);
display.fillRect(barX + 1, barY + 1, (val * maxW) / 100, h - 2, color);
}
else if (id == MENU_ID_MUTATION) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); } else if (id == MENU_ID_MUTATION) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
else if (id == MENU_ID_PROTECTED_MODE) { display.print(F(": ")); display.print(protectedMode ? F("ON") : F("OFF")); } else if (id == MENU_ID_PROTECTED_MODE) { display.print(F(": ")); display.print(protectedMode ? F("ON") : F("OFF")); }

View File

@ -94,15 +94,58 @@ static void handleInput() {
if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS; if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS;
break; break;
case UI_EDIT_ROOT: case UI_EDIT_ROOT:
currentRoot += delta; globalRoot += delta;
if (currentRoot < 0) currentRoot = 11; if (globalRoot < 0) globalRoot = 11;
if (currentRoot > 11) currentRoot = 0; if (globalRoot > 11) globalRoot = 0;
currentRoot = globalRoot; // Reset current to global
SequenceGenerator::updateScale(); SequenceGenerator::updateScale();
if (isPlaying) { if (isPlaying) {
sequenceChangeScheduled = true; sequenceChangeScheduled = true;
SequenceGenerator::generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence); SequenceGenerator::generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence);
} }
break; break;
case UI_EDIT_PROG_ORDER:
if (delta > 0) progressionOrder = PROG_ORDER_RANDOM;
else progressionOrder = PROG_ORDER_SEQUENCE;
break;
case UI_EDIT_PROG_TEMPLATE:
templateSelectionIndex += delta;
if (templateSelectionIndex < 0) templateSelectionIndex = numProgressions - 1;
if (templateSelectionIndex >= numProgressions) templateSelectionIndex = 0;
break;
case UI_EDIT_PROG_LENGTH:
currentProgression.length += delta;
if (currentProgression.length < 1) currentProgression.length = 1;
if (currentProgression.length > MAX_PROG_STEPS) currentProgression.length = MAX_PROG_STEPS;
progressionStep = 0; // Reset step
break;
case UI_EDIT_PROG_STEP_ROOT:
{
int stepIdx = stepEditIndex / 3;
currentProgression.steps[stepIdx].rootOffset += delta;
if (currentProgression.steps[stepIdx].rootOffset < -12) currentProgression.steps[stepIdx].rootOffset = -12;
if (currentProgression.steps[stepIdx].rootOffset > 12) currentProgression.steps[stepIdx].rootOffset = 12;
}
break;
case UI_EDIT_PROG_STEP_TYPE:
{
int stepIdx = stepEditIndex / 3;
int type = currentProgression.steps[stepIdx].scaleType;
type += delta;
if (type < 0) type = 9;
if (type > 9) type = 0;
currentProgression.steps[stepIdx].scaleType = type;
}
break;
case UI_EDIT_PROG_STEP_REPS:
{
int stepIdx = stepEditIndex / 3;
int val = currentProgression.steps[stepIdx].repeats + delta;
if (val < 1) val = 1;
if (val > 16) val = 16;
currentProgression.steps[stepIdx].repeats = val;
}
break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
{ {
currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1); currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1);
@ -119,6 +162,15 @@ static void handleInput() {
trackIntensity[randomizeTrack] = current; trackIntensity[randomizeTrack] = current;
} }
break; break;
case UI_EDIT_CHANCE:
{
int current = trackChance[randomizeTrack];
current += delta;
if (current < 0) current = 0;
if (current > 100) current = 100;
trackChance[randomizeTrack] = current;
}
break;
} }
if (currentState == UI_RANDOMIZE_TRACK_EDIT) { if (currentState == UI_RANDOMIZE_TRACK_EDIT) {
randomizeTrack += (delta > 0 ? 1 : -1); randomizeTrack += (delta > 0 ? 1 : -1);
@ -186,6 +238,17 @@ static void handleInput() {
break; break;
case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break; case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break;
case MENU_ID_PROG_ORDER: currentState = UI_EDIT_PROG_ORDER; break;
case MENU_ID_PROG_APPLY_TEMPLATE: currentState = UI_EDIT_PROG_TEMPLATE; templateSelectionIndex = 0; break;
case MENU_ID_PROG_LENGTH: currentState = UI_EDIT_PROG_LENGTH; break;
case MENU_ID_PROG_STEP_START ... (MENU_ID_PROG_STEP_END - 1):
stepEditIndex = menuItems[menuSelection].id - MENU_ID_PROG_STEP_START;
if (stepEditIndex % 3 == 0) currentState = UI_EDIT_PROG_STEP_ROOT;
else if (stepEditIndex % 3 == 1) currentState = UI_EDIT_PROG_STEP_TYPE;
else currentState = UI_EDIT_PROG_STEP_REPS;
break;
case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break; case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break;
case MENU_ID_STEPS: currentState = UI_EDIT_STEPS; break; case MENU_ID_STEPS: currentState = UI_EDIT_STEPS; break;
@ -200,6 +263,7 @@ static void handleInput() {
case MENU_ID_MUTE: trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break; case MENU_ID_MUTE: trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break;
case MENU_ID_FLAVOUR: currentState = UI_EDIT_FLAVOUR; break; case MENU_ID_FLAVOUR: currentState = UI_EDIT_FLAVOUR; break;
case MENU_ID_INTENSITY: currentState = UI_EDIT_INTENSITY; break; case MENU_ID_INTENSITY: currentState = UI_EDIT_INTENSITY; break;
case MENU_ID_CHANCE: currentState = UI_EDIT_CHANCE; break;
case MENU_ID_MUTATION: mutationEnabled = !mutationEnabled; break; case MENU_ID_MUTATION: mutationEnabled = !mutationEnabled; break;
case MENU_ID_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break; case MENU_ID_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break;
@ -207,12 +271,6 @@ static void handleInput() {
case MENU_ID_RESET: factoryReset(); break; case MENU_ID_RESET: factoryReset(); break;
default: default:
if (menuItems[menuSelection].id >= MENU_ID_SCALE_TYPE_CHROMATIC && menuItems[menuSelection].id <= MENU_ID_SCALE_TYPE_CHORD_7) {
int typeIndex = menuItems[menuSelection].id - MENU_ID_SCALE_TYPE_CHROMATIC;
enabledScaleTypes ^= (1 << typeIndex);
if (enabledScaleTypes == 0) enabledScaleTypes = (1 << typeIndex); // Prevent disabling all
break;
}
if (menuItems[menuSelection].id >= MENU_ID_THEME_1 && menuItems[menuSelection].id <= MENU_ID_THEME_7) { if (menuItems[menuSelection].id >= MENU_ID_THEME_1 && menuItems[menuSelection].id <= MENU_ID_THEME_7) {
const int selectedTheme = menuItems[menuSelection].id - MENU_ID_THEME_1 + 1; const int selectedTheme = menuItems[menuSelection].id - MENU_ID_THEME_1 + 1;
if (isPlaying) { if (isPlaying) {
@ -256,6 +314,29 @@ static void handleInput() {
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
saveSequence(true); saveSequence(true);
break; break;
case UI_EDIT_PROG_ORDER:
currentState = UI_MENU_MAIN;
saveSequence(true);
break;
case UI_EDIT_PROG_TEMPLATE:
// Apply template
if (templateSelectionIndex > 0) {
currentProgression = progressions[templateSelectionIndex];
progressionStep = 0;
} else {
// Off/Clear
currentProgression.length = 0;
}
currentState = UI_MENU_MAIN;
saveSequence(true);
break;
case UI_EDIT_PROG_LENGTH:
case UI_EDIT_PROG_STEP_ROOT:
case UI_EDIT_PROG_STEP_TYPE:
case UI_EDIT_PROG_STEP_REPS:
currentState = UI_MENU_MAIN;
saveSequence(true);
break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
if (isPlaying) { if (isPlaying) {
@ -284,6 +365,20 @@ static void handleInput() {
} }
saveSequence(true); saveSequence(true);
break; break;
case UI_EDIT_CHANCE:
currentState = UI_MENU_MAIN;
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
if (!sequenceChangeScheduled) {
memcpy(nextSequence, sequence, sizeof(sequence));
}
SequenceGenerator::generateTrackData(randomizeTrack, theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
break;
case UI_RANDOMIZE_TRACK_EDIT: case UI_RANDOMIZE_TRACK_EDIT:
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
saveSequence(true); saveSequence(true);
@ -412,7 +507,26 @@ void loopUI() {
int nextTheme = random(1, 8); // Themes 1-7 int nextTheme = random(1, 8); // Themes 1-7
int repeats = random(1, 9); // 1-8 repeats int repeats = random(1, 9); // 1-8 repeats
if (currentProgression.length > 0) {
// Progression Mode
const ChordProgression& prog = currentProgression;
currentRoot = (globalRoot + prog.steps[progressionStep].rootOffset) % 12;
if (currentRoot < 0) currentRoot += 12;
currentScaleType = prog.steps[progressionStep].scaleType;
SequenceGenerator::updateScale();
queuedProgressionStep = progressionStep;
repeats = prog.steps[progressionStep].repeats;
if (progressionOrder == PROG_ORDER_SEQUENCE)
progressionStep = (progressionStep + 1) % prog.length;
else
progressionStep = random(prog.length);
} else {
// Random Mode
SequenceGenerator::pickRandomScaleType(nextTheme); SequenceGenerator::pickRandomScaleType(nextTheme);
}
SequenceGenerator::generateSequenceData(nextTheme, nextSequence); SequenceGenerator::generateSequenceData(nextTheme, nextSequence);
queuedTheme = nextTheme; queuedTheme = nextTheme;
nextSongRepeats = repeats; nextSongRepeats = repeats;