Giter Club home page Giter Club logo

uclock's Introduction

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.

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).

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.

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.

Interface

The uClock library API operates through attached callback functions 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

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
  4. PPQN_384 384 Pulses Per Quarter Note
  5. PPQN_480 480 Pulses Per Quarter Note
  6. 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:

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)

Tick resolution and sequencers

If created a device using setClock16PPQNOutput only you just change the API call to setOnStep. If you were dependent on setClock96PPQNOutput you might need to review your tick counting system, depending on which PPQN clock resolution you choose to use.

Examples

You will find more complete examples on examples/ folder:

#include <uClock.h>

// external or internal sync?
bool _external_sync_on = false;

// the main uClock PPQN resolution ticking
void onPPQNCallback(uint32_t tick) {
  // tick your sequencers or tickable devices...
}

void onStepCallback(uint32_t step) {
  // triger step data for sequencer device...
}

// 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 when clock starts by using uClock.start() method.
void onClockStartCallback() {
  // send start signal to...
}

// The callback function called when clock stops by using uClock.stop() method.
void onClockStopCallback() {
  // send stop signal to...
}

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 ]
  // not mandatory to call, the default is 96PPQN if not set
  uClock.setPPQN(uClock.PPQN_96);

  // you need to use at least one!
  uClock.setOnPPQN(onPPQNCallback);
  uClock.setOnStep(onStepCallback);
  uClock.setOnSync24(onSync24Callback);

  uClock.setOnClockStart(onClockStartCallback);
  uClock.setOnClockStop(onClockStopCallback);

  // set external sync mode?
  if (_external_sync_on) {
    uClock.setMode(uClock.EXTERNAL_CLOCK);
  }

  // starts clock
  uClock.start();
}

void loop() {
  // do we need to external sync?
  if (_external_sync_on) {
    // watch for external sync signal income
    bool signal_income = true; // your external input signal check will be this condition result
    if (signal_income) {
      // at each clockMe call uClock will process and handle external/internal syncronization
      uClock.clockMe();
    }
  }
}

MIDI Examples

Here a few examples on the usage of Clock library for MIDI devices, keep in mind the need to make your own MIDI interface, more details will be avaliable soon but until that, you can find good material over the net about the subject.

If you don't have native USB/MIDI support on your microcontroller and don't want to build a MIDI interface and you are going to use your arduino only with your PC, you can use a Serial-to-Midi bridge and connects your arduino via USB cable to your conputer to use it as a MIDI tool like this one.

A Simple MIDI Sync Box sketch example

Here is an example on how to create a simple MIDI Sync Box on Arduino boards

#include <uClock.h>

// MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards.
#define MIDI_CLOCK 0xF8
#define MIDI_START 0xFA
#define MIDI_STOP  0xFC

// The callback function called by Clock each Pulse of 24PPQN clock resolution.
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  Serial.write(MIDI_CLOCK);
}

// The callback function called when clock starts by using Clock.start() method.
void onClockStart() {
  Serial.write(MIDI_START);
}

// The callback function called when clock stops by using Clock.stop() method.
void onClockStop() {
  Serial.write(MIDI_STOP);
}

void setup() {

  // Initialize serial communication at 31250 bits per second, the default MIDI serial speed communication:
  Serial.begin(31250);

  // Inits the clock
  uClock.init();
  // 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.setOnClockStopOutput(onClockStop);
  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);

  // Starts the clock, tick-tac-tick-tac...
  uClock.start();

}

// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment...
void loop() {

}

An example on how to create a simple MIDI Sync Box on Teensy boards and USB Midi setup. Select "MIDI" from the Tools->USB Type menu for Teensy to becomes a USB MIDI first.

#include <uClock.h>

// The callback function called by Clock each Pulse of 96PPQN clock resolution.
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  usbMIDI.sendRealTime(usbMIDI.Clock);
}

// The callback function called when clock starts by using Clock.start() method.
void onClockStart() {
  usbMIDI.sendRealTime(usbMIDI.Start);
}

// The callback function called when clock stops by using Clock.stop() method.
void onClockStop() {
  usbMIDI.sendRealTime(usbMIDI.Stop);
}

void setup() {
  // Inits the clock
  uClock.init();
  // 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.setOnClockStopOutput(onClockStop);
  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);
  // Starts the clock, tick-tac-tick-tac...
  uClock.start();
}

// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment...
void loop() {

}

Acid Step Sequencer

A clone of Roland TB303 step sequencer main engine, here is an example with no user interface for interaction. If you're looking for a user interactable TB303 sequencer engine clone with user interface please take a look here https://github.com/midilab/uClock/tree/master/examples/AcidStepSequencer.

