fix input clock ppqn calculus

develop
midilab 1 week ago
parent c7b081954d
commit 1a569e4cb1
  1. 51
      README.md
  2. 24
      src/uClock.cpp
  3. 20
      src/uClock.h

@ -1,21 +1,37 @@
# 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. **setOnPPQN(onPPQNCallback) > onPPQNCallback(uint32_t tick)** calls on each new pulse based on selected PPQN resolution (if no PPQN set, the default is 96PPQN) 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)** good way to code old style step sequencer based on 16th note schema (not dependent on PPQN resolution) 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)** good way to code a clock machine, or keep your devices synced with your device 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 uClock Start event 4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event.
5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** on uClock Stop 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) ### 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,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 ## 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:

@ -150,6 +150,7 @@ void uClockClass::calculateReferencedata()
{ {
mod_clock_ref = ppqn / clock_ppqn; mod_clock_ref = ppqn / clock_ppqn;
mod_sync24_ref = ppqn / PPQN_24; mod_sync24_ref = ppqn / PPQN_24;
mod_sync48_ref = ppqn / PPQN_48;
mod_step_ref = ppqn / 4; mod_step_ref = ppqn / 4;
} }
@ -157,7 +158,7 @@ void uClockClass::setPPQN(PPQNResolution resolution)
{ {
// stop clock to make it safe changing those references // stop clock to make it safe changing those references
// so we avoid volatile then and ATOMIC everywhere // so we avoid volatile then and ATOMIC everywhere
stop(); //stop();
ppqn = resolution; ppqn = resolution;
calculateReferencedata(); calculateReferencedata();
} }
@ -166,10 +167,9 @@ void uClockClass::setClockPPQN(PPQNResolution resolution)
{ {
// stop clock to make it safe changing those references // stop clock to make it safe changing those references
// so we avoid volatile then and ATOMIC everywhere // so we avoid volatile then and ATOMIC everywhere
stop(); //stop();
clock_ppqn = resolution; clock_ppqn = resolution;
calculateReferencedata(); calculateReferencedata();
//mod_clock_ref = ppqn / clock_ppqn;
} }
void uClockClass::start() void uClockClass::start()
@ -256,7 +256,7 @@ void uClockClass::run()
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)clock_ppqn) * 60.0);
} }
void uClockClass::setMode(SyncMode tempo_mode) void uClockClass::setMode(SyncMode tempo_mode)
@ -282,10 +282,12 @@ void uClockClass::resetCounters()
{ {
tick = 0; tick = 0;
int_clock_tick = 0; int_clock_tick = 0;
sync24_tick = 0;
mod_clock_counter = 0; mod_clock_counter = 0;
mod_step_counter = 0; mod_step_counter = 0;
mod_sync24_counter = 0; mod_sync24_counter = 0;
sync24_tick = 0;
mod_sync48_counter = 0;
sync48_tick = 0;
step_counter = 0; step_counter = 0;
ext_clock_tick = 0; ext_clock_tick = 0;
ext_clock_us = 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! // PPQNCallback time!
if (onPPQNCallback) { if (onPPQNCallback) {
onPPQNCallback(tick); onPPQNCallback(tick);
@ -502,6 +515,7 @@ void uClockClass::handleTimerInt()
// increment mod counters // increment mod counters
++mod_clock_counter; ++mod_clock_counter;
++mod_sync24_counter; ++mod_sync24_counter;
++mod_sync48_counter;
++mod_step_counter; ++mod_step_counter;
} }

@ -57,7 +57,7 @@ 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 300
@ -84,6 +84,8 @@ class uClockClass {
}; };
enum PPQNResolution { enum PPQNResolution {
PPQN_1 = 1,
PPQN_2 = 2,
PPQN_4 = 4, PPQN_4 = 4,
PPQN_8 = 8, PPQN_8 = 8,
PPQN_12 = 12, PPQN_12 = 12,
@ -107,9 +109,19 @@ class uClockClass {
onStepCallback = callback; onStepCallback = callback;
} }
// setOnSync1
// setOnSync2
// setOnSync4
// setOnSync8
// setOnSync12
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;
@ -174,6 +186,7 @@ class uClockClass {
void (*onPPQNCallback)(uint32_t tick); void (*onPPQNCallback)(uint32_t tick);
void (*onStepCallback)(uint32_t step); void (*onStepCallback)(uint32_t step);
void (*onSync24Callback)(uint32_t tick); void (*onSync24Callback)(uint32_t tick);
void (*onSync48Callback)(uint32_t tick);
void (*onClockStartCallback)(); void (*onClockStartCallback)();
void (*onClockStopCallback)(); void (*onClockStopCallback)();
@ -183,7 +196,6 @@ class uClockClass {
PPQNResolution clock_ppqn = PPQN_24; PPQNResolution clock_ppqn = PPQN_24;
uint32_t tick; uint32_t tick;
uint32_t int_clock_tick; uint32_t int_clock_tick;
uint32_t sync24_tick;
uint8_t mod_clock_counter; uint8_t mod_clock_counter;
uint8_t mod_clock_ref; uint8_t mod_clock_ref;
uint8_t mod_step_counter; uint8_t mod_step_counter;
@ -191,6 +203,10 @@ class uClockClass {
uint32_t step_counter; // should we go uint16_t? uint32_t step_counter; // should we go uint16_t?
uint8_t mod_sync24_counter; uint8_t mod_sync24_counter;
uint8_t mod_sync24_ref; 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 // external clock control
volatile uint32_t ext_clock_us; volatile uint32_t ext_clock_us;
volatile uint32_t ext_clock_tick; volatile uint32_t ext_clock_tick;

Loading…
Cancel
Save