diff --git a/SharedState.cpp b/SharedState.cpp index b51ea59..889e4e9 100644 --- a/SharedState.cpp +++ b/SharedState.cpp @@ -23,6 +23,8 @@ MenuItem menuItems[] = { { "Playback", MENU_ID_PLAYBACK, false, false, 1 }, { "Melody", MENU_ID_MELODY, false, false, 1 }, { "Scale", MENU_ID_SCALE, false, false, 1 }, + { "Root", MENU_ID_ROOT, false, false, 1 }, + { "Type", MENU_ID_SCALE_TYPE, false, false, 1 }, { "Tempo", MENU_ID_TEMPO, false, false, 1 }, { "Song Mode", MENU_ID_SONG_MODE, false, false, 1 }, { "Track", MENU_ID_GROUP_TRACK, true, true, 0 }, @@ -112,7 +114,10 @@ int numScaleNotes = 0; int melodySeeds[NUM_TRACKS]; volatile int queuedTheme = -1; volatile int currentThemeIndex = 1; -extern const uint32_t EEPROM_MAGIC = 0x4242424E; +int currentRoot = 0; // C +int currentScaleType = 1; // Major +int enabledScaleTypes = 2; // Major (1<<1) +extern const uint32_t EEPROM_MAGIC = 0x42424250; MelodyStrategy* strategies[] = { new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), diff --git a/SharedState.h b/SharedState.h index b339854..0e962d3 100644 --- a/SharedState.h +++ b/SharedState.h @@ -19,6 +19,8 @@ enum MenuItemID { MENU_ID_PLAYBACK, MENU_ID_MELODY, MENU_ID_SCALE, + MENU_ID_ROOT, + MENU_ID_SCALE_TYPE, MENU_ID_TEMPO, MENU_ID_SONG_MODE, @@ -73,6 +75,9 @@ extern int numScaleNotes; extern int melodySeeds[NUM_TRACKS]; extern volatile int queuedTheme; extern volatile int currentThemeIndex; +extern int currentRoot; +extern int currentScaleType; +extern int enabledScaleTypes; extern const uint32_t EEPROM_MAGIC; extern MelodyStrategy* strategies[]; diff --git a/TrackerTypes.h b/TrackerTypes.h index 601be71..bc08af4 100644 --- a/TrackerTypes.h +++ b/TrackerTypes.h @@ -26,7 +26,9 @@ enum UIState { UI_RANDOMIZE_TRACK_EDIT, UI_SCALE_EDIT, UI_SCALE_NOTE_EDIT, - UI_SCALE_TRANSPOSE + UI_SCALE_TRANSPOSE, + UI_EDIT_ROOT, + UI_EDIT_SCALE_TYPE }; inline void sortArray(int arr[], int size) { diff --git a/UIManager.cpp b/UIManager.cpp index 6f53534..fc7251c 100644 --- a/UIManager.cpp +++ b/UIManager.cpp @@ -110,6 +110,39 @@ void UIManager::draw(UIState currentState, int menuSelection, display.setCursor(0, 50); display.println(F(" (Press to confirm)")); break; + case UI_EDIT_ROOT: + display.println(F("SET ROOT NOTE")); + display.drawLine(0, 8, 128, 8, SSD1306_WHITE); + display.setCursor(20, 25); + display.setTextSize(2); + { + const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; + display.print(noteNames[currentRoot % 12]); + } + display.setTextSize(1); + display.setCursor(0, 50); + display.println(F(" (Press to confirm)")); + break; + case UI_EDIT_SCALE_TYPE: + display.println(F("ENABLED SCALES")); + display.drawLine(0, 8, 128, 8, SSD1306_WHITE); + display.setCursor(0, 20); + if (menuSelection < 10) { + const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"}; + display.setTextSize(2); + display.print(typeNames[menuSelection]); + display.setTextSize(1); + display.setCursor(0, 45); + bool isEnabled = (enabledScaleTypes & (1 << menuSelection)); + display.print(isEnabled ? F("[ENABLED]") : F("[DISABLED]")); + } else { + display.setTextSize(2); + display.print(F("Back")); + } + display.setTextSize(1); + display.setCursor(0, 55); + display.println(F(" (Press to toggle)")); + break; case UI_EDIT_INTENSITY: drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10); break; @@ -294,6 +327,14 @@ 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]); + } + else if (id == MENU_ID_SCALE_TYPE) { + const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"}; + display.print(F(": ")); if (currentScaleType >= 0 && currentScaleType < 10) display.print(typeNames[currentScaleType]); + } 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); } diff --git a/UIThread.cpp b/UIThread.cpp index e2b22d3..338a015 100644 --- a/UIThread.cpp +++ b/UIThread.cpp @@ -22,6 +22,7 @@ static Step local_sequence[NUM_TRACKS][NUM_STEPS]; static void handleInput(); static int scaleEditSelection = 0; +static int scaleTypeEditIndex = 0; static int scaleEditNoteIndex = 0; static void drawUI(); static void updateLeds(); @@ -29,6 +30,8 @@ static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]); static void savePatch(int bank, int slot); static void loadPatch(int bank, int slot); +static void updateScale(); +static void pickRandomScaleType(); void saveSequence(bool quiet) { midi.lock(); @@ -50,6 +53,9 @@ void saveSequence(bool quiet) { for(int i=0; i 12) numScaleNotes = 0; for (int i = 0; i<12; i++) { @@ -125,6 +134,9 @@ static void savePatch(int bank, int slot) { int addr = 512 + patchIndex * 256; // Start after main save, 256 bytes per patch 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); @@ -150,6 +162,9 @@ static void loadPatch(int bank, int slot) { int addr = 512 + patchIndex * 256; 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++) { @@ -225,6 +240,7 @@ static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) { } void generateTheme(int themeType) { + pickRandomScaleType(); generateSequenceData(themeType, local_sequence); Serial.println(F("Generating theme.")); @@ -249,6 +265,67 @@ void mutateSequence(Step (*target)[NUM_STEPS]) { } } +static void updateScale() { + // 0: Chromatic, 1: Major, 2: Minor, 3: Harm Min, 4: Pent Maj, 5: Pent Min, 6: Chord Maj, 7: Chord Min, 8: Chord Dim, 9: Chord 7 + int intervals[12]; + int count = 0; + + switch(currentScaleType) { + case 0: // Chromatic + for(int i=0; i<12; i++) intervals[count++] = i; + break; + case 1: // Major + intervals[0]=0; intervals[1]=2; intervals[2]=4; intervals[3]=5; intervals[4]=7; intervals[5]=9; intervals[6]=11; count=7; + break; + case 2: // Minor + intervals[0]=0; intervals[1]=2; intervals[2]=3; intervals[3]=5; intervals[4]=7; intervals[5]=8; intervals[6]=10; count=7; + break; + case 3: // Harmonic Minor + intervals[0]=0; intervals[1]=2; intervals[2]=3; intervals[3]=5; intervals[4]=7; intervals[5]=8; intervals[6]=11; count=7; + break; + case 4: // Pentatonic Major + intervals[0]=0; intervals[1]=2; intervals[2]=4; intervals[3]=7; intervals[4]=9; count=5; + break; + case 5: // Pentatonic Minor + intervals[0]=0; intervals[1]=3; intervals[2]=5; intervals[3]=7; intervals[4]=10; count=5; + break; + case 6: // Chord Major + intervals[0]=0; intervals[1]=4; intervals[2]=7; count=3; + break; + case 7: // Chord Minor + intervals[0]=0; intervals[1]=3; intervals[2]=7; count=3; + break; + case 8: // Chord Dim + intervals[0]=0; intervals[1]=3; intervals[2]=6; count=3; + break; + case 9: // Chord 7 + intervals[0]=0; intervals[1]=4; intervals[2]=7; intervals[3]=10; count=4; + break; + } + + midi.lock(); + numScaleNotes = count; + for(int i=0; i 0) { + currentScaleType = candidates[random(count)]; + updateScale(); + } +} + static void handleInput() { // Handle Encoder Rotation int delta = 0; @@ -289,6 +366,21 @@ static void handleInput() { if (numSteps[randomizeTrack] < 1) numSteps[randomizeTrack] = 1; 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; + updateScale(); + if (isPlaying) { + sequenceChangeScheduled = true; + generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence); + } + break; + case UI_EDIT_SCALE_TYPE: + scaleTypeEditIndex += delta; + if (scaleTypeEditIndex < 0) scaleTypeEditIndex = 10; + if (scaleTypeEditIndex > 10) scaleTypeEditIndex = 0; + break; case UI_EDIT_FLAVOUR: { currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1); @@ -408,6 +500,11 @@ static void handleInput() { scaleEditSelection = 0; break; + case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break; + case MENU_ID_SCALE_TYPE: + currentState = UI_EDIT_SCALE_TYPE; + scaleTypeEditIndex = 0; + break; case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break; case MENU_ID_STEPS: currentState = UI_EDIT_STEPS; break; @@ -467,6 +564,19 @@ static void handleInput() { currentState = UI_MENU_MAIN; saveSequence(true); break; + case UI_EDIT_ROOT: + currentState = UI_MENU_MAIN; + saveSequence(true); + break; + case UI_EDIT_SCALE_TYPE: + if (scaleTypeEditIndex == 10) { + currentState = UI_MENU_MAIN; + saveSequence(true); + } else { + enabledScaleTypes ^= (1 << scaleTypeEditIndex); + if (enabledScaleTypes == 0) enabledScaleTypes = (1 << scaleTypeEditIndex); // Prevent disabling all + } + break; case UI_EDIT_FLAVOUR: currentState = UI_MENU_MAIN; if (isPlaying) { @@ -606,6 +716,8 @@ static void drawUI() { local_currentState = currentState; if (local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) { local_menuSelection = scaleEditSelection; + } else if (local_currentState == UI_EDIT_SCALE_TYPE) { + local_menuSelection = scaleTypeEditIndex; } else { local_menuSelection = menuSelection; }