// Roland TB303 Step Sequencer engine clone.
// No interface here, just the engine as example.
// author: midilab [email protected]
// Under MIT license
#include "Arduino.h"
#include <uClock.h>

// Sequencer config
#define STEP_MAX_SIZE      16
#define NOTE_LENGTH        12 // min: 1 max: 23 DO NOT EDIT BEYOND!!! 12 = 50% on 96ppqn, same as original tb303. 62.5% for triplets time signature
#define NOTE_VELOCITY      90
#define ACCENT_VELOCITY    127

// MIDI config
#define MIDI_CHANNEL      0 // 0 = channel 1

// do not edit below!
#define NOTE_STACK_SIZE    3

// MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards.
#define MIDI_CLOCK 0xF8
#define MIDI_START 0xFA
#define MIDI_STOP  0xFC
#define NOTE_ON    0x90
#define NOTE_OFF   0x80

// Sequencer data
typedef struct
{
  uint8_t note;
  bool accent;
  bool glide;
  bool rest;
} SEQUENCER_STEP_DATA;

typedef struct
{
  uint8_t note;
  int8_t length;
} STACK_NOTE_DATA;

// main sequencer data
SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE];
STACK_NOTE_DATA _note_stack[NOTE_STACK_SIZE];
uint16_t _step_length = STEP_MAX_SIZE;

// make sure all above sequencer data are modified atomicly only
// eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7);
#define ATOMIC(X) noInterrupts(); X; interrupts();

// shared data to be used for user interface feedback
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);
  Serial.write(byte1);
  Serial.write(byte2);
}

// The callback function called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step.
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 ) {

    // check for glide event ahead of _step
    step = _step;
    for ( uint16_t i = 1; i < _step_length; i++  ) {
      ++step;
      step = step % _step_length;
      if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) {
        length = NOTE_LENGTH + (i * 24);
        break;
      } else if ( _sequencer[step].rest == false ) {
        break;
      }
    }

    // find a free note stack to fit in
    for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
      if ( _note_stack[i].length == -1 ) {
        _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);    
        return;
      }
    }
  }  
}

// The callback function called by uClock each Pulse of 96PPQN clock resolution.
void onPPQNCallback(uint32_t tick) 
{
  // Send MIDI_CLOCK to external hardware
  Serial.write(MIDI_CLOCK);

  // handle note on stack
  for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
    if ( _note_stack[i].length != -1 ) {
      --_note_stack[i].length;
      if ( _note_stack[i].length == 0 ) {
        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() 
{
  Serial.write(MIDI_START);
  _playing = true;
}

// The callback function called when clock stops by using Clock.stop() method.
void onClockStop() 
{
  Serial.write(MIDI_STOP);
  // send all note off on sequencer stop
  for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
    sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0);
    _note_stack[i].length = -1;
  }
  _playing = false;
}

void setup() 
{
  // Initialize serial communication
  // the default MIDI serial speed communication at 31250 bits per second
  Serial.begin(31250); 

  // Inits the clock
  uClock.init();
  
  // Set the callback function for the clock output to send MIDI Sync message.
  uClock.setOnPPQN(onPPQNCallback);
  
  // Set the callback function for the step sequencer on 16ppqn
  uClock.setOnStep(onStepCallback);  
  
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);  
  uClock.setOnClockStop(onClockStop);
  
  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);

  // initing sequencer data
  for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) {
    _sequencer[i].note = 48;
    _sequencer[i].accent = false;
    _sequencer[i].glide = false;
    _sequencer[i].rest = false;
  }

  // initing note stack data
  for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
    _note_stack[i].note = 0;
    _note_stack[i].length = -1;
  }

  // pins, buttons, leds and pots config
  //configureYourUserInterface();
  
  // start sequencer
  uClock.start();
}

// User interaction goes here
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[]!
  // IMPORTANT!!! Sequencer main data are used inside a interrupt enabled by uClock for BPM clock timing. Make sure all sequencer data are modified atomicly using this macro ATOMIC();
  // eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7);
  //processYourButtons();
  //processYourLeds();
  //processYourPots();
}

uclock's People

Contributors

doctea avatar eefh avatar jackson-devices avatar m-r-m-s avatar midilab avatar vanzuiden avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uclock's Issues

Adding additional ARM support for uClock (Adafruit QT Py M0)?

Hi @midilab,

I have been working with another ARM board, the Adafruit QT Py M0 which is pin compatible to the Seeed Xiao board with a few hardware differences. Honestly it is a little bit easier to work with on development side and it also uses the current version of the Adafruit Tiny USB library unlike the Seeed Xiao.

Since the Adafruit M0 board is so similar, I thought it would be an easy switch over to using it since you updated the uClock library to support the Xiao.

