Skip to content

Commit

Permalink
added clock decimal precision(128.5bpm). added a buffer for average i…
Browse files Browse the repository at this point in the history
…nterval tick reading on getTempo() to smooth slave tempo retrive. Revised Teensy USB midi clock monitor
  • Loading branch information
midilab committed Nov 1, 2020
1 parent 2f70ed8 commit c71e556
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@
* MIDI hid compilant slave clock box with
* monitor support using oled displays
*
* Making use of a 250 usceconds timer to
* Making use of a 16 usceconds timer to
* handle MIDI input to avoid jitter on clock
*
* You need the following libraries to make it work
* - u8g2
* - uClock
*
* This example make use of drift values (1, 4)
* 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.
*/

#include <U8x8lib.h>

//
Expand All @@ -28,8 +34,8 @@ IntervalTimer teensyTimer;
#define MIDI_START 0xFA
#define MIDI_STOP 0xFC

char bpm_str[3];
uint16_t bpm = 126;
char bpm_str[4];
float bpm = 126;
uint8_t bpm_blink_timer = 1;
uint8_t clock_state = 1;

Expand Down Expand Up @@ -60,6 +66,7 @@ void onClockStart() {

void onClockStop() {
usbMIDI.sendRealTime(MIDI_STOP);
digitalWrite(LED_BUILTIN, LOW);
}

// External clock handlers
Expand Down Expand Up @@ -105,28 +112,44 @@ void setup() {
//
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
uClock.setDrift(1, 4);
uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// For MIDI Sync Start and Stop
uClock.setOnClockStartOutput(onClockStart);
uClock.setOnClockStopOutput(onClockStop);
uClock.setMode(uClock.EXTERNAL_CLOCK);
// make use of 250us timer to handle midi input sync
teensyTimer.begin(handleMidiInput, 250);
// make use of 16us timer to handle midi input sync
teensyTimer.begin(handleMidiInput, 16);
teensyTimer.priority(80);
}

void handleMidiInput() {
while (usbMIDI.read()) {
}
}

void printBpm(float _bpm, uint8_t col, uint8_t line) {
int b = (int)_bpm;
int c = (int)((_bpm-b)*10);
itoa(b, bpm_str, 10);
if (b > 99) {
u8x8->drawUTF8(col, line, bpm_str);
} else {
bpm_str[2] = "\0";
u8x8->drawUTF8(col, line, " ");
u8x8->drawUTF8(col+1, line, bpm_str);
}
u8x8->drawUTF8(col+3, line, ".");
itoa(c, bpm_str, 10);
u8x8->drawUTF8(col+4, line, bpm_str);
u8x8->drawUTF8(col+5, line, "bpm");
}

void loop() {
if (bpm != uClock.getTempo()) {
bpm = uClock.getTempo();
itoa(bpm, bpm_str, 10);
u8x8->drawUTF8(10, 7, bpm_str);
u8x8->drawUTF8(13, 7, "bpm");
printBpm(bpm, 8, 7);
}
if (clock_state != uClock.state) {
clock_state = uClock.state;
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=uClock
version=0.9.2
version=0.9.4
author=Romulo Silva <[email protected]>, Manuel Odendahl <[email protected]>
maintainer=Romulo Silva <[email protected]>
sentence=BPM clock generator for Arduino and Teensy boards
Expand Down
42 changes: 29 additions & 13 deletions src/uClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 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 - [email protected]
* @version 0.9.3
* @version 0.9.4
* @author Romulo Silva
* @date 08/21/2020
* @license MIT - (c) 2020 - Romulo Silva - [email protected]
Expand Down Expand Up @@ -39,13 +39,14 @@ IntervalTimer _teensyTimer;
void teensyInterrupt();
void initTeensyTimer()
{
_teensyTimer.begin(teensyInterrupt, 16);
// 62500Hz
_teensyTimer.begin(teensyInterrupt, 16);
// 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).
//_teensyTimer.priority(128);
_teensyTimer.priority(0);
}
#else
void initArduinoTimer()
Expand Down Expand Up @@ -103,6 +104,7 @@ uClockClass::uClockClass()
// 1 is good on teensy lc usb midi
internal_drift = 11;
external_drift = 11;
tempo = 120;
pll_x = 220;
start_timer = 0;
last_interval = 0;
Expand All @@ -116,11 +118,13 @@ uClockClass::uClockClass()
onClock16PPQNCallback = NULL;
onClockStartCallback = NULL;
onClockStopCallback = NULL;

// first interval calculus
setTempo(tempo);
}

void uClockClass::init()
{
setTempo(120);
initWorkTimer();
}

Expand Down Expand Up @@ -161,7 +165,7 @@ void uClockClass::pause()
}
}

