// DisplayHandler.cpp #include "DisplayHandler.h" #include "Mod.h" #include "globals.h" #include "pico/stdlib.h" #include #include #include "hardware/i2c.h" #include "pico-ssd1306/shapeRenderer/ShapeRenderer.h" #include "pico-ssd1306/ssd1306.h" #include "pico-ssd1306/textRenderer/TextRenderer.h" pico_ssd1306::SSD1306 *display = nullptr; extern void update_BPM(bool up); DisplayHandler::DisplayHandler(Gate *outputs[]) { this->outputs = outputs; currentScreen = 0; currentOut = -1; updateScreen = 1; cursorPosition = 0; mainMaxCursorPosition = 10; outMaxCursorPosition = std::size(out_pages) - 1; cursorClick = 0; } void DisplayHandler::setup() { i2c_init(i2c1, 400 * 1000); gpio_set_function(SCREEN_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(SCREEN_SCL_PIN, GPIO_FUNC_I2C); gpio_pull_up(SCREEN_SDA_PIN); gpio_pull_up(SCREEN_SCL_PIN); display = new pico_ssd1306::SSD1306(i2c1, 0x3C, pico_ssd1306::Size::W128xH64); display->setOrientation(0); } void DisplayHandler::moveCursor(bool dir) { if (onOutScreen) { if (cursorClick == 0) { if (dir == 1) { cursorPosition++; currentScreen++; } else { cursorPosition--; currentScreen--; } if (cursorPosition > outMaxCursorPosition) { cursorPosition = 0; currentScreen = 0; } if (cursorPosition < 0) { cursorPosition = outMaxCursorPosition; currentScreen = outMaxCursorPosition; } } else { // click = 1 if (currentScreen == 1) { // mod screen outputs[currentOut]->editing = 1; if (dir == 1) { outputs[currentOut]->modifierSelectionIndex++; } else { outputs[currentOut]->modifierSelectionIndex--; } if (outputs[currentOut]->modifierSelectionIndex < 0) { outputs[currentOut]->modifierSelectionIndex = 0; } if (outputs[currentOut]->modifierSelectionIndex > std::size(MOD_TYPES) - 1) { outputs[currentOut]->modifierSelectionIndex = std::size(MOD_TYPES) - 1; } } else if (currentScreen == 2) { // shape control int currentShape = (int)outputs[currentOut]->shape; if (dir == 1) { currentShape++; } else { currentShape--; } if (currentShape >= SHAPE_COUNT) { currentShape = 0; } if (currentShape < 0) { currentShape = SHAPE_COUNT - 1; } outputs[currentOut]->shape = (WaveShape)currentShape; outputs[currentOut]->writeAnalog(0); } else if (currentScreen == 3) { // level control outputs[currentOut]->editing = 1; if (dir == 1) { outputs[currentOut]->level++; } else { outputs[currentOut]->level--; } if (outputs[currentOut]->level > 100) { outputs[currentOut]->level = 100; } if (outputs[currentOut]->level < 1) { outputs[currentOut]->level = 1; } } else if (currentScreen == 4) { // width control outputs[currentOut]->editing = 1; if (dir == 1) { outputs[currentOut]->width++; } else { outputs[currentOut]->width--; } if (outputs[currentOut]->width > 100) { outputs[currentOut]->width = 100; } if (outputs[currentOut]->width < 1) { outputs[currentOut]->width = 1; } outputs[currentOut]->setWidth(outputs[currentOut]->width); } else if (currentScreen == 5) { // SWING outputs[currentOut]->editing = 1; if (dir == 1) { outputs[currentOut]->swing++; } else { outputs[currentOut]->swing--; } if (outputs[currentOut]->swing > 100) { outputs[currentOut]->swing = 100; } if (outputs[currentOut]->swing < 50) { outputs[currentOut]->swing = 50; } } else if (currentScreen == 6) { // PROBABILITY outputs[currentOut]->editing = 1; if (dir == 1) { outputs[currentOut]->p++; } else { outputs[currentOut]->p--; } if (outputs[currentOut]->p > 100) { outputs[currentOut]->p = 100; } if (outputs[currentOut]->p < 0) { outputs[currentOut]->p = 0; } } else if (currentScreen == 7) { // STICKY outputs[currentOut]->sticky ^= true; } else if (currentScreen == 8) { // CV1 Active matrix.slots[outputs[currentOut]->slotIdx1].active ^= true; } else if (currentScreen == 9) { // CV1 Source if (dir == 1) { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx++; if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx == currentOut) { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx++; } } else { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx--; if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx == currentOut) { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx--; } } if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx > 7) { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 0; } if (matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx < 0) { matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx = 7; } } else if (currentScreen == 10) { // CV1 DEST int currentDest = (int)matrix.slots[outputs[currentOut]->slotIdx1].destParam; if (dir == 1) { currentDest++; } else { currentDest--; } if (currentDest >= DEST_COUNT) { currentDest = 0; } if (currentDest < 0) { currentDest = DEST_COUNT - 1; } matrix.slots[outputs[currentOut]->slotIdx1].destParam = (ModDest)currentDest; } else if (currentScreen == 11) { // CV1 AMT if (dir == 1) { matrix.slots[outputs[currentOut]->slotIdx1].amount++; } else { matrix.slots[outputs[currentOut]->slotIdx1].amount--; } } else if (currentScreen == 12) { // CV1 INV matrix.slots[outputs[currentOut]->slotIdx1].inverted ^= true; } else if (currentScreen == 13) { // CV2 Active matrix.slots[outputs[currentOut]->slotIdx2].active ^= true; } else if (currentScreen == 14) { // CV2 Source if (dir == 1) { matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx++; } else { matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx--; } if (matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx > 7) { matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx = 0; } if (matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx < 0) { matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx = 7; } } else if (currentScreen == 15) { // CV2 DEST int currentDest = (int)matrix.slots[outputs[currentOut]->slotIdx2].destParam; if (dir == 1) { currentDest++; } else { currentDest--; } if (currentDest >= DEST_COUNT) { currentDest = 0; } if (currentDest < 0) { currentDest = DEST_COUNT - 1; } matrix.slots[outputs[currentOut]->slotIdx2].destParam = (ModDest)currentDest; } else if (currentScreen == 16) { // CV2 AMT if (dir == 1) { matrix.slots[outputs[currentOut]->slotIdx2].amount++; } else { matrix.slots[outputs[currentOut]->slotIdx2].amount--; } } else if (currentScreen == 17) { // CV2 INV matrix.slots[outputs[currentOut]->slotIdx2].inverted ^= true; } else if (currentScreen == 18) { // MUTE outputs[currentOut]->editing = 1; outputs[currentOut]->isEnabled ^= true; } } } else { if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen update_BPM(dir); } else { if (dir == 1) { cursorPosition++; } else { cursorPosition--; } if (cursorPosition > mainMaxCursorPosition) { cursorPosition = 0; } if (cursorPosition < 0) { cursorPosition = mainMaxCursorPosition; } } } updateScreen = 1; } void DisplayHandler::handleClick() { cursorClick ^= true; if (onOutScreen) { if (currentScreen == 0) { // exit screen cursorPosition = currentOut + 1; currentOut = -1; currentScreen = 0; onOutScreen = 0; cursorClick = false; } if (currentScreen == 1 && outputs[currentOut]->editing == 1) { outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex); outputs[currentOut]->editing = 0; cursorClick = false; } } else { if (currentScreen == 0) { // on main screen if (cursorPosition == 0) { // Change BPM } else if (cursorPosition > 0 && cursorPosition < 9) { // go to out screen currentOut = cursorPosition - 1; cursorPosition = 1; currentScreen = 1; onOutScreen = 1; cursorClick = 0; } else if (cursorPosition == 9) { // PLAY/PAUSE BUTTON PLAY ^= true; } } } updateScreen = 1; } void DisplayHandler::render() { if (updateScreen) { display->clear(); if (currentScreen == 0 && currentOut == -1) { // main screen renderMainPage(); } else if (currentOut != -1) { renderOutPage(); } display->sendBuffer(); updateScreen = 0; } } void DisplayHandler::renderMainPage() { std::string bpm_string = "BPM: " + std::to_string(BPM); if (cursorPosition == 0) { if (cursorClick == 1) { pico_ssd1306::fillRect(display, 0, 0, 100, 18); pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2, pico_ssd1306::WriteMode::SUBTRACT); } else { pico_ssd1306::drawRect(display, 0, 0, 100, 18, pico_ssd1306::WriteMode::ADD); pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2); } } else { pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2); } uint8_t cursor_x = 2; uint8_t cursor_y = 25; for (uint8_t i = 1; i < 9; i++) { if (i == 5) { cursor_x = 2; cursor_y += 20; } if (cursorPosition == i) { pico_ssd1306::drawRect(display, cursor_x - 2, cursor_y - 4, cursor_x + 14, cursor_y + 16, pico_ssd1306::WriteMode::ADD); } pico_ssd1306::drawText(display, font_12x16, std::to_string(i).c_str(), cursor_x, cursor_y); cursor_x += 30; } if (cursorPosition == 9) { // PLAY BUTTON pico_ssd1306::fillRect(display, 120, 8, 130, 18, pico_ssd1306::WriteMode::ADD); pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10, pico_ssd1306::WriteMode::SUBTRACT); } else { pico_ssd1306::drawText(display, font_8x8, PLAY ? ">" : "#", 120, 10); } if (cursorPosition == 10) { // OPTIONS BUTTON pico_ssd1306::fillRect(display, 120, 28, 130, 38, pico_ssd1306::WriteMode::ADD); pico_ssd1306::drawText(display, font_8x8, "*", 120, 30, pico_ssd1306::WriteMode::SUBTRACT); } else { pico_ssd1306::drawText(display, font_8x8, "*", 120, 30); } } void DisplayHandler::renderOutPage() { uint8_t visualOut = currentOut + 1; std::string title = std::to_string(visualOut) + "| " + out_pages[currentScreen]; pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2); std::string param_string; if (currentScreen == 0) { // exit screen param_string = "<--"; } else if (currentScreen == 1) { // modifier screen uint8_t modifier_selection_index = outputs[currentOut]->modifierSelectionIndex; param_string = MODIFIER_STRINGS[modifier_selection_index]; } else if (currentScreen == 2) { // shape screen param_string = waveShapeToString(outputs[currentOut]->shape); } else if (currentScreen == 3) { // level screen param_string = std::to_string(outputs[currentOut]->level) + "%"; } else if (currentScreen == 4) { // Width screen param_string = std::to_string(outputs[currentOut]->width) + "%"; } else if (currentScreen == 5) { // Swing screen param_string = std::to_string(outputs[currentOut]->swing) + "%"; } else if (currentScreen == 6) { // Probability screen param_string = std::to_string(outputs[currentOut]->p) + "%"; } else if (currentScreen == 7) { // STICKY Screen param_string = outputs[currentOut]->sticky ? "ON" : "OFF"; } else if (currentScreen == 8) { // CV1 Active Screen param_string = matrix.slots[outputs[currentOut]->slotIdx1].active ? "ON" : "OFF"; } else if (currentScreen == 9) { // CV1 Source Chan Screen param_string = std::to_string( matrix.slots[outputs[currentOut]->slotIdx1].sourceIdx + 1); } else if (currentScreen == 10) { // CV1 DEST Screen param_string = modDestToString( (ModDest)matrix.slots[outputs[currentOut]->slotIdx1].destParam); } else if (currentScreen == 11) { // CV1 AMT Screen param_string = std::to_string( (int)matrix.slots[outputs[currentOut]->slotIdx1].amount) + "%"; } else if (currentScreen == 12) { // CV1 INV Screen param_string = matrix.slots[outputs[currentOut]->slotIdx1].inverted ? "ON" : "OFF"; } else if (currentScreen == 13) { // CV2 Active Screen param_string = matrix.slots[outputs[currentOut]->slotIdx2].active ? "ON" : "OFF"; } else if (currentScreen == 14) { // CV2 Source Chan Screen param_string = std::to_string( matrix.slots[outputs[currentOut]->slotIdx2].sourceIdx + 1); } else if (currentScreen == 15) { // CV2 DEST Screen param_string = modDestToString( (ModDest)matrix.slots[outputs[currentOut]->slotIdx2].destParam); } else if (currentScreen == 16) { // CV2 AMT Screen param_string = std::to_string( (int)matrix.slots[outputs[currentOut]->slotIdx2].amount) + "%"; } else if (currentScreen == 17) { // CV1 INV Screen param_string = matrix.slots[outputs[currentOut]->slotIdx2].inverted ? "ON" : "OFF"; } else if (currentScreen == 18) { // Mute Screen param_string = outputs[currentOut]->isEnabled ? "ON" : "OFF"; } if (cursorClick) { pico_ssd1306::fillRect(display, 1, 42, 50, 60); pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45, pico_ssd1306::WriteMode::SUBTRACT); } else { pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45); } }