Merge pull request #5 from midilab/v1

V1
pull/7/head v1.0.0
midilab 3 years ago committed by GitHub
commit b78a289b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      README.md
  2. 2
      examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino
  3. 2
      examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino
  4. 2
      examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino
  5. 7
      examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino
  6. 287
      src/uClock.cpp
  7. 59
      src/uClock.h

@ -97,8 +97,6 @@ void onClockStop() {
}
void setup() {
// drift for USB Teensy
uClock.setDrift(1);
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.
@ -317,16 +315,3 @@ void loop()
//processYourPots();
}
```
## Troubleshooting
If you slave host are not showing correct of close bpm on sync, please try to use the drift variable to adjust. It normaly goes from value 4(good for clock over USB) to 11(good for common MIDI interfaces running at 31250 speed). The default value is 11.
To use MIDI via USB on Leonardo boards please start setting the drift to 4:
uClock.setDrift(4);
For teensy boards and USB Midi try:
uClock.setDrift(1);
Please use uClock.setDrift() before uClock.init();

@ -103,8 +103,6 @@ void setup() {
//
// uClock Setup
//
// Drift for arudino leonardo over USB as MIDI HID
uClock.setSlaveDrift(10);
uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// For MIDI Sync Start and Stop

@ -55,8 +55,6 @@ void setup() {
pinMode(LED_BUILTIN, OUTPUT);
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.

@ -76,8 +76,6 @@ void setup() {
usbMIDI.setHandleStop(onExternalStop);
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.

@ -11,11 +11,6 @@
* - u8g2
* - uClock
*
* This example make use of drift values (6, 1)
* respectively for internal and external drift reference.
* This example was tested on a macbook
* running ableton live 9 as master clock
*
* This example code is in the public domain.
*/
@ -111,8 +106,6 @@ void setup() {
// uClock Setup
//
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// For MIDI Sync Start and Stop

@ -1,11 +1,10 @@
/*!
* @file uClock.cpp
* Project BPM clock generator for Arduino
* @brief A Library to implement BPM clock tick calls using hardware timer1 interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560.
* Derived work from mididuino MidiClock class. (c) 2008 - 2011 - Manuel Odendahl - wesen@ruinwesen.com
* @version 0.10.6
* @brief A Library to implement BPM clock tick calls using hardware timer interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560 and Teensy LC.
* @version 1.0.0
* @author Romulo Silva
* @date 13/03/2022
* @date 01/04/2022
* @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co
*
* Permission is hereby granted, free of charge, to any person obtaining a
@ -28,16 +27,6 @@
*/
#include "uClock.h"
// pickup a avr timer to make use.
// pickup only one!
// try to avoid timer0, only use it if you know what you are doing.
// 0 = delay(), millis() e micros()
// 1 = Servo.h library(any other?)
// 2 = tone()
//#define AVR_TIMER_0
#define AVR_TIMER_1
//#define AVR_TIMER_2
//
// Timer setup for work clock
//
@ -46,68 +35,37 @@ IntervalTimer _uclockTimer;
void uclockISR();
void uclockInitTimer()
{
_uclockTimer.begin(uclockISR, 16);
ATOMIC(
// begin at 120bpm (20833us)
_uclockTimer.begin(uclockISR, 20833);
// Set the interrupt priority level, controlling which other interrupts
// this timer is allowed to interrupt. Lower numbers are higher priority,
// with 0 the highest and 255 the lowest. Most other interrupts default to 128.
// As a general guideline, interrupt routines that run longer should be given
// lower priority (higher numerical values).
_uclockTimer.priority(0);
)
}
#else
void uclockInitTimer()
{
#if defined(AVR_TIMER_0)
ATOMIC(
// Timer0 init
TCCR0A = 0;
TCCR0B = 0;
TCNT0 = 0;
// set compare match register for 62500 Hz increments
// = 16000000 / (1 * 62500) - 1 (must be <256)
OCR0A = 255;
// turn on CTC mode
TCCR0B |= (1 << WGM02);
// Set CS02, CS01 and CS00 bits for 1 prescaler
TCCR0B |= (0 << CS02) | (0 << CS01) | (1 << CS00);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
)
#endif
#if defined(AVR_TIMER_1)
ATOMIC(
// Timer1 init
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// set compare match register for 62500 Hz increments
// = 16000000 / (1 * 62500) - 1 (must be <65536)
OCR1A = 255;
// begin at 120bpm (48.0007680122882 Hz)
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
// set compare match register for 48.0007680122882 Hz increments
OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// Set CS12, CS11 and CS10 bits for 8 prescaler
TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
)
#endif
#if defined(AVR_TIMER_2)
ATOMIC(
// Timer2 init
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
// set compare match register for 62500 Hz increments
// = 16000000 / (1 * 62500) - 1 (must be <256)
OCR2A = 255;
// turn on CTC mode
TCCR2B |= (1 << WGM22);
// Set CS22, CS21 and CS20 bits for 1 prescaler
TCCR2B |= (0 << CS22) | (0 << CS21) | (1 << CS20);
// enable timer compare interrupt
TIMSK2 |= (1 << OCIE2A);
)
#endif
}
#endif
@ -118,28 +76,23 @@ static inline uint32_t phase_mult(uint32_t val)
return (val * PHASE_FACTOR) >> 8;
}
static inline uint16_t clock_diff(uint16_t old_clock, uint16_t new_clock)
static inline uint32_t clock_diff(uint32_t old_clock, uint32_t new_clock)
{
if (new_clock >= old_clock) {
return new_clock - old_clock;
} else {
return new_clock + (65535 - old_clock);
return new_clock + (4294967295 - old_clock);
}
}
uClockClass::uClockClass()
{
// drift is used to sligth calibrate with your slave clock
drift = 1;
slave_drift = 0;
pll_x = 220;
tempo = 120;
start_timer = 0;
last_interval = 0;
sync_interval = 0;
state = PAUSED;
mode = INTERNAL_CLOCK;
ext_interval_acc = 0;
resetCounters();
onClock96PPQNCallback = NULL;
@ -154,7 +107,6 @@ uClockClass::uClockClass()
void uClockClass::init()
{
// init work clock timer interrupt at 16 microseconds
uclockInitTimer();
}
@ -195,6 +147,50 @@ void uClockClass::pause()
}
}
void uClockClass::setTimerTempo(float bpm)
{
// 96 ppqn resolution
tick_us_interval = (60000000 / 24 / bpm);
tick_hertz_interval = 1/((float)tick_us_interval/1000000);
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
ATOMIC(
_uclockTimer.update(tick_us_interval);
)
#else
uint32_t ocr;
uint8_t tccr = 0;
// 16bits avr timer setup
if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) {
// Set CS12, CS11 and CS10 bits for 1 prescaler
tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10);
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) {
// Set CS12, CS11 and CS10 bits for 8 prescaler
tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10);
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) {
// Set CS12, CS11 and CS10 bits for 64 prescaler
tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10);
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) {
// Set CS12, CS11 and CS10 bits for 256 prescaler
tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10);
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) {
// Set CS12, CS11 and CS10 bits for 1024 prescaler
tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10);
} else {
// tempo not achiavable
return;
}
ATOMIC(
TCCR1B = 0;
OCR1A = ocr-1;
TCCR1B |= (1 << WGM12);
TCCR1B |= tccr;
)
#endif
}
void uClockClass::setTempo(float bpm)
{
if (mode == EXTERNAL_CLOCK) {
@ -205,13 +201,15 @@ void uClockClass::setTempo(float bpm)
return;
}
setTimerTempo(bpm);
tempo = bpm;
}
ATOMIC(
//interval = (freq_resolution / (tempo * 24 / 60)) - drift;
//interval = 62500 / (tempo * 24 / 60) - drift;
interval = (uint16_t)((156250.0 / tempo) - drift);
)
float inline uClockClass::freqToBpm(uint32_t freq)
{
float usecs = 1/((float)freq/1000000.0);
return (float)((float)(usecs/24.0) * 60.0);
}
float uClockClass::getTempo()
@ -222,56 +220,12 @@ float uClockClass::getTempo()
acc += ext_interval_buffer[i];
}
if (acc != 0) {
// get average interval, because MIDI sync world is a wild place...
//tempo = (((float)freq_resolution/24) * 60) / (float)(acc / EXT_INTERVAL_BUFFER_SIZE);
// derivated one time calc value = ( freq_resolution / 24 ) * 60
tempo = (float)(156250.0 / (acc / EXT_INTERVAL_BUFFER_SIZE));
return freqToBpm(acc / EXT_INTERVAL_BUFFER_SIZE);
}
}
return tempo;
}
void uClockClass::setDrift(uint8_t value)
{
ATOMIC(drift = value)
// force set tempo to update runtime interval
setTempo(tempo);
}
void uClockClass::setSlaveDrift(uint8_t value)
{
ATOMIC(slave_drift = value)
}
uint8_t uClockClass::getDrift()
{
return drift;
}
// each interval is 16us
// this method is usefull for debug
uint16_t uClockClass::getInterval()
{
return interval;
}
// Main poolling tick call
uint8_t uClockClass::getTick(uint32_t * tick)
{
ATOMIC(
uint32_t last_tick = internal_tick;
)
if (*tick != last_tick) {
*tick = last_tick;
return 1;
}
if (last_tick - *tick > 1) {
*tick++;
return 1;
}
return 0;
}
void uClockClass::setMode(uint8_t tempo_mode)
{
mode = tempo_mode;
@ -293,13 +247,14 @@ void uClockClass::clockMe()
void uClockClass::resetCounters()
{
counter = 0;
last_clock = 0;
external_clock = 0;
internal_tick = 0;
external_tick = 0;
div32th_counter = 0;
div16th_counter = 0;
mod6_counter = 0;
indiv32th_counter = 0;
indiv16th_counter = 0;
inmod6_counter = 0;
ext_interval_idx = 0;
}
@ -318,11 +273,6 @@ void uClockClass::shuffle()
void uClockClass::handleExternalClock()
{
last_interval = clock_diff(last_clock, _clock);
last_clock = _clock;
// slave tick me!
external_tick++;
switch (state) {
case PAUSED:
@ -330,9 +280,32 @@ void uClockClass::handleExternalClock()
case STARTING:
state = STARTED;
external_clock = micros();
break;
case STARTED:
uint32_t u_timer = micros();
last_interval = clock_diff(external_clock, u_timer);
external_clock = u_timer;
if (inmod6_counter == 0) {
indiv16th_counter++;
indiv32th_counter++;
}
if (inmod6_counter == 3) {
indiv32th_counter++;
}
// slave tick me!
external_tick++;
inmod6_counter++;
if (inmod6_counter == 6) {
inmod6_counter = 0;
}
// accumulate interval incomming ticks data for getTempo() smooth reads on slave mode
if(++ext_interval_idx >= EXT_INTERVAL_BUFFER_SIZE) {
ext_interval_idx = 0;
@ -342,7 +315,7 @@ void uClockClass::handleExternalClock()
if (external_tick == 1) {
interval = last_interval;
} else {
interval = (((uint32_t)interval * (uint32_t)pll_x) + (uint32_t)(256 - pll_x) * (uint32_t)last_interval) >> 8;
interval = (((uint32_t)interval * (uint32_t)PLL_X) + (uint32_t)(256 - PLL_X) * (uint32_t)last_interval) >> 8;
}
break;
}
@ -350,12 +323,37 @@ void uClockClass::handleExternalClock()
void uClockClass::handleTimerInt()
{
if (counter == 0) {
// update internal clock base counter
counter = interval;
if (mode == EXTERNAL_CLOCK) {
// sync tick position with external tick clock
if ((internal_tick < external_tick) || (internal_tick > (external_tick + 1))) {
internal_tick = external_tick;
div32th_counter = indiv32th_counter;
div16th_counter = indiv16th_counter;
mod6_counter = inmod6_counter;
}
uint32_t counter = interval;
uint32_t u_timer = micros();
sync_interval = clock_diff(external_clock, u_timer);
if (internal_tick <= external_tick) {
counter -= phase_mult(sync_interval);
} else {
if (counter > sync_interval) {
counter += phase_mult(counter - sync_interval);
}
}
// update internal clock timer frequency
float bpm = freqToBpm(counter);
if (bpm != tempo) {
if (bpm >= MIN_BPM && bpm <= MAX_BPM) {
tempo = bpm;
setTimerTempo(tempo);
}
}
}
// need a callback?
// please, use the polling method with getTick() instead...
if (onClock96PPQNCallback) {
onClock96PPQNCallback(&internal_tick);
}
@ -382,27 +380,10 @@ void uClockClass::handleTimerInt()
internal_tick++;
mod6_counter++;
if (mode == EXTERNAL_CLOCK) {
sync_interval = clock_diff(last_clock, _clock);
if ((internal_tick < external_tick) || (internal_tick > (external_tick + 1))) {
internal_tick = external_tick;
}
if (internal_tick <= external_tick) {
counter -= phase_mult(sync_interval);
} else {
if (counter > sync_interval) {
counter += phase_mult(counter - sync_interval);
}
}
}
if (mod6_counter == 6) {
mod6_counter = 0;
}
} else {
counter--;
}
}
// elapsed time support
@ -452,32 +433,22 @@ uint32_t uClockClass::getPlayTime()
umodular::clock::uClockClass uClock;
volatile uint16_t _clock = 0;
volatile uint32_t _timer = 0;
//
// TIMER INTERRUPT HANDLER
// Clocked at: 62.5kHz/16usec
//
//
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
void uclockISR()
#else
#if defined(AVR_TIMER_0)
ISR(TIMER0_COMPA_vect)
#endif
#if defined(AVR_TIMER_1)
ISR(TIMER1_COMPA_vect)
#endif
#if defined(AVR_TIMER_2)
ISR(TIMER2_COMPA_vect)
#endif
#endif
{
// global timer counter
_timer = millis();
if (uClock.state == uClock.STARTED) {
_clock++;
uClock.handleTimerInt();
}
}

@ -1,11 +1,10 @@
/*!
* @file uClock.cpp
* @file uClock.h
* Project BPM clock generator for Arduino
* @brief A Library to implement BPM clock tick calls using hardware timer1 interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560.
* Derived work from mididuino MidiClock class. (c) 2008 - 2011 - Manuel Odendahl - wesen@ruinwesen.com
* @version 0.10.6
* @brief A Library to implement BPM clock tick calls using hardware timer interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560 and Teensy LC.
* @version 1.0.0
* @author Romulo Silva
* @date 13/03/2022
* @date 01/04/2022
* @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co
*
* Permission is hereby granted, free of charge, to any person obtaining a
@ -35,8 +34,16 @@
namespace umodular { namespace clock {
#define AVR_CLOCK_FREQ 16000000
#define PHASE_FACTOR 16
#define PLL_X 220
// for smooth slave tempo calculate display you should raise this value
// 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 SECS_PER_MIN (60UL)
@ -52,35 +59,39 @@ class uClockClass {
private:
void setTimerTempo(float bpm);
float inline freqToBpm(uint32_t freq);
void (*onClock96PPQNCallback)(uint32_t * tick);
void (*onClock32PPQNCallback)(uint32_t * tick);
void (*onClock16PPQNCallback)(uint32_t * tick);
void (*onClockStartCallback)();
void (*onClockStopCallback)();
// internal clock control
volatile uint32_t internal_tick;
volatile uint32_t div32th_counter;
volatile uint32_t div16th_counter;
volatile uint8_t mod6_counter;
// external clock control
volatile uint32_t external_clock;
volatile uint32_t external_tick;
volatile uint16_t interval;
volatile uint16_t last_clock;
volatile uint32_t indiv32th_counter;
volatile uint32_t indiv16th_counter;
volatile uint8_t inmod6_counter;
uint32_t div32th_counter;
uint32_t div16th_counter;
uint8_t mod6_counter;
uint16_t counter;
uint16_t pll_x;
uint32_t last_tick;
uint8_t drift;
uint8_t slave_drift;
volatile uint32_t interval;
volatile uint32_t last_interval;
uint32_t sync_interval;
uint32_t tick_us_interval;
float tick_hertz_interval;
float tempo;
uint32_t start_timer;
uint8_t mode;
uint16_t last_interval;
uint16_t sync_interval;
uint16_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE];
uint32_t ext_interval_acc;
volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE];
uint16_t ext_interval_idx;
public:
@ -131,11 +142,6 @@ class uClockClass {
void pause();
void setTempo(float bpm);
float getTempo();
void setDrift(uint8_t value);
uint8_t getDrift();
void setSlaveDrift(uint8_t value);
uint16_t getInterval();
uint8_t getTick(uint32_t *_tick);
// external timming control
void setMode(uint8_t tempo_mode);
@ -153,7 +159,6 @@ class uClockClass {
uint8_t getNumberOfDays(uint32_t time);
uint32_t getNowTimer();
uint32_t getPlayTime();
};
} } // end namespace umodular::clock

Loading…
Cancel
Save