diff --git a/CMakeLists.txt b/CMakeLists.txt index c255846..55cb17f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ include_directories(include) add_executable(clock src/main.cpp src/Gate.cpp + src/DisplayHandler.cpp + src/EncoderHandler.cpp ) # Enable USB output (useful for later printf debugging) diff --git a/include/DisplayHandler.h b/include/DisplayHandler.h new file mode 100644 index 0000000..491f7e7 --- /dev/null +++ b/include/DisplayHandler.h @@ -0,0 +1,37 @@ +// DisplayHandler.h +#ifndef DisplayHandler_h +#define DisplayHandler_h + +#include +#include +#include +#include "Gate.h" + + +class DisplayHandler { + private: + char buffer[32]; + uint8_t currentScreen; + std::string screens[7]; + std::array out_pages = {"Exit", "Mod", "Width", "Prob"}; + bool onOutScreen = 0; + void renderMainPage(); + void renderOutPage(); + + + public: + DisplayHandler(Gate* outputs[]); + Gate** outputs; + bool updateScreen; + int8_t cursorPosition; + uint8_t mainMaxCursorPosition; + uint8_t outMaxCursorPosition; + int8_t currentOut; + bool cursorClick; + void setup(); + void render(); + void handleClick(); + void moveCursor(bool dir = 1); +}; + +#endif diff --git a/include/EncoderHandler.h b/include/EncoderHandler.h new file mode 100644 index 0000000..d44f1ad --- /dev/null +++ b/include/EncoderHandler.h @@ -0,0 +1,28 @@ +// EncoderHandler.h +#ifndef EncoderHandler_h +#define EncoderHandler_h + +#include +#include +#include "pico/multicore.h" +#include "DisplayHandler.h" + + +class EncoderHandler { + private: + + + public: + EncoderHandler(DisplayHandler* display_handler); + + DisplayHandler* display_handler; + uint8_t encoder_pos; + bool button_pressed; + uint16_t clk_last_state; + + void setup(); + static void gpio_callback(uint gpio, uint32_t events); + void moveCursor(bool dir = 1); +}; + +#endif diff --git a/include/Gate.h b/include/Gate.h index 1dedb59..7b3aba0 100644 --- a/include/Gate.h +++ b/include/Gate.h @@ -12,21 +12,22 @@ class Gate { int16_t cycle; uint32_t dur; uint32_t len; - uint8_t width; - uint8_t divideMode; uint16_t div; - uint16_t modifier; - std::string divString; - uint8_t p; public: Gate(uint8_t pin); uint8_t pin; + uint8_t editing; + int8_t modifierSelectionIndex; + uint8_t divideMode; + uint16_t modifier; + uint8_t width; + uint8_t p; void turnOn(); void turnOff(); void setLen(uint32_t currentPeriod); - void setDiv(uint16_t newDiv, uint8_t divide = 1); + void setDiv(uint8_t modifier_selection_index); void setWidth(uint16_t newWidth); void setP(uint16_t prob); diff --git a/include/globals.h b/include/globals.h index 6e8776d..a81d5ed 100644 --- a/include/globals.h +++ b/include/globals.h @@ -3,6 +3,8 @@ #include "pico/stdlib.h" #include +#include +#include static constexpr uint8_t OUT_1_PIN = 0; static constexpr uint8_t OUT_2_PIN = 2; @@ -25,6 +27,15 @@ extern volatile uint8_t BPM; static constexpr uint32_t MINUTE_US = 60000000; static constexpr uint8_t PPQN = 96; +static uint32_t MASTER_TICK = 0; + +// Modifiers in UI order +static std::array MODIFIERS = {8, 4, 2, 0, 1, 2, 3, 4, 8, 16}; +// Modifier type; 0 = multiplicaton, 1 = division; matched with MODIFIERS +static std::array MOD_TYPES = {0, 0, 0, 0, 1, 1, 1, 1, 1, 1}; +// Modifier string +static std::array MODIFIER_STRINGS = {"x8", "x4", "x2", "x0", "/1", "/2", "/3", "/4", "/8", "/16"}; + inline uint32_t millis() { return to_ms_since_boot(get_absolute_time()); } diff --git a/src/DisplayHandler.cpp b/src/DisplayHandler.cpp new file mode 100644 index 0000000..b2d0926 --- /dev/null +++ b/src/DisplayHandler.cpp @@ -0,0 +1,229 @@ +// DisplayHandler.cpp +#include "pico/stdlib.h" +#include "DisplayHandler.h" +#include "globals.h" +#include +#include + +#include "hardware/i2c.h" +#include "pico-ssd1306/ssd1306.h" +#include "pico-ssd1306/shapeRenderer/ShapeRenderer.h" +#include "pico-ssd1306/textRenderer/TextRenderer.h" + +//TODO: +// the pulses are not even... they occur independently of eachother. + +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 = 8; + 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 (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; + currentOut = -1; + currentScreen = 0; + onOutScreen = 0; + } + + if (currentScreen == 1 && outputs[currentOut]->editing == 1) { + outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex); + cursorClick = 0; + outputs[currentOut]->editing = 0; + } + } 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; + } + + } + } + + 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; + + } +} + + +void DisplayHandler::renderOutPage() { + std::string title = std::to_string(currentOut) + "| " + 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) { // Width screen + param_string = std::to_string(outputs[currentOut]->width) + "%"; + + } else if (currentScreen == 3) { // Probability screen + param_string = std::to_string(outputs[currentOut]->p) + "%"; + } + + 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); + } + +} diff --git a/src/EncoderHandler.cpp b/src/EncoderHandler.cpp new file mode 100644 index 0000000..97be2cb --- /dev/null +++ b/src/EncoderHandler.cpp @@ -0,0 +1,71 @@ +// EncoderHandler.cpp +#include "pico/stdlib.h" +#include "EncoderHandler.h" +#include "DisplayHandler.h" +#include "globals.h" +#include +#include + + +static EncoderHandler* self = nullptr; + + +EncoderHandler::EncoderHandler(DisplayHandler* display_handler) { + this->display_handler = display_handler; + self = this; + encoder_pos = 0; + button_pressed = 0; +} + + +void EncoderHandler::gpio_callback(uint gpio, uint32_t events) { + static uint64_t last_sw_time = 0; + static uint64_t last_rotate_time = 0; + uint64_t now = to_us_since_boot(get_absolute_time()); + + if (gpio == ENCODER_SW_PIN) { // handle button press + if (now - last_sw_time > 200000) { + self->display_handler->handleClick(); + last_sw_time = now; + } + } else if (gpio == ENCODER_CLK_PIN) { // handle encoder turn + + if (now - last_rotate_time < 5000) return; + + if (events & GPIO_IRQ_EDGE_FALL) { + if (gpio_get(ENCODER_CLK_PIN) == 0) { + + uint16_t dt_state = gpio_get(ENCODER_DT_PIN); + + if (dt_state) { + self->display_handler->moveCursor(); + } else { + self->display_handler->moveCursor(0); + } + } + + last_rotate_time = now; + } + } +} + + +void EncoderHandler::setup() { + gpio_init(ENCODER_SW_PIN); + gpio_set_dir(ENCODER_SW_PIN, GPIO_IN); + gpio_pull_up(ENCODER_SW_PIN); + + gpio_init(ENCODER_CLK_PIN); + gpio_set_dir(ENCODER_CLK_PIN, GPIO_IN); + gpio_pull_up(ENCODER_CLK_PIN); + + gpio_init(ENCODER_DT_PIN); + gpio_set_dir(ENCODER_DT_PIN, GPIO_IN); + gpio_pull_up(ENCODER_DT_PIN); + + clk_last_state = gpio_get(ENCODER_CLK_PIN); + + gpio_set_irq_enabled_with_callback(20, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &EncoderHandler::gpio_callback); + gpio_set_irq_enabled(21, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + gpio_set_irq_enabled(22, GPIO_IRQ_EDGE_FALL, true); +} diff --git a/src/Gate.cpp b/src/Gate.cpp index 5a60b9b..3dd0ec9 100644 --- a/src/Gate.cpp +++ b/src/Gate.cpp @@ -9,11 +9,12 @@ Gate::Gate(uint8_t pin) { this->pin = pin; state = 0; - divideMode = 1; // 1 divison | 0 multiplication - modifier = 1; // divide mode modifier (4x, /32, etc) - div = 1; // cycles needed before a pulse based on divide mode and modifier + modifierSelectionIndex = 3; + editing = 0; + divideMode = 0; // 1 divison | 0 multiplication + modifier = 0; // divide mode modifier (4x, /32, etc) + div = 0; // cycles needed before a pulse based on divide mode and modifier cycle = 0; // how many cycles have passed since last pulse - divString = ""; // string for screen .. probably does not belong here dur = 0; // how long pulse is on width = 50; // pulse width @@ -30,16 +31,24 @@ void Gate::setLen(uint32_t currentPeriod) { len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0); } -void Gate::setDiv(uint16_t modifier, uint8_t divide) { - if (divide == 1) { +void Gate::setDiv(uint8_t modifier_selecton_index) { + printf("HOW ABOUT HERE\n"); + + uint8_t modifier = MODIFIERS[modifier_selecton_index]; + uint8_t mod_type = MOD_TYPES[modifier_selecton_index]; + if (mod_type == 1) { div = PPQN * modifier; - divString = "/" + std::to_string(modifier); } else { div = PPQN / modifier; - divString = "x" + std::to_string(modifier); } - divideMode = divide; + divideMode = mod_type; this->modifier = modifier; + + if (this->modifier == 0) { + turnOff(); + } + + setWidth(this->width); }; void Gate::setWidth(uint16_t newWidth) { diff --git a/src/main.cpp b/src/main.cpp index bb88392..6d0bc56 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,15 @@ #include #include #include -#include "hardware/i2c.h" #include "hardware/structs/rosc.h" #include "pico/multicore.h" #include "pico/stdlib.h" #include "pico/time.h" -#include "pico-ssd1306/ssd1306.h" -#include "pico-ssd1306/textRenderer/TextRenderer.h" - #include "globals.h" #include "Gate.h" +#include "DisplayHandler.h" +#include "EncoderHandler.h" struct repeating_timer bpm_timer = {0}; @@ -30,17 +28,16 @@ Gate out6(OUT_6_PIN); Gate out7(OUT_7_PIN); Gate out8(OUT_8_PIN); -volatile bool updateScreen = 1; -pico_ssd1306::SSD1306* display = nullptr; +static Gate* outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8}; -volatile uint8_t encoder_pos = 0; -volatile bool button_pressed = 0; -volatile uint16_t clk_last_state; +static DisplayHandler display_handler(outputs); +static EncoderHandler encoder_handler(&display_handler); bool timer_callback(struct repeating_timer *t) { if (PLAY == 1) { beatToggle = true; + MASTER_TICK += 1; } return true; } @@ -69,117 +66,67 @@ void update_BPM(bool up) { } -void gpio_callback(uint gpio, uint32_t events) { - if (gpio == ENCODER_SW_PIN) { // handle button press - - } else if (gpio == ENCODER_CLK_PIN || gpio == ENCODER_DT_PIN) { // handle encoder turn - uint16_t clk_state = gpio_get(ENCODER_CLK_PIN); - uint16_t dt_state = gpio_get(ENCODER_DT_PIN); - - if (clk_state != clk_last_state && clk_state == 1) - if (dt_state != clk_state) { - encoder_pos++; // Clockwise - update_BPM(1); - } else { - encoder_pos--; // Counter-clockwise - update_BPM(0); - } - - updateScreen = 1; - - clk_last_state = clk_state; - } -} - - -void setup_encoder() { - gpio_init(ENCODER_SW_PIN); - gpio_set_dir(ENCODER_SW_PIN, GPIO_IN); - gpio_pull_up(ENCODER_SW_PIN); - - gpio_init(ENCODER_CLK_PIN); - gpio_set_dir(ENCODER_CLK_PIN, GPIO_IN); - gpio_pull_up(ENCODER_CLK_PIN); - - gpio_init(ENCODER_DT_PIN); - gpio_set_dir(ENCODER_DT_PIN, GPIO_IN); - gpio_pull_up(ENCODER_DT_PIN); - - clk_last_state = gpio_get(ENCODER_CLK_PIN); - - gpio_set_irq_enabled_with_callback(20, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); - gpio_set_irq_enabled(21, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); - gpio_set_irq_enabled(22, GPIO_IRQ_EDGE_FALL, true); -} - - -void setup_screen() { - 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 core1_entry() { multicore_fifo_pop_blocking(); char buffer[32]; while (true) { - if (updateScreen) { - display->clear(); - - snprintf(buffer, sizeof(buffer), "BPM: %u", BPM); - pico_ssd1306::drawText(display, font_12x16, buffer, 0, 0); - - display->sendBuffer(); - - updateScreen = 0; - } + display_handler.render(); } } void setup_outs() { - Gate* my_outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8}; - for (auto g : my_outputs) { + for (auto g : outputs) { gpio_init(g->pin); gpio_set_dir(g->pin, GPIO_OUT); g->setLen(period_us); } // manual setup - out1.setDiv(8, 0); - out1.setWidth(50); + out1.setDiv(1); + // out1.setWidth(50); + // + // out2.setDiv(4, 0); + // out2.setWidth(50); + // + // out3.setDiv(2, 0); + // out3.setWidth(50); + // + // out4.setDiv(1); + // out4.setWidth(50); + // + // out5.setDiv(2); + // out5.setWidth(50); + // + // out6.setDiv(4); + // out6.setWidth(50); + // + // out7.setDiv(8); + // out7.setWidth(50); + // + // out8.setDiv(16); + // out8.setWidth(50); +} - out2.setDiv(4, 0); - out2.setWidth(50); - out3.setDiv(2, 0); - out3.setWidth(50); +void handle_outs() { + if (beatToggle) { + for (Gate* g: outputs) { + if (g->modifier != 0) { + g->turnOn(); + } + } - out4.setDiv(1); - out4.setWidth(50); - - out5.setDiv(2); - out5.setWidth(50); - - out6.setDiv(4); - out6.setWidth(50); - - out7.setDiv(8); - out7.setWidth(50); - - out8.setDiv(16); - out8.setWidth(50); + beatToggle = false; + } + for (Gate* g: outputs) { + if (g->modifier != 0) { + g->turnOff(); + } + } } @@ -187,37 +134,17 @@ int main() { stdio_init_all(); srand(rosc_hw->randombit); - setup_screen(); + display_handler.setup(); multicore_launch_core1(core1_entry); multicore_fifo_push_blocking(1); - setup_encoder(); + encoder_handler.setup(); + setup_outs(); update_period(); while (true) { - if (beatToggle) { - out1.turnOn(); - out2.turnOn(); - out3.turnOn(); - out4.turnOn(); - out5.turnOn(); - out6.turnOn(); - out7.turnOn(); - out8.turnOn(); - - beatToggle = false; - } - - out1.turnOff(); - out2.turnOff(); - out3.turnOff(); - out4.turnOff(); - out5.turnOff(); - out6.turnOff(); - out7.turnOff(); - out8.turnOff(); + handle_outs(); } - }