From 4a3f2b8eb2cfcddebbdd6e762fc8e108b4381346 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sat, 21 Feb 2026 23:19:51 +0100 Subject: [PATCH] Scale editor --- TrackerTypes.h | 5 ++- UIManager.cpp | 79 ++++++++++++++++++++++++++++++++ UIThread.cpp | 119 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 190 insertions(+), 13 deletions(-) diff --git a/TrackerTypes.h b/TrackerTypes.h index e836568..d8b0e3f 100644 --- a/TrackerTypes.h +++ b/TrackerTypes.h @@ -22,7 +22,10 @@ enum UIState { UI_EDIT_STEPS, UI_EDIT_FLAVOUR, UI_SETUP_PLAYMODE_EDIT, - UI_RANDOMIZE_TRACK_EDIT + UI_RANDOMIZE_TRACK_EDIT, + UI_SCALE_EDIT, + UI_SCALE_NOTE_EDIT, + UI_SCALE_TRANSPOSE }; inline void sortArray(int arr[], int size) { diff --git a/UIManager.cpp b/UIManager.cpp index 991a1ce..8f4c047 100644 --- a/UIManager.cpp +++ b/UIManager.cpp @@ -119,6 +119,85 @@ void UIManager::draw(UIState currentState, int menuSelection, display.setCursor(0, 50); display.println(F(" (Press to confirm)")); break; + case UI_SCALE_EDIT: + case UI_SCALE_NOTE_EDIT: + case UI_SCALE_TRANSPOSE: + display.println(F("EDIT SCALE")); + display.drawLine(0, 8, 128, 8, SSD1306_WHITE); + + int totalItems = numScaleNotes + 5; // Back + Randomize + Notes + Add + Remove + Transpose + int startIdx = 0; + if (menuSelection >= 4) startIdx = menuSelection - 3; + + int y = 12; + for (int i = startIdx; i < totalItems; i++) { + if (y > 54) break; + + if (i == menuSelection) { + display.fillRect(0, y, 75, 9, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); + } else { + display.setTextColor(SSD1306_WHITE); + } + display.setCursor(2, y + 1); + + if (i == 0) { + display.print(F("Back")); + } else if (i == 1) { + display.print(F("Randomize")); + } else if (i <= numScaleNotes + 1) { + int noteIdx = i - 2; + const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; + display.print(noteNames[scaleNotes[noteIdx]]); + if (currentState == UI_SCALE_NOTE_EDIT && i == menuSelection) { + display.print(F(" <")); + } + } else if (i == numScaleNotes + 2) { + display.print(F("Transpose")); + if (currentState == UI_SCALE_TRANSPOSE) { + display.print(F(" < >")); + } + } else if (i == numScaleNotes + 3) { + display.print(F("Add Note")); + } else if (i == numScaleNotes + 4) { + display.print(F("Remove Note")); + } + y += 9; + } + + // Piano Roll Preview + int px = 82; + int py = 20; + int wk_w = 5; + int wk_h = 20; + int bk_w = 4; + int bk_h = 12; + + // White keys: C, D, E, F, G, A, B + int whiteNotes[] = {0, 2, 4, 5, 7, 9, 11}; + for (int k = 0; k < 7; k++) { + bool active = false; + for (int j = 0; j < numScaleNotes; j++) { + if (scaleNotes[j] == whiteNotes[k]) { active = true; break; } + } + if (active) display.fillRect(px + k*6, py, wk_w, wk_h, SSD1306_WHITE); + else display.drawRect(px + k*6, py, wk_w, wk_h, SSD1306_WHITE); + } + + // Black keys: C#, D#, F#, G#, A# + int blackNotes[] = {1, 3, 6, 8, 10}; + int blackOffsets[] = {3, 9, 21, 27, 33}; + for (int k = 0; k < 5; k++) { + bool active = false; + for (int j = 0; j < numScaleNotes; j++) { + if (scaleNotes[j] == blackNotes[k]) { active = true; break; } + } + int bx = px + blackOffsets[k]; + display.fillRect(bx - 1, py - 1, bk_w + 2, bk_h + 2, SSD1306_BLACK); + if (active) display.fillRect(bx, py, bk_w, bk_h, SSD1306_WHITE); + else display.drawRect(bx, py, bk_w, bk_h, SSD1306_WHITE); + } + break; } display.display(); } diff --git a/UIThread.cpp b/UIThread.cpp index 2c1d6c5..e0ddb9a 100644 --- a/UIThread.cpp +++ b/UIThread.cpp @@ -18,6 +18,8 @@ static Step local_sequence[NUM_TRACKS][NUM_STEPS]; static void handleInput(); +static int scaleEditSelection = 0; +static int scaleEditNoteIndex = 0; static void drawUI(); static void updateLeds(); static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]); @@ -179,6 +181,38 @@ static void handleInput() { if (currentStrategyIndices[randomizeTrack] >= numStrategies) currentStrategyIndices[randomizeTrack] = 0; } break; + case UI_SCALE_EDIT: + scaleEditSelection += (delta > 0 ? 1 : -1); + if (scaleEditSelection < 0) scaleEditSelection = numScaleNotes + 4; + if (scaleEditSelection > numScaleNotes + 4) scaleEditSelection = 0; + break; + case UI_SCALE_NOTE_EDIT: + scaleNotes[scaleEditNoteIndex] += (delta > 0 ? 1 : -1); + if (scaleNotes[scaleEditNoteIndex] < 0) scaleNotes[scaleEditNoteIndex] = 11; + if (scaleNotes[scaleEditNoteIndex] > 11) scaleNotes[scaleEditNoteIndex] = 0; + midi.lock(); + midi.sendNoteOn(60 + scaleNotes[scaleEditNoteIndex], 100, midiChannels[randomizeTrack]); + midi.unlock(); + delay(50); + midi.lock(); + midi.sendNoteOff(60 + scaleNotes[scaleEditNoteIndex], midiChannels[randomizeTrack]); + midi.unlock(); + break; + case UI_SCALE_TRANSPOSE: + if (delta != 0) { + int shift = delta % 12; + if (shift < 0) shift += 12; + for(int i=0; i 0 ? 1 : -1); @@ -242,16 +276,8 @@ static void handleInput() { break; case MENU_ID_SCALE: - generateRandomScale(); - if (isPlaying) { - int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; - midi.lock(); - // Regenerate all tracks with new scale - generateSequenceData(theme, nextSequence); - sequenceChangeScheduled = true; - midi.unlock(); - } - saveSequence(true); + currentState = UI_SCALE_EDIT; + scaleEditSelection = 0; break; case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break; @@ -319,6 +345,71 @@ static void handleInput() { currentState = UI_MENU_MAIN; saveSequence(true); break; + case UI_SCALE_EDIT: + if (scaleEditSelection == 0) { + currentState = UI_MENU_MAIN; + saveSequence(true); + } else if (scaleEditSelection == 1) { + generateRandomScale(); + if (isPlaying) { + int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; + midi.lock(); + generateSequenceData(theme, nextSequence); + sequenceChangeScheduled = true; + midi.unlock(); + } + saveSequence(true); + } else if (scaleEditSelection <= numScaleNotes + 1) { + scaleEditNoteIndex = scaleEditSelection - 2; + currentState = UI_SCALE_NOTE_EDIT; + } else if (scaleEditSelection == numScaleNotes + 2) { + currentState = UI_SCALE_TRANSPOSE; + } else if (scaleEditSelection == numScaleNotes + 3) { + if (numScaleNotes < 12) { + int next = (numScaleNotes > 0) ? (scaleNotes[numScaleNotes-1] + 1) % 12 : 0; + scaleNotes[numScaleNotes] = next; + numScaleNotes++; + scaleEditSelection--; // Move cursor to the new note + if (isPlaying) { + int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; + midi.lock(); + generateSequenceData(theme, nextSequence); + sequenceChangeScheduled = true; + midi.unlock(); + } + saveSequence(true); + } + } else { + if (numScaleNotes > 1) { + numScaleNotes--; + scaleEditSelection--; + if (isPlaying) { + int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; + midi.lock(); + generateSequenceData(theme, nextSequence); + sequenceChangeScheduled = true; + midi.unlock(); + } + saveSequence(true); + } + } + break; + case UI_SCALE_NOTE_EDIT: + sortArray(scaleNotes, numScaleNotes); + if (isPlaying) { + int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; + midi.lock(); + generateSequenceData(theme, nextSequence); + sequenceChangeScheduled = true; + midi.unlock(); + } + currentState = UI_SCALE_EDIT; + saveSequence(true); + break; + case UI_SCALE_TRANSPOSE: + currentState = UI_SCALE_EDIT; + saveSequence(true); + break; } } } @@ -358,7 +449,11 @@ static void drawUI() { local_randomizeTrack = randomizeTrack; local_currentState = currentState; - local_menuSelection = menuSelection; + if (local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) { + local_menuSelection = scaleEditSelection; + } else { + local_menuSelection = menuSelection; + } local_midiChannel = midiChannels[local_randomizeTrack]; local_tempo = tempo; local_numSteps = numSteps; @@ -424,7 +519,7 @@ static void updateLeds() { // It's a TRACK section item (Track, Mute, Flavour, Mutation, Themes) ledDisplayMode = MODE_MONO; } - } else if (local_currentState == UI_EDIT_FLAVOUR || local_currentState == UI_RANDOMIZE_TRACK_EDIT) { + } else if (local_currentState == UI_EDIT_FLAVOUR || local_currentState == UI_RANDOMIZE_TRACK_EDIT || local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) { // These are entered from TRACK section items ledDisplayMode = MODE_MONO; }