Multi resolution support for input and output signals.

void setOutputPPQN(PPQNResolution resolution);
void setInputPPQN(PPQNResolution resolution);

PPQN_1 1 Pulses Per Quarter Note (only input)
PPQN_2 2 Pulses Per Quarter Note (only input)
PPQN_4 4 Pulses Per Quarter Note
PPQN_8 8 Pulses Per Quarter Note
PPQN_12 12 Pulses Per Quarter Note
PPQN_24 24 Pulses Per Quarter Note
PPQN_48 48 Pulses Per Quarter Note
PPQN_96 96 Pulses Per Quarter Note
PPQN_384 384 Pulses Per Quarter Note
PPQN_480 480 Pulses Per Quarter Note
PPQN_960 960 Pulses Per Quarter Note
main
midilab 2 weeks ago committed by GitHub
commit 35074971a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 66
      README.md
  2. 4
      examples/AcidStepSequencer/AcidStepSequencer.ino
  3. 8
      examples/AcidStepSequencer/DefaultUserInterface.ino
  4. 40
      examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino
  5. 2
      library.json
  6. 2
      library.properties
  7. 9
      src/platforms/avr.h
  8. 32
      src/platforms/esp32-nofrertos.h
  9. 6
      src/platforms/esp32.h
  10. 238
      src/uClock.cpp
  11. 110
      src/uClock.h

@ -1,21 +1,40 @@
# uClock # 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 ## Interface
The uClock library API operates through attached callback functions mechanism: The uClock library API operates through an attached callback function mechanism:
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
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.
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) 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.
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
### Software Timer mode - for unsupported boards (or avoiding usage of interrupts) ### 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. 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,31 +56,16 @@ 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 ## 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: 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:
### setCallback function name changes ### 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)_ - `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 _setOnStep(onStepCall)_ and it's not dependent on clock PPQN resolution - `setClock16PPQNOutput(ClockOut16PPQN)` is now renamed to **`setOnStep(onStepCall)`**, and it's not dependent on clock PPQN resolution.
- **setOnClockStartOutput(onClockStartCallback)** is now _setOnClockStart(onClockStartCallback)_ - `setOnClockStartOutput(onClockStartCallback)` is now renamed to **`setOnClockStart(onClockStartCallback)`**.
- **setOnClockStopOutput(onClockStopCallback)** is now _setOnClockStop(onClockStopCallback)_ - `setOnClockStopOutput(onClockStopCallback)` is now renamed to **`setOnClockStop(onClockStopCallback)`**.
### Tick resolution and sequencers ### Tick resolution and sequencers
@ -112,7 +116,7 @@ void setup() {
uClock.setPPQN(uClock.PPQN_96); uClock.setPPQN(uClock.PPQN_96);
// you need to use at least one! // you need to use at least one!
uClock.setOnPPQN(onPPQNCallback); uClock.setOnOutputPPQN(onPPQNCallback);
uClock.setOnStep(onStepCallback); uClock.setOnStep(onStepCallback);
uClock.setOnSync24(onSync24Callback); uClock.setOnSync24(onSync24Callback);
@ -395,7 +399,7 @@ void setup()
uClock.init(); uClock.init();
// Set the callback function for the clock output to send MIDI Sync message. // 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 // Set the callback function for the step sequencer on 16ppqn
uClock.setOnStep(onStepCallback); uClock.setOnStep(onStepCallback);

@ -101,7 +101,7 @@ void onStepCallback(uint32_t tick)
} }
// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. // 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 // handle note on stack
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
@ -158,7 +158,7 @@ void setup()
uClock.init(); uClock.init();
// Set the callback function for the clock output to send MIDI Sync message. // Set the callback function for the clock output to send MIDI Sync message.
uClock.setOnPPQN(onPPQNCallback); uClock.setOnOutputPPQN(onOutputPPQNCallback);
// for MIDI sync // for MIDI sync
uClock.setOnSync24(onSync24Callback); uClock.setOnSync24(onSync24Callback);

@ -84,15 +84,15 @@ void processInterface()
processPots(); processPots();
} }
void tempoInterface(uint32_t * tick) void tempoInterface(uint32_t tick)
{ {
// BPM led indicator // 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; _bpm_blink_timer = 8;
digitalWrite(PLAY_STOP_LED_PIN , HIGH); 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); 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); digitalWrite(PLAY_STOP_LED_PIN , LOW);
_bpm_blink_timer = 1; _bpm_blink_timer = 1;
} }