However, the Arduino core for the QT Py board does not include the same TCC0 and TC3 helper libraries as the Xiao SAMD implementation unfortunately. Those libraries are actually based of the work of this timer interrupt library that I believe should work with the QT Py M0: https://github.com/khoih-prog/TimerInterrupt_Generic

Here is an example of using hardware timers directly:
https://github.com/khoih-prog/TimerInterrupt_Generic#1-using-only-hardware-timer-directly-1

The MACRO for the QT Py M0 can be __SAMD21E18A__ or ADAFRUIT_QTPY_M0

Using the above example, I started to try and figure out how to implement in uClock.cpp but got stuck.
Specifically:

  1. What value should #define TIMER_INTERVAL_MS be?

  2. What the if def should be for uclockInitTimer() ? Maybe:

#if defined(ADAFRUIT_QTPY_M0)
     ITimer.attachInterruptInterval_MS(init_clock, uclockISR)
#endif
  1. What the if def should be for void uClockClass::setTimerTempo(float bpm) ?

Sorry I can't be more helpful to do a pull request but I hope this gets the process going. Thanks!

ESP32/ESP32-s3 crash Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).

Hi,

I'm facing a Guru Meditation Error on my ESP32 Dev Module and ESP32-S3 Dev Module boards when trying to implement a basic code. The error occurs when sending the MIDI clock message, and it leads to a reboot with the following message:

Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).

Additionally, the backtrace shows corruption in the core 1 and core 0 register dumps.

Code:

#include "Arduino.h"
#include <HardwareSerial.h>
#include <MIDI.h>
#include <uClock.h>

#define TXD2 17
#define RXD2 18
#define LED 2

byte clockCounter = 0; // Count the MIDI clock pulses, 24 pulses per quarter note
bool midiClockLedOn = false;

MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI_Serial);

// The callback function which will be called by Clock each Pulse of 96PPQN clock resolution.
void ClockOut96PPQN(uint32_t tick)
{
    // Send MIDI_CLOCK to external gears
    HandleSendMIDIClock();
}

void setup()
{
    Serial.begin(115200); // setup HW serial for debug communication
    Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2); // setup Serial2 for MIDI control

    pinMode(LED, OUTPUT); // set the LED pin as an output
    digitalWrite(LED, LOW); // turn off the LED

    MIDI_Serial.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI_Serial communications, listen to all channels
    MIDI_Serial.turnThruOff();

    // Inits the clock
    uClock.init();
    // Set the callback function for the clock output to send MIDI Sync message.
    uClock.setClock96PPQNOutput(ClockOut96PPQN);
    // Set the clock BPM to 126 BPM
    uClock.setTempo(126);

    // Starts the clock, tick-tac-tick-tac...
    uClock.start();
};

void loop() {
    // Empty loop, no need to execute anything repeatedly.
};

void HandleSendMIDIClock()
{
    //MIDI_Serial.sendRealTime(midi::Clock); // <= crashes as soon commented out
    // Serial2.write(0xF8); <= tried also this, same crash

    // Increment the clock counter, wrap around after 24 pulses
    clockCounter = (clockCounter + 1) & 0x0F;

    handle_bpm_led(clockCounter); // leds work fine
}

void handle_bpm_led(uint32_t tick)
{
    // BPM led indicator
    if (tick == 0 || tick % 24 == 0) { // Each quarter note
        midiClockLedOn = true;
        digitalWrite(LED, HIGH);
    } else if (midiClockLedOn && tick % 24 == 6) { // Turn off the LED after 6 ticks (1/16th note duration)
        midiClockLedOn = false;
        digitalWrite(LED, LOW);
    }
}

Error:

Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1). 

Core  1 register dump:
PC      : 0x4008ab2a  PS      : 0x00060735  A0      : 0x80089aa2  A1      : 0x3ffbf16c  
A2      : 0x3ffb8a4c  A3      : 0x3ffb81a4  A4      : 0x00000004  A5      : 0x00060723  
A6      : 0x00060723  A7      : 0x00000001  A8      : 0x3ffb81a4  A9      : 0x00000018  
A10     : 0x3ffb81a4  A11     : 0x00000018  A12     : 0x00000004  A13     : 0x00060723  
A14     : 0x007bf318  A15     : 0x003fffff  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008416d  LEND    : 0x40084175  LCOUNT  : 0x00000027  
Core  1 was running in ISR context:
EPC1    : 0x400db3fb  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x00000000


Backtrace: 0x4008ab27:0x3ffbf16c |<-CORRUPTED


