From 01bf09d4f5f1cf9d984a5029d604a8f908454db5 Mon Sep 17 00:00:00 2001 From: Adam Wonak Date: Mon, 2 Jun 2025 21:56:17 -0700 Subject: [PATCH] Consistent clock behavior for ext cv and midi. --- clock.h | 58 +++++++++++++++++++++----------- examples/clock_mod/clock_mod.ino | 48 +++++++++++++++----------- gravity.cpp | 16 ++++----- gravity.h | 8 ++--- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/clock.h b/clock.h index c73c0b1..44eac80 100644 --- a/clock.h +++ b/clock.h @@ -24,7 +24,10 @@ #define MIDI_CONTINUE 0xFB const int DEFAULT_TEMPO = 120; -static bool MIDI_ENABLED = false; + +typedef void (*ExtCallback)(void); +static ExtCallback extUserCallback = nullptr; +static void serialEventNoop(uint8_t msg, uint8_t status) {} enum Source { SOURCE_INTERNAL, @@ -38,12 +41,12 @@ class Clock { public: void Init() { NeoSerial.begin(31250); - NeoSerial.attachInterrupt(onSerialEvent); // Static pin definition for pulse out. pinMode(PULSE_OUT_PIN, OUTPUT); // Initialize the clock library + uClock.setExtIntervalBuffer(32); uClock.init(); uClock.setClockMode(uClock.INTERNAL_CLOCK); uClock.setOutputPPQN(uClock.PPQN_96); @@ -52,14 +55,15 @@ class Clock { // MIDI events. uClock.setOnClockStart(sendMIDIStart); uClock.setOnClockStop(sendMIDIStop); - uClock.setOnSync24(sendMidiClock); + uClock.setOnSync24(sendMIDIClock); uClock.setOnSync48(sendPulseOut); uClock.start(); } - // Handler for receiving clock trigger(PPQN_4 or PPQN_24). - void AttachExtHandler(void (*callback)(void)) { + // Handle external clock tick and call user callback when receiving clock trigger (PPQN_4, PPQN_24, or MIDI). + void AttachExtHandler(void (*callback)()) { + extUserCallback = callback; attachInterrupt(digitalPinToInterrupt(EXT_PIN), callback, RISING); } @@ -70,8 +74,13 @@ class Clock { // Set the source of the clock mode. void SetSource(Source source) { + bool was_playing = !IsPaused(); uClock.stop(); - MIDI_ENABLED = false; + // If source is currently MIDI, disable the serial interrupt handler. + if (source_ == SOURCE_EXTERNAL_MIDI) { + NeoSerial.attachInterrupt(serialEventNoop); + } + source_ = source; switch (source) { case SOURCE_INTERNAL: uClock.setClockMode(uClock.INTERNAL_CLOCK); @@ -87,52 +96,64 @@ class Clock { case SOURCE_EXTERNAL_MIDI: uClock.setClockMode(uClock.EXTERNAL_CLOCK); uClock.setInputPPQN(uClock.PPQN_24); - MIDI_ENABLED = true; + NeoSerial.attachInterrupt(onSerialEvent); break; } - uClock.start(); + if (was_playing) { + uClock.start(); + } } + // Return true if the current selected source is externl (PPQN_4, PPQN_24, or MIDI). bool ExternalSource() { return uClock.getClockMode() == uClock.EXTERNAL_CLOCK; } + // Return true if the current selected source is the internal master clock. bool InternalSource() { return uClock.getClockMode() == uClock.INTERNAL_CLOCK; } + // Returns the current BPM tempo. int Tempo() { return uClock.getTempo(); } + // Set the clock tempo to a int between 1 and 400. void SetTempo(int tempo) { return uClock.setTempo(tempo); } + // Record an external clock tick received to process external/internal syncronization. void Tick() { uClock.clockMe(); } - void Pause() { - uClock.pause(); - } - - void Reset() { + // Start the internal clock. + void Start() { uClock.start(); } + // Stop internal clock clock. + void Stop() { + uClock.stop(); + } + + // Returns true if the clock is not running. bool IsPaused() { return uClock.clock_state == uClock.PAUSED; } private: + Source source_ = SOURCE_INTERNAL; + static void onSerialEvent(uint8_t msg, uint8_t status) { - // if (!MIDI_ENABLED) { - // return; - // } + // Note: uClock start and stop will echo to MIDI. switch (msg) { case MIDI_CLOCK: - uClock.clockMe(); + if (extUserCallback) { + extUserCallback(); + } break; case MIDI_STOP: uClock.stop(); @@ -141,7 +162,6 @@ class Clock { case MIDI_CONTINUE: uClock.start(); break; - } } @@ -153,7 +173,7 @@ class Clock { NeoSerial.write(MIDI_STOP); } - static void sendMidiClock(uint32_t tick) { + static void sendMIDIClock(uint32_t tick) { NeoSerial.write(MIDI_CLOCK); } diff --git a/examples/clock_mod/clock_mod.ino b/examples/clock_mod/clock_mod.ino index 9fd4f65..e2823be 100644 --- a/examples/clock_mod/clock_mod.ino +++ b/examples/clock_mod/clock_mod.ino @@ -13,7 +13,7 @@ * * BTN1: Play/pause the internal clock. * - * BTN2: Reset all clocks. + * BTN2: Stop all clocks. * */ @@ -68,8 +68,8 @@ void setup() { gravity.Init(); // Clock handlers. - gravity.clock.AttachExtHandler(ExtClock); - gravity.clock.AttachIntHandler(IntClock); + gravity.clock.AttachIntHandler(HandleIntClockTick); + gravity.clock.AttachExtHandler(HandleExtClockTick); // Encoder rotate and press handlers. gravity.encoder.AttachPressHandler(HandleEncoderPressed); @@ -95,16 +95,9 @@ void loop() { // Firmware handlers. // -void ExtClock() { - if (gravity.clock.ExternalSource()) { - gravity.clock.Tick(); - app.refresh_screen = true; - } -} - -void IntClock(uint32_t tick) { +void HandleIntClockTick(uint32_t tick) { for (int i = 0; i < OUTPUT_COUNT; i++) { - const auto& channel = app.channel[i]; + auto& channel = app.channel[i]; auto& output = gravity.outputs[i]; const uint32_t mod_pulses = clock_mod_pulses[channel.clock_mod_index]; @@ -126,18 +119,27 @@ void IntClock(uint32_t tick) { } } -void HandlePlayPressed() { - gravity.clock.Pause(); - if (gravity.clock.IsPaused()) { - for (int i = 0; i < OUTPUT_COUNT; i++) { - gravity.outputs[i].Low(); - } +void HandleExtClockTick() { + // Ignore tick if not using external source. + if (!gravity.clock.ExternalSource()) { + return; } + gravity.clock.Tick(); + app.refresh_screen = true; +} + +void HandlePlayPressed() { + gravity.clock.IsPaused() + ? gravity.clock.Start() + : gravity.clock.Stop(); + ResetOutputs(); app.refresh_screen = true; } void HandleShiftPressed() { - gravity.clock.Reset(); + gravity.clock.Stop(); + ResetOutputs(); + app.refresh_screen = true; } void HandleEncoderPressed() { @@ -228,6 +230,12 @@ Channel& GetSelectedChannel() { return app.channel[app.selected_channel - 1]; } +void ResetOutputs() { + for (int i = 0; i < OUTPUT_COUNT; i++) { + gravity.outputs[i].Low(); + } +} + // // UI Display functions. // @@ -352,7 +360,7 @@ void DisplayChannelPage() { gravity.display.setFontMode(1); gravity.display.setDrawColor(2); gravity.display.setFont(LARGE_FONT); - + int textWidth; int textY = 26; int subTextY = 42; diff --git a/gravity.cpp b/gravity.cpp index 5c00f0a..00efe16 100644 --- a/gravity.cpp +++ b/gravity.cpp @@ -12,17 +12,17 @@ #include "gravity.h" void Gravity::Init() { - InitClock(); - InitInputs(); - InitOutputs(); - InitDisplay(); + initClock(); + initInputs(); + initOutputs(); + initDisplay(); } -void Gravity::InitClock() { +void Gravity::initClock() { clock.Init(); } -void Gravity::InitInputs() { +void Gravity::initInputs() { shift_button.Init(SHIFT_BTN_PIN); play_button.Init(PLAY_BTN_PIN); @@ -40,7 +40,7 @@ void Gravity::InitInputs() { PCMSK1 |= B00001000; } -void Gravity::InitOutputs() { +void Gravity::initOutputs() { // Initialize each of the outputs with it's GPIO pins and probability. outputs[0].Init(OUT_CH1); outputs[1].Init(OUT_CH2); @@ -49,7 +49,7 @@ void Gravity::InitOutputs() { outputs[4].Init(OUT_CH5); outputs[5].Init(OUT_CH6); } -void Gravity::InitDisplay() { +void Gravity::initDisplay() { // OLED Display configuration. display.begin(); } diff --git a/gravity.h b/gravity.h index 6bd4aed..9bba82d 100644 --- a/gravity.h +++ b/gravity.h @@ -37,10 +37,10 @@ class Gravity { AnalogInput cv2; private: - void InitClock(); - void InitDisplay(); - void InitInputs(); - void InitOutputs(); + void initClock(); + void initDisplay(); + void initInputs(); + void initOutputs(); }; extern Gravity gravity;