@ -4,7 +4,7 @@
bool _external_sync_on = false; bool _external_sync_on = false;
// the main uClock PPQN resolution ticking // the main uClock PPQN resolution ticking
void onPPQNCallback(uint32_t tick) { void onOutputPPQNCallback(uint32_t tick) {
// tick your sequencers or tickable devices... // tick your sequencers or tickable devices...
} }
@ -12,11 +12,31 @@ void onStepCallback(uint32_t step) {
// triger step data for sequencer device... // 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. // The callback function called by uClock each Pulse of 24PPQN clock resolution.
void onSync24Callback(uint32_t tick) { void onSync24Callback(uint32_t tick) {
// send sync signal to... // 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. // The callback function called when clock starts by using uClock.start() method.
void onClockStartCallback() { void onClockStartCallback() {
// send start signal to... // send start signal to...
@ -32,22 +52,30 @@ void setup() {
// inits the clock library // inits the clock library
uClock.init(); uClock.init();
// avaliable resolutions // avaliable output PPQN resolutions for this example
// [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] // [ 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 // 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! // you need to use at least one!
uClock.setOnPPQN(onPPQNCallback); uClock.setOnOutputPPQN(onOutputPPQNCallback);
uClock.setOnStep(onStepCallback); 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); uClock.setOnSync24(onSync24Callback);
// some korg machines do 48ppqn
uClock.setOnSync48(onSync48Callback);
uClock.setOnClockStart(onClockStartCallback); uClock.setOnClockStart(onClockStartCallback);
uClock.setOnClockStop(onClockStopCallback); uClock.setOnClockStop(onClockStopCallback);
// set external sync mode? // set external sync mode?
if (_external_sync_on) { if (_external_sync_on) {
uClock.setMode(uClock.EXTERNAL_CLOCK); uClock.setClockMode(uClock.EXTERNAL_CLOCK);
} }
// starts clock // starts clock

@ -1,6 +1,6 @@
{ {
"name": "uClock", "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)", "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", "keywords": "bpm, clock, timing, tick, music, generator",
"repository": "repository":

@ -1,5 +1,5 @@
name=uClock name=uClock
version=2.2.0 version=2.2.1
author=Romulo Silva <contact@midilab.co> author=Romulo Silva <contact@midilab.co>
maintainer=Romulo Silva <contact@midilab.co> maintainer=Romulo Silva <contact@midilab.co>
sentence=BPM clock generator for Arduino platform. sentence=BPM clock generator for Arduino platform.

@ -6,6 +6,15 @@
// TODO: we should do this using macro guards for avrs different clocks freqeuncy setup at compile time // TODO: we should do this using macro guards for avrs different clocks freqeuncy setup at compile time
#define AVR_CLOCK_FREQ 16000000 #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) void initTimer(uint32_t init_clock)
{ {
ATOMIC( ATOMIC(

@ -0,0 +1,32 @@
#include <Arduino.h>
#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);
}

@ -3,11 +3,7 @@
#include <freertos/semphr.h> #include <freertos/semphr.h>
// esp32-specific timer // esp32-specific timer
#define TIMER_ID 0
hw_timer_t * _uclockTimer = NULL; 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 // 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 #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 // create the clockTask
xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle);
_uclockTimer = timerBegin(1000000); _uclockTimer = timerBegin(init_clock);
// attach to generic uclock ISR // attach to generic uclock ISR
timerAttachInterrupt(_uclockTimer, &handlerISR); timerAttachInterrupt(_uclockTimer, &handlerISR);

@ -2,7 +2,7 @@
* @file uClock.cpp * @file uClock.cpp
* Project BPM clock generator for Arduino * 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) * @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 * @author Romulo Silva
* @date 10/06/2017 * @date 10/06/2017
* @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co
@ -121,17 +121,23 @@ uClockClass::uClockClass()
start_timer = 0; start_timer = 0;
last_interval = 0; last_interval = 0;
sync_interval = 0; sync_interval = 0;
state = PAUSED; clock_state = PAUSED;
mode = INTERNAL_CLOCK; clock_mode = INTERNAL_CLOCK;
resetCounters(); resetCounters();
onPPQNCallback = nullptr; onOutputPPQNCallback = nullptr;
onSync1Callback = nullptr;
onSync2Callback = nullptr;
onSync4Callback = nullptr;
onSync8Callback = nullptr;
onSync12Callback = nullptr;
onSync24Callback = nullptr; onSync24Callback = nullptr;
onSync48Callback = nullptr;
onStepCallback = nullptr; onStepCallback = nullptr;
onClockStartCallback = nullptr; onClockStartCallback = nullptr;
onClockStopCallback = nullptr; onClockStopCallback = nullptr;
// first ppqn references calculus // initialize reference data
setPPQN(PPQN_96); calculateReferencedata();
} }
void uClockClass::init() void uClockClass::init()
@ -143,18 +149,40 @@ void uClockClass::init()
uint32_t uClockClass::bpmToMicroSeconds(float bpm) uint32_t uClockClass::bpmToMicroSeconds(float bpm)
{ {
return (60000000.0f / (float)ppqn / bpm); return (60000000.0f / (float)output_ppqn / bpm);
} }
void uClockClass::setPPQN(PPQNResolution resolution) void uClockClass::calculateReferencedata()
{ {
// stop clock to make it safe changing those references mod_clock_ref = output_ppqn / input_ppqn;
// so we avoid volatile then and ATOMIC everyone mod_sync1_ref = output_ppqn / PPQN_1;
stop(); mod_sync2_ref = output_ppqn / PPQN_2;
ppqn = resolution; mod_sync4_ref = output_ppqn / PPQN_4;
// calculate the mod24 and mod_step tick reference trigger mod_sync8_ref = output_ppqn / PPQN_8;
mod24_ref = ppqn / 24; mod_sync12_ref = output_ppqn / PPQN_12;
mod_step_ref = ppqn / 4; mod_sync24_ref = output_ppqn / PPQN_24;
mod_sync48_ref = output_ppqn / PPQN_48;
mod_step_ref = output_ppqn / 4;
}
void uClockClass::setOutputPPQN(PPQNResolution resolution)
{
// dont allow PPQN lower than PPQN_4 for output clock (to avoid problems with mod_step_ref)
if (resolution < PPQN_4)
return;
ATOMIC(
output_ppqn = resolution;
calculateReferencedata();
)
}
void uClockClass::setInputPPQN(PPQNResolution resolution)
{
ATOMIC(
input_ppqn = resolution;
calculateReferencedata();
)
} }
void uClockClass::start() void uClockClass::start()
@ -166,16 +194,16 @@ void uClockClass::start()
onClockStartCallback(); onClockStartCallback();
} }
if (mode == INTERNAL_CLOCK) { if (clock_mode == INTERNAL_CLOCK) {
state = STARTED; clock_state = STARTED;
} else { } else {
state = STARTING; clock_state = STARTING;
} }
} }
void uClockClass::stop() void uClockClass::stop()
{ {
state = PAUSED; clock_state = PAUSED;
start_timer = 0; start_timer = 0;
resetCounters(); resetCounters();
if (onClockStopCallback) { if (onClockStopCallback) {
@ -185,8 +213,8 @@ void uClockClass::stop()
void uClockClass::pause() void uClockClass::pause()
{ {
if (mode == INTERNAL_CLOCK) { if (clock_mode == INTERNAL_CLOCK) {
if (state == PAUSED) { if (clock_state == PAUSED) {
start(); start();
} else { } else {
stop(); stop();
@ -196,7 +224,7 @@ void uClockClass::pause()
void uClockClass::setTempo(float bpm) void uClockClass::setTempo(float bpm)
{ {
if (mode == EXTERNAL_CLOCK) { if (clock_mode == EXTERNAL_CLOCK) {
return; return;
} }
@ -213,7 +241,7 @@ void uClockClass::setTempo(float bpm)
float uClockClass::getTempo() float uClockClass::getTempo()
{ {
if (mode == EXTERNAL_CLOCK) { if (clock_mode == EXTERNAL_CLOCK) {
uint32_t acc = 0; uint32_t acc = 0;
// wait the buffer to get full // wait the buffer to get full
if (ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE-1] == 0) { if (ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE-1] == 0) {
@ -238,26 +266,25 @@ void uClockClass::run()
#endif #endif
} }
// this function is based on sync24PPQN
float inline uClockClass::freqToBpm(uint32_t freq) float inline uClockClass::freqToBpm(uint32_t freq)
{ {
float usecs = 1/((float)freq/1000000.0); float usecs = 1/((float)freq/1000000.0);
return (float)((float)(usecs/(float)24) * 60.0); return (float)((float)(usecs/(float)input_ppqn) * 60.0);
} }
void uClockClass::setMode(SyncMode tempo_mode) void uClockClass::setClockMode(ClockMode tempo_mode)
{ {
mode = tempo_mode; clock_mode = tempo_mode;
} }
uClockClass::SyncMode uClockClass::getMode() uClockClass::ClockMode uClockClass::getClockMode()
{ {
return mode; return clock_mode;
} }
void uClockClass::clockMe() void uClockClass::clockMe()
{ {
if (mode == EXTERNAL_CLOCK) { if (clock_mode == EXTERNAL_CLOCK) {
ATOMIC( ATOMIC(
handleExternalClock() handleExternalClock()
) )
@ -268,22 +295,38 @@ void uClockClass::resetCounters()
{ {
tick = 0; tick = 0;
int_clock_tick = 0; int_clock_tick = 0;
mod24_counter = 0; mod_clock_counter = 0;
mod_step_counter = 0; mod_step_counter = 0;
step_counter = 0; step_counter = 0;
ext_clock_tick = 0; ext_clock_tick = 0;
ext_clock_us = 0; ext_clock_us = 0;
ext_interval_idx = 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++) { for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) {
ext_interval_buffer[i] = 0; ext_interval_buffer[i] = 0;
} }
} }
// TODO: Tap stuff
void uClockClass::tap() void uClockClass::tap()
{ {
// tap me // we can make use of mod_sync1_ref for tap
//uint8_t mod_tap_ref = output_ppqn / PPQN_1;
// we only set tap if ClockMode is INTERNAL_CLOCK
} }
void uClockClass::setShuffle(bool active) void uClockClass::setShuffle(bool active)
@ -356,7 +399,7 @@ bool inline uClockClass::processShuffle()
last_shff = shff; 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) { if (mod_shuffle == 0 && shuffle_shoot_ctrl == true) {
// keep track of next note shuffle for current note lenght control // keep track of next note shuffle for current note lenght control
shuffle_length_ctrl = shuffle.step[(step_counter+1)%shuffle.size]; shuffle_length_ctrl = shuffle.step[(step_counter+1)%shuffle.size];
@ -371,15 +414,14 @@ bool inline uClockClass::processShuffle()
return false; return false;
} }
// it is expected to be called in 24PPQN
void uClockClass::handleExternalClock() void uClockClass::handleExternalClock()
{ {
switch (state) { switch (clock_state) {
case PAUSED: case PAUSED:
break; break;
case STARTING: case STARTING:
state = STARTED; clock_state = STARTED;
ext_clock_us = micros(); ext_clock_us = micros();
break; break;
@ -391,7 +433,7 @@ void uClockClass::handleExternalClock()
// external clock tick me! // external clock tick me!
ext_clock_tick++; 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) { if(++ext_interval_idx >= EXT_INTERVAL_BUFFER_SIZE) {
ext_interval_idx = 0; ext_interval_idx = 0;
} }
@ -408,19 +450,19 @@ void uClockClass::handleExternalClock()
void uClockClass::handleTimerInt() void uClockClass::handleTimerInt()
{ {
// reset mod24 counter reference ? // track main input clock counter
if (mod24_counter == mod24_ref) if (mod_clock_counter == mod_clock_ref)
mod24_counter = 0; mod_clock_counter = 0;
// process sync signals first please... // process sync signals first please...
if (mod24_counter == 0) { if (mod_clock_counter == 0) {
if (mode == EXTERNAL_CLOCK) { if (clock_mode == EXTERNAL_CLOCK) {
// sync tick position with external tick clock // sync tick position with external tick clock
if ((int_clock_tick < ext_clock_tick) || (int_clock_tick > (ext_clock_tick + 1))) { if ((int_clock_tick < ext_clock_tick) || (int_clock_tick > (ext_clock_tick + 1))) {
int_clock_tick = ext_clock_tick; int_clock_tick = ext_clock_tick;
tick = int_clock_tick * mod24_ref; tick = int_clock_tick * mod_clock_ref;
mod24_counter = tick % mod24_ref; mod_clock_counter = tick % mod_clock_ref;
mod_step_counter = tick % mod_step_ref; mod_step_counter = tick % mod_step_ref;
} }
@ -446,38 +488,108 @@ void uClockClass::handleTimerInt()
} }
} }
// internal clock tick me!
++int_clock_tick;
}
++mod_clock_counter;
// 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 (onSync24Callback) {
onSync24Callback(int_clock_tick); if (mod_sync24_counter == mod_sync24_ref)
mod_sync24_counter = 0;
if (mod_sync24_counter == 0) {
onSync24Callback(sync24_tick);
++sync24_tick;
} }
// internal clock tick me! sync24 tick too ++mod_sync24_counter;
++int_clock_tick;
} }
// PPQNCallback time! // Sync48 callback
if (onPPQNCallback) { if (onSync48Callback) {
onPPQNCallback(tick); if (mod_sync48_counter == mod_sync48_ref)
mod_sync48_counter = 0;
if (mod_sync48_counter == 0) {
onSync48Callback(sync48_tick);
++sync48_tick;
}
++mod_sync48_counter;
} }
// reset step mod counter reference ? // main PPQNCallback
if (mod_step_counter == mod_step_ref) if (onOutputPPQNCallback) {
mod_step_counter = 0; onOutputPPQNCallback(tick);
++tick;
}
// step callback to support 16th old school style sequencers // step callback to support 16th old school style sequencers
// with builtin shuffle for this callback only // with builtin shuffle for this callback only
if (onStepCallback) { if (onStepCallback) {
if (mod_step_counter == mod_step_ref)
mod_step_counter = 0;
// processShufle make use of mod_step_counter == 0 logic too // processShufle make use of mod_step_counter == 0 logic too
if (processShuffle()) { if (processShuffle()) {
onStepCallback(step_counter); onStepCallback(step_counter);
// going forward to the next step call // going forward to the next step call
++step_counter; ++step_counter;
} }
}
// tick me!
++tick;
// increment mod counters
++mod24_counter;
++mod_step_counter; ++mod_step_counter;
}
} }
// elapsed time support // elapsed time support
@ -532,16 +644,12 @@ volatile uint32_t _millis = 0;
// //
// TIMER HANDLER // TIMER HANDLER
// //
#if defined(ARDUINO_ARCH_AVR)
ISR(TIMER1_COMPA_vect)
#else
void uClockHandler() void uClockHandler()
#endif
{ {
// global timer counter // global timer counter
_millis = millis(); _millis = millis();
if (uClock.state == uClock.STARTED) { if (uClock.clock_state == uClock.STARTED) {
uClock.handleTimerInt(); uClock.handleTimerInt();
} }
} }

@ -2,7 +2,7 @@
* @file uClock.h * @file uClock.h
* Project BPM clock generator for Arduino * 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) * @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 * @author Romulo Silva
* @date 10/06/2017 * @date 10/06/2017
* @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co
@ -34,17 +34,9 @@
namespace umodular { namespace clock { namespace umodular { namespace clock {
// for extended steps in memory style and make use of 96ppqn for record propurse we can // Shuffle templates are specific for each PPQN output resolution
// keep array[step] memory layout and add new information about note possition to be check for the entire ppqn pulse // min: -(output_ppqn/4)-1 ticks
// example: for a whole 24 pulses we only check array[step].offset that can vary from 0 to 24(ppqn/4) // max: (output_ppqn/4)-1 ticks
// 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 // adjust the size of you template if more than 16 shuffle step info needed
#define MAX_SHUFFLE_TEMPLATE_SIZE 16 #define MAX_SHUFFLE_TEMPLATE_SIZE 16
typedef struct { typedef struct {
@ -57,10 +49,10 @@ typedef struct {
// in between 64 to 128. // in between 64 to 128.
// note: this doesn't impact on sync time, only display time getTempo() // 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 // 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 MIN_BPM 1
#define MAX_BPM 300 #define MAX_BPM 400
#define PHASE_FACTOR 16 #define PHASE_FACTOR 16
#define PLL_X 220 #define PLL_X 220
@ -72,7 +64,7 @@ typedef struct {
class uClockClass { class uClockClass {
public: public:
enum SyncMode { enum ClockMode {
INTERNAL_CLOCK = 0, INTERNAL_CLOCK = 0,
EXTERNAL_CLOCK EXTERNAL_CLOCK
}; };
@ -84,6 +76,11 @@ class uClockClass {
}; };
enum PPQNResolution { enum PPQNResolution {
PPQN_1 = 1,
PPQN_2 = 2,
PPQN_4 = 4,
PPQN_8 = 8,
PPQN_12 = 12,
PPQN_24 = 24, PPQN_24 = 24,
PPQN_48 = 48, PPQN_48 = 48,
PPQN_96 = 96, PPQN_96 = 96,
@ -92,22 +89,47 @@ class uClockClass {
PPQN_960 = 960 PPQN_960 = 960
}; };
ClockState state; ClockState clock_state;
uClockClass(); uClockClass();
void setOnPPQN(void (*callback)(uint32_t tick)) { void setOnOutputPPQN(void (*callback)(uint32_t tick)) {
onPPQNCallback = callback; onOutputPPQNCallback = callback;
} }
void setOnStep(void (*callback)(uint32_t step)) { void setOnStep(void (*callback)(uint32_t step)) {
onStepCallback = callback; onStepCallback = callback;
} }
// multiple output clock signatures
void setOnSync1(void (*callback)(uint32_t tick)) {
onSync1Callback = callback;
}
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)) { void setOnSync24(void (*callback)(uint32_t tick)) {
onSync24Callback = callback; onSync24Callback = callback;
} }
void setOnSync48(void (*callback)(uint32_t tick)) {
onSync48Callback = callback;
}
void setOnClockStart(void (*callback)()) { void setOnClockStart(void (*callback)()) {
onClockStartCallback = callback; onClockStartCallback = callback;
} }
@ -117,7 +139,8 @@ class uClockClass {
} }
void init(); void init();
void setPPQN(PPQNResolution resolution); void setOutputPPQN(PPQNResolution resolution);
void setInputPPQN(PPQNResolution resolution);
void handleTimerInt(); void handleTimerInt();
void handleExternalClock(); void handleExternalClock();
@ -134,8 +157,8 @@ class uClockClass {
void run(); void run();
// external timming control // external timming control
void setMode(SyncMode tempo_mode); void setClockMode(ClockMode tempo_mode);
SyncMode getMode(); ClockMode getClockMode();
void clockMe(); void clockMe();
// shuffle // shuffle
@ -162,26 +185,55 @@ class uClockClass {
private: private:
float inline freqToBpm(uint32_t freq); float inline freqToBpm(uint32_t freq);
void calculateReferencedata();
// shuffle // shuffle
bool inline processShuffle(); bool inline processShuffle();
void (*onPPQNCallback)(uint32_t tick); void (*onOutputPPQNCallback)(uint32_t tick);
void (*onStepCallback)(uint32_t step); 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 (*onSync24Callback)(uint32_t tick);
void (*onSync48Callback)(uint32_t tick);
void (*onClockStartCallback)(); void (*onClockStartCallback)();
void (*onClockStopCallback)(); void (*onClockStopCallback)();
// internal clock control // clock input/output control
// uint16_t ppqn; PPQNResolution output_ppqn = PPQN_96;
PPQNResolution ppqn = PPQN_96; PPQNResolution input_ppqn = PPQN_24;
// output and internal counters, ticks and references
uint32_t tick; uint32_t tick;
uint32_t int_clock_tick; uint32_t int_clock_tick;
uint8_t mod24_counter; uint8_t mod_clock_counter;
uint8_t mod24_ref; uint16_t mod_clock_ref;
uint8_t mod_step_counter; uint8_t mod_step_counter;
uint8_t mod_step_ref; uint8_t mod_step_ref;
uint32_t step_counter; // should we go uint16_t? uint32_t step_counter;
uint8_t mod_sync1_counter;
uint16_t mod_sync1_ref;
uint32_t sync1_tick;
uint8_t mod_sync2_counter;
uint16_t mod_sync2_ref;
uint32_t sync2_tick;
uint8_t mod_sync4_counter;
uint16_t mod_sync4_ref;
uint32_t sync4_tick;
uint8_t mod_sync8_counter;
uint16_t mod_sync8_ref;
uint32_t sync8_tick;
uint8_t mod_sync12_counter;
uint16_t mod_sync12_ref;
uint32_t sync12_tick;
uint8_t mod_sync24_counter;
uint16_t mod_sync24_ref;
uint32_t sync24_tick;
uint8_t mod_sync48_counter;
uint16_t mod_sync48_ref;
uint32_t sync48_tick;
// external clock control // external clock control
volatile uint32_t ext_clock_us; volatile uint32_t ext_clock_us;
@ -192,7 +244,7 @@ class uClockClass {
float tempo; float tempo;
uint32_t start_timer; uint32_t start_timer;
SyncMode mode; ClockMode clock_mode;
volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE]; volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE];
uint16_t ext_interval_idx; uint16_t ext_interval_idx;

Loading…
Cancel
Save