Core  0 register dump:
PC      : 0x4008acc7  PS      : 0x00060035  A0      : 0x800896cb  A1      : 0x3ffbea3c  
A2      : 0x3ffbf318  A3      : 0xb33fffff  A4      : 0x0000abab  A5      : 0x00060023  
A6      : 0x00060021  A7      : 0x0000cdcd  A8      : 0x0000abab  A9      : 0xffffffff  
A10     : 0x3ffc298c  A11     : 0x00000000  A12     : 0x3ffc2988  A13     : 0x00000007  
A14     : 0x007bf318  A15     : 0x003fffff  SAR     : 0x0000001d  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  


Backtrace: 0x4008acc4:0x3ffbea3c |<-CORRUPTED

Bord config (vscode):

{
    "configuration": "JTAGAdapter=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=none",
    "board": "esp32:esp32:esp32",
    "port": "/dev/tty.SLAB_USBtoUART",
    "output": "../build",
    "sketch": "testUclock.ino",
    "programmer": "esptool"
}

Additional Information:

Board used: ESP32 Dev Module and ESP32-S3 Dev Module.
Libraries used: Arduino, HardwareSerial, MIDI, and uClock.
The backtrace shows corruption in core 1 and core 0 register dumps.

I was able once or twice to let the code run for 10 seconds on ESP32-S3 and the clock accuracy is perfect, 126 sent, 126 get. but suddenly it crashed over and over...

Has the library already been tested with these platforms?

Something that I have missed out?

Renaming the default branch from master

Hi @midilab ,

Would you be willing to rename the default branch to main from master? The master-slave terminology has references to human chattel slavery. Github has already begun rolling out this change for the default to main for new repositories.

For reference:

https://github.com/github/renaming and https://github.com/github/renaming#renaming-existing-branches

also:
https://cdm.link/2020/06/lets-dump-master-slave-terms/

I realize this would also apply to the uClock code base as well i.e. examples and other references so perhaps I can do a pull request or someone else in order to help out? Using the terms main and secondary seem like good replacement terminology.

Thanks for your consideration and again for all the great work!

What external signal can I sync to?

Hi,
is this library meant to feed a midi clock into the arduino and then generate different sync signals out of it or would it be possible to use an audio signal (ie 96 short pulses per quarter) that the arduino generates other sync signals from or does it have to be DC? Sorry for maybe asking dumb questions, I am having sync problems with Ableton and my external gear and being able to use an audiofile for timing reference would be awesome. Can't afford something like a multiclock unfortunately :(
If this is possible what specifications would the signal have to have?
Thanks for sharing your work :)

uclock 96PPQN query/help

I'm building a custom arpeggiator and hoping to use your uclock library and a Teensy 4.

I'm looking to operate the arp at 96PPQN but when I use the library with the example setups the ClockOut96PPQN callback seems to run at 24PPQN (so everything runs slow on my arp).

I also note the examples all send out midi beat clock from the ClockOut96PPQN callback but I understand beat clock is 24PPQN - I thought you'd need to send a beat clock every 4 96PPQN ticks?

I'm fairly new to this so may not have picked up things correctly so apologies if wasting your time.

Thanks for sharing your library, really appreciate the effort you have put into it. Paul

uClock support for Seeeduino XIAO?

Hi @midilab,

I am considering using the Seeeduino XIAO for a MIDI project and wanted to know if it is supported? It seems possible as the XIAO comparable to the Teensy LC. Perhaps with the 1.0 release it is now supported but I wanted to confirm. I found this old thread that may provide some clues.

XIAO Reference: https://wiki.seeedstudio.com/Seeeduino-XIAO/#features

Also reference here: https://www.sigmdel.ca/michel/ha/xiao/seeeduino_xiao_01_en.html#timers

Thank you!

External clock and send midi notes not in sync

I'm testing a simple setup on a teensy 4.1:

#include <SPI.h>
#include <uClock.h>

void startClock() { usbMIDI.sendRealTime(usbMIDI.Start); }

void stopClock() { usbMIDI.sendRealTime(usbMIDI.Stop); }

void sendClock(uint32_t *tick) {
  usbMIDI.sendRealTime(usbMIDI.Clock);

  if ((*tick) % 96 == 0) {
    usbMIDI.sendNoteOn(62, 127, 1);
  };
}

void setup() {
  usbMIDI.begin();
  uClock.init();
  uClock.setClock96PPQNOutput(sendClock);
  uClock.setOnClockStartOutput(startClock);
  uClock.setOnClockStopOutput(startClock);
  uClock.setTempo(120);

  delay(5000);
  uClock.start();
}

void loop() {}

The teensy is connected to (and controlling the tempo of) Ableton Live 11 via USB. The tempo works perfectly, but the note I send is relly off. It is ~118ms too late. A few ms of latency I expected because the clock signal has a higher priority than a normal note I guess. Can you think of any reason this is happening?

