From c28cceabcc4f6281b9cfc4c060fe7289a84be8c6 Mon Sep 17 00:00:00 2001 From: midilab Date: Fri, 6 Oct 2017 06:55:22 -0300 Subject: [PATCH] Initial commit. v0.8 --- README.md | 85 ++++++++++++++++ uClock.cpp | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++ uClock.h | 124 +++++++++++++++++++++++ 3 files changed, 501 insertions(+) create mode 100755 README.md create mode 100755 uClock.cpp create mode 100755 uClock.h diff --git a/README.md b/README.md new file mode 100755 index 0000000..75d0f1e --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# 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](http://projectgus.github.io/hairless-midiserial/). + +### A Simple MIDI Sync Box sketch example +Here is a example on how to create a simple MIDI Sync Box + +```c++ +#include + +// 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() { + +} +``` + +### A Simple Step Sequencer +A simple step sequencer for MIDI devices: + + +```c++ +Soon... +``` + diff --git a/uClock.cpp b/uClock.cpp new file mode 100755 index 0000000..febfb07 --- /dev/null +++ b/uClock.cpp @@ -0,0 +1,292 @@ +/*! + * @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.8 + * @author Romulo Silva + * @date 10/06/17 + * @license MIT - (c) 2017 - Romulo Silva - contact@midilab.co + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "uClock.h" + +namespace umodular { namespace clock { + +uClockClass::uClockClass() +{ + init(); + mode = INTERNAL_CLOCK; + setTempo(120); + + onClock96PPQNCallback = NULL; + onClock32PPQNCallback = NULL; + onClock16PPQNCallback = NULL; + onClockStartCallback = NULL; + onClockStopCallback = NULL; +} + +void uClockClass::init() +{ + uint8_t tmpSREG; + + state = PAUSED; + counter = 0; + last_clock = 0; + div96th_counter = 0; + div32th_counter = 0; + div16th_counter = 0; + mod6_counter = 0; + indiv96th_counter = 0; + inmod6_counter = 0; + pll_x = 220; + + // + // Configure timers and prescale + // Timmer1: ATMega128, ATMega328, AtMega16U4 and AtMega32U4 + tmpSREG = SREG; + cli(); + TCCR1A = _BV(WGM10); + TCCR1B = _BV(CS10) | _BV(WGM12); // every cycle + TIMSK1 |= _BV(TOIE1); + SREG = tmpSREG; +} + +uint16_t clock_diff(uint16_t old_clock, uint16_t new_clock) +{ + if (new_clock >= old_clock) { + return new_clock - old_clock; + } else { + return new_clock + (65535 - old_clock); + } +} + +void uClockClass::handleClock() +{ + uint16_t cur_clock = _clock; + uint16_t diff; + + if (cur_clock > last_clock) { + diff = cur_clock - last_clock; + } else { + diff = cur_clock + (65535 - last_clock); + } + + last_interval = diff; + last_clock = cur_clock; + indiv96th_counter++; + inmod6_counter++; + + if (inmod6_counter == 6) { + inmod6_counter = 0; + } + + switch (state) { + + case PAUSED: + break; + + case STARTING: + state = STARTED; + break; + + case STARTED: + if (indiv96th_counter == 2) { + interval = diff; + } else { + interval = (((uint32_t)interval * (uint32_t)pll_x) + (uint32_t)(256 - pll_x) * (uint32_t)diff) >> 8; + } + break; + + } + +} + +void uClockClass::handleStart() +{ + if (mode == EXTERNAL_CLOCK) { + init(); + state = STARTING; + mod6_counter = 0; + div96th_counter = 0; + div32th_counter = 0; + div16th_counter = 0; + counter = 0; + } +} + +void uClockClass::handleStop() +{ + if (mode == EXTERNAL_CLOCK) { + state = PAUSED; + } +} + +#define PHASE_FACTOR 16 +static uint32_t phase_mult(uint32_t val) +{ + return (val * PHASE_FACTOR) >> 8; +} + +void uClockClass::start() +{ + if (mode == INTERNAL_CLOCK) { + state = STARTED; + mod6_counter = 0; + div96th_counter = 0; + div32th_counter = 0; + div16th_counter = 0; + if (onClockStartCallback) { + onClockStartCallback(); + } + } +} + +void uClockClass::stop() +{ + if (mode == INTERNAL_CLOCK) { + state = PAUSED; + if (onClockStopCallback) { + onClockStopCallback(); + } + } +} + +void uClockClass::pause() +{ + if (mode == INTERNAL_CLOCK) { + if (state == PAUSED) { + start(); + } else { + stop(); + } + } +} + +void uClockClass::setTempo(uint16_t _tempo) +{ + if ( tempo == _tempo ) { + return; + } + uint8_t tmpSREG = SREG; + cli(); + tempo = _tempo; + interval = (uint32_t)((uint32_t)156250 / tempo) - 16; + SREG = tmpSREG; +} + +void uClockClass::clockMe() +{ + if (uClock.mode == uClock.EXTERNAL_CLOCK) { + uClock.handleClock(); + } +} + +// TODO: Tap stuff +void uClockClass::tap() +{ + // tap me +} + +// TODO: Shuffle stuff +void uClockClass::shuffle() +{ + // shuffle me +} + +void uClockClass::handleTimerInt() +{ + if (counter == 0) { + + counter = interval; + + if (mod6_counter == 0) { + if (onClock16PPQNCallback) { + onClock16PPQNCallback(&div16th_counter); + } + if (onClock32PPQNCallback) { + onClock32PPQNCallback(&div32th_counter); + } + div16th_counter++; + div32th_counter++; + } + + if (mod6_counter == 3) { + if (onClock32PPQNCallback) { + onClock32PPQNCallback(&div32th_counter); + } + div32th_counter++; + } + + //div96th_counter++; + //mod6_counter++; + + if (mode == EXTERNAL_CLOCK) { + uint16_t cur_clock = _clock; + uint16_t diff = clock_diff(last_clock, cur_clock); + if ((div96th_counter < indiv96th_counter) || (div96th_counter > (indiv96th_counter + 1))) { + div96th_counter = indiv96th_counter; + mod6_counter = inmod6_counter; + } + if (div96th_counter <= indiv96th_counter) { + counter -= phase_mult(diff); + } else { + if (counter > diff) { + counter += phase_mult(counter - diff); + } + } + } + + if (onClock96PPQNCallback) { + onClock96PPQNCallback(&div96th_counter); + } + + if (mod6_counter == 6) { + mod6_counter = 0; + } + + div96th_counter++; + mod6_counter++; + + } else { + counter--; + } + +} + +} } // end namespace umodular::clock + +umodular::clock::uClockClass uClock; + +volatile uint16_t _clock = 0; + +// +// TIMER1 HANDLER INTERRUPT +// +ISR(TIMER1_OVF_vect) +{ + if (uClock.state == uClock.STARTED) { + _clock++; + uClock.handleTimerInt(); + } +} + + diff --git a/uClock.h b/uClock.h new file mode 100755 index 0000000..1a8a32c --- /dev/null +++ b/uClock.h @@ -0,0 +1,124 @@ +/*! + * @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.8 + * @author Romulo Silva + * @date 10/06/17 + * @license MIT - (c) 2017 - Romulo Silva - contact@midilab.co + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef __U_CLOCK_H__ +#define __U_CLOCK_H__ + +#include +#include + +namespace umodular { namespace clock { + +class uClockClass { + + public: + + void (*onClock96PPQNCallback)(uint32_t * tick); + void (*onClock32PPQNCallback)(uint32_t * tick); + void (*onClock16PPQNCallback)(uint32_t * tick); + void (*onClockStartCallback)(); + void (*onClockStopCallback)(); + + uint32_t div96th_counter; + uint32_t div32th_counter; + uint32_t div16th_counter; + uint8_t mod6_counter; + uint8_t inmod6_counter; + uint16_t interval; + uint16_t counter; + uint16_t last_clock; + uint16_t last_interval; + uint32_t indiv96th_counter; + uint16_t pll_x; + uint16_t tempo; + + enum { + PAUSED = 0, + STARTING, + STARTED + } state; + + enum { + OFF = 0, + INTERNAL_CLOCK, + EXTERNAL_CLOCK + } mode; + + uClockClass(); + + void setClock96PPQNOutput(void (*callback)(uint32_t * tick)) { + onClock96PPQNCallback = callback; + } + + void setClock32PPQNOutput(void (*callback)(uint32_t * tick)) { + onClock32PPQNCallback = callback; + } + + void setClock16PPQNOutput(void (*callback)(uint32_t * tick)) { + onClock16PPQNCallback = callback; + } + + void setOnClockStartOutput(void (*callback)()) { + onClockStartCallback = callback; + } + + void setOnClockStopOutput(void (*callback)()) { + onClockStopCallback = callback; + } + + void init(); + void handleClock(); + void handleStart(); + void handleStop(); + void handleTimerInt(); + + // external class control + void start(); + void stop(); + void pause(); + void setTempo(uint16_t tempo); + uint16_t getTempo(); + + // External timming control + void clockMe(); + + void shuffle(); + void tap(); +}; + +} } // end namespace umodular::clock + +extern umodular::clock::uClockClass uClock; + +extern "C" { +extern volatile uint16_t _clock; +} + +#endif /* __U_CLOCK_H__ */ +