From 4203dbb613ba428e5a32317cf3d49e07838ad24f Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 16 Nov 2024 10:34:25 -0300 Subject: [PATCH 01/13] - added non freertos version as option for esp32 users --- library.json | 2 +- library.properties | 2 +- src/platforms/avr.h | 9 +++++++++ src/platforms/esp32-nofrertos.h | 32 ++++++++++++++++++++++++++++++++ src/platforms/esp32.h | 6 +----- src/uClock.cpp | 6 +----- src/uClock.h | 2 +- 7 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 src/platforms/esp32-nofrertos.h diff --git a/library.json b/library.json index c66d060..c0c98c2 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "uClock", - "version": "2.2.0", + "version": "2.2.1", "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)", "keywords": "bpm, clock, timing, tick, music, generator", "repository": diff --git a/library.properties b/library.properties index 0f4cce0..300684b 100755 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=uClock -version=2.2.0 +version=2.2.1 author=Romulo Silva maintainer=Romulo Silva sentence=BPM clock generator for Arduino platform. diff --git a/src/platforms/avr.h b/src/platforms/avr.h index 9904c92..9931dac 100644 --- a/src/platforms/avr.h +++ b/src/platforms/avr.h @@ -6,6 +6,15 @@ // TODO: we should do this using macro guards for avrs different clocks freqeuncy setup at compile time #define AVR_CLOCK_FREQ 16000000 +// forward declaration of uClockHandler +void uClockHandler(); + +// AVR ISR Entrypoint +ISR(TIMER1_COMPA_vect) +{ + uClockHandler(); +} + void initTimer(uint32_t init_clock) { ATOMIC( diff --git a/src/platforms/esp32-nofrertos.h b/src/platforms/esp32-nofrertos.h new file mode 100644 index 0000000..70d2720 --- /dev/null +++ b/src/platforms/esp32-nofrertos.h @@ -0,0 +1,32 @@ +#include + +#define TIMER_ID 0 + +hw_timer_t * _uclockTimer = NULL; +portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; +#define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux); + +// forward declaration of uClockHandler +void uClockHandler(); + +// ISR handler +void ARDUINO_ISR_ATTR handlerISR(void) +{ + uClockHandler(); +} + +void initTimer(uint32_t init_clock) +{ + _uclockTimer = timerBegin(init_clock); + + // attach to generic uclock ISR + timerAttachInterrupt(_uclockTimer, &handlerISR); + + // init clock tick time + timerAlarm(_uclockTimer, init_clock, true, 0); +} + +void setTimer(uint32_t us_interval) +{ + timerAlarmWrite(_uclockTimer, us_interval, true); +} \ No newline at end of file diff --git a/src/platforms/esp32.h b/src/platforms/esp32.h index 372ca93..f0ef639 100644 --- a/src/platforms/esp32.h +++ b/src/platforms/esp32.h @@ -3,11 +3,7 @@ #include // esp32-specific timer -#define TIMER_ID 0 hw_timer_t * _uclockTimer = NULL; -// mutex control for ISR -//portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; -//#define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux); // FreeRTOS main clock task size in bytes #define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size @@ -47,7 +43,7 @@ void initTimer(uint32_t init_clock) // create the clockTask xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); - _uclockTimer = timerBegin(1000000); + _uclockTimer = timerBegin(init_clock); // attach to generic uclock ISR timerAttachInterrupt(_uclockTimer, &handlerISR); diff --git a/src/uClock.cpp b/src/uClock.cpp index 9f27935..c06a9fa 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -2,7 +2,7 @@ * @file uClock.cpp * Project BPM clock generator for Arduino * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.2.0 + * @version 2.2.1 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co @@ -532,11 +532,7 @@ volatile uint32_t _millis = 0; // // TIMER HANDLER // -#if defined(ARDUINO_ARCH_AVR) -ISR(TIMER1_COMPA_vect) -#else void uClockHandler() -#endif { // global timer counter _millis = millis(); diff --git a/src/uClock.h b/src/uClock.h index df1a08d..45f0899 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -2,7 +2,7 @@ * @file uClock.h * Project BPM clock generator for Arduino * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.2.0 + * @version 2.2.1 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co From c7b081954d92eebfa907e0c98e00eab9bc903f34 Mon Sep 17 00:00:00 2001 From: midilab Date: Sun, 16 Feb 2025 20:02:59 -0300 Subject: [PATCH 02/13] added input clock resolution other than 24ppqn --- src/uClock.cpp | 65 ++++++++++++++++++++++++++++++++++---------------- src/uClock.h | 14 ++++++++--- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index c06a9fa..1d42175 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -130,8 +130,8 @@ uClockClass::uClockClass() onStepCallback = nullptr; onClockStartCallback = nullptr; onClockStopCallback = nullptr; - // first ppqn references calculus - setPPQN(PPQN_96); + // first ppqn references calculus for ppqn and clock resolution + calculateReferencedata(); } void uClockClass::init() @@ -146,15 +146,30 @@ uint32_t uClockClass::bpmToMicroSeconds(float bpm) return (60000000.0f / (float)ppqn / bpm); } +void uClockClass::calculateReferencedata() +{ + mod_clock_ref = ppqn / clock_ppqn; + mod_sync24_ref = ppqn / PPQN_24; + mod_step_ref = ppqn / 4; +} + void uClockClass::setPPQN(PPQNResolution resolution) { // stop clock to make it safe changing those references - // so we avoid volatile then and ATOMIC everyone + // so we avoid volatile then and ATOMIC everywhere stop(); ppqn = resolution; - // calculate the mod24 and mod_step tick reference trigger - mod24_ref = ppqn / 24; - mod_step_ref = ppqn / 4; + calculateReferencedata(); +} + +void uClockClass::setClockPPQN(PPQNResolution resolution) +{ + // stop clock to make it safe changing those references + // so we avoid volatile then and ATOMIC everywhere + stop(); + clock_ppqn = resolution; + calculateReferencedata(); + //mod_clock_ref = ppqn / clock_ppqn; } void uClockClass::start() @@ -238,7 +253,6 @@ void uClockClass::run() #endif } -// this function is based on sync24PPQN float inline uClockClass::freqToBpm(uint32_t freq) { float usecs = 1/((float)freq/1000000.0); @@ -268,8 +282,10 @@ void uClockClass::resetCounters() { tick = 0; int_clock_tick = 0; - mod24_counter = 0; + sync24_tick = 0; + mod_clock_counter = 0; mod_step_counter = 0; + mod_sync24_counter = 0; step_counter = 0; ext_clock_tick = 0; ext_clock_us = 0; @@ -371,7 +387,7 @@ bool inline uClockClass::processShuffle() return false; } -// it is expected to be called in 24PPQN +// TODO: needs to check clock signals against current phase lock parameters(based on 24ppqn sync) void uClockClass::handleExternalClock() { switch (state) { @@ -408,19 +424,19 @@ void uClockClass::handleExternalClock() void uClockClass::handleTimerInt() { - // reset mod24 counter reference ? - if (mod24_counter == mod24_ref) - mod24_counter = 0; + // reset mod_clock_counter + if (mod_clock_counter == mod_clock_ref) + mod_clock_counter = 0; // process sync signals first please... - if (mod24_counter == 0) { + if (mod_clock_counter == 0) { if (mode == EXTERNAL_CLOCK) { // sync tick position with external tick clock if ((int_clock_tick < ext_clock_tick) || (int_clock_tick > (ext_clock_tick + 1))) { int_clock_tick = ext_clock_tick; - tick = int_clock_tick * mod24_ref; - mod24_counter = tick % mod24_ref; + tick = int_clock_tick * mod_clock_ref; + mod_clock_counter = tick % mod_clock_ref; mod_step_counter = tick % mod_step_ref; } @@ -446,13 +462,21 @@ void uClockClass::handleTimerInt() } } - if (onSync24Callback) { - onSync24Callback(int_clock_tick); - } - // internal clock tick me! sync24 tick too + // internal clock tick me! ++int_clock_tick; } + // Sync24 callback + if (mod_sync24_counter == mod_sync24_ref) + mod_sync24_counter = 0; + + if (onSync24Callback) { + if (mod_sync24_counter == 0) { + onSync24Callback(sync24_tick); + ++sync24_tick; + } + } + // PPQNCallback time! if (onPPQNCallback) { onPPQNCallback(tick); @@ -476,7 +500,8 @@ void uClockClass::handleTimerInt() // tick me! ++tick; // increment mod counters - ++mod24_counter; + ++mod_clock_counter; + ++mod_sync24_counter; ++mod_step_counter; } diff --git a/src/uClock.h b/src/uClock.h index 45f0899..b720839 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -84,6 +84,9 @@ class uClockClass { }; enum PPQNResolution { + PPQN_4 = 4, + PPQN_8 = 8, + PPQN_12 = 12, PPQN_24 = 24, PPQN_48 = 48, PPQN_96 = 96, @@ -118,6 +121,7 @@ class uClockClass { void init(); void setPPQN(PPQNResolution resolution); + void setClockPPQN(PPQNResolution resolution); void handleTimerInt(); void handleExternalClock(); @@ -162,6 +166,7 @@ class uClockClass { private: float inline freqToBpm(uint32_t freq); + void calculateReferencedata(); // shuffle bool inline processShuffle(); @@ -175,14 +180,17 @@ class uClockClass { // internal clock control // uint16_t ppqn; PPQNResolution ppqn = PPQN_96; + PPQNResolution clock_ppqn = PPQN_24; uint32_t tick; uint32_t int_clock_tick; - uint8_t mod24_counter; - uint8_t mod24_ref; + uint32_t sync24_tick; + uint8_t mod_clock_counter; + uint8_t mod_clock_ref; uint8_t mod_step_counter; uint8_t mod_step_ref; uint32_t step_counter; // should we go uint16_t? - + uint8_t mod_sync24_counter; + uint8_t mod_sync24_ref; // external clock control volatile uint32_t ext_clock_us; volatile uint32_t ext_clock_tick; From 1a569e4cb1efb7edb1e855ea344f1aa2c6739521 Mon Sep 17 00:00:00 2001 From: midilab Date: Tue, 25 Mar 2025 08:35:59 -0300 Subject: [PATCH 03/13] fix input clock ppqn calculus --- README.md | 51 +++++++++++++++++++++++++------------------------- src/uClock.cpp | 24 +++++++++++++++++++----- src/uClock.h | 20 ++++++++++++++++++-- 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7c52d2c..3ec6aaa 100755 --- a/README.md +++ b/README.md @@ -1,21 +1,37 @@ # uClock -The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interruption. It is designed to be multi-architecture, portable, and easy to use within the open source community universe. +The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interrupt. It is built to be multi-architecture, portable, and easy to use within the open-source ecosystem. -We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)**. +We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on various **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, ESP32, Raspberry Pi Pico, Seeed Studio XIAO M0, and RP2040)**. -The absence of real-time features necessary for creating professional-level embedded devices for music and video on open source community-based platforms like Arduino led to the development of uClock. By leveraging the use of timer hardware interruptions, the library can schedule and manage real-time-like processing with safe shared resource access through its API. +The absence of real-time features necessary for creating professional-level embedded devices for music and video on open-source community-based platforms like Arduino led to the development of uClock. By leveraging timer hardware interrupts, the library can schedule and manage real-time processing with safe shared resource access through its API. -With uClock, you gain the ability to create professional-grade sequencers, sync boxes, or generate a precise BPM clock for external devices in the realms of music, audio/video productions, performances, or tech art installations. The library offers an external synchronization schema that enables you to generate an internal clock based on an external clock source, allowing you to master your entire MIDI setup or any other protocols according to your specific preferences and requirements. +With uClock, you can create professional-grade sequencers, sync boxes, or generate a precise BPM clock for external devices in music, audio/video production, performances, or tech art installations. The library offers an external synchronization schema that enables you to generate an internal clock based on an external clock source, allowing you to control your entire MIDI setup or any other protocols according to your specific preferences and requirements. ## Interface -The uClock library API operates through attached callback functions mechanism: +The uClock library API operates through an attached callback function mechanism: -1. **setOnPPQN(onPPQNCallback) > onPPQNCallback(uint32_t tick)** calls on each new pulse based on selected PPQN resolution (if no PPQN set, the default is 96PPQN) -2. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** good way to code old style step sequencer based on 16th note schema (not dependent on PPQN resolution) -3. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** good way to code a clock machine, or keep your devices synced with your device -4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** on uClock Start event -5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** on uClock Stop event +1. **setOnPPQN(onPPQNCallback) > onPPQNCallback(uint32_t tick)** Calls are made on each new pulse based on the selected PPQN resolution (if no PPQN is set, the default is 96 PPQN). +2. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** A good way to code an old-style step sequencer based on a 16th-note schema (not dependent on PPQN resolution). +3. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices synced with your system. +4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event. +5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** On the uClock Stop event. + +### Clock input/output resolutions + +1. **PPQN_4** 4 Pulses Per Quarter Note +2. **PPQN_8** 8 Pulses Per Quarter Note +3. **PPQN_12** 12 Pulses Per Quarter Note +4. **PPQN_24** 24 Pulses Per Quarter Note +5. **PPQN_48** 48 Pulses Per Quarter Note +6. **PPQN_96** 96 Pulses Per Quarter Note +7. **PPQN_384** 384 Pulses Per Quarter Note +8. **PPQN_480** 480 Pulses Per Quarter Note +9. **PPQN_960** 960 Pulses Per Quarter Note + +To generate a MIDI sync signal and synchronize external MIDI devices, you can start with a resolution of 24 PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available on the market. By sending 24 pulses per quarter-note interval, you can ensure effective synchronization among your MIDI devices. + +If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. ### Software Timer mode - for unsupported boards (or avoiding usage of interrupts) If a supported board isn't detected during compilation then a generic fallback approach will be used. This does not utilise any interrupts and so does not ensure accurate timekeeping. This can be useful to port your projects to boards that do not have support in uClock yet, or to test if suspected bugs in your code are related to interactions with interrupts or task handling. @@ -37,21 +53,6 @@ void loop() { } ``` -## Set your own resolution for your clock needs - -1. **PPQN_24** 24 Pulses Per Quarter Note -2. **PPQN_48** 48 Pulses Per Quarter Note -3. **PPQN_96** 96 Pulses Per Quarter Note -1. **PPQN_384** 384 Pulses Per Quarter Note -2. **PPQN_480** 480 Pulses Per Quarter Note -3. **PPQN_960** 960 Pulses Per Quarter Note - -To generate a MIDI sync signal and synchronize external MIDI devices, you can start working with the resolution of 24PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available in the market. By sending 24 pulses per quarter note interval, you can ensure effective synchronization among your MIDI devices. - -If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. - -Furthermore, it is possible to utilize all three resolutions simultaneously, allowing for flexibility based on your specific requirements and preferences. - ## uClock v2.0 Breaking Changes If you are coming from uClock version < 2.0 versions, pay attention to the breaking changes so you can update your code to reflect the new API interface: diff --git a/src/uClock.cpp b/src/uClock.cpp index 1d42175..405cb87 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -150,6 +150,7 @@ void uClockClass::calculateReferencedata() { mod_clock_ref = ppqn / clock_ppqn; mod_sync24_ref = ppqn / PPQN_24; + mod_sync48_ref = ppqn / PPQN_48; mod_step_ref = ppqn / 4; } @@ -157,7 +158,7 @@ void uClockClass::setPPQN(PPQNResolution resolution) { // stop clock to make it safe changing those references // so we avoid volatile then and ATOMIC everywhere - stop(); + //stop(); ppqn = resolution; calculateReferencedata(); } @@ -166,10 +167,9 @@ void uClockClass::setClockPPQN(PPQNResolution resolution) { // stop clock to make it safe changing those references // so we avoid volatile then and ATOMIC everywhere - stop(); + //stop(); clock_ppqn = resolution; calculateReferencedata(); - //mod_clock_ref = ppqn / clock_ppqn; } void uClockClass::start() @@ -256,7 +256,7 @@ void uClockClass::run() float inline uClockClass::freqToBpm(uint32_t freq) { float usecs = 1/((float)freq/1000000.0); - return (float)((float)(usecs/(float)24) * 60.0); + return (float)((float)(usecs/(float)clock_ppqn) * 60.0); } void uClockClass::setMode(SyncMode tempo_mode) @@ -282,10 +282,12 @@ void uClockClass::resetCounters() { tick = 0; int_clock_tick = 0; - sync24_tick = 0; mod_clock_counter = 0; mod_step_counter = 0; mod_sync24_counter = 0; + sync24_tick = 0; + mod_sync48_counter = 0; + sync48_tick = 0; step_counter = 0; ext_clock_tick = 0; ext_clock_us = 0; @@ -477,6 +479,17 @@ void uClockClass::handleTimerInt() } } + // Sync48 callback + if (mod_sync48_counter == mod_sync48_ref) + mod_sync48_counter = 0; + + if (onSync48Callback) { + if (mod_sync48_counter == 0) { + onSync48Callback(sync48_tick); + ++sync48_tick; + } + } + // PPQNCallback time! if (onPPQNCallback) { onPPQNCallback(tick); @@ -502,6 +515,7 @@ void uClockClass::handleTimerInt() // increment mod counters ++mod_clock_counter; ++mod_sync24_counter; + ++mod_sync48_counter; ++mod_step_counter; } diff --git a/src/uClock.h b/src/uClock.h index b720839..96cd47b 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -57,7 +57,7 @@ typedef struct { // in between 64 to 128. // note: this doesn't impact on sync time, only display time getTempo() // if you dont want to use it, set it to 1 for memory save -#define EXT_INTERVAL_BUFFER_SIZE 24 +#define EXT_INTERVAL_BUFFER_SIZE 128 #define MIN_BPM 1 #define MAX_BPM 300 @@ -84,6 +84,8 @@ class uClockClass { }; enum PPQNResolution { + PPQN_1 = 1, + PPQN_2 = 2, PPQN_4 = 4, PPQN_8 = 8, PPQN_12 = 12, @@ -107,9 +109,19 @@ class uClockClass { onStepCallback = callback; } + // setOnSync1 + // setOnSync2 + // setOnSync4 + // setOnSync8 + // setOnSync12 + void setOnSync24(void (*callback)(uint32_t tick)) { onSync24Callback = callback; } + + void setOnSync48(void (*callback)(uint32_t tick)) { + onSync48Callback = callback; + } void setOnClockStart(void (*callback)()) { onClockStartCallback = callback; @@ -174,6 +186,7 @@ class uClockClass { void (*onPPQNCallback)(uint32_t tick); void (*onStepCallback)(uint32_t step); void (*onSync24Callback)(uint32_t tick); + void (*onSync48Callback)(uint32_t tick); void (*onClockStartCallback)(); void (*onClockStopCallback)(); @@ -183,7 +196,6 @@ class uClockClass { PPQNResolution clock_ppqn = PPQN_24; uint32_t tick; uint32_t int_clock_tick; - uint32_t sync24_tick; uint8_t mod_clock_counter; uint8_t mod_clock_ref; uint8_t mod_step_counter; @@ -191,6 +203,10 @@ class uClockClass { uint32_t step_counter; // should we go uint16_t? uint8_t mod_sync24_counter; uint8_t mod_sync24_ref; + uint32_t sync24_tick; + uint8_t mod_sync48_counter; + uint8_t mod_sync48_ref; + uint32_t sync48_tick; // external clock control volatile uint32_t ext_clock_us; volatile uint32_t ext_clock_tick; From e1d6674635b2c2f121958cdfea89923aab89a200 Mon Sep 17 00:00:00 2001 From: midilab Date: Tue, 25 Mar 2025 10:33:56 -0300 Subject: [PATCH 04/13] added clock output callback for the following resolutions 1PPQN, 2PPQN, 4PPQN, 8PPQN, 12PPQN and 48PPQN --- src/uClock.cpp | 137 +++++++++++++++++++++++++++++++++++++------------ src/uClock.h | 57 ++++++++++++++------ 2 files changed, 145 insertions(+), 49 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index 405cb87..182314b 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -126,7 +126,13 @@ uClockClass::uClockClass() resetCounters(); onPPQNCallback = nullptr; + onSync1Callback = nullptr; + onSync2Callback = nullptr; + onSync4Callback = nullptr; + onSync8Callback = nullptr; + onSync12Callback = nullptr; onSync24Callback = nullptr; + onSync48Callback = nullptr; onStepCallback = nullptr; onClockStartCallback = nullptr; onClockStopCallback = nullptr; @@ -149,6 +155,11 @@ uint32_t uClockClass::bpmToMicroSeconds(float bpm) void uClockClass::calculateReferencedata() { mod_clock_ref = ppqn / clock_ppqn; + mod_sync1_ref = ppqn / PPQN_1; + mod_sync2_ref = ppqn / PPQN_2; + mod_sync4_ref = ppqn / PPQN_4; + mod_sync8_ref = ppqn / PPQN_8; + mod_sync12_ref = ppqn / PPQN_12; mod_sync24_ref = ppqn / PPQN_24; mod_sync48_ref = ppqn / PPQN_48; mod_step_ref = ppqn / 4; @@ -156,20 +167,18 @@ void uClockClass::calculateReferencedata() void uClockClass::setPPQN(PPQNResolution resolution) { - // stop clock to make it safe changing those references - // so we avoid volatile then and ATOMIC everywhere - //stop(); - ppqn = resolution; - calculateReferencedata(); + ATOMIC( + ppqn = resolution; + calculateReferencedata(); + ) } void uClockClass::setClockPPQN(PPQNResolution resolution) { - // stop clock to make it safe changing those references - // so we avoid volatile then and ATOMIC everywhere - //stop(); - clock_ppqn = resolution; - calculateReferencedata(); + ATOMIC( + clock_ppqn = resolution; + calculateReferencedata(); + ) } void uClockClass::start() @@ -284,14 +293,25 @@ void uClockClass::resetCounters() int_clock_tick = 0; mod_clock_counter = 0; mod_step_counter = 0; - mod_sync24_counter = 0; - sync24_tick = 0; - mod_sync48_counter = 0; - sync48_tick = 0; step_counter = 0; ext_clock_tick = 0; ext_clock_us = 0; ext_interval_idx = 0; + // sync output counters + mod_sync1_counter = 0; + sync1_tick = 0; + mod_sync2_counter = 0; + sync2_tick = 0; + mod_sync4_counter = 0; + sync4_tick = 0; + mod_sync8_counter = 0; + sync8_tick = 0; + mod_sync12_counter = 0; + sync12_tick = 0; + mod_sync24_counter = 0; + sync24_tick = 0; + mod_sync48_counter = 0; + sync48_tick = 0; for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) { ext_interval_buffer[i] = 0; @@ -426,7 +446,7 @@ void uClockClass::handleExternalClock() void uClockClass::handleTimerInt() { - // reset mod_clock_counter + // track main input clock counter if (mod_clock_counter == mod_clock_ref) mod_clock_counter = 0; @@ -467,56 +487,105 @@ void uClockClass::handleTimerInt() // internal clock tick me! ++int_clock_tick; } + ++mod_clock_counter; - // Sync24 callback - if (mod_sync24_counter == mod_sync24_ref) - mod_sync24_counter = 0; + // ALL OUTPUT SYNC CALLBACKS + // Sync1 callback + if (onSync1Callback) { + if (mod_sync1_counter == mod_sync1_ref) + mod_sync1_counter = 0; + if (mod_sync1_counter == 0) { + onSync1Callback(sync1_tick); + ++sync1_tick; + } + ++mod_sync1_counter; + } + // Sync2 callback + if (onSync2Callback) { + if (mod_sync2_counter == mod_sync2_ref) + mod_sync2_counter = 0; + if (mod_sync2_counter == 0) { + onSync2Callback(sync2_tick); + ++sync2_tick; + } + ++mod_sync2_counter; + } + + // Sync4 callback + if (onSync4Callback) { + if (mod_sync4_counter == mod_sync4_ref) + mod_sync4_counter = 0; + if (mod_sync4_counter == 0) { + onSync4Callback(sync4_tick); + ++sync4_tick; + } + ++mod_sync4_counter; + } + + // Sync8 callback + if (onSync8Callback) { + if (mod_sync8_counter == mod_sync8_ref) + mod_sync8_counter = 0; + if (mod_sync8_counter == 0) { + onSync8Callback(sync8_tick); + ++sync8_tick; + } + ++mod_sync8_counter; + } + + // Sync12 callback + if (onSync12Callback) { + if (mod_sync12_counter == mod_sync12_ref) + mod_sync12_counter = 0; + if (mod_sync12_counter == 0) { + onSync12Callback(sync12_tick); + ++sync12_tick; + } + ++mod_sync12_counter; + } + + // Sync24 callback if (onSync24Callback) { + if (mod_sync24_counter == mod_sync24_ref) + mod_sync24_counter = 0; if (mod_sync24_counter == 0) { onSync24Callback(sync24_tick); ++sync24_tick; } + ++mod_sync24_counter; } // Sync48 callback - if (mod_sync48_counter == mod_sync48_ref) - mod_sync48_counter = 0; - if (onSync48Callback) { + if (mod_sync48_counter == mod_sync48_ref) + mod_sync48_counter = 0; if (mod_sync48_counter == 0) { onSync48Callback(sync48_tick); ++sync48_tick; } + ++mod_sync48_counter; } - // PPQNCallback time! + // main PPQNCallback if (onPPQNCallback) { onPPQNCallback(tick); + ++tick; } - // reset step mod counter reference ? - if (mod_step_counter == mod_step_ref) - mod_step_counter = 0; - // step callback to support 16th old school style sequencers // with builtin shuffle for this callback only if (onStepCallback) { + if (mod_step_counter == mod_step_ref) + mod_step_counter = 0; // processShufle make use of mod_step_counter == 0 logic too if (processShuffle()) { onStepCallback(step_counter); // going forward to the next step call ++step_counter; } + ++mod_step_counter; } - - // tick me! - ++tick; - // increment mod counters - ++mod_clock_counter; - ++mod_sync24_counter; - ++mod_sync48_counter; - ++mod_step_counter; } // elapsed time support diff --git a/src/uClock.h b/src/uClock.h index 96cd47b..ab4f4ed 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -34,16 +34,6 @@ namespace umodular { namespace clock { -// for extended steps in memory style and make use of 96ppqn for record propurse we can -// keep array[step] memory layout and add new information about note possition to be check for the entire ppqn pulse -// example: for a whole 24 pulses we only check array[step].offset that can vary from 0 to 24(ppqn/4) -// time/tick notation and representation notes: -// one quarter note == 4 steps in 16th notes step sequencer style -// PPQN / 4 = pulses in between steps(from step sequencer perspective, a quarter note have 4 steps) -// 24 PPQN (6 pulses per step) -// 48 PPQN (12 pulses per step) -// 96 PPQN (24 pulses per step) - // min: -(ppqn/4)-1 step, max: (ppqn/4)-1 steps // adjust the size of you template if more than 16 shuffle step info needed #define MAX_SHUFFLE_TEMPLATE_SIZE 16 @@ -108,12 +98,27 @@ class uClockClass { void setOnStep(void (*callback)(uint32_t step)) { onStepCallback = callback; } + + // multiple output clock signatures + void setOnSync1(void (*callback)(uint32_t tick)) { + onSync1Callback = callback; + } - // setOnSync1 - // setOnSync2 - // setOnSync4 - // setOnSync8 - // setOnSync12 + void setOnSync2(void (*callback)(uint32_t tick)) { + onSync2Callback = callback; + } + + void setOnSync4(void (*callback)(uint32_t tick)) { + onSync4Callback = callback; + } + + void setOnSync8(void (*callback)(uint32_t tick)) { + onSync8Callback = callback; + } + + void setOnSync12(void (*callback)(uint32_t tick)) { + onSync12Callback = callback; + } void setOnSync24(void (*callback)(uint32_t tick)) { onSync24Callback = callback; @@ -185,6 +190,11 @@ class uClockClass { void (*onPPQNCallback)(uint32_t tick); void (*onStepCallback)(uint32_t step); + void (*onSync1Callback)(uint32_t tick); + void (*onSync2Callback)(uint32_t tick); + void (*onSync4Callback)(uint32_t tick); + void (*onSync8Callback)(uint32_t tick); + void (*onSync12Callback)(uint32_t tick); void (*onSync24Callback)(uint32_t tick); void (*onSync48Callback)(uint32_t tick); void (*onClockStartCallback)(); @@ -201,6 +211,23 @@ class uClockClass { uint8_t mod_step_counter; uint8_t mod_step_ref; uint32_t step_counter; // should we go uint16_t? + + // clock output counters, ticks and references + uint8_t mod_sync1_counter; + uint8_t mod_sync1_ref; + uint32_t sync1_tick; + uint8_t mod_sync2_counter; + uint8_t mod_sync2_ref; + uint32_t sync2_tick; + uint8_t mod_sync4_counter; + uint8_t mod_sync4_ref; + uint32_t sync4_tick; + uint8_t mod_sync8_counter; + uint8_t mod_sync8_ref; + uint32_t sync8_tick; + uint8_t mod_sync12_counter; + uint8_t mod_sync12_ref; + uint32_t sync12_tick; uint8_t mod_sync24_counter; uint8_t mod_sync24_ref; uint32_t sync24_tick; From 3619eb53f6e2c445b6966a692b72cc9c747d6222 Mon Sep 17 00:00:00 2001 From: midilab Date: Wed, 26 Mar 2025 17:53:03 -0300 Subject: [PATCH 05/13] update all mod_clock_refs to uint16_t to avoid overflow on higher PPQNs. --- src/uClock.cpp | 23 ++++++++++++----------- src/uClock.h | 27 +++++++++++++-------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index 182314b..2e64054 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -155,18 +155,19 @@ uint32_t uClockClass::bpmToMicroSeconds(float bpm) void uClockClass::calculateReferencedata() { mod_clock_ref = ppqn / clock_ppqn; - mod_sync1_ref = ppqn / PPQN_1; - mod_sync2_ref = ppqn / PPQN_2; - mod_sync4_ref = ppqn / PPQN_4; - mod_sync8_ref = ppqn / PPQN_8; - mod_sync12_ref = ppqn / PPQN_12; - mod_sync24_ref = ppqn / PPQN_24; - mod_sync48_ref = ppqn / PPQN_48; - mod_step_ref = ppqn / 4; + mod_sync1_ref = ppqn / PPQN_1; + mod_sync2_ref = ppqn / PPQN_2; + mod_sync4_ref = ppqn / PPQN_4; + mod_sync8_ref = ppqn / PPQN_8; + mod_sync12_ref = ppqn / PPQN_12; + mod_sync24_ref = ppqn / PPQN_24; + mod_sync48_ref = ppqn / PPQN_48; + mod_step_ref = ppqn / 4; } void uClockClass::setPPQN(PPQNResolution resolution) { + // TODO: dont allow PPQN lower than 4(to avoid problems with mod_step_ref) ATOMIC( ppqn = resolution; calculateReferencedata(); @@ -318,10 +319,11 @@ void uClockClass::resetCounters() } } -// TODO: Tap stuff void uClockClass::tap() { - // tap me + // we can make use of mod_sync1_ref for tap + //uint8_t mod_tap_ref = ppqn / PPQN_1; + // we only set tap if SyncMode is INTERNAL_CLOCK } void uClockClass::setShuffle(bool active) @@ -409,7 +411,6 @@ bool inline uClockClass::processShuffle() return false; } -// TODO: needs to check clock signals against current phase lock parameters(based on 24ppqn sync) void uClockClass::handleExternalClock() { switch (state) { diff --git a/src/uClock.h b/src/uClock.h index ab4f4ed..ef56c4c 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -50,7 +50,7 @@ typedef struct { #define EXT_INTERVAL_BUFFER_SIZE 128 #define MIN_BPM 1 -#define MAX_BPM 300 +#define MAX_BPM 400 #define PHASE_FACTOR 16 #define PLL_X 220 @@ -200,40 +200,39 @@ class uClockClass { void (*onClockStartCallback)(); void (*onClockStopCallback)(); - // internal clock control - // uint16_t ppqn; + // clock control PPQNResolution ppqn = PPQN_96; PPQNResolution clock_ppqn = PPQN_24; + // output and internal counters, ticks and references uint32_t tick; uint32_t int_clock_tick; uint8_t mod_clock_counter; - uint8_t mod_clock_ref; + uint16_t mod_clock_ref; uint8_t mod_step_counter; uint8_t mod_step_ref; - uint32_t step_counter; // should we go uint16_t? - - // clock output counters, ticks and references + uint32_t step_counter; uint8_t mod_sync1_counter; - uint8_t mod_sync1_ref; + uint16_t mod_sync1_ref; uint32_t sync1_tick; uint8_t mod_sync2_counter; - uint8_t mod_sync2_ref; + uint16_t mod_sync2_ref; uint32_t sync2_tick; uint8_t mod_sync4_counter; - uint8_t mod_sync4_ref; + uint16_t mod_sync4_ref; uint32_t sync4_tick; uint8_t mod_sync8_counter; - uint8_t mod_sync8_ref; + uint16_t mod_sync8_ref; uint32_t sync8_tick; uint8_t mod_sync12_counter; - uint8_t mod_sync12_ref; + uint16_t mod_sync12_ref; uint32_t sync12_tick; uint8_t mod_sync24_counter; - uint8_t mod_sync24_ref; + uint16_t mod_sync24_ref; uint32_t sync24_tick; uint8_t mod_sync48_counter; - uint8_t mod_sync48_ref; + uint16_t mod_sync48_ref; uint32_t sync48_tick; + // external clock control volatile uint32_t ext_clock_us; volatile uint32_t ext_clock_tick; From 45ecc4d2ca66aad7877dfac395a1e94f48fd7157 Mon Sep 17 00:00:00 2001 From: midilab Date: Wed, 26 Mar 2025 18:05:32 -0300 Subject: [PATCH 06/13] update README with new setOnSyncXX() options and newer 1PPQN and 2PPQN support --- README.md | 22 ++++++++++++---------- src/uClock.cpp | 5 ++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3ec6aaa..a34a5dd 100755 --- a/README.md +++ b/README.md @@ -13,21 +13,23 @@ The uClock library API operates through an attached callback function mechanism: 1. **setOnPPQN(onPPQNCallback) > onPPQNCallback(uint32_t tick)** Calls are made on each new pulse based on the selected PPQN resolution (if no PPQN is set, the default is 96 PPQN). 2. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** A good way to code an old-style step sequencer based on a 16th-note schema (not dependent on PPQN resolution). -3. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices synced with your system. +3. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices synced with your system is to use setOnSyncXX(), where XX represents the PPQN you want to use. MIDI specs expect 24 PPQN, but if you're working with other devices that are not MIDI standard, you can choose a different PPQN value. Please check the supported PPQNs to choose from. 4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event. 5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** On the uClock Stop event. ### Clock input/output resolutions -1. **PPQN_4** 4 Pulses Per Quarter Note -2. **PPQN_8** 8 Pulses Per Quarter Note -3. **PPQN_12** 12 Pulses Per Quarter Note -4. **PPQN_24** 24 Pulses Per Quarter Note -5. **PPQN_48** 48 Pulses Per Quarter Note -6. **PPQN_96** 96 Pulses Per Quarter Note -7. **PPQN_384** 384 Pulses Per Quarter Note -8. **PPQN_480** 480 Pulses Per Quarter Note -9. **PPQN_960** 960 Pulses Per Quarter Note +1. **PPQN_1** 1 Pulses Per Quarter Note (only input) +2. **PPQN_2** 2 Pulses Per Quarter Note (only input) +3. **PPQN_4** 4 Pulses Per Quarter Note +4. **PPQN_8** 8 Pulses Per Quarter Note +5. **PPQN_12** 12 Pulses Per Quarter Note +6. **PPQN_24** 24 Pulses Per Quarter Note +7. **PPQN_48** 48 Pulses Per Quarter Note +8. **PPQN_96** 96 Pulses Per Quarter Note +9. **PPQN_384** 384 Pulses Per Quarter Note +10. **PPQN_480** 480 Pulses Per Quarter Note +11. **PPQN_960** 960 Pulses Per Quarter Note To generate a MIDI sync signal and synchronize external MIDI devices, you can start with a resolution of 24 PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available on the market. By sending 24 pulses per quarter-note interval, you can ensure effective synchronization among your MIDI devices. diff --git a/src/uClock.cpp b/src/uClock.cpp index 2e64054..a6de775 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -167,7 +167,10 @@ void uClockClass::calculateReferencedata() void uClockClass::setPPQN(PPQNResolution resolution) { - // TODO: dont allow PPQN lower than 4(to avoid problems with mod_step_ref) + // dont allow PPQN lower than 4 for output clock (to avoid problems with mod_step_ref) + if (resolution < PPQN_4) + return; + ATOMIC( ppqn = resolution; calculateReferencedata(); From 0d5ab3733cf1d4b735473d92580a51244c43dec6 Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 29 Mar 2025 11:00:31 -0300 Subject: [PATCH 07/13] after input and output clock setup support, rename some methods of uClock to avoid API usage miss intepretation. --- src/uClock.cpp | 80 +++++++++++++++++++++++++------------------------- src/uClock.h | 26 ++++++++-------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index a6de775..6934e5b 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -121,8 +121,8 @@ uClockClass::uClockClass() start_timer = 0; last_interval = 0; sync_interval = 0; - state = PAUSED; - mode = INTERNAL_CLOCK; + clock_state = PAUSED; + clock_mode = INTERNAL_CLOCK; resetCounters(); onPPQNCallback = nullptr; @@ -136,7 +136,7 @@ uClockClass::uClockClass() onStepCallback = nullptr; onClockStartCallback = nullptr; onClockStopCallback = nullptr; - // first ppqn references calculus for ppqn and clock resolution + // initialize reference data calculateReferencedata(); } @@ -149,38 +149,38 @@ void uClockClass::init() uint32_t uClockClass::bpmToMicroSeconds(float bpm) { - return (60000000.0f / (float)ppqn / bpm); + return (60000000.0f / (float)output_ppqn / bpm); } void uClockClass::calculateReferencedata() { - mod_clock_ref = ppqn / clock_ppqn; - mod_sync1_ref = ppqn / PPQN_1; - mod_sync2_ref = ppqn / PPQN_2; - mod_sync4_ref = ppqn / PPQN_4; - mod_sync8_ref = ppqn / PPQN_8; - mod_sync12_ref = ppqn / PPQN_12; - mod_sync24_ref = ppqn / PPQN_24; - mod_sync48_ref = ppqn / PPQN_48; - mod_step_ref = ppqn / 4; + mod_clock_ref = output_ppqn / input_ppqn; + mod_sync1_ref = output_ppqn / PPQN_1; + mod_sync2_ref = output_ppqn / PPQN_2; + mod_sync4_ref = output_ppqn / PPQN_4; + mod_sync8_ref = output_ppqn / PPQN_8; + mod_sync12_ref = output_ppqn / PPQN_12; + mod_sync24_ref = output_ppqn / PPQN_24; + mod_sync48_ref = output_ppqn / PPQN_48; + mod_step_ref = output_ppqn / 4; } -void uClockClass::setPPQN(PPQNResolution resolution) +void uClockClass::setOutputPPQN(PPQNResolution resolution) { - // dont allow PPQN lower than 4 for output clock (to avoid problems with mod_step_ref) + // dont allow PPQN lower than PPQN_4 for output clock (to avoid problems with mod_step_ref) if (resolution < PPQN_4) return; ATOMIC( - ppqn = resolution; + output_ppqn = resolution; calculateReferencedata(); ) } -void uClockClass::setClockPPQN(PPQNResolution resolution) +void uClockClass::setInputPPQN(PPQNResolution resolution) { ATOMIC( - clock_ppqn = resolution; + input_ppqn = resolution; calculateReferencedata(); ) } @@ -194,16 +194,16 @@ void uClockClass::start() onClockStartCallback(); } - if (mode == INTERNAL_CLOCK) { - state = STARTED; + if (clock_mode == INTERNAL_CLOCK) { + clock_state = STARTED; } else { - state = STARTING; + clock_state = STARTING; } } void uClockClass::stop() { - state = PAUSED; + clock_state = PAUSED; start_timer = 0; resetCounters(); if (onClockStopCallback) { @@ -213,8 +213,8 @@ void uClockClass::stop() void uClockClass::pause() { - if (mode == INTERNAL_CLOCK) { - if (state == PAUSED) { + if (clock_mode == INTERNAL_CLOCK) { + if (clock_state == PAUSED) { start(); } else { stop(); @@ -224,7 +224,7 @@ void uClockClass::pause() void uClockClass::setTempo(float bpm) { - if (mode == EXTERNAL_CLOCK) { + if (clock_mode == EXTERNAL_CLOCK) { return; } @@ -241,7 +241,7 @@ void uClockClass::setTempo(float bpm) float uClockClass::getTempo() { - if (mode == EXTERNAL_CLOCK) { + if (clock_mode == EXTERNAL_CLOCK) { uint32_t acc = 0; // wait the buffer to get full if (ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE-1] == 0) { @@ -269,22 +269,22 @@ void uClockClass::run() float inline uClockClass::freqToBpm(uint32_t freq) { float usecs = 1/((float)freq/1000000.0); - return (float)((float)(usecs/(float)clock_ppqn) * 60.0); + return (float)((float)(usecs/(float)input_ppqn) * 60.0); } -void uClockClass::setMode(SyncMode tempo_mode) +void uClockClass::setMode(ClockMode tempo_mode) { - mode = tempo_mode; + clock_mode = tempo_mode; } -uClockClass::SyncMode uClockClass::getMode() +uClockClass::ClockMode uClockClass::getMode() { - return mode; + return clock_mode; } void uClockClass::clockMe() { - if (mode == EXTERNAL_CLOCK) { + if (clock_mode == EXTERNAL_CLOCK) { ATOMIC( handleExternalClock() ) @@ -325,8 +325,8 @@ void uClockClass::resetCounters() void uClockClass::tap() { // we can make use of mod_sync1_ref for tap - //uint8_t mod_tap_ref = ppqn / PPQN_1; - // we only set tap if SyncMode is INTERNAL_CLOCK + //uint8_t mod_tap_ref = output_ppqn / PPQN_1; + // we only set tap if ClockMode is INTERNAL_CLOCK } void uClockClass::setShuffle(bool active) @@ -399,7 +399,7 @@ bool inline uClockClass::processShuffle() last_shff = shff; - // shuffle_shoot_ctrl helps keep track if we have shoot or not a note for the step space of ppqn/4 pulses + // shuffle_shoot_ctrl helps keep track if we have shoot or not a note for the step space of output_ppqn/4 pulses if (mod_shuffle == 0 && shuffle_shoot_ctrl == true) { // keep track of next note shuffle for current note lenght control shuffle_length_ctrl = shuffle.step[(step_counter+1)%shuffle.size]; @@ -416,12 +416,12 @@ bool inline uClockClass::processShuffle() void uClockClass::handleExternalClock() { - switch (state) { + switch (clock_state) { case PAUSED: break; case STARTING: - state = STARTED; + clock_state = STARTED; ext_clock_us = micros(); break; @@ -433,7 +433,7 @@ void uClockClass::handleExternalClock() // external clock tick me! ext_clock_tick++; - // accumulate interval incomming ticks data for getTempo() smooth reads on slave mode + // accumulate interval incomming ticks data for getTempo() smooth reads on slave clock_mode if(++ext_interval_idx >= EXT_INTERVAL_BUFFER_SIZE) { ext_interval_idx = 0; } @@ -457,7 +457,7 @@ void uClockClass::handleTimerInt() // process sync signals first please... if (mod_clock_counter == 0) { - if (mode == EXTERNAL_CLOCK) { + if (clock_mode == EXTERNAL_CLOCK) { // sync tick position with external tick clock if ((int_clock_tick < ext_clock_tick) || (int_clock_tick > (ext_clock_tick + 1))) { int_clock_tick = ext_clock_tick; @@ -649,7 +649,7 @@ void uClockHandler() // global timer counter _millis = millis(); - if (uClock.state == uClock.STARTED) { + if (uClock.clock_state == uClock.STARTED) { uClock.handleTimerInt(); } } diff --git a/src/uClock.h b/src/uClock.h index ef56c4c..a7edada 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -34,7 +34,9 @@ namespace umodular { namespace clock { -// min: -(ppqn/4)-1 step, max: (ppqn/4)-1 steps +// Shuffle templates are specific for each PPQN output resolution +// min: -(output_ppqn/4)-1 ticks +// max: (output_ppqn/4)-1 ticks // adjust the size of you template if more than 16 shuffle step info needed #define MAX_SHUFFLE_TEMPLATE_SIZE 16 typedef struct { @@ -62,7 +64,7 @@ typedef struct { class uClockClass { public: - enum SyncMode { + enum ClockMode { INTERNAL_CLOCK = 0, EXTERNAL_CLOCK }; @@ -87,11 +89,11 @@ class uClockClass { PPQN_960 = 960 }; - ClockState state; + ClockState clock_state; uClockClass(); - void setOnPPQN(void (*callback)(uint32_t tick)) { + void setOnOutputPPQN(void (*callback)(uint32_t tick)) { onPPQNCallback = callback; } @@ -137,8 +139,8 @@ class uClockClass { } void init(); - void setPPQN(PPQNResolution resolution); - void setClockPPQN(PPQNResolution resolution); + void setOutputPPQN(PPQNResolution resolution); + void setInputPPQN(PPQNResolution resolution); void handleTimerInt(); void handleExternalClock(); @@ -155,8 +157,8 @@ class uClockClass { void run(); // external timming control - void setMode(SyncMode tempo_mode); - SyncMode getMode(); + void setClockMode(ClockMode tempo_mode); + ClockMode getClockMode(); void clockMe(); // shuffle @@ -200,9 +202,9 @@ class uClockClass { void (*onClockStartCallback)(); void (*onClockStopCallback)(); - // clock control - PPQNResolution ppqn = PPQN_96; - PPQNResolution clock_ppqn = PPQN_24; + // clock input/output control + PPQNResolution output_ppqn = PPQN_96; + PPQNResolution input_ppqn = PPQN_24; // output and internal counters, ticks and references uint32_t tick; uint32_t int_clock_tick; @@ -242,7 +244,7 @@ class uClockClass { float tempo; uint32_t start_timer; - SyncMode mode; + ClockMode clock_mode; volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE]; uint16_t ext_interval_idx; From 35906e2ab17dba80c84fd06faa195b7e5edd675d Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 29 Mar 2025 12:05:31 -0300 Subject: [PATCH 08/13] rename callback PPQNCallback fo OutputPPQNCAllback to follow method prefix --- src/uClock.cpp | 6 +++--- src/uClock.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index 6934e5b..237623a 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -125,7 +125,7 @@ uClockClass::uClockClass() clock_mode = INTERNAL_CLOCK; resetCounters(); - onPPQNCallback = nullptr; + onOutputPPQNCallback = nullptr; onSync1Callback = nullptr; onSync2Callback = nullptr; onSync4Callback = nullptr; @@ -572,8 +572,8 @@ void uClockClass::handleTimerInt() } // main PPQNCallback - if (onPPQNCallback) { - onPPQNCallback(tick); + if (onOutputPPQNCallback) { + onOutputPPQNCallback(tick); ++tick; } diff --git a/src/uClock.h b/src/uClock.h index a7edada..97ca6ac 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -94,7 +94,7 @@ class uClockClass { uClockClass(); void setOnOutputPPQN(void (*callback)(uint32_t tick)) { - onPPQNCallback = callback; + onOutputPPQNCallback = callback; } void setOnStep(void (*callback)(uint32_t step)) { @@ -190,7 +190,7 @@ class uClockClass { // shuffle bool inline processShuffle(); - void (*onPPQNCallback)(uint32_t tick); + void (*onOutputPPQNCallback)(uint32_t tick); void (*onStepCallback)(uint32_t step); void (*onSync1Callback)(uint32_t tick); void (*onSync2Callback)(uint32_t tick); From bef499d8222650aa3f728174b24cdd8192600f4d Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 29 Mar 2025 12:12:48 -0300 Subject: [PATCH 09/13] fixes class method name references changes on cpp file --- src/uClock.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index 237623a..72e324f 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -272,12 +272,12 @@ float inline uClockClass::freqToBpm(uint32_t freq) return (float)((float)(usecs/(float)input_ppqn) * 60.0); } -void uClockClass::setMode(ClockMode tempo_mode) +void uClockClass::setClockMode(ClockMode tempo_mode) { clock_mode = tempo_mode; } -uClockClass::ClockMode uClockClass::getMode() +uClockClass::ClockMode uClockClass::getClockMode() { return clock_mode; } From 016d1fed18f8381285d1412ba3fc2ecebeef89ef Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 29 Mar 2025 14:15:43 -0300 Subject: [PATCH 10/13] added new api calls description for multi resolution input/output feature. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a34a5dd..1e6feab 100755 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ With uClock, you can create professional-grade sequencers, sync boxes, or genera ## Interface The uClock library API operates through an attached callback function mechanism: -1. **setOnPPQN(onPPQNCallback) > onPPQNCallback(uint32_t tick)** Calls are made on each new pulse based on the selected PPQN resolution (if no PPQN is set, the default is 96 PPQN). -2. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** A good way to code an old-style step sequencer based on a 16th-note schema (not dependent on PPQN resolution). -3. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices synced with your system is to use setOnSyncXX(), where XX represents the PPQN you want to use. MIDI specs expect 24 PPQN, but if you're working with other devices that are not MIDI standard, you can choose a different PPQN value. Please check the supported PPQNs to choose from. -4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event. -5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** On the uClock Stop event. +1. **setOnOutputPPQN(onPPQNCallback) > onOutputPPQNCallback(uint32_t tick)** Calls are made on each new output pulse based on the selected PPQN resolution (if no PPQN is set, the default is 96 PPQN). +2. **setOnInputPPQN(onPPQNCallback) > onInputPPQNCallback(uint32_t tick)** Set the expected input PPQN (Pulses Per Quarter Note) resolution for external clock sync. +3. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** A good way to code an old-style step sequencer based on a 16th-note schema, which is not dependent on PPQN (Pulses Per Quarter Note) output config. +4. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices in sync with your system is to use setOnSyncXX(), where XX represents the PPQN (Pulses Per Quarter Note) value you want to use. MIDI specifications typically expect 24 PPQN, but if you're working with other devices that are not MIDI standard, you can choose a different PPQN value. Please refer to the supported PPQNs to select from. You can use one or more setOnSyncXX callbacks for different sync output signatures. +5. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event. +6. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** On the uClock Stop event. ### Clock input/output resolutions From 5dc63bdb7d7fbe2858ef3cdc43960cb4c049bdf9 Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 29 Mar 2025 14:25:46 -0300 Subject: [PATCH 11/13] update breakchanges from 1.x to newer api calls --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1e6feab..af26695 100755 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ If you are coming from uClock version < 2.0 versions, pay attention to the break ### setCallback function name changes -- **setClock96PPQNOutput(onClock96PPQNOutputCallback)** is now _setOnPPQN(onPPQNCallback)_ and this clock depends on the PPQN setup using _setPPQN(clockPPQNResolution)_. For clock setup you now use a separated callback via _setOnSync24(onSync24Callback)_ -- **setClock16PPQNOutput(ClockOut16PPQN)** is now _setOnStep(onStepCall)_ and it's not dependent on clock PPQN resolution -- **setOnClockStartOutput(onClockStartCallback)** is now _setOnClockStart(onClockStartCallback)_ -- **setOnClockStopOutput(onClockStopCallback)** is now _setOnClockStop(onClockStopCallback)_ +- `setClock96PPQNOutput(onClock96PPQNOutputCallback)` is now renamed to **`setOnOutputPPQN(onOutputPPQNCallback)`**, and its tick count is based on the PPQN setup using **`setOutputPPQN(clockOutputPPQNResolution)`**. For clock ticks, you now use a separated callback via **`setOnSyncXX(onSyncXXCallback)`**, where XX represents one of the available PPQN values +- `setClock16PPQNOutput(ClockOut16PPQN)` is now renamed to **`setOnStep(onStepCall)`**, and it's not dependent on clock PPQN resolution. +- `setOnClockStartOutput(onClockStartCallback)` is now renamed to **`setOnClockStart(onClockStartCallback)`**. +- `setOnClockStopOutput(onClockStopCallback)` is now renamed to **`setOnClockStop(onClockStopCallback)`**. ### Tick resolution and sequencers From 1222d7af54db0f87feb6b96592c1868d9cba0fd6 Mon Sep 17 00:00:00 2001 From: midilab Date: Sun, 30 Mar 2025 09:45:21 -0300 Subject: [PATCH 12/13] update new api call signatue on examples --- .../AcidStepSequencer/AcidStepSequencer.ino | 4 +- .../DefaultUserInterface.ino | 8 ++-- .../GenericMasterOrExternalSync.ino | 40 ++++++++++++++++--- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/examples/AcidStepSequencer/AcidStepSequencer.ino b/examples/AcidStepSequencer/AcidStepSequencer.ino index 41f3928..474a61e 100644 --- a/examples/AcidStepSequencer/AcidStepSequencer.ino +++ b/examples/AcidStepSequencer/AcidStepSequencer.ino @@ -101,7 +101,7 @@ void onStepCallback(uint32_t tick) } // The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. -void onPPQNCallback(uint32_t tick) +void onOutputPPQNCallback(uint32_t tick) { // handle note on stack for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { @@ -158,7 +158,7 @@ void setup() uClock.init(); // Set the callback function for the clock output to send MIDI Sync message. - uClock.setOnPPQN(onPPQNCallback); + uClock.setOnOutputPPQN(onOutputPPQNCallback); // for MIDI sync uClock.setOnSync24(onSync24Callback); diff --git a/examples/AcidStepSequencer/DefaultUserInterface.ino b/examples/AcidStepSequencer/DefaultUserInterface.ino index 904d651..ad8bed9 100644 --- a/examples/AcidStepSequencer/DefaultUserInterface.ino +++ b/examples/AcidStepSequencer/DefaultUserInterface.ino @@ -84,15 +84,15 @@ void processInterface() processPots(); } -void tempoInterface(uint32_t * tick) +void tempoInterface(uint32_t tick) { // BPM led indicator - if ( !(*tick % (96)) || (*tick == 0) ) { // first compass step will flash longer + if ( !(tick % (96)) || (tick == 0) ) { // first compass step will flash longer _bpm_blink_timer = 8; digitalWrite(PLAY_STOP_LED_PIN , HIGH); - } else if ( !(*tick % (24)) ) { // each quarter led on + } else if ( !(tick % (24)) ) { // each quarter led on digitalWrite(PLAY_STOP_LED_PIN , HIGH); - } else if ( !(*tick % _bpm_blink_timer) ) { // get led off + } else if ( !(tick % _bpm_blink_timer) ) { // get led off digitalWrite(PLAY_STOP_LED_PIN , LOW); _bpm_blink_timer = 1; } diff --git a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino index 366e938..7cef51b 100644 --- a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino +++ b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino @@ -4,7 +4,7 @@ bool _external_sync_on = false; // the main uClock PPQN resolution ticking -void onPPQNCallback(uint32_t tick) { +void onOutputPPQNCallback(uint32_t tick) { // tick your sequencers or tickable devices... } @@ -12,11 +12,31 @@ void onStepCallback(uint32_t step) { // triger step data for sequencer device... } +// The callback function called by uClock each Pulse of 1PPQN clock resolution. +void onSync1Callback(uint32_t tick) { + // send sync signal to... +} + +// The callback function called by uClock each Pulse of 2PPQN clock resolution. +void onSync2Callback(uint32_t tick) { + // send sync signal to... +} + +// The callback function called by uClock each Pulse of 4PPQN clock resolution. +void onSync4Callback(uint32_t tick) { + // send sync signal to... +} + // The callback function called by uClock each Pulse of 24PPQN clock resolution. void onSync24Callback(uint32_t tick) { // send sync signal to... } +// The callback function called by uClock each Pulse of 48PPQN clock resolution. +void onSync48Callback(uint32_t tick) { + // send sync signal to... +} + // The callback function called when clock starts by using uClock.start() method. void onClockStartCallback() { // send start signal to... @@ -32,22 +52,30 @@ void setup() { // inits the clock library uClock.init(); - // avaliable resolutions - // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] + // avaliable output PPQN resolutions for this example + // [ uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] // not mandatory to call, the default is 96PPQN if not set - uClock.setPPQN(uClock.PPQN_96); + uClock.setOutputPPQN(uClock.PPQN_96); // you need to use at least one! - uClock.setOnPPQN(onPPQNCallback); + uClock.setOnOutputPPQN(onOutputPPQNCallback); uClock.setOnStep(onStepCallback); + // multi sync output signatures avaliable + // normaly used by eurorack modular modules + uClock.setOnSync1(onSync1Callback); + uClock.setOnSync2(onSync2Callback); + uClock.setOnSync4(onSync4Callback); + // midi sync standard uClock.setOnSync24(onSync24Callback); + // some korg machines do 48ppqn + uClock.setOnSync48(onSync48Callback); uClock.setOnClockStart(onClockStartCallback); uClock.setOnClockStop(onClockStopCallback); // set external sync mode? if (_external_sync_on) { - uClock.setMode(uClock.EXTERNAL_CLOCK); + uClock.setClockMode(uClock.EXTERNAL_CLOCK); } // starts clock From c90d69c7383856aff5efc916b8a3bfc8fa18b087 Mon Sep 17 00:00:00 2001 From: midilab Date: Wed, 30 Apr 2025 07:17:07 -0300 Subject: [PATCH 13/13] fix api naming --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index af26695..4ef20f4 100755 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ In order for software timer mode to work, you need to add a call to your `loop() ```c++ void loop() { uClock.run(); - + // do anything else you need to do inside loop()... // you can intercalate your main processing with other uClock.run() calls to avoid timming accuracy loss. //uClock.run(); @@ -73,7 +73,7 @@ If created a device using setClock16PPQNOutput only you just change the API call # Examples -You will find more complete examples on examples/ folder: +You will find more complete examples on examples/ folder: ```c++ #include @@ -116,7 +116,7 @@ void setup() { uClock.setPPQN(uClock.PPQN_96); // you need to use at least one! - uClock.setOnPPQN(onPPQNCallback); + uClock.setOnOutputPPQN(onPPQNCallback); uClock.setOnStep(onStepCallback); uClock.setOnSync24(onSync24Callback); @@ -189,7 +189,7 @@ void setup() { // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN uClock.setOnSync24(onSync24Callback); // Set the callback function for MIDI Start and Stop messages. - uClock.setOnClockStartOutput(onClockStart); + uClock.setOnClockStartOutput(onClockStart); uClock.setOnClockStopOutput(onClockStop); // Set the clock BPM to 126 BPM uClock.setTempo(126); @@ -232,7 +232,7 @@ void setup() { // Set the callback function for the clock output to send MIDI Sync message. based on 24PPQN uClock.setOnSync24(onSync24Callback); // Set the callback function for MIDI Start and Stop messages. - uClock.setOnClockStartOutput(onClockStart); + uClock.setOnClockStartOutput(onClockStart); uClock.setOnClockStopOutput(onClockStop); // Set the clock BPM to 126 BPM uClock.setTempo(126); @@ -306,7 +306,7 @@ bool _playing = false; uint16_t _step = 0; void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) -{ +{ // send midi message command = command | (uint8_t)MIDI_CHANNEL; Serial.write(command); @@ -315,14 +315,14 @@ void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) } // The callback function called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. -void onStepCallback(uint32_t tick) +void onStepCallback(uint32_t tick) { uint16_t step; uint16_t length = NOTE_LENGTH; - + // get actual step. _step = tick % _step_length; - + // send note on only if this step are not in rest mode if ( _sequencer[_step].rest == false ) { @@ -345,15 +345,15 @@ void onStepCallback(uint32_t tick) _note_stack[i].note = _sequencer[_step].note; _note_stack[i].length = length; // send note on - sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); + sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); return; } } - } + } } // The callback function called by uClock each Pulse of 96PPQN clock resolution. -void onPPQNCallback(uint32_t tick) +void onPPQNCallback(uint32_t tick) { // Send MIDI_CLOCK to external hardware Serial.write(MIDI_CLOCK); @@ -366,19 +366,19 @@ void onPPQNCallback(uint32_t tick) sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0); _note_stack[i].length = -1; } - } + } } } // The callback function called when clock starts by using Clock.start() method. -void onClockStart() +void onClockStart() { Serial.write(MIDI_START); _playing = true; } // The callback function called when clock stops by using Clock.stop() method. -void onClockStop() +void onClockStop() { Serial.write(MIDI_STOP); // send all note off on sequencer stop @@ -389,25 +389,25 @@ void onClockStop() _playing = false; } -void setup() +void setup() { // Initialize serial communication // the default MIDI serial speed communication at 31250 bits per second - Serial.begin(31250); + Serial.begin(31250); // Inits the clock uClock.init(); - + // Set the callback function for the clock output to send MIDI Sync message. - uClock.setOnPPQN(onPPQNCallback); - + uClock.setOnOutputPPQN(onPPQNCallback); + // Set the callback function for the step sequencer on 16ppqn - uClock.setOnStep(onStepCallback); - + uClock.setOnStep(onStepCallback); + // Set the callback function for MIDI Start and Stop messages. - uClock.setOnClockStart(onClockStart); + uClock.setOnClockStart(onClockStart); uClock.setOnClockStop(onClockStop); - + // Set the clock BPM to 126 BPM uClock.setTempo(126); @@ -427,13 +427,13 @@ void setup() // pins, buttons, leds and pots config //configureYourUserInterface(); - + // start sequencer uClock.start(); } // User interaction goes here -void loop() +void loop() { // Controls your 303 engine interacting with user here... // you can change data by using _sequencer[] and _step_length only! do not mess with _note_stack[]!