Another issue I have (which could also just be me misunterstanding the MIDI clock standard an the PPQN) is that in the setup above, I would expect to hear one note per quarter note (so if I turn on a metronome I would hear one note per metronome tick). But it seems to be only one note per bar.

Support for the ESP32/ESP32S2/ESP32S3 platforms

These MCUs are powerfull enough to run MIDI stuff along with some audio routines, also S2 and S3 are capable of being USB-HOST devices, so stand-alone units allowing direct connecting of USB-MIDI keyboards/controllers can be built.

Update Platformio library registry to latest uClock version?

Hi @midilab ,
This is not urgent but if it is possible to update uClock to the latest version in the Platformio Library Registry, it would be greatly appreciated. It is currently at version 0.10.6 https://registry.platformio.org/libraries/midilab/uClock/versions
The registry should show midilab/uClock@^1.1.1 instead of midilab/uClock@^0.10.6

I am able to install the latest uClock version manually in the platformio.ini file if I pull the repo directly (shown below) but others wanting the latest version of uClock may end up installing older version from the Platformio Registry. Thank you!

[env:teensylc]
platform = teensy
board = teensylc
framework = arduino
lib_deps =  https://github.com/midilab/uClock.git

Possible to use concurrently with the USB_Host_Shield_2.0 library?

Hi,

I'm trying to use the uClock library with the USB Host Shield library (https://github.com/YuuichiAkagawa/Arduino-UHS2MIDI and https://github.com/felis/USB_Host_Shield_2.0). I want to use it to sync clocks with multiple devices and CV clock.

I've got a sketch mostly working for this purpose but wanted to give it more rock-solid timing so attempted to implement uClock with it, available here: https://github.com/doctea/usb_midi_clocker/tree/uclock

However, having added uClock, it seems to stop the USB Host library from working and detecting devices. I imagine perhaps because of some interrupt conflict or suchlike? It seems like the Arduino crashes and restarts when the two are enabled together.

Its a bit beyond me to work out how to debug and fix this -- are there any pointers I should start with and is there something that can be done to make the two libraries coexist happily?

Thanks,

'IntervalTimer' does not name a type - Seeed Xiao samd21 m0

Hi,

First of all, thank you for creating this library. I have been working on the prototype of a sync device and have been struggling to get a solid external midi sync going. From what I’ve seen your library seems to be a great starting point for what I’m trying to achieve.

I’m coming from a pi pico with python setup and just received a Xiao m0 today to test your library.

I had some trouble getting the library to run. I later found that version 1.3.0 or newer fails to compile due to the following error

In file included from PATH/Arduino/libraries/uClock/src/uClock.cpp:46:0:
PATH/Arduino/libraries/uClock/src/platforms/samd.h:11:1: error: 'IntervalTimer' does not name a type
 IntervalTimer _uclockTimer;
 ^~~~~~~~~~~~~

exit status 1

Compilation error: exit status 1

Version 1.2.0 or older compiles and runs fine on the Xiao.

I'm looking forward to build a prototype with uClock tomorrow!

Intermittent freeze on Teensy 4.1 when trying to send CC from inside loop()?

Hi! I have uClock working well in my Teensy USB MIDI project, except if I try to sendControlChange from inside loop(). If I do that then it doesn't last long before the program freezes.

I think its hitting some kind of race condition with USB, as Teensy CrashReport doesn't see to get triggered, and in certain cases its possible to avoid the freeze - or to recover from the frozen state - by connecting the USB Serial monitor. (I've only been able to replicate this in my 'full' project though.)

I'm using utils/atomic.h ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {}, which as far as I understand should handle enabling and disabling interrupts, and prevent concurrency related crashes -- wrapping sections of my loop() code in this macro has solved every crash so far except this one. I've also tried using __disable_irqs and __enable_irqs instead, but I get the same freezes regardless.

My use case here is that there are CCs that I need to send separately from the main clock ticks.

I've made a simple test sketch replicating the problem, attached here. Hopefully this will make it easy to replicate the problem. Defining SEND_CCS_IN_LOOP to 'true' will cause the Teensy to freeze within not much time, a minute at most. Defining it to 'false' seems to run indefinitely.

My test setup is with the Teensy 4.1 USB Host port connected to two USB hubs, and with multiple USB MIDI devices attached to the hubs. It doesn't seem to matter much which devices are connected. Curious whether the same behaviour is replicated on other hardware.

Any ideas what could be causing this, and hopefully how to avoid it, please? :)

/* based on USB MIDI Sync Box example
*/

// switch this between true / false to see problem
// when true, Teensy will freeze up intermittently
// when false, Teensy will run successfully forever
#define SEND_CCS_IN_LOOP true // false

