From 90693e8c2a07b5ec803b70e98b31d36884929176 Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Mon, 19 Jan 2026 18:57:38 -0500 Subject: [PATCH 1/4] encoder constrols screen --- master_clock.ino | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/master_clock.ino b/master_clock.ino index 2e43ef2..9eb1947 100644 --- a/master_clock.ino +++ b/master_clock.ino @@ -188,9 +188,9 @@ void checkRPot() { Serial.println(currentPos); if (currentPos > previousPos) { - BPM++; + handleBPMChange(1); } else { - BPM--; + handleBPMChange(0); } forceScreenUpdate = 1; @@ -202,6 +202,17 @@ void checkRPot() { } } +void handleBPMChange(byte increase) { + if (increase == 1) { + BPM++; + } else { + BPM--; + } + + period = (minute / BPM) / ppqn; + Timer1.setPeriod(period); +} + void updateEncoder() { // The ISR should be as short and fast as possible // Check the state of the DT pin to determine direction From 20281ad17a05c4c93db5c583e06ccb9e443ba4d7 Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Mon, 19 Jan 2026 20:23:24 -0500 Subject: [PATCH 2/4] working screen UI but broken throttling --- master_clock.ino | 63 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/master_clock.ino b/master_clock.ino index 9eb1947..c424f8f 100644 --- a/master_clock.ino +++ b/master_clock.ino @@ -45,6 +45,10 @@ unsigned long screenLastUpdate = 0; const unsigned long screenThrottle = 200; byte forceScreenUpdate = 1; +byte currentScreen = 0; +byte editMode = 0; +int mainScreenCursorPos = 0; +byte maxMainScreenPos = 8; void setup() { Serial.begin(9600); @@ -58,6 +62,7 @@ void setup() { // screen setup u8g2.begin(); + u8g2.setFontMode(1); // display.clearDisplay(); // display.setTextSize(2); @@ -150,16 +155,39 @@ void updateScreen() { if (currentTime - screenLastUpdate >= screenThrottle && forceScreenUpdate == 1) { screenLastUpdate = currentTime; forceScreenUpdate = 0; - // The firstPage()/nextPage() loop manages where the drawing commands go u8g2.firstPage(); do { - // --- Drawing Commands for the CURRENT Page --- - // All drawing commands must live INSIDE this do/while loop. - u8g2.setFont(u8g2_font_helvR08_tf); - u8g2.setCursor(0, 10); - u8g2.print(F("BPM: ")); - u8g2.print(BPM); // Example of dynamic data + // 12px - nonbold + if (mainScreenCursorPos == 0) { + u8g2.setFont(u8g2_font_7x14B_mf); + } else { + u8g2.setFont(u8g2_font_7x14_mf); + } + u8g2.setCursor(0, 15); + u8g2.print(F("BPM: ")); + u8g2.print(BPM); + + u8g2.setFont(u8g2_font_7x14_mf); + u8g2.setCursor(0, 45); + + char buffer[5]; + for (byte i = 1; i < 9; i++) { + if (i == 5) { + u8g2.setCursor(0, 60); + } + + if (mainScreenCursorPos == i) { + u8g2.setFont(u8g2_font_7x14B_mf); + } + + sprintf(buffer, "[%d] ", i); + u8g2.print(buffer); + + if (mainScreenCursorPos == i) { + u8g2.setFont(u8g2_font_7x14_mf); + } + }; } while (u8g2.nextPage()); } } @@ -186,11 +214,22 @@ void checkRPot() { if (currentPos != previousPos) { Serial.print("Encoder Position: "); Serial.println(currentPos); - - if (currentPos > previousPos) { - handleBPMChange(1); - } else { - handleBPMChange(0); + + // on home screen + if (currentScreen == 0) { + if (editMode == 0) { + if (currentPos > previousPos) { + mainScreenCursorPos++; + if (mainScreenCursorPos > maxMainScreenPos) { + mainScreenCursorPos = 0; + } + } else { + mainScreenCursorPos--; + if (mainScreenCursorPos < 0) { + mainScreenCursorPos = maxMainScreenPos; + } + } + } } forceScreenUpdate = 1; From 3f257adf49da341fc4c17d6d6cb551949ec977a2 Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Thu, 19 Feb 2026 23:32:34 -0500 Subject: [PATCH 3/4] cpp conversion --- CMakeLists.txt | 34 +++++++++++++++++ include/Gate.h | 36 ++++++++++++++++++ include/globals.h | 47 +++++++++++++++++++++++ src/Gate.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 299 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/Gate.h create mode 100644 include/globals.h create mode 100644 src/Gate.cpp create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cde0064 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.13) + +# 1. Generate the map for your Neovim clangd linter +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Include the SDK's CMake entry point +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +project(clock C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +include_directories(include) + +add_executable(clock + src/main.cpp + src/Gate.cpp +) + +# Enable USB output (useful for later printf debugging) +pico_enable_stdio_usb(clock 1) +pico_enable_stdio_uart(clock 0) + +# Pull in standard library and hardware abstraction +target_link_libraries(clock + pico_stdlib + hardware_gpio + hardware_i2c +) + +# Create the .uf2 file +pico_add_extra_outputs(clock) diff --git a/include/Gate.h b/include/Gate.h new file mode 100644 index 0000000..1dedb59 --- /dev/null +++ b/include/Gate.h @@ -0,0 +1,36 @@ +// Gate.h +#ifndef Gate_h +#define Gate_h + +#include +#include + + +class Gate { + private: + bool state; + 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; + + void turnOn(); + void turnOff(); + void setLen(uint32_t currentPeriod); + void setDiv(uint16_t newDiv, uint8_t divide = 1); + void setWidth(uint16_t newWidth); + void setP(uint16_t prob); + + bool getState(); +}; + +#endif diff --git a/include/globals.h b/include/globals.h new file mode 100644 index 0000000..c848cb8 --- /dev/null +++ b/include/globals.h @@ -0,0 +1,47 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#include + +#endif // GLOBALS_H + +/* +TODO: + +PRE-DAC: +[x] Figure out multiplicative beats X2 X4 X8 X16 X32? +[ ] Swing/Phase (same thing) +[x] Probability +[ ] Humanization +[ ] Euclidian Rhythms + [ ] Steps - # of steps for a full pattern + [ ] Hits - how many hits across the steps, must be less than steps + [ ] Offset - move the starting point of the pattern +[ ] Logic (NO | AND | OR | XOR) +[ ] Mute +[ ] Save +[ ] Load + +POST-DAC: +[ ] Different Wave Forms +[ ] Different Voltage levels +[ ] v/oct? + +100BPM +4800BPM + +POSSIBLE DIVISIONS: +1: x48 +16: x32 +32: x16 +40: x8 +44: x4 +46: x2 +--- +48*1: 1 ** THIS NEEDS TO BE PASSED IN AS DIVIDE MODE +48*2 = 96: /2 +48*4 = 192: /4 +48*8 = /8 +48*16 = /16 + +*/ diff --git a/src/Gate.cpp b/src/Gate.cpp new file mode 100644 index 0000000..432ac49 --- /dev/null +++ b/src/Gate.cpp @@ -0,0 +1,86 @@ +// Gate.cpp +#include "pico/stdlib.h" +#include "Gate.h" +#include "globals.h" +#include +#include + +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 + 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 + len = 0; // max len a pulse can be on, as determined by width + + p = 100; // probability of a pulse +} + +bool Gate::getState() { + return state; +} + +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) { + div = ppqn * modifier; + divString = "/" + std::to_string(modifier); + } else { + div = ppqn / modifier; + divString = "x" + std::to_string(modifier); + } + divideMode = divide; + this->modifier = modifier; +}; + +void Gate::setWidth(uint16_t newWidth) { + width = newWidth; + if (divideMode == 1) { + len = (uint32_t)((double)(minute / BPM) * (width / 100.0) / 1000.0); + } else { + len = (uint32_t)((double)(minute / BPM / modifier) * (width / 100.0) / 1000.0); + } +}; + +void Gate::setP(uint16_t prob) { + this->p = prob; +} + +void Gate::turnOn() { + cycle += 1; + uint8_t pRes = 1; + + if (cycle == div) { + if (p < 100) { + uint32_t r = (rand() % 100) + 1; + if (r > p) { + pRes = 0; + } + } + + if (pRes == 1) { + state = 1; + digitalWrite(pin, state); + dur = millis(); + } + cycle = 0; + }; +} + +void Gate::turnOff() { + if (state == 1 && millis() - dur >= len) { + state = 0; + digitalWrite(pin, state); + dur = 0; + }; +} + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f6f1451 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,96 @@ +#include +#include "hardware/structs/rosc.h" +#include "pico/stdlib.h" +#include "pico/time.h" +#include + +#include "globals.h" +#include "Gate.h" + +static constexpr uint8_t OUT_1_PIN = 0; +static constexpr uint8_t OUT_2_PIN = 2; +static constexpr uint8_t OUT_3_PIN = 4; +static constexpr uint8_t OUT_4_PIN = 6; +static constexpr uint8_t OUT_5_PIN = 8; +static constexpr uint8_t OUT_6_PIN = 10; +static constexpr uint8_t OUT_7_PIN = 12; +static constexpr uint8_t OUT_8_PIN = 14; + +static constexpr uint8_t SCREEN_SCL_PIN = 18; +static constexpr uint8_t SCREEN_SDA_PIN = 19; + +static constexpr uint8_t ENCODER_CLK_PIN = 20; +static constexpr uint8_t ENCODER_DT_PIN = 21; +static constexpr uint8_t ENCODER_SW_PIN = 22; + +volatile uint8_t PLAY = 1; +volatile uint8_t BPM = 100; + +static constexpr uint32_t MINUTE_US = 60000000; +static constexpr uint8_t PPQN = 96; +volatile uint32_t period_us = 0; + +struct repeating_timer bpm_timer; + +volatile bool beatToggle = false; + +Gate out1(OUT_1_PIN); +Gate out2(OUT_2_PIN); +Gate out3(OUT_3_PIN); +Gate out4(OUT_4_PIN); +Gate out5(OUT_5_PIN); +Gate out6(OUT_6_PIN); +Gate out7(OUT_7_PIN); +Gate out8(OUT_8_PIN); + + +bool timer_callback(struct repeating_timer *t) { + if (PLAY == 1) { + beatToggle = true; + } + return true; +} + + +void init_timer(uint32_t period_us) { + cancel_repeating_timer(&bpm_timer); + add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer); +} + + +void update_period() { + period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN); + init_timer(period_us); +} + + +int main() { + stdio_init_all(); + + srand(rosc_hw->randombit); + + gpio_init(out1.pin); + gpio_init(out2.pin); + gpio_init(out3.pin); + gpio_init(out4.pin); + gpio_init(out5.pin); + gpio_init(out6.pin); + gpio_init(out7.pin); + gpio_init(out8.pin); + + gpio_set_dir(out1.pin, GPIO_OUT); + gpio_set_dir(out2.pin, GPIO_OUT); + gpio_set_dir(out3.pin, GPIO_OUT); + gpio_set_dir(out4.pin, GPIO_OUT); + gpio_set_dir(out5.pin, GPIO_OUT); + gpio_set_dir(out6.pin, GPIO_OUT); + gpio_set_dir(out7.pin, GPIO_OUT); + gpio_set_dir(out8.pin, GPIO_OUT); + + update_period(); + + while (true) { + + } + +} From 95d89518460735a28bf92ee39b7916e5b4610ebf Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Thu, 19 Feb 2026 23:38:17 -0500 Subject: [PATCH 4/4] fixing --- Out.cpp | 87 ------------------------------------------------------- Out.h | 33 --------------------- globals.h | 51 -------------------------------- 3 files changed, 171 deletions(-) delete mode 100644 Out.cpp delete mode 100644 Out.h delete mode 100644 globals.h diff --git a/Out.cpp b/Out.cpp deleted file mode 100644 index 33beeb2..0000000 --- a/Out.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// Out.cpp -#include "Arduino.h" -#include "Out.h" -#include "globals.h" - -Out::Out(byte pin) { - this->pin = pin; - state = LOW; - - 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 - 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 - len = 0; // max len a pulse can be on, as determined by width - - p = 100; // probability of a pulse - - - pinMode(pin, OUTPUT); -} - -int Out::getState() { - return state; -} - -void Out::setLen(unsigned long currentPeriod) { - len = (unsigned long)((double)currentPeriod * (width / 100.0) / 1000.0); -} - -void Out::setDiv(int modifier, byte divide = 1) { - if (divide == 1) { - div = ppqn * modifier; - divString = "/" + modifier; - } else { - div = ppqn / modifier; - divString = "x" + modifier; - } - divideMode = divide; - this->modifier = modifier; -}; - -void Out::setWidth(int newWidth) { - width = newWidth; - if (divideMode == 1) { - len = (unsigned long)((double)(minute / BPM) * (width / 100.0) / 1000.0); - } else { - len = (unsigned long)((double)(minute / BPM / modifier) * (width / 100.0) / 1000.0); - } -}; - -void Out::setP(int prob) { - this->p = prob; -} - -void Out::turnOn() { - cycle += 1; - byte pRes = 1; - - if (cycle == div) { - if (p < 100) { - long r = random(1, 101); - if (r > p) { - pRes = 0; - } - } - - if (pRes == 1) { - state = HIGH; - digitalWrite(pin, state); - dur = millis(); - } - cycle = 0; - }; -} - -void Out::turnOff() { - if (state == HIGH && millis() - dur >= len) { - state = LOW; - digitalWrite(pin, state); - dur = 0; - }; -} - diff --git a/Out.h b/Out.h deleted file mode 100644 index c5e3833..0000000 --- a/Out.h +++ /dev/null @@ -1,33 +0,0 @@ -// Out.h -#ifndef Out_h -#define Out_h - -#include - -class Out { - private: - byte pin; - unsigned char state; - int cycle; - unsigned long dur; - unsigned long len; - byte width; - byte divideMode; - int div; - int modifier; - String divString; - int p; - - public: - Out(byte pin); - void turnOn(); - void turnOff(); - void setLen(unsigned long currentPeriod); - void setDiv(int newDiv, byte divide = 1); - void setWidth(int newWidth); - void setP(int prob); - int getState(); - -}; - -#endif diff --git a/globals.h b/globals.h deleted file mode 100644 index d2ea81a..0000000 --- a/globals.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef GLOBALS_H -#define GLOBALS_H - -#include - -extern byte BPM; -extern unsigned long minute; -extern unsigned long period; -extern byte ppqn; -#endif // GLOBALS_H - -/* -TODO: - -PRE-DAC: -[x] Figure out multiplicative beats X2 X4 X8 X16 X32? -[ ] Swing/Phase (same thing) -[x] Probability -[ ] Humanization -[ ] Euclidian Rhythms - [ ] Steps - # of steps for a full pattern - [ ] Hits - how many hits across the steps, must be less than steps - [ ] Offset - move the starting point of the pattern -[ ] Logic (NO | AND | OR | XOR) -[ ] Mute -[ ] Save -[ ] Load - -POST-DAC: -[ ] Different Wave Forms -[ ] Different Voltage levels -[ ] v/oct? - -100BPM -4800BPM - -POSSIBLE DIVISIONS: -1: x48 -16: x32 -32: x16 -40: x8 -44: x4 -46: x2 ---- -48*1: 1 ** THIS NEEDS TO BE PASSED IN AS DIVIDE MODE -48*2 = 96: /2 -48*4 = 192: /4 -48*8 = /8 -48*16 = /16 - -*/