void uClockClass::setTempo(uint16_t bpm)
void uClockClass::setTempo(float bpm)
{
if (mode == EXTERNAL_CLOCK) {
return;
Expand All @@ -177,20 +181,27 @@ void uClockClass::setTempo(uint16_t bpm)

tempo = bpm;

ATOMIC(
ATOMIC(
interval = (uint16_t)((156250.0 / tempo) - internal_drift);
//interval = 62500 / (tempo * 24 / 60) - internal_drift;
interval = (uint16_t)(156250 / tempo) - internal_drift;
)
}

uint16_t uClockClass::getTempo()
float uClockClass::getTempo()
{
if (mode == EXTERNAL_CLOCK) {
uint16_t external_interval;
ATOMIC(
external_interval = interval;
)
tempo = (156250 / (external_interval + external_drift));
uint32_t acc = 0;
uint8_t acc_counter = 0;
for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) {
if ( ext_interval_buffer[i] != 0) {
acc += ext_interval_buffer[i];
++acc_counter;
}
}
if (acc != 0) {
// get average interval, because MIDI sync world is a wild place...
tempo = (float)(156250.0 / ((acc / acc_counter) + external_drift));
}
}
return tempo;
}
Expand Down Expand Up @@ -232,6 +243,8 @@ void uClockClass::resetCounters()
mod6_counter = 0;
indiv96th_counter = 0;
inmod6_counter = 0;
ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE] = {0};
ext_interval_idx = 0;
}

// TODO: Tap stuff
Expand Down Expand Up @@ -273,6 +286,9 @@ void uClockClass::handleExternalClock()
} else {
interval = (((uint32_t)interval * (uint32_t)pll_x) + (uint32_t)(256 - pll_x) * (uint32_t)last_interval) >> 8;
}
// accumulate interval incomming ticks data(for a better getTempo stability over bad clocks)
ext_interval_buffer[ext_interval_idx] = interval;
ext_interval_idx = ++ext_interval_idx % EXT_INTERVAL_BUFFER_SIZE;
break;
}
}
Expand Down
19 changes: 9 additions & 10 deletions src/uClock.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 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 - [email protected]
* @version 0.9.3
* @version 0.9.4
* @author Romulo Silva
* @date 08/21/2020
* @license MIT - (c) 2020 - Romulo Silva - [email protected]
Expand Down Expand Up @@ -35,6 +35,8 @@

#define PHASE_FACTOR 16

#define EXT_INTERVAL_BUFFER_SIZE 40

#define SECS_PER_MIN (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY (SECS_PER_HOUR * 24L)
Expand All @@ -47,11 +49,6 @@ enum {
STARTED
} state;

enum {
INTERNAL_CLOCK = 0,
EXTERNAL_CLOCK
} mode;

class uClockClass {

private:
Expand All @@ -67,7 +64,6 @@ class uClockClass {
volatile uint16_t interval;
volatile uint16_t last_clock;

uint8_t ppqn;
uint32_t div96th_counter;
uint32_t div32th_counter;
uint32_t div16th_counter;
Expand All @@ -77,13 +73,16 @@ class uClockClass {
uint16_t pll_x;
uint8_t internal_drift;
uint8_t external_drift;
uint16_t tempo;
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];
uint16_t ext_interval_idx;

public:

uint8_t INTERNAL_CLOCK = 0;
Expand Down Expand Up @@ -122,8 +121,8 @@ class uClockClass {
void start();
void stop();
void pause();
void setTempo(uint16_t bpm);
uint16_t getTempo();
void setTempo(float bpm);
float getTempo();
void setDrift(uint8_t internal, uint8_t external = 255);

// external timming control
Expand Down

0 comments on commit c71e556

Please sign in to comment.