// for attempting to disable interrupts while doing loop() stuff
#include <util/atomic.h>

#include <uClock.h>

// for using Teensy USB MIDI device 
#include "USBHost_t36.h"
#include <MIDI.h>

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);
USBHub hub3(myusb);
KeyboardController keyboard1(myusb);
KeyboardController keyboard2(myusb);
MIDIDevice midi1(myusb);
MIDIDevice midi2(myusb);
MIDIDevice midi3(myusb);
MIDIDevice midi4(myusb);
MIDIDevice midi5(myusb);
MIDIDevice midi6(myusb);
MIDIDevice midi7(myusb);
MIDIDevice midi8(myusb);

uint32_t loop_counter = 0;
uint32_t tick_counter = 0;

uint8_t bpm_blink_timer = 1;
void handle_bpm_led(uint32_t tick)
{
  // BPM led indicator
  if ( !(tick % (96)) || (tick == 1) ) {  // first compass step will flash longer
    bpm_blink_timer = 8;
    digitalWrite(LED_BUILTIN, HIGH);
  } else if ( !(tick % (24)) ) {   // each quarter led on
    digitalWrite(LED_BUILTIN, HIGH);
  } else if ( !(tick % bpm_blink_timer) ) { // get led off
    digitalWrite(LED_BUILTIN, LOW);
    bpm_blink_timer = 1;
  }
}

// Internal clock handlers
void ClockOut96PPQN(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  //myusb.sendRealTime(midi::Clock);
  handle_bpm_led(tick);

  tick_counter++;

  int8_t note_on = tick_counter % 127;
  int8_t note_off = (tick_counter-6) % 127;

  midi1.sendRealTime(midi::Clock);
  midi2.sendRealTime(midi::Clock);
  midi3.sendRealTime(midi::Clock);
  midi4.sendRealTime(midi::Clock);
  midi5.sendRealTime(midi::Clock);
  midi6.sendRealTime(midi::Clock);
  midi7.sendRealTime(midi::Clock);
  midi8.sendRealTime(midi::Clock);

  midi1.sendNoteOn(note_on, 127, 1);
  midi2.sendNoteOn(note_on, 127, 1);
  midi3.sendNoteOn(note_on, 127, 1);
  midi4.sendNoteOn(note_on, 127, 1);
  midi5.sendNoteOn(note_on, 127, 1);
  midi6.sendNoteOn(note_on, 127, 1);
  midi7.sendNoteOn(note_on, 127, 1);
  midi8.sendNoteOn(note_on, 127, 1);

  midi1.sendNoteOff(note_off, 0, 1);
  midi2.sendNoteOff(note_off, 0, 1);
  midi3.sendNoteOff(note_off, 0, 1);
  midi4.sendNoteOff(note_off, 0, 1);
  midi5.sendNoteOff(note_off, 0, 1);
  midi6.sendNoteOff(note_off, 0, 1);
  midi7.sendNoteOff(note_off, 0, 1);
  midi8.sendNoteOff(note_off, 0, 1);
  
  /*if (random(100)<1) {
      midi1.sendControlChange(random(127), random(127), 1);
      midi2.sendControlChange(random(127), random(127), 1);
      midi3.sendControlChange(random(127), random(127), 1);
      midi4.sendControlChange(random(127), random(127), 1);
      midi5.sendControlChange(random(127), random(127), 1);
      midi6.sendControlChange(random(127), random(127), 1);
      midi7.sendControlChange(random(127), random(127), 1);
      midi8.sendControlChange(random(127), random(127), 1);
    }*/

}

void onClockStart() {
  //myusb.sendRealTime(myUsb.Start);
  midi1.sendRealTime(midi::Start);
  midi2.sendRealTime(midi::Start);
  midi3.sendRealTime(midi::Start);
  midi4.sendRealTime(midi::Start);
  midi5.sendRealTime(midi::Start);
  midi6.sendRealTime(midi::Start);
  midi7.sendRealTime(midi::Start);
  midi8.sendRealTime(midi::Start);
}

void onClockStop() {
  //myUsb.sendRealTime(myUsb.Stop);
  midi1.sendRealTime(midi::Stop);
  midi2.sendRealTime(midi::Stop);
  midi3.sendRealTime(midi::Stop);
  midi4.sendRealTime(midi::Stop);
  midi5.sendRealTime(midi::Stop);
  midi6.sendRealTime(midi::Stop);
  midi7.sendRealTime(midi::Stop);
  midi8.sendRealTime(midi::Stop);
}

