#include "UIManager.h" #include "config.h" #include "SharedState.h" // --- HARDWARE CONFIGURATION --- #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C UIManager ui; UIManager::UIManager() : display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET) { } void UIManager::begin() { // Setup Display Wire.begin(); if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } display.clearDisplay(); display.display(); // Setup NeoPixel Matrix ledMatrix.begin(); } void UIManager::showMessage(const char* msg) { display.clearDisplay(); display.setCursor(10, 25); display.setTextColor(SSD1306_WHITE); display.setTextSize(2); display.print(msg); display.display(); delay(500); display.setTextSize(1); } void UIManager::draw(UIState currentState, int menuSelection, int midiChannel, int tempo, MelodyStrategy* currentStrategy, int queuedTheme, int currentThemeIndex, int numScaleNotes, const int* scaleNotes, int melodySeed, int currentTrackNumSteps, bool mutationEnabled, bool songModeEnabled, const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); switch(currentState) { case UI_MENU_MAIN: drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, currentTrackNumSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities); break; case UI_SETUP_CHANNEL_EDIT: drawEditScreen("SET MIDI CHANNEL", "CH: ", midiChannel); break; case UI_EDIT_TEMPO: drawEditScreen("SET TEMPO", "BPM: ", tempo); break; case UI_EDIT_STEPS: drawEditScreen("SET STEPS", "LEN: ", currentTrackNumSteps); break; case UI_EDIT_FLAVOUR: drawEditScreen("SET FLAVOUR", "", currentStrategy->getName()); break; 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]); } break; case UI_EDIT_INTENSITY: drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10); break; case UI_RANDOMIZE_TRACK_EDIT: drawEditScreen("SET TRACK", "TRK: ", randomizeTrack + 1); break; } display.display(); } void UIManager::drawNumberEditor(const char* title, int value, int minVal, int maxVal) { display.println(title); display.drawLine(0, 8, 128, 8, SSD1306_WHITE); // Display value display.setCursor(20, 20); display.setTextSize(2); display.print(value); // Graphical bar int barWidth = 100; int barX = (SCREEN_WIDTH - barWidth) / 2; int barY = 40; int barHeight = 10; float percentage = (float)(value - minVal) / (maxVal - minVal); int fillWidth = (int)(percentage * barWidth); display.drawRect(barX, barY, barWidth, barHeight, SSD1306_WHITE); display.fillRect(barX, barY, fillWidth, barHeight, SSD1306_WHITE); display.setTextSize(1); display.setCursor(0, 54); display.println(F(" (Press to confirm)")); } void UIManager::drawEditScreen(const char* title, const char* label, const char* valueStr) { display.println(title); display.drawLine(0, 8, 128, 8, SSD1306_WHITE); display.setCursor(20, 25); display.setTextSize(2); if (label && *label) display.print(label); display.print(valueStr); display.setTextSize(1); display.setCursor(0, 50); display.println(F(" (Press to confirm)")); } void UIManager::drawEditScreen(const char* title, const char* label, int value) { char buf[16]; itoa(value, buf, 10); drawEditScreen(title, label, buf); } void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName, int queuedTheme, int currentThemeIndex, int numScaleNotes, const int* scaleNotes, int melodySeed, int currentTrackNumSteps, bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) { // Calculate visual cursor position and scroll offset int visualCursor = 0; for(int i=0; i= MAX_LINES) { startVisualIndex = visualCursor - (MAX_LINES - 1); } int currentVisualIndex = 0; int y = 0; for (int i = 0; i < menuItemsCount; i++) { if (!isItemVisible(i)) continue; if (currentVisualIndex >= startVisualIndex) { if (y > 55) break; if (i == selection) { display.fillRect(0, y, 128, 9, SSD1306_WHITE); display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); } else { display.setTextColor(SSD1306_WHITE); } int x = 2 + (menuItems[i].indentLevel * 6); display.setCursor(x, y + 1); if (menuItems[i].isGroup) { 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(" *")); } } MenuItemID id = menuItems[i].id; if (id == MENU_ID_CHANNEL) { display.print(F(": ")); display.print(midiChannel); } // Dynamic values if (id == MENU_ID_PLAYBACK) { display.print(F(": ")); display.print(isPlaying ? F("ON") : F("OFF")); } else if (id == MENU_ID_MELODY) { display.print(F(": ")); display.print(melodySeed); } 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_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_INTENSITY) { display.print(F(": ")); display.print(trackIntensities[randomizeTrack]); int val = trackIntensities[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) / 10, h - 2, color); } 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")); } if (id >= MENU_ID_THEME_1 && id <= MENU_ID_THEME_7) { int themeIdx = id - MENU_ID_THEME_1 + 1; if (queuedTheme == themeIdx) display.print(F(" [NEXT]")); if (currentThemeIndex == themeIdx) display.print(F(" *")); } y += 9; } currentVisualIndex++; } } void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, UIState currentState, bool songModeEnabled, int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode, int selectedTrack, const int* numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute) { ledMatrix.update(sequence, playbackStep, isPlaying, currentState, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled, playMode, selectedTrack, numSteps, trackMute); }