From 99add4e2ac3bf1de30a6e768a85af9fefc575078 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sat, 7 Mar 2026 21:27:08 +0100 Subject: [PATCH] Song progression --- Persistence.cpp | 15 +++--- PlaybackThread.cpp | 1 + SequenceGenerator.cpp | 13 +---- SharedState.cpp | 62 +++++++++++++++++++----- SharedState.h | 31 +++++++----- TrackerTypes.h | 27 ++++++++++- UIManager.cpp | 77 +++++++++++++++++++++++------ UIThread.cpp | 110 ++++++++++++++++++++++++++++++++++++++---- 8 files changed, 268 insertions(+), 68 deletions(-) diff --git a/Persistence.cpp b/Persistence.cpp index ff4cc5b..300f875 100644 --- a/Persistence.cpp +++ b/Persistence.cpp @@ -26,9 +26,11 @@ void Persistence::saveSequence(bool quiet) { for(int i=0; i 12) numScaleNotes = 0; for (int i = 0; i<12; i++) { @@ -109,7 +114,6 @@ void Persistence::savePatch(int bank, int slot) { midi.lock(); EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType); - EEPROM.put(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); for (int i = 0; i < 12; i++) { EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int); @@ -137,7 +141,6 @@ void Persistence::loadPatch(int bank, int slot, Step (*scratchBuffer)[NUM_STEPS] midi.lock(); EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot); EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType); - EEPROM.get(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0; for (int i = 0; i < 12; i++) { diff --git a/PlaybackThread.cpp b/PlaybackThread.cpp index 41f7ffc..77179ea 100644 --- a/PlaybackThread.cpp +++ b/PlaybackThread.cpp @@ -97,6 +97,7 @@ static void handlePlayback() { if (sequenceChangeScheduled) { memcpy(local_sequence, local_nextSequence, sizeof(local_sequence)); midi.lock(); + visualProgressionStep = queuedProgressionStep; memcpy(sequence, local_sequence, sizeof(local_sequence)); sequenceChangeScheduled = false; midi.unlock(); diff --git a/SequenceGenerator.cpp b/SequenceGenerator.cpp index 88de16f..0a3c737 100644 --- a/SequenceGenerator.cpp +++ b/SequenceGenerator.cpp @@ -80,15 +80,6 @@ void SequenceGenerator::pickRandomScaleType(int themeType) { for(int i=0; i 0) { - currentScaleType = candidates[random(count)]; - SequenceGenerator::updateScale(); - } + currentScaleType = random(10); + SequenceGenerator::updateScale(); } diff --git a/SharedState.cpp b/SharedState.cpp index 9910d6a..cfff9e2 100644 --- a/SharedState.cpp +++ b/SharedState.cpp @@ -21,22 +21,34 @@ bool protectedMode = false; volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10}; // 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[] = { { "Main", MENU_ID_GROUP_MAIN, true, true, 0 }, { "Playback", MENU_ID_PLAYBACK, false, false, 1 }, { "Melody", MENU_ID_MELODY, false, false, 1 }, { "Root", MENU_ID_ROOT, false, false, 1 }, - { "Scale Type", MENU_ID_SCALE_TYPE, true, false, 1 }, - { "Chromatic", MENU_ID_SCALE_TYPE_CHROMATIC, false, false, 2 }, - { "Major", MENU_ID_SCALE_TYPE_MAJOR, false, false, 2 }, - { "Minor", MENU_ID_SCALE_TYPE_MINOR, false, false, 2 }, - { "Harm. Min", MENU_ID_SCALE_TYPE_HARM_MIN, false, false, 2 }, - { "Pent. Maj", MENU_ID_SCALE_TYPE_PENT_MAJ, false, false, 2 }, - { "Pent. Min", MENU_ID_SCALE_TYPE_PENT_MIN, false, false, 2 }, - { "Chord Maj", MENU_ID_SCALE_TYPE_CHORD_MAJ, false, false, 2 }, - { "Chord Min", MENU_ID_SCALE_TYPE_CHORD_MIN, false, false, 2 }, - { "Chord Dim", MENU_ID_SCALE_TYPE_CHORD_DIM, false, false, 2 }, - { "Chord 7", MENU_ID_SCALE_TYPE_CHORD_7, false, false, 2 }, + { "Progression", MENU_ID_GROUP_PROGRESSION, true, false, 1 }, + { "Order", MENU_ID_PROG_ORDER, false, false, 2 }, + { "Apply Template", MENU_ID_PROG_APPLY_TEMPLATE, false, false, 2 }, + { "Sequence", MENU_ID_GROUP_PROG_SEQUENCE, true, false, 2 }, + { "Length", MENU_ID_PROG_LENGTH, false, false, 3 }, + PROG_STEP_ITEMS(1), + PROG_STEP_ITEMS(2), + PROG_STEP_ITEMS(3), + PROG_STEP_ITEMS(4), + PROG_STEP_ITEMS(5), + 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 }, { "Song Mode", MENU_ID_SONG_MODE, false, false, 1 }, { "Track", MENU_ID_GROUP_TRACK, true, true, 0 }, @@ -106,6 +118,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; for (int i = index - 1; i >= 0; i--) { if (menuItems[i].indentLevel < menuItems[index].indentLevel) { @@ -126,10 +145,27 @@ int numScaleNotes = 0; int melodySeeds[NUM_TRACKS]; volatile int queuedTheme = -1; volatile int currentThemeIndex = 1; +int globalRoot = 0; // The "Key" of the song int currentRoot = 0; // C int currentScaleType = 1; // Major -int enabledScaleTypes = 2; // Major (1<<1) -extern const uint32_t EEPROM_MAGIC = 0x42424250; +extern const uint32_t EEPROM_MAGIC = 0x42424253; + +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[] = { new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), diff --git a/SharedState.h b/SharedState.h index 429ec55..c75903d 100644 --- a/SharedState.h +++ b/SharedState.h @@ -19,17 +19,15 @@ enum MenuItemID { MENU_ID_PLAYBACK, MENU_ID_MELODY, MENU_ID_ROOT, - MENU_ID_SCALE_TYPE, - MENU_ID_SCALE_TYPE_CHROMATIC, - MENU_ID_SCALE_TYPE_MAJOR, - MENU_ID_SCALE_TYPE_MINOR, - MENU_ID_SCALE_TYPE_HARM_MIN, - MENU_ID_SCALE_TYPE_PENT_MAJ, - MENU_ID_SCALE_TYPE_PENT_MIN, - MENU_ID_SCALE_TYPE_CHORD_MAJ, - MENU_ID_SCALE_TYPE_CHORD_MIN, - MENU_ID_SCALE_TYPE_CHORD_DIM, - MENU_ID_SCALE_TYPE_CHORD_7, + + MENU_ID_GROUP_PROGRESSION, + MENU_ID_PROG_ORDER, + MENU_ID_PROG_APPLY_TEMPLATE, + MENU_ID_GROUP_PROG_SEQUENCE, + MENU_ID_PROG_LENGTH, + MENU_ID_PROG_STEP_START, + MENU_ID_PROG_STEP_END = MENU_ID_PROG_STEP_START + (MAX_PROG_STEPS * 3), + MENU_ID_TEMPO, MENU_ID_SONG_MODE, @@ -84,10 +82,19 @@ extern int numScaleNotes; extern int melodySeeds[NUM_TRACKS]; extern volatile int queuedTheme; extern volatile int currentThemeIndex; +extern int globalRoot; extern int currentRoot; extern volatile int trackIntensity[NUM_TRACKS]; 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 MelodyStrategy* strategies[]; diff --git a/TrackerTypes.h b/TrackerTypes.h index 7ae2063..f312f2b 100644 --- a/TrackerTypes.h +++ b/TrackerTypes.h @@ -10,6 +10,25 @@ struct Step { 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 { MODE_MONO, MODE_POLY @@ -24,7 +43,13 @@ enum UIState { UI_EDIT_INTENSITY, UI_SETUP_PLAYMODE_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) { diff --git a/UIManager.cpp b/UIManager.cpp index 8423f90..6f2602b 100644 --- a/UIManager.cpp +++ b/UIManager.cpp @@ -72,9 +72,34 @@ void UIManager::draw(UIState currentState, int menuSelection, case UI_EDIT_ROOT: { 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; + 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: drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10); break; @@ -154,6 +179,8 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i if (currentVisualIndex >= startVisualIndex) { if (y > 55) break; + MenuItemID id = menuItems[i].id; + if (i == selection) { display.fillRect(0, y, 128, 9, SSD1306_WHITE); display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); @@ -168,21 +195,17 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i display.print(menuItems[i].expanded ? F("v ") : F("> ")); } - 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(" *")); + 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(", ")); } } - - MenuItemID id = menuItems[i].id; + display.print(menuItems[i].label); if (id == MENU_ID_CHANNEL) { display.print(F(": ")); display.print(midiChannel); @@ -195,13 +218,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_ROOT) { 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_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_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_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) { display.print(F(": ")); display.print(trackIntensities[randomizeTrack]); diff --git a/UIThread.cpp b/UIThread.cpp index 9f552e9..2088ce1 100644 --- a/UIThread.cpp +++ b/UIThread.cpp @@ -94,15 +94,58 @@ static void handleInput() { if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS; break; case UI_EDIT_ROOT: - currentRoot += delta; - if (currentRoot < 0) currentRoot = 11; - if (currentRoot > 11) currentRoot = 0; + globalRoot += delta; + if (globalRoot < 0) globalRoot = 11; + if (globalRoot > 11) globalRoot = 0; + currentRoot = globalRoot; // Reset current to global SequenceGenerator::updateScale(); if (isPlaying) { sequenceChangeScheduled = true; SequenceGenerator::generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence); } 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: { currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1); @@ -186,6 +229,17 @@ static void handleInput() { 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_STEPS: currentState = UI_EDIT_STEPS; break; @@ -207,12 +261,6 @@ static void handleInput() { case MENU_ID_RESET: factoryReset(); break; 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) { const int selectedTheme = menuItems[menuSelection].id - MENU_ID_THEME_1 + 1; if (isPlaying) { @@ -256,6 +304,29 @@ static void handleInput() { currentState = UI_MENU_MAIN; saveSequence(true); 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: currentState = UI_MENU_MAIN; if (isPlaying) { @@ -412,7 +483,26 @@ void loopUI() { int nextTheme = random(1, 8); // Themes 1-7 int repeats = random(1, 9); // 1-8 repeats - SequenceGenerator::pickRandomScaleType(nextTheme); + 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::generateSequenceData(nextTheme, nextSequence); queuedTheme = nextTheme; nextSongRepeats = repeats;