void setup() {
  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);
  
  Serial.begin(115200);
  Serial.println("BOOTING");

	myusb.begin();

  // Setup our clock system
  // Inits the clock
  uClock.init();
  // Set the callback function for the clock output to send MIDI Sync message.
  uClock.setClock96PPQNOutput(ClockOut96PPQN);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStartOutput(onClockStart);  
  uClock.setOnClockStopOutput(onClockStop);
  // Set the clock BPM to 126 BPM
  uClock.setTempo(90);
  // Starts the clock, tick-tac-tick-tac..
  uClock.start();
}

// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment..
void loop() {
  loop_counter++;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
	  myusb.Task();
  }
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    midi1.read();
    midi2.read();
    midi3.read();
    midi4.read();
    midi5.read();
    midi6.read();
    midi7.read();
    midi8.read();
  }

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    if (SEND_CCS_IN_LOOP && random(100)<1) {
      // weirdly, in my 'full proper project' that has other stuff going on, adding this 
      // serial debug output and connecting to serial monitor seems to prevent the 'freeze'...
      // seems to have no effect in this simple test case though
      Serial.printf("sending some random control changes: %u\n", loop_counter);
      Serial.flush();

      midi1.sendControlChange(random(127), random(127), 1);
      midi2.sendControlChange(random(127), random(127), 1);
      midi3.sendControlChange(random(127), random(127), 1);
      midi4.sendControlChange(random(127), random(127), 1);
      midi5.sendControlChange(random(127), random(127), 1);
      midi6.sendControlChange(random(127), random(127), 1);
      midi7.sendControlChange(random(127), random(127), 1);
      midi8.sendControlChange(random(127), random(127), 1);

      /*midi1.sendNoteOn(random(127), random(127), 1);
      midi2.sendNoteOn(random(127), random(127), 1);
      midi3.sendNoteOn(random(127), random(127), 1);
      midi4.sendNoteOn(random(127), random(127), 1);
      midi5.sendNoteOn(random(127), random(127), 1);
      midi6.sendNoteOn(random(127), random(127), 1);
      midi7.sendNoteOn(random(127), random(127), 1);
      midi8.sendNoteOn(random(127), random(127), 1);*/

    }
  }

}

external incoming clock ticks supported by uClock?

@midilab can uClock be implemented to take incoming clock ticks from an external MIDI device and display the incoming BPM while passing on the clock ticks to another MIDI connected sound source? If so would you be able to share an example? Thanks!

Clock bug on Arduino board?

Hi,

Thank you for creating this library.

I am testing a clock generator with uClock on Arduino and ESP32(M5stack).
When changing Tempo from 77 to 76 on the Arduino board, the clock momentarily collapses.
Could someone please check?

ArduinoIDE 2.2.1
Arduino UNO and Nano(uClock Ver. 0.10.0 to 1.5.1) NG.
Arduino UNO and Nano(uClock Ver. up to 0.9.4) OK.
ESP32(uClock Ver. 1.4.2) OK.

This is code for test.
Three buttons are required. (D2,D3,D4)



#include "Arduino.h"
#include <uClock.h>

#define MIDI_CLOCK 0xF8
#define MIDI_START 0xFA
#define MIDI_STOP 0xFC

void ClockOut96PPQN(uint32_t tick)
{
Serial.write(MIDI_CLOCK);
}

void setup()
{
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);

Serial.begin(31250);

uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
uClock.setTempo(77);

uClock.start();

}

void loop()
{
if (digitalRead(2) == LOW) {
uClock.setTempo(78);
}
if (digitalRead(3) == LOW) {
uClock.setTempo(77);
}
if (digitalRead(4) == LOW) {
uClock.setTempo(76);
}
}


Sorry if I am something wrong, and I apologize for my poor English.
Thanks.

potential bug in TeensyUsbSlaveMidiClockMonitor example

Hey @midilab I realized I didn't test uClock's latest version on the Teensy LC when we were testing the Xiao compatibility.

I went back and tested the following examples:

1 - MidiClock
2 - TeensyUsbMasterMidiClock
3 - TeensyUsbSlaveMidiClock
4 - TeensyUsbSlaveMidiClockMonitor

1-3 work with no issues , however example 4 is having some issues.

It seems to lock up with an initial BPM of 42 or higher. The tempo LED freezes solid upon receiving a MIDI start byte. Additionally, there is no display of the tempo value, "bpm"or the "playing" or "stopped" messages on the the OLED. It only shows "uClock" .

If you power on the Teensy LC and the tempo of the main clock is below 42 bpm, the tempo LED does not lock up and you can adjust the bpm beyond 42 bpm (20-300 BPM is what I tested from external sequencer clock) and it will continue to work. However, there is still no display of the tempo value, "bpm"or the "playing" or "stopped" messages on the the OLED.

