You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
midilab 698c789514 Roland TB303 main engine added as example for a step sequencer using uClock 7 years ago
examples added lock system for octave and note pots to avoid messing when change from one step to another for editing pruporse. 7 years ago
src External sync tempo extraction from clock pulses added. 7 years ago
LICENSE Initial commit 7 years ago
README.md Roland TB303 main engine added as example for a step sequencer using uClock 7 years ago
library.properties Arduino >= 1.5 library style support 7 years ago

README.md

uClock

BPM clock generator for Arduino is a library to implement BPM clock tick calls using hardware timer1 interruption for tight and solid timming clock ticks. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560.

Generate your self tight BPM clock for music, audio/video productions, performances or instalations. You can clock your MIDI setup or sync different protocols as you wish.

Interface

Clock library interfaces via attached callback function running on a hardware interrupt and is able to process the following resolutions:

  1. 16PPQN 16 Pulses Per Quarter Note
  2. 32PPQN 32 Pulses Per Quarter Note
  3. 96PPQN 96 Pulses Per Quarter Note

To generate a MIDI sync signal to sync external MIDI devices for example, you need to work with the resolution of 96PPQN to follow the standards of MIDI protocol that handles the clock based on 24PPQN.

For a simple old felling step sequencer a 16PPQN resolution is a good way to start coding your own step sequencer.

You can also use all the 3 resolutions at the same time for whatever reason you think you should.

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 dont 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 a example on how to create a simple MIDI Sync Box

#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 wich will be called by Clock each Pulse of 96PPQN clock resolution.
void ClockOut96PPQN(uint32_t * tick) {
  // Send MIDI_CLOCK to external gears
  Serial.write(MIDI_CLOCK);
}

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

// The callback function wich will be 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.
  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(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, the schematics for user interface are avaliable on example folder.

// Roland TB303 Step Sequencer engine clone.
// No interface here, just the engine as example.
#include "Arduino.h"
#include <uClock.h>

// Sequencer config
#define STEP_MAX_SIZE      16
#define NOTE_LENGTH        4 // min: 1 max: 5 DO NOT EDIT BEYOND!!!
#define NOTE_VELOCITY      90
#define ACCENT_VELOCITY    127

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

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

SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE];

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

STACK_NOTE_DATA _note_stack[2];

bool _playing = false;
uint16_t _step = 0;
uint16_t _step_length = STEP_MAX_SIZE;

// 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

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 wich will be called by uClock each Pulse of 16PPQN clock resolution.
// Each call represents exactly one step here.
void ClockOut16PPQN(uint32_t * tick) 
{
  uint16_t step;
  bool glide_ahead;
  
  // get actual step.
  _step = *tick % _step_length;
  
  // send note on only if this step are not in rest mode
  if ( _sequencer[_step].rest == false ) {
    sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY);
    // do we have a glide ahead us?
    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 ) {
        _note_stack[1].note = _sequencer[_step].note;
        _note_stack[1].length = NOTE_LENGTH + (i * 6);
        glide_ahead = true;
        break;
      } else if ( _sequencer[step].rest == false ) {
        glide_ahead = false;
        break;
      }
    }
    if ( glide_ahead == false ) {
      _note_stack[0].note = _sequencer[_step].note;
      _note_stack[0].length = NOTE_LENGTH;
    }
  }  
}

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

  // handle note on stack
  // [1] is notes to be glided, its in hold on mode until we reach the glided step
  if ( _note_stack[1].length != -1 ) {
    --_note_stack[1].length;
    if ( _note_stack[1].length == 0 ) {
      sendMidiMessage(NOTE_OFF, _note_stack[1].note, 0);
      _note_stack[1].length = -1;
    }
  }  
  // [0] is the actual step note stack
  if ( _note_stack[0].length != -1 ) {
    --_note_stack[0].length;
    if ( _note_stack[0].length == 0 ) {
      sendMidiMessage(NOTE_OFF, _note_stack[0].note, 0);
      _note_stack[0].length = -1;
    }
  }
}

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

// The callback function wich will be called when clock stops by using Clock.stop() method.
void onClockStop() 
{
  Serial.write(MIDI_STOP);
  sendMidiMessage(NOTE_OFF, _note_stack[1].note, 0);
  sendMidiMessage(NOTE_OFF, _note_stack[0].note, 0);
}

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.setClock96PPQNOutput(ClockOut96PPQN);
  
  // Set the callback function for the step sequencer on 16ppqn
  uClock.setClock16PPQNOutput(ClockOut16PPQN);  
  
  // 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);

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

  // pins, buttons, leds and pots config
  //configureYourUserInterface();
}

// User interaction goes here
void loop() 
{
  //processYourButtons();
  //processYourLeds();
  //processYourPots();
}