Changing the #define EXT_INTERVAL_BUFFER_SIZE value doesn't seem to resolve the issue. Anything else you recommend?

I'll keep poking around and see what I can find but I wanted to report in case you or someone else can reproduce. Thank you.

Sync with external PPQN_48 will give double BPM on uClock.setOnSync24 with setPPQN(uClock.PPQN_48)

As the title says, I have an external PPQN_48 signal that I try to convert to midi clock. It seems the uClock.setOnSync24 is called on every tick and not in sync to PPQN_48. Do I get the documentation wrong? With external signal set to 24 ppq bpm is in sync, with 48ppq I get double bpm. Here my code:

In setup:

 uClock.init();                              // init uClock first
 uClock.setPPQN(uClock.PPQN_48);             // internat ppq setting
 uClock.setMode(uClock.EXTERNAL_CLOCK);      // sync to external clock source
 uClock.setOnSync24(onSync24Callback);       // calback on 24ppq for midi out
 uClock.start();                             // uClock start

callback ans sync function via IRQ

void ppqInterrupt() { // IRSR to sync uclock to FALLING edge on input pin
  uClock.clockMe();   // sync
}                     // end

void onSync24Callback(uint32_t tick) { // midi clock out on ppqn24
  mySerial.write(MIDI_CLOCK);          // MIDI clock byte definitions
  digitalWrite(13, HIGH);              // LED on
}                                      // end


uClock supported by Raspi pico ?

Hi,
i am already using you marvellous library to run a sequencer/arpeggiator on Arduino Mega and it is particularly reliable.
Is the uClock library compatible with the RP2040 used in Raspberry pico boards ? it 's a dual core ARM Cortex M0+ @133MHZ
Best regards

JV

uClock does not compile with Raspberry Pi Pico / RP2040

using latest arduino-pico core: https://github.com/earlephilhower/arduino-pico

G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\uClock.cpp: In function 'void uclockInitTimer()':
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\uClock.cpp:70:5: error: 'initTimer' was not declared in this scope
70 | initTimer(uClock.bpmToMicroSeconds(120.00));
| ^~~~~~~~~
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\uClock.cpp: In function 'void setTimerTempo(float)':
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\uClock.cpp:75:5: error: 'setTimer' was not declared in this scope; did you mean 'setitimer'?
75 | setTimer(uClock.bpmToMicroSeconds(bpm));
| ^~~~~~~~
| setitimer

uClock does not compile with ESP32-C3

As the title says:

In file included from G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\uClock.cpp:52:
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\platforms/esp32.h:28:48: error: macro "portYIELD_FROM_ISR" passed 1 arguments, but takes just 0
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
^
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\platforms/esp32.h: In function 'void handlerISR()':
G:\PortableApps\arduino\portable\sketchbook\libraries\uClock\src\platforms/esp32.h:28:5: error: 'portYIELD_FROM_ISR' was not declared in this scope
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

old ESP32 and ESP32 S2 compiles, ESP32 C3 not!

Multiple warnings when compiling uClock for Arduino

Even a simple sketch like:
#include <uClock.h>

void setup() {
// put your setup code here, to run once:

}

void loop() {
// put your main code here, to run repeatedly:

}

generates the following warnings... Is there something I'm doing wrong? Thanks

F:\Documents\Arduino\libraries\uClock-master\src\uClock.cpp: In member function 'void umodular::clock::uClockClass::handleTimerInt()':
F:\Documents\Arduino\libraries\uClock-master\src\uClock.cpp:358:39: warning: invalid conversion from 'volatile uint32_t* {aka volatile long unsigned int*}' to 'uint32_t* {aka long unsigned int*}' [-fpermissive]
onClock96PPQNCallback(&internal_tick);
^
F:\Documents\Arduino\libraries\uClock-master\src\uClock.cpp:363:42: warning: invalid conversion from 'volatile uint32_t* {aka volatile long unsigned int*}' to 'uint32_t* {aka long unsigned int*}' [-fpermissive]
onClock32PPQNCallback(&div32th_counter);
^
F:\Documents\Arduino\libraries\uClock-master\src\uClock.cpp:366:42: warning: invalid conversion from 'volatile uint32_t* {aka volatile long unsigned int*}' to 'uint32_t* {aka long unsigned int*}' [-fpermissive]
onClock16PPQNCallback(&div16th_counter);
^
F:\Documents\Arduino\libraries\uClock-master\src\uClock.cpp:374:42: warning: invalid conversion from 'volatile uint32_t* {aka volatile long unsigned int*}' to 'uint32_t* {aka long unsigned int*}' [-fpermissive]
onClock32PPQNCallback(&div32th_counter);
^

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.