├── LICENSE ├── README.md ├── examples ├── AVRUartSlaveMidiClockMonitor │ └── AVRUartSlaveMidiClockMonitor.ino ├── AcidStepSequencer │ ├── AcidStepSequencer.ino │ ├── DefaultUserInterface.ino │ ├── HardwareInterface.ino │ ├── README.md │ └── acid_step_sequencer-protoboard-v001.png ├── ESP32UartMasterMidiClock │ └── ESP32UartMasterMidiClock.ino ├── GenericMasterOrExternalSync │ └── GenericMasterOrExternalSync.ino ├── LeonardoUsbSlaveMidiClockMonitor │ └── LeonardoUsbSlaveMidiClockMonitor.ino ├── MidiClock │ └── MidiClock.ino ├── RP2040UsbUartMasterClock │ ├── RP2040UsbUartMasterClock.ino │ └── builtin_led.ino ├── STM32UartMasterMidiClock │ └── STM32UartMasterMidiClock.ino ├── TeensyUsbMasterMidiClock │ ├── TeensyUsbMasterMidiClock.ino │ └── name.c ├── TeensyUsbSlaveMidiClock │ ├── TeensyUsbSlaveMidiClock.ino │ └── name.c ├── TeensyUsbSlaveMidiClockMonitor │ ├── TeensyUsbSlaveMidiClockMonitor.ino │ └── name.c └── XiaoUsbMasterMidiClock │ └── XiaoUsbMasterMidiClock.ino ├── library.json ├── library.properties └── src ├── platforms ├── avr.h ├── esp32-nofrertos.h ├── esp32.h ├── rp2040.h ├── samd.h ├── software.h ├── stm32.h └── teensy.h ├── uClock.cpp └── uClock.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 midilab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uClock 2 | 3 | The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interrupt. It is built to be multi-architecture, portable, and easy to use within the open-source ecosystem. 4 | 5 | We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on various **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, ESP32, Raspberry Pi Pico, Seeed Studio XIAO M0, and RP2040)**. 6 | 7 | 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 timer hardware interrupts, the library can schedule and manage real-time processing with safe shared resource access through its API. 8 | 9 | With uClock, you can create professional-grade sequencers, sync boxes, or generate a precise BPM clock for external devices in music, audio/video production, 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 control your entire MIDI setup or any other protocols according to your specific preferences and requirements. 10 | 11 | ## Interface 12 | The uClock library API operates through an attached callback function mechanism: 13 | 14 | 1. **setOnOutputPPQN(onPPQNCallback) > onOutputPPQNCallback(uint32_t tick)** Calls are made on each new output pulse based on the selected PPQN resolution (if no PPQN is set, the default is 96 PPQN). 15 | 2. **setOnInputPPQN(onPPQNCallback) > onInputPPQNCallback(uint32_t tick)** Set the expected input PPQN (Pulses Per Quarter Note) resolution for external clock sync. 16 | 3. **setOnStep(onStepCallback) > onStepCallback(uint32_t step)** A good way to code an old-style step sequencer based on a 16th-note schema, which is not dependent on PPQN (Pulses Per Quarter Note) output config. 17 | 4. **setOnSync24(onSync24Callback) > onSync24Callback(uint32_t tick)** A good way to code a clock machine or keep your devices in sync with your system is to use setOnSyncXX(), where XX represents the PPQN (Pulses Per Quarter Note) value you want to use. MIDI specifications typically expect 24 PPQN, but if you're working with other devices that are not MIDI standard, you can choose a different PPQN value. Please refer to the supported PPQNs to select from. You can use one or more setOnSyncXX callbacks for different sync output signatures. 18 | 5. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** On the uClock Start event. 19 | 6. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** On the uClock Stop event. 20 | 21 | ### Clock input/output resolutions 22 | 23 | 1. **PPQN_1** 1 Pulses Per Quarter Note (only input) 24 | 2. **PPQN_2** 2 Pulses Per Quarter Note (only input) 25 | 3. **PPQN_4** 4 Pulses Per Quarter Note 26 | 4. **PPQN_8** 8 Pulses Per Quarter Note 27 | 5. **PPQN_12** 12 Pulses Per Quarter Note 28 | 6. **PPQN_24** 24 Pulses Per Quarter Note 29 | 7. **PPQN_48** 48 Pulses Per Quarter Note 30 | 8. **PPQN_96** 96 Pulses Per Quarter Note 31 | 9. **PPQN_384** 384 Pulses Per Quarter Note 32 | 10. **PPQN_480** 480 Pulses Per Quarter Note 33 | 11. **PPQN_960** 960 Pulses Per Quarter Note 34 | 35 | To generate a MIDI sync signal and synchronize external MIDI devices, you can start with a resolution of 24 PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available on the market. By sending 24 pulses per quarter-note interval, you can ensure effective synchronization among your MIDI devices. 36 | 37 | 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. 38 | 39 | ### Software Timer mode - for unsupported boards (or avoiding usage of interrupts) 40 | If a supported board isn't detected during compilation then a generic fallback approach will be used. This does not utilise any interrupts and so does not ensure accurate timekeeping. This can be useful to port your projects to boards that do not have support in uClock yet, or to test if suspected bugs in your code are related to interactions with interrupts or task handling. 41 | 42 | You can force this non-interrupt "software timer mode" even on supported boards by defining the build flag `USE_UCLOCK_SOFTWARE_TIMER`. 43 | 44 | In order for software timer mode to work, you need to add a call to your `loop()` function to process ticks. For example, 45 | 46 | ```c++ 47 | void loop() { 48 | uClock.run(); 49 | 50 | // do anything else you need to do inside loop()... 51 | // you can intercalate your main processing with other uClock.run() calls to avoid timming accuracy loss. 52 | //uClock.run(); 53 | // do anything other inside loop()... 54 | //uClock.run(); 55 | // the faster you can call uClock.run() without blocking the better and accurate timming you can achieve. 56 | } 57 | ``` 58 | 59 | ## uClock v2.0 Breaking Changes 60 | 61 | 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: 62 | 63 | ### setCallback function name changes 64 | 65 | - `setClock96PPQNOutput(onClock96PPQNOutputCallback)` is now renamed to **`setOnOutputPPQN(onOutputPPQNCallback)`**, and its tick count is based on the PPQN setup using **`setOutputPPQN(clockOutputPPQNResolution)`**. For clock ticks, you now use a separated callback via **`setOnSyncXX(onSyncXXCallback)`**, where XX represents one of the available PPQN values 66 | - `setClock16PPQNOutput(ClockOut16PPQN)` is now renamed to **`setOnStep(onStepCall)`**, and it's not dependent on clock PPQN resolution. 67 | - `setOnClockStartOutput(onClockStartCallback)` is now renamed to **`setOnClockStart(onClockStartCallback)`**. 68 | - `setOnClockStopOutput(onClockStopCallback)` is now renamed to **`setOnClockStop(onClockStopCallback)`**. 69 | 70 | ### Tick resolution and sequencers 71 | 72 | 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. 73 | 74 | # Examples 75 | 76 | You will find more complete examples on examples/ folder: 77 | 78 | ```c++ 79 | #include 80 | 81 | // external or internal sync? 82 | bool _external_sync_on = false; 83 | 84 | // the main uClock PPQN resolution ticking 85 | void onPPQNCallback(uint32_t tick) { 86 | // tick your sequencers or tickable devices... 87 | } 88 | 89 | void onStepCallback(uint32_t step) { 90 | // triger step data for sequencer device... 91 | } 92 | 93 | // The callback function called by uClock each Pulse of 24PPQN clock resolution. 94 | void onSync24Callback(uint32_t tick) { 95 | // send sync signal to... 96 | } 97 | 98 | // The callback function called when clock starts by using uClock.start() method. 99 | void onClockStartCallback() { 100 | // send start signal to... 101 | } 102 | 103 | // The callback function called when clock stops by using uClock.stop() method. 104 | void onClockStopCallback() { 105 | // send stop signal to... 106 | } 107 | 108 | void setup() { 109 | 110 | // inits the clock library 111 | uClock.init(); 112 | 113 | // avaliable resolutions 114 | // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] 115 | // not mandatory to call, the default is 96PPQN if not set 116 | uClock.setPPQN(uClock.PPQN_96); 117 | 118 | // you need to use at least one! 119 | uClock.setOnOutputPPQN(onPPQNCallback); 120 | uClock.setOnStep(onStepCallback); 121 | uClock.setOnSync24(onSync24Callback); 122 | 123 | uClock.setOnClockStart(onClockStartCallback); 124 | uClock.setOnClockStop(onClockStopCallback); 125 | 126 | // set external sync mode? 127 | if (_external_sync_on) { 128 | uClock.setMode(uClock.EXTERNAL_CLOCK); 129 | } 130 | 131 | // starts clock 132 | uClock.start(); 133 | } 134 | 135 | void loop() { 136 | // do we need to external sync? 137 | if (_external_sync_on) { 138 | // watch for external sync signal income 139 | bool signal_income = true; // your external input signal check will be this condition result 140 | if (signal_income) { 141 | // at each clockMe call uClock will process and handle external/internal syncronization 142 | uClock.clockMe(); 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | ## MIDI Examples 149 | 150 | 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. 151 | 152 | 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](http://projectgus.github.io/hairless-midiserial/). 153 | 154 | ### A Simple MIDI Sync Box sketch example 155 | 156 | Here is an example on how to create a simple MIDI Sync Box on Arduino boards 157 | 158 | ```c++ 159 | #include 160 | 161 | // MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards. 162 | #define MIDI_CLOCK 0xF8 163 | #define MIDI_START 0xFA 164 | #define MIDI_STOP 0xFC 165 | 166 | // The callback function called by Clock each Pulse of 24PPQN clock resolution. 167 | void onSync24Callback(uint32_t tick) { 168 | // Send MIDI_CLOCK to external gears 169 | Serial.write(MIDI_CLOCK); 170 | } 171 | 172 | // The callback function called when clock starts by using Clock.start() method. 173 | void onClockStart() { 174 | Serial.write(MIDI_START); 175 | } 176 | 177 | // The callback function called when clock stops by using Clock.stop() method. 178 | void onClockStop() { 179 | Serial.write(MIDI_STOP); 180 | } 181 | 182 | void setup() { 183 | 184 | // Initialize serial communication at 31250 bits per second, the default MIDI serial speed communication: 185 | Serial.begin(31250); 186 | 187 | // Inits the clock 188 | uClock.init(); 189 | // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN 190 | uClock.setOnSync24(onSync24Callback); 191 | // Set the callback function for MIDI Start and Stop messages. 192 | uClock.setOnClockStartOutput(onClockStart); 193 | uClock.setOnClockStopOutput(onClockStop); 194 | // Set the clock BPM to 126 BPM 195 | uClock.setTempo(126); 196 | 197 | // Starts the clock, tick-tac-tick-tac... 198 | uClock.start(); 199 | 200 | } 201 | 202 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 203 | void loop() { 204 | 205 | } 206 | ``` 207 | 208 | 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. 209 | 210 | ```c++ 211 | #include 212 | 213 | // The callback function called by Clock each Pulse of 96PPQN clock resolution. 214 | void onSync24Callback(uint32_t tick) { 215 | // Send MIDI_CLOCK to external gears 216 | usbMIDI.sendRealTime(usbMIDI.Clock); 217 | } 218 | 219 | // The callback function called when clock starts by using Clock.start() method. 220 | void onClockStart() { 221 | usbMIDI.sendRealTime(usbMIDI.Start); 222 | } 223 | 224 | // The callback function called when clock stops by using Clock.stop() method. 225 | void onClockStop() { 226 | usbMIDI.sendRealTime(usbMIDI.Stop); 227 | } 228 | 229 | void setup() { 230 | // Inits the clock 231 | uClock.init(); 232 | // Set the callback function for the clock output to send MIDI Sync message. based on 24PPQN 233 | uClock.setOnSync24(onSync24Callback); 234 | // Set the callback function for MIDI Start and Stop messages. 235 | uClock.setOnClockStartOutput(onClockStart); 236 | uClock.setOnClockStopOutput(onClockStop); 237 | // Set the clock BPM to 126 BPM 238 | uClock.setTempo(126); 239 | // Starts the clock, tick-tac-tick-tac... 240 | uClock.start(); 241 | } 242 | 243 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 244 | void loop() { 245 | 246 | } 247 | ``` 248 | 249 | ### Acid Step Sequencer 250 | 251 | 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. 252 | 253 | ```c++ 254 | // Roland TB303 Step Sequencer engine clone. 255 | // No interface here, just the engine as example. 256 | // author: midilab contact@midilab.co 257 | // Under MIT license 258 | #include "Arduino.h" 259 | #include 260 | 261 | // Sequencer config 262 | #define STEP_MAX_SIZE 16 263 | #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 264 | #define NOTE_VELOCITY 90 265 | #define ACCENT_VELOCITY 127 266 | 267 | // MIDI config 268 | #define MIDI_CHANNEL 0 // 0 = channel 1 269 | 270 | // do not edit below! 271 | #define NOTE_STACK_SIZE 3 272 | 273 | // MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. 274 | #define MIDI_CLOCK 0xF8 275 | #define MIDI_START 0xFA 276 | #define MIDI_STOP 0xFC 277 | #define NOTE_ON 0x90 278 | #define NOTE_OFF 0x80 279 | 280 | // Sequencer data 281 | typedef struct 282 | { 283 | uint8_t note; 284 | bool accent; 285 | bool glide; 286 | bool rest; 287 | } SEQUENCER_STEP_DATA; 288 | 289 | typedef struct 290 | { 291 | uint8_t note; 292 | int8_t length; 293 | } STACK_NOTE_DATA; 294 | 295 | // main sequencer data 296 | SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE]; 297 | STACK_NOTE_DATA _note_stack[NOTE_STACK_SIZE]; 298 | uint16_t _step_length = STEP_MAX_SIZE; 299 | 300 | // make sure all above sequencer data are modified atomicly only 301 | // eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7); 302 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 303 | 304 | // shared data to be used for user interface feedback 305 | bool _playing = false; 306 | uint16_t _step = 0; 307 | 308 | void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) 309 | { 310 | // send midi message 311 | command = command | (uint8_t)MIDI_CHANNEL; 312 | Serial.write(command); 313 | Serial.write(byte1); 314 | Serial.write(byte2); 315 | } 316 | 317 | // The callback function called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. 318 | void onStepCallback(uint32_t tick) 319 | { 320 | uint16_t step; 321 | uint16_t length = NOTE_LENGTH; 322 | 323 | // get actual step. 324 | _step = tick % _step_length; 325 | 326 | // send note on only if this step are not in rest mode 327 | if ( _sequencer[_step].rest == false ) { 328 | 329 | // check for glide event ahead of _step 330 | step = _step; 331 | for ( uint16_t i = 1; i < _step_length; i++ ) { 332 | ++step; 333 | step = step % _step_length; 334 | if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) { 335 | length = NOTE_LENGTH + (i * 24); 336 | break; 337 | } else if ( _sequencer[step].rest == false ) { 338 | break; 339 | } 340 | } 341 | 342 | // find a free note stack to fit in 343 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 344 | if ( _note_stack[i].length == -1 ) { 345 | _note_stack[i].note = _sequencer[_step].note; 346 | _note_stack[i].length = length; 347 | // send note on 348 | sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); 349 | return; 350 | } 351 | } 352 | } 353 | } 354 | 355 | // The callback function called by uClock each Pulse of 96PPQN clock resolution. 356 | void onPPQNCallback(uint32_t tick) 357 | { 358 | // Send MIDI_CLOCK to external hardware 359 | Serial.write(MIDI_CLOCK); 360 | 361 | // handle note on stack 362 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 363 | if ( _note_stack[i].length != -1 ) { 364 | --_note_stack[i].length; 365 | if ( _note_stack[i].length == 0 ) { 366 | sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0); 367 | _note_stack[i].length = -1; 368 | } 369 | } 370 | } 371 | } 372 | 373 | // The callback function called when clock starts by using Clock.start() method. 374 | void onClockStart() 375 | { 376 | Serial.write(MIDI_START); 377 | _playing = true; 378 | } 379 | 380 | // The callback function called when clock stops by using Clock.stop() method. 381 | void onClockStop() 382 | { 383 | Serial.write(MIDI_STOP); 384 | // send all note off on sequencer stop 385 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 386 | sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0); 387 | _note_stack[i].length = -1; 388 | } 389 | _playing = false; 390 | } 391 | 392 | void setup() 393 | { 394 | // Initialize serial communication 395 | // the default MIDI serial speed communication at 31250 bits per second 396 | Serial.begin(31250); 397 | 398 | // Inits the clock 399 | uClock.init(); 400 | 401 | // Set the callback function for the clock output to send MIDI Sync message. 402 | uClock.setOnOutputPPQN(onPPQNCallback); 403 | 404 | // Set the callback function for the step sequencer on 16ppqn 405 | uClock.setOnStep(onStepCallback); 406 | 407 | // Set the callback function for MIDI Start and Stop messages. 408 | uClock.setOnClockStart(onClockStart); 409 | uClock.setOnClockStop(onClockStop); 410 | 411 | // Set the clock BPM to 126 BPM 412 | uClock.setTempo(126); 413 | 414 | // initing sequencer data 415 | for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { 416 | _sequencer[i].note = 48; 417 | _sequencer[i].accent = false; 418 | _sequencer[i].glide = false; 419 | _sequencer[i].rest = false; 420 | } 421 | 422 | // initing note stack data 423 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 424 | _note_stack[i].note = 0; 425 | _note_stack[i].length = -1; 426 | } 427 | 428 | // pins, buttons, leds and pots config 429 | //configureYourUserInterface(); 430 | 431 | // start sequencer 432 | uClock.start(); 433 | } 434 | 435 | // User interaction goes here 436 | void loop() 437 | { 438 | // Controls your 303 engine interacting with user here... 439 | // you can change data by using _sequencer[] and _step_length only! do not mess with _note_stack[]! 440 | // 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(); 441 | // eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7); 442 | //processYourButtons(); 443 | //processYourLeds(); 444 | //processYourPots(); 445 | } 446 | ``` 447 | -------------------------------------------------------------------------------- /examples/AVRUartSlaveMidiClockMonitor/AVRUartSlaveMidiClockMonitor.ino: -------------------------------------------------------------------------------- 1 | /* Uart MIDI Sync Slave Box Monitor 2 | * 3 | * This example demonstrates how to create a 4 | * MIDI slave clock box with 5 | * monitor support using oled display 6 | * 7 | * MIDI in must be provided via an opto-isolator to pin RX/D0 8 | * Tested on an Arduino Uno. 9 | * 10 | * You need the following libraries to make it work 11 | * - Midi Library 12 | * - u8g2 13 | * - uClock 14 | * This example code is in the public domain. 15 | * 16 | * Code by midilab contact@midilab.co 17 | * Example modified by Jackson Devices contact@jacksondevices.com 18 | */ 19 | #include 20 | #include 21 | 22 | // 23 | // BPM Clock support 24 | // 25 | #include 26 | 27 | MIDI_CREATE_DEFAULT_INSTANCE(); 28 | U8X8 * u8x8; 29 | 30 | // MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. 31 | #define MIDI_CLOCK 0xF8 32 | #define MIDI_START 0xFA 33 | #define MIDI_STOP 0xFC 34 | 35 | float bpm = 126.0; 36 | uint8_t bpm_blink_timer = 1; 37 | uint8_t clock_state = 1; 38 | uint8_t clock_mode = 0; 39 | 40 | void handle_bpm_led(uint32_t tick) { 41 | // BPM led indicator 42 | if (!(tick % (96)) || (tick == 1)) { // first compass step will flash longer 43 | bpm_blink_timer = 8; 44 | digitalWrite(LED_BUILTIN, HIGH); 45 | } else if (!(tick % (24))) { // each quarter led on 46 | digitalWrite(LED_BUILTIN, HIGH); 47 | } else if (!(tick % bpm_blink_timer)) { // get led off 48 | digitalWrite(LED_BUILTIN, LOW); 49 | bpm_blink_timer = 1; 50 | } 51 | } 52 | 53 | // Internal clock handlers 54 | void onSync24Callback(uint32_t tick) { 55 | // Send MIDI_CLOCK to external gears 56 | MIDI.sendRealTime(MIDI_CLOCK); 57 | handle_bpm_led(tick); 58 | } 59 | 60 | void onClockStart() { 61 | MIDI.sendRealTime(MIDI_START); 62 | } 63 | 64 | void onClockStop() { 65 | MIDI.sendRealTime(MIDI_STOP); 66 | digitalWrite(LED_BUILTIN, LOW); 67 | } 68 | 69 | // External clock handlers 70 | void onExternalClock() { 71 | uClock.clockMe(); 72 | } 73 | 74 | void onExternalStart() { 75 | uClock.start(); 76 | } 77 | 78 | void onExternalStop() { 79 | uClock.stop(); 80 | digitalWrite(LED_BUILTIN, LOW); 81 | } 82 | 83 | void setup() { 84 | // 85 | // MIDI setup 86 | // 87 | // Initiate MIDI communications, listen to all channels, disable soft MIDI thru 88 | MIDI.begin(); 89 | MIDI.turnThruOff(); 90 | MIDI.setHandleClock(onExternalClock); 91 | MIDI.setHandleStart(onExternalStart); 92 | MIDI.setHandleStop(onExternalStop); 93 | 94 | // An led to display MIDI reception 95 | pinMode(LED_BUILTIN, OUTPUT); 96 | digitalWrite(LED_BUILTIN, LOW); 97 | 98 | // 99 | // OLED setup 100 | // Please check you oled model to correctly init him 101 | // The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp 102 | // 103 | //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 104 | u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 105 | u8x8->begin(); 106 | u8x8->setFont(u8x8_font_pressstart2p_r); 107 | u8x8->clear(); 108 | u8x8->setFlipMode(true); 109 | u8x8->drawUTF8(0, 0, "uClock"); 110 | 111 | // 112 | // uClock Setup 113 | // 114 | uClock.init(); 115 | uClock.setOnSync24(onSync24Callback); 116 | // For MIDI Sync Start and Stop 117 | uClock.setOnClockStart(onClockStart); 118 | uClock.setOnClockStop(onClockStop); 119 | uClock.setMode(uClock.EXTERNAL_CLOCK); 120 | //uClock.setTempo(136.5); 121 | //uClock.start(); 122 | } 123 | 124 | void loop() { 125 | while(MIDI.read()) {} 126 | // DO NOT ADD MORE PROCESS HERE AT THE COST OF LOSING CLOCK SYNC 127 | // Since arduino make use of Serial RX interruption we need to 128 | // read Serial as fast as we can on the loop 129 | if (bpm != uClock.getTempo()) { 130 | bpm = uClock.getTempo(); 131 | u8x8->drawUTF8(8, 7, String(bpm, 1).c_str()); 132 | u8x8->drawUTF8(8 + 5, 7, "BPM"); 133 | // clear display ghost number for 2 digit 134 | // coming from 3 digit bpm changes 135 | if (bpm < 100) { 136 | u8x8->drawUTF8(8 + 4, 7, " "); 137 | } 138 | } 139 | if (clock_state != uClock.state) { 140 | clock_state = uClock.state; 141 | if (clock_state >= 1) { 142 | u8x8->drawUTF8(0, 7, "Playing"); 143 | } else { 144 | u8x8->drawUTF8(0, 7, "Stopped "); 145 | } 146 | } 147 | if (clock_mode != uClock.getMode()) { 148 | clock_mode = uClock.getMode(); 149 | if (clock_mode == uClock.EXTERNAL_CLOCK) { 150 | u8x8->drawUTF8(10, 0, "Slave "); 151 | } else { 152 | u8x8->drawUTF8(10, 0, "Master"); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /examples/AcidStepSequencer/AcidStepSequencer.ino: -------------------------------------------------------------------------------- 1 | // Acid StepSequencer, a Roland TB303 step sequencer engine clone 2 | // author: midilab contact@midilab.co 3 | // under MIT license 4 | #include "Arduino.h" 5 | #include 6 | 7 | // Sequencer config 8 | #define STEP_MAX_SIZE 16 9 | #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 10 | #define NOTE_VELOCITY 90 11 | #define ACCENT_VELOCITY 127 12 | 13 | // MIDI modes 14 | #define MIDI_CHANNEL 0 // 0 = channel 1 15 | #define MIDI_MODE 16 | //#define SERIAL_MODE 17 | 18 | // do not edit from here! 19 | #define NOTE_STACK_SIZE 3 20 | 21 | // MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. 22 | #define MIDI_CLOCK 0xF8 23 | #define MIDI_START 0xFA 24 | #define MIDI_STOP 0xFC 25 | #define NOTE_ON 0x90 26 | #define NOTE_OFF 0x80 27 | 28 | // Sequencer data 29 | typedef struct 30 | { 31 | uint8_t note; 32 | bool accent; 33 | bool glide; 34 | bool rest; 35 | } SEQUENCER_STEP_DATA; 36 | 37 | typedef struct 38 | { 39 | uint8_t note; 40 | int8_t length; 41 | } STACK_NOTE_DATA; 42 | 43 | // main sequencer data 44 | SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE]; 45 | STACK_NOTE_DATA _note_stack[NOTE_STACK_SIZE]; 46 | uint16_t _step_length = STEP_MAX_SIZE; 47 | 48 | // make sure all above sequencer data are modified atomicly only 49 | // eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7); 50 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 51 | 52 | // shared data to be used for user interface feedback 53 | bool _playing = false; 54 | uint16_t _step = 0; 55 | 56 | void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) 57 | { 58 | // send midi message 59 | command = command | (uint8_t)MIDI_CHANNEL; 60 | Serial.write(command); 61 | Serial.write(byte1); 62 | Serial.write(byte2); 63 | } 64 | 65 | // Each call represents exactly one step. 66 | void onStepCallback(uint32_t tick) 67 | { 68 | uint16_t step; 69 | uint16_t length = NOTE_LENGTH; 70 | 71 | // get actual step. 72 | _step = tick % _step_length; 73 | 74 | // send note on only if this step are not in rest mode 75 | if ( _sequencer[_step].rest == false ) { 76 | 77 | // check for glide event ahead of _step 78 | step = _step; 79 | for ( uint16_t i = 1; i < _step_length; i++ ) { 80 | ++step; 81 | step = step % _step_length; 82 | if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) { 83 | length = NOTE_LENGTH + (i * 24); 84 | break; 85 | } else if ( _sequencer[step].rest == false ) { 86 | break; 87 | } 88 | } 89 | 90 | // find a free note stack to fit in 91 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 92 | if ( _note_stack[i].length == -1 ) { 93 | _note_stack[i].note = _sequencer[_step].note; 94 | _note_stack[i].length = length; 95 | // send note on 96 | sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); 97 | return; 98 | } 99 | } 100 | } 101 | } 102 | 103 | // The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. 104 | void onOutputPPQNCallback(uint32_t tick) 105 | { 106 | // handle note on stack 107 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 108 | if ( _note_stack[i].length != -1 ) { 109 | --_note_stack[i].length; 110 | if ( _note_stack[i].length == 0 ) { 111 | sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0); 112 | _note_stack[i].length = -1; 113 | } 114 | } 115 | } 116 | 117 | // user feedback about sequence time events 118 | tempoInterface(tick); 119 | } 120 | 121 | void onSync24Callback(uint32_t tick) { 122 | // Send MIDI_CLOCK to external gears 123 | Serial.write(MIDI_CLOCK); 124 | } 125 | 126 | // The callback function wich will be called when clock starts by using Clock.start() method. 127 | void onClockStart() 128 | { 129 | Serial.write(MIDI_START); 130 | _playing = true; 131 | } 132 | 133 | // The callback function wich will be called when clock stops by using Clock.stop() method. 134 | void onClockStop() 135 | { 136 | Serial.write(MIDI_STOP); 137 | // send all note off on sequencer stop 138 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 139 | sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0); 140 | _note_stack[i].length = -1; 141 | } 142 | _playing = false; 143 | } 144 | 145 | void setup() 146 | { 147 | // Initialize serial communication 148 | #ifdef MIDI_MODE 149 | // the default MIDI serial speed communication at 31250 bits per second 150 | Serial.begin(31250); 151 | #endif 152 | #ifdef SERIAL_MODE 153 | // for usage with a PC with a serial to MIDI bridge 154 | Serial.begin(115200); 155 | #endif 156 | 157 | // Inits the clock 158 | uClock.init(); 159 | 160 | // Set the callback function for the clock output to send MIDI Sync message. 161 | uClock.setOnOutputPPQN(onOutputPPQNCallback); 162 | 163 | // for MIDI sync 164 | uClock.setOnSync24(onSync24Callback); 165 | 166 | // Set the callback function for the step sequencer on 16ppqn 167 | uClock.setOnStep(onStepCallback); 168 | 169 | // Set the callback function for MIDI Start and Stop messages. 170 | uClock.setOnClockStart(onClockStart); 171 | uClock.setOnClockStop(onClockStop); 172 | 173 | // Set the clock BPM to 126 BPM 174 | uClock.setTempo(126); 175 | 176 | // initing sequencer data 177 | for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { 178 | _sequencer[i].note = 48; 179 | _sequencer[i].accent = false; 180 | _sequencer[i].glide = false; 181 | _sequencer[i].rest = false; 182 | } 183 | 184 | // initing note stack data 185 | for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) { 186 | _note_stack[i].note = 0; 187 | _note_stack[i].length = -1; 188 | } 189 | 190 | // pins, buttons, leds and pots config 191 | configureInterface(); 192 | } 193 | 194 | // User interaction goes here 195 | void loop() 196 | { 197 | processInterface(); 198 | } 199 | -------------------------------------------------------------------------------- /examples/AcidStepSequencer/DefaultUserInterface.ino: -------------------------------------------------------------------------------- 1 | 2 | #define SEQUENCER_MIN_BPM 50 3 | #define SEQUENCER_MAX_BPM 177 4 | 5 | // Ui config 6 | #define LOCK_POT_SENSTIVITY 3 7 | 8 | // hardware setup to fit different kinda of setups and arduino models 9 | #define OCTAVE_POT_PIN A3 10 | #define NOTE_POT_PIN A2 11 | #define STEP_LENGTH_POT_PIN A1 12 | #define TEMPO_POT_PIN A0 13 | 14 | #define PREVIOUS_STEP_BUTTON_PIN 2 15 | #define NEXT_STEP_BUTTON_PIN 3 16 | #define REST_BUTTON_PIN 4 17 | #define GLIDE_BUTTON_PIN 5 18 | #define ACCENT_BUTTON_PIN 6 19 | #define PLAY_STOP_BUTTON_PIN 7 20 | 21 | #define PREVIOUS_STEP_LED_PIN 8 22 | #define NEXT_STEP_LED_PIN 9 23 | #define REST_LED_PIN 10 24 | #define GLIDE_LED_PIN 11 25 | #define ACCENT_LED_PIN 12 26 | #define PLAY_STOP_LED_PIN 13 27 | 28 | // User Interface data 29 | uint16_t _step_edit = 0; 30 | uint8_t _last_octave = 3; 31 | uint8_t _last_note = 0; 32 | 33 | uint8_t _bpm_blink_timer = 1; 34 | 35 | void configureInterface() 36 | { 37 | // Buttons config 38 | // use internal pullup for buttons 39 | pinMode(PREVIOUS_STEP_BUTTON_PIN, INPUT_PULLUP); 40 | pinMode(NEXT_STEP_BUTTON_PIN, INPUT_PULLUP); 41 | pinMode(REST_BUTTON_PIN, INPUT_PULLUP); 42 | pinMode(GLIDE_BUTTON_PIN, INPUT_PULLUP); 43 | pinMode(ACCENT_BUTTON_PIN, INPUT_PULLUP); 44 | pinMode(PLAY_STOP_BUTTON_PIN, INPUT_PULLUP); 45 | 46 | // Leds config 47 | pinMode(PREVIOUS_STEP_LED_PIN, OUTPUT); 48 | pinMode(NEXT_STEP_LED_PIN, OUTPUT); 49 | pinMode(REST_LED_PIN, OUTPUT); 50 | pinMode(GLIDE_LED_PIN, OUTPUT); 51 | pinMode(ACCENT_LED_PIN, OUTPUT); 52 | pinMode(PLAY_STOP_LED_PIN, OUTPUT); 53 | 54 | digitalWrite(PREVIOUS_STEP_LED_PIN, LOW); 55 | digitalWrite(NEXT_STEP_LED_PIN, LOW); 56 | digitalWrite(REST_LED_PIN, LOW); 57 | digitalWrite(GLIDE_LED_PIN, LOW); 58 | digitalWrite(ACCENT_LED_PIN, LOW); 59 | digitalWrite(PLAY_STOP_LED_PIN, LOW); 60 | 61 | // getting first value state 62 | pressed(PREVIOUS_STEP_BUTTON_PIN); 63 | pressed(NEXT_STEP_BUTTON_PIN); 64 | pressed(REST_BUTTON_PIN); 65 | pressed(GLIDE_BUTTON_PIN); 66 | pressed(ACCENT_BUTTON_PIN); 67 | pressed(PLAY_STOP_BUTTON_PIN); 68 | 69 | // getting first values 70 | getPotChanges(OCTAVE_POT_PIN, 0, 10); 71 | getPotChanges(NOTE_POT_PIN, 0, 11); 72 | getPotChanges(STEP_LENGTH_POT_PIN, 1, STEP_MAX_SIZE); 73 | getPotChanges(TEMPO_POT_PIN, SEQUENCER_MIN_BPM, SEQUENCER_MAX_BPM); 74 | 75 | lockPotsState(true); 76 | 77 | //acidRandomize(); 78 | } 79 | 80 | void processInterface() 81 | { 82 | processButtons(); 83 | processLeds(); 84 | processPots(); 85 | } 86 | 87 | void tempoInterface(uint32_t tick) 88 | { 89 | // BPM led indicator 90 | if ( !(tick % (96)) || (tick == 0) ) { // first compass step will flash longer 91 | _bpm_blink_timer = 8; 92 | digitalWrite(PLAY_STOP_LED_PIN , HIGH); 93 | } else if ( !(tick % (24)) ) { // each quarter led on 94 | digitalWrite(PLAY_STOP_LED_PIN , HIGH); 95 | } else if ( !(tick % _bpm_blink_timer) ) { // get led off 96 | digitalWrite(PLAY_STOP_LED_PIN , LOW); 97 | _bpm_blink_timer = 1; 98 | } 99 | } 100 | 101 | void sendPreviewNote(uint16_t step) 102 | { 103 | unsigned long milliTime, preMilliTime; 104 | 105 | sendMidiMessage(NOTE_ON, _sequencer[step].note, _sequencer[step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); 106 | 107 | // avoid delay() call because of uClock timmer1 usage 108 | //delay(200); 109 | preMilliTime = millis(); 110 | while ( true ) { 111 | milliTime = millis(); 112 | if (abs(milliTime - preMilliTime) >= 200) { 113 | break; 114 | } 115 | } 116 | 117 | sendMidiMessage(NOTE_OFF, _sequencer[step].note, 0); 118 | } 119 | 120 | void processPots() 121 | { 122 | static int8_t octave, note, step_note; 123 | static int16_t tempo, step_length; 124 | 125 | octave = getPotChanges(OCTAVE_POT_PIN, 0, 10); 126 | if ( octave != -1 ) { 127 | _last_octave = octave; 128 | } 129 | 130 | note = getPotChanges(NOTE_POT_PIN, 0, 11); 131 | if ( note != -1 ) { 132 | _last_note = note; 133 | } 134 | 135 | // changes on octave or note pot? 136 | if ( octave != -1 || note != -1 ) { 137 | ATOMIC(_sequencer[_step_edit].note = (_last_octave * 8) + _last_note); 138 | if ( _playing == false && _sequencer[_step_edit].rest == false ) { 139 | sendPreviewNote(_step_edit); 140 | } 141 | } 142 | 143 | step_length = getPotChanges(STEP_LENGTH_POT_PIN, 1, STEP_MAX_SIZE); 144 | if ( step_length != -1 ) { 145 | ATOMIC(_step_length = step_length); 146 | if ( _step_edit >= _step_length ) { 147 | _step_edit = _step_length-1; 148 | } 149 | } 150 | 151 | tempo = getPotChanges(TEMPO_POT_PIN, SEQUENCER_MIN_BPM, SEQUENCER_MAX_BPM); 152 | if ( tempo != -1 ) { 153 | //uClock.setTempo(tempo); 154 | } 155 | } 156 | 157 | void processButtons() 158 | { 159 | // play/stop 160 | if ( pressed(PLAY_STOP_BUTTON_PIN) ) { 161 | if ( _playing == false ) { 162 | // Starts the clock, tick-tac-tick-tac... 163 | uClock.start(); 164 | } else { 165 | // stop the clock 166 | uClock.stop(); 167 | } 168 | } 169 | 170 | // ramdom test 171 | //if ( pressed(PREVIOUS_STEP_BUTTON_PIN) && pressed(NEXT_STEP_BUTTON_PIN) ) { 172 | //acidRandomize(); 173 | //return; 174 | //} 175 | 176 | // previous step edit 177 | if ( pressed(PREVIOUS_STEP_BUTTON_PIN) ) { 178 | if ( _step_edit != 0 ) { 179 | // add a lock here for octave and note to not mess with edit mode when moving steps around 180 | lockPotsState(true); 181 | --_step_edit; 182 | } else { // TODO: just for tests.. take this guy off here and put it on second page 183 | acidRandomize(); 184 | } 185 | if ( _playing == false && _sequencer[_step_edit].rest == false ) { 186 | sendPreviewNote(_step_edit); 187 | } 188 | } 189 | 190 | // next step edit 191 | if ( pressed(NEXT_STEP_BUTTON_PIN) ) { 192 | if ( _step_edit < _step_length-1 ) { 193 | // add a lock here for octave and note to not mess with edit mode when moving steps around 194 | lockPotsState(true); 195 | ++_step_edit; 196 | } 197 | if ( _playing == false && _sequencer[_step_edit].rest == false ) { 198 | sendPreviewNote(_step_edit); 199 | } 200 | } 201 | 202 | // step rest 203 | if ( pressed(REST_BUTTON_PIN) ) { 204 | ATOMIC(_sequencer[_step_edit].rest = !_sequencer[_step_edit].rest); 205 | if ( _playing == false && _sequencer[_step_edit].rest == false ) { 206 | sendPreviewNote(_step_edit); 207 | } 208 | } 209 | 210 | // step glide 211 | if ( pressed(GLIDE_BUTTON_PIN) ) { 212 | ATOMIC(_sequencer[_step_edit].glide = !_sequencer[_step_edit].glide); 213 | } 214 | 215 | // step accent 216 | if ( pressed(ACCENT_BUTTON_PIN) ) { 217 | ATOMIC(_sequencer[_step_edit].accent = !_sequencer[_step_edit].accent); 218 | if ( _playing == false && _sequencer[_step_edit].rest == false ) { 219 | sendPreviewNote(_step_edit); 220 | } 221 | } 222 | } 223 | 224 | void processLeds() 225 | { 226 | // Editing First Step? 227 | if ( _step_edit == 0 ) { 228 | digitalWrite(PREVIOUS_STEP_LED_PIN , HIGH); 229 | } else { 230 | digitalWrite(PREVIOUS_STEP_LED_PIN , LOW); 231 | } 232 | 233 | // Editing Last Step? 234 | if ( _step_edit == _step_length-1 ) { 235 | digitalWrite(NEXT_STEP_LED_PIN , HIGH); 236 | } else { 237 | digitalWrite(NEXT_STEP_LED_PIN , LOW); 238 | } 239 | 240 | // Rest 241 | if ( _sequencer[_step_edit].rest == true ) { 242 | digitalWrite(REST_LED_PIN , HIGH); 243 | } else { 244 | digitalWrite(REST_LED_PIN , LOW); 245 | } 246 | 247 | // Glide 248 | if ( _sequencer[_step_edit].glide == true ) { 249 | digitalWrite(GLIDE_LED_PIN , HIGH); 250 | } else { 251 | digitalWrite(GLIDE_LED_PIN , LOW); 252 | } 253 | 254 | // Accent 255 | if ( _sequencer[_step_edit].accent == true ) { 256 | digitalWrite(ACCENT_LED_PIN , HIGH); 257 | } else { 258 | digitalWrite(ACCENT_LED_PIN , LOW); 259 | } 260 | 261 | // shut down play led if we are stoped 262 | if ( _playing == false ) { 263 | digitalWrite(PLAY_STOP_LED_PIN , LOW); 264 | } 265 | } 266 | 267 | void acidRandomize() 268 | { 269 | // ramdom it all 270 | for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { 271 | ATOMIC(_sequencer[i].note = random(36, 70)); // octave 2 to 4. octave 3 to 5 (40 - 83) 272 | ATOMIC(_sequencer[i].accent = random(0, 2)); 273 | ATOMIC(_sequencer[i].glide = random(0, 2)); 274 | ATOMIC(_sequencer[i].rest = random(0, 1)); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /examples/AcidStepSequencer/HardwareInterface.ino: -------------------------------------------------------------------------------- 1 | 2 | #define POT_NUMBER 4 3 | #define BUTTON_NUMBER 6 4 | 5 | // pot data 6 | typedef struct 7 | { 8 | uint8_t pin; 9 | uint16_t state; 10 | bool lock; 11 | } POT_DATA; 12 | 13 | // button data 14 | typedef struct 15 | { 16 | uint8_t pin; 17 | bool state; 18 | } BUTTON_DATA; 19 | 20 | POT_DATA _pot[POT_NUMBER]; 21 | BUTTON_DATA _button[BUTTON_NUMBER]; 22 | 23 | void lockPotsState(bool lock) 24 | { 25 | for ( uint8_t i = 0; i < POT_NUMBER; i++ ) { 26 | _pot[i].lock = lock; 27 | } 28 | } 29 | 30 | bool pressed(uint8_t button_pin) 31 | { 32 | bool value; 33 | bool * last_value; 34 | 35 | switch(button_pin) { 36 | case PREVIOUS_STEP_BUTTON_PIN: 37 | last_value = &_button[0].state; 38 | break; 39 | case NEXT_STEP_BUTTON_PIN: 40 | last_value = &_button[1].state; 41 | break; 42 | case REST_BUTTON_PIN: 43 | last_value = &_button[2].state; 44 | break; 45 | case GLIDE_BUTTON_PIN: 46 | last_value = &_button[3].state; 47 | break; 48 | case ACCENT_BUTTON_PIN: 49 | last_value = &_button[4].state; 50 | break; 51 | case PLAY_STOP_BUTTON_PIN: 52 | last_value = &_button[5].state; 53 | break; 54 | default: 55 | return false; 56 | } 57 | 58 | value = digitalRead(button_pin); 59 | 60 | // check, using pullup pressed button goes LOW 61 | if ( value != *last_value && value == LOW ) { 62 | *last_value = value; 63 | return true; 64 | } else { 65 | *last_value = value; 66 | return false; 67 | } 68 | 69 | } 70 | 71 | int16_t getPotChanges(uint8_t pot_pin, uint16_t min_value, uint16_t max_value) 72 | { 73 | uint16_t value, value_ranged, last_value_ranged; 74 | uint16_t * last_value; 75 | bool * lock_pot; 76 | uint8_t pot_sensitivity = 1; 77 | 78 | switch(pot_pin) { 79 | case OCTAVE_POT_PIN: 80 | last_value = &_pot[0].state; 81 | lock_pot = &_pot[0].lock; 82 | break; 83 | case NOTE_POT_PIN: 84 | last_value = &_pot[1].state; 85 | lock_pot = &_pot[1].lock; 86 | break; 87 | case STEP_LENGTH_POT_PIN: 88 | last_value = &_pot[2].state; 89 | lock_pot = &_pot[2].lock; 90 | break; 91 | case TEMPO_POT_PIN: 92 | last_value = &_pot[3].state; 93 | lock_pot = &_pot[3].lock; 94 | break; 95 | default: 96 | return -1; 97 | } 98 | 99 | // get absolute value 100 | value = analogRead(pot_pin); 101 | 102 | // range that value and our last_value 103 | value_ranged = (value / (1024 / ((max_value - min_value) + 1))) + min_value; 104 | last_value_ranged = (*last_value / (1024 / ((max_value - min_value) + 1))) + min_value; 105 | 106 | // a lock system to not mess with some data(pots are terrible for some kinda of user interface data controls, but lets keep it low cost!) 107 | if ( *lock_pot == true ) { 108 | pot_sensitivity = LOCK_POT_SENSTIVITY; 109 | } 110 | 111 | if ( abs(value_ranged - last_value_ranged) >= pot_sensitivity ) { 112 | *last_value = value; 113 | if ( *lock_pot == true ) { 114 | *lock_pot = false; 115 | } 116 | return value_ranged; 117 | } else { 118 | return -1; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /examples/AcidStepSequencer/README.md: -------------------------------------------------------------------------------- 1 | # TB303 Step Sequencer engine clone 2 | 3 | With some user interface changes for cheap construction of a functional TB303 engine we present you the interface: 4 | 5 | ![Image of uMODULAR ctrl16 pcb top view](https://raw.githubusercontent.com/midilab/uClock/development/examples/AcidStepSequencer/acid_step_sequencer-protoboard-v001.png) 6 | 7 | ## Interface from left to rigth 8 | 9 | POT1: Octave 10 | POT2: Note 11 | POT3: Sequence Length 12 | POT4: Sequencer BPM Tempo 13 | 14 | Button1: Prev Step 15 | Button2: Next Step 16 | Button3: Rest 17 | Button4: Glide 18 | Button5: Accent 19 | Buttons6: Play/Stop -------------------------------------------------------------------------------- /examples/AcidStepSequencer/acid_step_sequencer-protoboard-v001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midilab/uClock/35074971a32d958c650088f5df21670236f89cb2/examples/AcidStepSequencer/acid_step_sequencer-protoboard-v001.png -------------------------------------------------------------------------------- /examples/ESP32UartMasterMidiClock/ESP32UartMasterMidiClock.ino: -------------------------------------------------------------------------------- 1 | /* Uart MIDI Sync Box 2 | * 3 | * This example demonstrates how to change the Uart MIDI 4 | * device name on ESP32 family. 5 | * 6 | * This example code is in the public domain. 7 | * 8 | * ... 9 | * 10 | */ 11 | #include 12 | 13 | // MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards. 14 | #define MIDI_CLOCK 0xF8 15 | #define MIDI_START 0xFA 16 | #define MIDI_STOP 0xFC 17 | 18 | // the blue led 19 | #define LED_BUILTIN 2 20 | 21 | uint8_t bpm_blink_timer = 1; 22 | void handle_bpm_led(uint32_t tick) 23 | { 24 | // BPM led indicator 25 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 26 | bpm_blink_timer = 8; 27 | digitalWrite(LED_BUILTIN, HIGH); 28 | } else if ( !(tick % (24)) ) { // each quarter led on 29 | bpm_blink_timer = 1; 30 | digitalWrite(LED_BUILTIN, HIGH); 31 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 32 | digitalWrite(LED_BUILTIN, LOW); 33 | } 34 | } 35 | 36 | // Internal clock handlers 37 | void onSync24Callback(uint32_t tick) { 38 | // Send MIDI_CLOCK to external gears 39 | Serial.write(MIDI_CLOCK); 40 | handle_bpm_led(tick); 41 | } 42 | 43 | void onClockStart() { 44 | Serial.write(MIDI_START); 45 | } 46 | 47 | void onClockStop() { 48 | Serial.write(MIDI_STOP); 49 | } 50 | 51 | void setup() { 52 | // Initialize serial communication at 31250 bits per second, the default MIDI serial speed communication: 53 | Serial.begin(31250); 54 | 55 | // A led to count bpms 56 | pinMode(LED_BUILTIN, OUTPUT); 57 | 58 | // Setup our clock system 59 | // Inits the clock 60 | uClock.init(); 61 | // Set the callback function for the clock output to send MIDI Sync message. 62 | uClock.setOnSync24(onSync24Callback); 63 | // Set the callback function for MIDI Start and Stop messages. 64 | uClock.setOnClockStart(onClockStart); 65 | uClock.setOnClockStop(onClockStop); 66 | // Set the clock BPM to 126 BPM 67 | uClock.setTempo(126); 68 | // Starts the clock, tick-tac-tick-tac... 69 | uClock.start(); 70 | } 71 | 72 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 73 | void loop() { 74 | 75 | } -------------------------------------------------------------------------------- /examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // external or internal sync? 4 | bool _external_sync_on = false; 5 | 6 | // the main uClock PPQN resolution ticking 7 | void onOutputPPQNCallback(uint32_t tick) { 8 | // tick your sequencers or tickable devices... 9 | } 10 | 11 | void onStepCallback(uint32_t step) { 12 | // triger step data for sequencer device... 13 | } 14 | 15 | // The callback function called by uClock each Pulse of 1PPQN clock resolution. 16 | void onSync1Callback(uint32_t tick) { 17 | // send sync signal to... 18 | } 19 | 20 | // The callback function called by uClock each Pulse of 2PPQN clock resolution. 21 | void onSync2Callback(uint32_t tick) { 22 | // send sync signal to... 23 | } 24 | 25 | // The callback function called by uClock each Pulse of 4PPQN clock resolution. 26 | void onSync4Callback(uint32_t tick) { 27 | // send sync signal to... 28 | } 29 | 30 | // The callback function called by uClock each Pulse of 24PPQN clock resolution. 31 | void onSync24Callback(uint32_t tick) { 32 | // send sync signal to... 33 | } 34 | 35 | // The callback function called by uClock each Pulse of 48PPQN clock resolution. 36 | void onSync48Callback(uint32_t tick) { 37 | // send sync signal to... 38 | } 39 | 40 | // The callback function called when clock starts by using uClock.start() method. 41 | void onClockStartCallback() { 42 | // send start signal to... 43 | } 44 | 45 | // The callback function called when clock stops by using uClock.stop() method. 46 | void onClockStopCallback() { 47 | // send stop signal to... 48 | } 49 | 50 | void setup() { 51 | 52 | // inits the clock library 53 | uClock.init(); 54 | 55 | // avaliable output PPQN resolutions for this example 56 | // [ uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] 57 | // not mandatory to call, the default is 96PPQN if not set 58 | uClock.setOutputPPQN(uClock.PPQN_96); 59 | 60 | // you need to use at least one! 61 | uClock.setOnOutputPPQN(onOutputPPQNCallback); 62 | uClock.setOnStep(onStepCallback); 63 | // multi sync output signatures avaliable 64 | // normaly used by eurorack modular modules 65 | uClock.setOnSync1(onSync1Callback); 66 | uClock.setOnSync2(onSync2Callback); 67 | uClock.setOnSync4(onSync4Callback); 68 | // midi sync standard 69 | uClock.setOnSync24(onSync24Callback); 70 | // some korg machines do 48ppqn 71 | uClock.setOnSync48(onSync48Callback); 72 | 73 | uClock.setOnClockStart(onClockStartCallback); 74 | uClock.setOnClockStop(onClockStopCallback); 75 | 76 | // set external sync mode? 77 | if (_external_sync_on) { 78 | uClock.setClockMode(uClock.EXTERNAL_CLOCK); 79 | } 80 | 81 | // starts clock 82 | uClock.start(); 83 | } 84 | 85 | void loop() { 86 | // do we need to external sync? 87 | if (_external_sync_on) { 88 | // watch for external sync signal income 89 | bool signal_income = true; // your external input signal check will be this condition result 90 | if (signal_income) { 91 | // at each clockMe call uClock will process and handle external/internal syncronization 92 | uClock.clockMe(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino: -------------------------------------------------------------------------------- 1 | /* USB MIDI Sync Slave Box Monitor 2 | * 3 | * This example demonstrates how to create a 4 | * MIDI hid compilant slave clock box with 5 | * monitor support using oled displays 6 | * 7 | * You need the following libraries to make it work 8 | * - Midi Library 9 | * - USB-MIDI and MIDIUSB 10 | * - u8g2 11 | * - uClock 12 | * This example code is in the public domain. 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | // 19 | // BPM Clock support 20 | // 21 | #include 22 | 23 | USBMIDI_CREATE_DEFAULT_INSTANCE(); 24 | U8X8 * u8x8; 25 | 26 | // MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. 27 | #define MIDI_CLOCK 0xF8 28 | #define MIDI_START 0xFA 29 | #define MIDI_STOP 0xFC 30 | 31 | float bpm = 126.0; 32 | uint8_t bpm_blink_timer = 1; 33 | uint8_t clock_state = 1; 34 | uint8_t clock_mode = 0; 35 | 36 | void handle_bpm_led(uint32_t tick) 37 | { 38 | // BPM led indicator 39 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 40 | bpm_blink_timer = 8; 41 | TXLED1; 42 | } else if ( !(tick % (24)) ) { // each quarter led on 43 | TXLED1; 44 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 45 | TXLED0; 46 | bpm_blink_timer = 1; 47 | } 48 | } 49 | 50 | // Internal clock handlers 51 | void onSync24Callback(uint32_t tick) { 52 | // Send MIDI_CLOCK to external gears 53 | MIDI.sendRealTime(MIDI_CLOCK); 54 | handle_bpm_led(tick); 55 | } 56 | 57 | void onClockStart() { 58 | MIDI.sendRealTime(MIDI_START); 59 | } 60 | 61 | void onClockStop() { 62 | MIDI.sendRealTime(MIDI_STOP); 63 | } 64 | 65 | // External clock handlers 66 | void onExternalClock() 67 | { 68 | uClock.clockMe(); 69 | } 70 | 71 | void onExternalStart() 72 | { 73 | uClock.start(); 74 | } 75 | 76 | void onExternalStop() 77 | { 78 | uClock.stop(); 79 | } 80 | 81 | void setup() { 82 | // 83 | // MIDI setup 84 | // 85 | MIDI.begin(); 86 | MIDI.setHandleClock(onExternalClock); 87 | MIDI.setHandleStart(onExternalStart); 88 | MIDI.setHandleStop(onExternalStop); 89 | 90 | // 91 | // OLED setup 92 | // Please check you oled model to correctly init him 93 | // 94 | //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 95 | u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 96 | u8x8->begin(); 97 | u8x8->setFont(u8x8_font_pressstart2p_r); 98 | u8x8->clear(); 99 | u8x8->setFlipMode(true); 100 | u8x8->drawUTF8(0, 0, "uClock"); 101 | 102 | // 103 | // uClock Setup 104 | // 105 | uClock.init(); 106 | uClock.setOnSync24(onSync24Callback); 107 | // For MIDI Sync Start and Stop 108 | uClock.setOnClockStart(onClockStart); 109 | uClock.setOnClockStop(onClockStop); 110 | uClock.setMode(uClock.EXTERNAL_CLOCK); 111 | //uClock.setTempo(136.5); 112 | //uClock.start(); 113 | } 114 | 115 | void loop() { 116 | while(MIDI.read()) {} 117 | // DO NOT ADD MORE PROCESS HERE AT THE COST OF LOSING CLOCK SYNC 118 | // Since arduino make use of Serial RX interruption we need to 119 | // read Serial as fast as we can on the loop 120 | if (bpm != uClock.getTempo()) { 121 | bpm = uClock.getTempo(); 122 | u8x8->drawUTF8(8, 7, String(bpm, 1).c_str()); 123 | u8x8->drawUTF8(8+5, 7, "BPM"); 124 | // clear display ghost number for 2 digit 125 | // coming from 3 digit bpm changes 126 | if (bpm < 100) { 127 | u8x8->drawUTF8(8+4, 7, " "); 128 | } 129 | } 130 | if (clock_state != uClock.state) { 131 | clock_state = uClock.state; 132 | if (clock_state >= 1) { 133 | u8x8->drawUTF8(0, 7, "Playing"); 134 | } else { 135 | u8x8->drawUTF8(0, 7, "Stopped"); 136 | } 137 | } 138 | if (clock_mode != uClock.getMode()) { 139 | clock_mode = uClock.getMode(); 140 | if (clock_mode == uClock.EXTERNAL_CLOCK) { 141 | u8x8->drawUTF8(10, 0, "Slave "); 142 | } else { 143 | u8x8->drawUTF8(10, 0, "Master"); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /examples/MidiClock/MidiClock.ino: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include 3 | 4 | // MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards. 5 | #define MIDI_CLOCK 0xF8 6 | #define MIDI_START 0xFA 7 | #define MIDI_STOP 0xFC 8 | 9 | // The callback function wich will be called by Clock each Pulse of 24PPQN clock resolution. 10 | void onSync24Callback(uint32_t tick) 11 | { 12 | // Send MIDI_CLOCK to external gears 13 | Serial.write(MIDI_CLOCK); 14 | } 15 | 16 | // The callback function wich will be called when clock starts by using Clock.start() method. 17 | void onClockStart() 18 | { 19 | Serial.write(MIDI_START); 20 | } 21 | 22 | // The callback function wich will be called when clock stops by using Clock.stop() method. 23 | void onClockStop() 24 | { 25 | Serial.write(MIDI_STOP); 26 | } 27 | 28 | void setup() 29 | { 30 | 31 | // Initialize serial communication at 31250 bits per second, the default MIDI serial speed communication: 32 | Serial.begin(31250); 33 | 34 | // Inits the clock 35 | uClock.init(); 36 | // Set the callback function for the clock output to send MIDI Sync message. 37 | uClock.setOnSync24(onSync24Callback); 38 | // Set the callback function for MIDI Start and Stop messages. 39 | uClock.setOnClockStart(onClockStart); 40 | uClock.setOnClockStop(onClockStop); 41 | // Set the clock BPM to 126 BPM 42 | uClock.setTempo(126); 43 | 44 | // Starts the clock, tick-tac-tick-tac... 45 | uClock.start(); 46 | 47 | } 48 | 49 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 50 | void loop() 51 | { 52 | 53 | } 54 | -------------------------------------------------------------------------------- /examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * USB/Uart MIDI Sync Box 3 | * 4 | * This example code is in the public domain. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | // Instantiate the MIDI interfaces 14 | Adafruit_USBD_MIDI usb_midi; 15 | MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); 16 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); 17 | 18 | // Do your rpi 2040 has a ws2812 RGB LED? set the pin! 19 | // otherwise keep it commented for normal LED_BUILTIN led blinking 20 | #define WS2812_BUILTIN_LED 16 21 | 22 | uint8_t bpm_blink_timer = 1; 23 | void handle_bpm_led(uint32_t tick) 24 | { 25 | // BPM led indicator 26 | if ( !(tick % (96)) || (tick == 1) ) { // first of 4 quarter pulse will flash longer 27 | bpm_blink_timer = 8; 28 | ledOn(); 29 | } else if ( !(tick % (24)) ) { // each quarter led on 30 | bpm_blink_timer = 1; 31 | ledOn(); 32 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 33 | ledOff(); 34 | } 35 | } 36 | 37 | // Internal clock handlers 38 | void onSync24Callback(uint32_t tick) { 39 | // Send MIDI_CLOCK to external gears 40 | MIDI.sendRealTime(midi::Clock); 41 | MIDI_USB.sendRealTime(midi::Clock); 42 | // blink tempo 43 | handle_bpm_led(tick); 44 | } 45 | 46 | void onClockStart() { 47 | MIDI.sendRealTime(midi::Start); 48 | MIDI_USB.sendRealTime(midi::Start); 49 | } 50 | 51 | void onClockStop() { 52 | MIDI.sendRealTime(midi::Stop); 53 | MIDI_USB.sendRealTime(midi::Stop); 54 | } 55 | 56 | void setup() { 57 | #if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) 58 | // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 59 | TinyUSB_Device_Init(0); 60 | #endif 61 | 62 | // Initialize USB midi stack 63 | MIDI_USB.begin(MIDI_CHANNEL_OMNI); 64 | // Initialize UART midi stack 65 | MIDI.begin(MIDI_CHANNEL_OMNI); 66 | 67 | // Initialize builtin led for clock timer blinking 68 | initBlinkLed(); 69 | 70 | // Setup our clock system 71 | // Inits the clock 72 | uClock.init(); 73 | // Set the callback function for the clock output to send MIDI Sync message. 74 | uClock.setOnSync24(onSync24Callback); 75 | // Set the callback function for MIDI Start and Stop messages. 76 | uClock.setOnClockStart(onClockStart); 77 | uClock.setOnClockStop(onClockStop); 78 | // Set the clock BPM to 126 BPM 79 | uClock.setTempo(126); 80 | // Starts the clock, tick-tac-tick-tac.. 81 | uClock.start(); 82 | } 83 | 84 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 85 | void loop() { 86 | // handle midi input? 87 | MIDI.read(); 88 | MIDI_USB.read(); 89 | } 90 | -------------------------------------------------------------------------------- /examples/RP2040UsbUartMasterClock/builtin_led.ino: -------------------------------------------------------------------------------- 1 | #if defined(WS2812_BUILTIN_LED) 2 | #include 3 | #define NUMPIXELS 1 4 | Adafruit_NeoPixel pixels(NUMPIXELS, WS2812_BUILTIN_LED, NEO_GRB + NEO_KHZ800); 5 | #endif 6 | 7 | // check the pinage for BUILTIN LED of your model in case LED_BUILTIN wont ligth up 8 | // this is valid only if you're not using rgb version ws2812 (WS2812_BUILTIN_LED) 9 | //#define LED_BUILTIN PIN_LED_B 10 | 11 | void initBlinkLed() { 12 | #if defined(WS2812_BUILTIN_LED) 13 | // use adafruit neo pixel 14 | pixels.begin(); 15 | #else 16 | // normal led pin 17 | pinMode(LED_BUILTIN, OUTPUT); 18 | #endif 19 | } 20 | 21 | void ledOn() { 22 | #if defined(WS2812_BUILTIN_LED) 23 | pixels.setPixelColor(0, pixels.Color(0, 0, 20)); 24 | pixels.show(); // turn the LED on (HIGH is the voltage level) 25 | #else 26 | digitalWrite(LED_BUILTIN, LOW); 27 | #endif 28 | } 29 | 30 | void ledOff() { 31 | #if defined(WS2812_BUILTIN_LED) 32 | pixels.setPixelColor(0, pixels.Color(0, 0, 0)); 33 | pixels.show(); 34 | #else 35 | digitalWrite(LED_BUILTIN, HIGH); 36 | #endif 37 | } -------------------------------------------------------------------------------- /examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino: -------------------------------------------------------------------------------- 1 | /* Uart MIDI out 2 | * 3 | * This example demonstrates how to send MIDI data via Uart 4 | * interface on STM32 family. 5 | * 6 | * This example code is in the public domain. 7 | * 8 | * Requires STM32Duino board manager to be installed. 9 | * 10 | * Define HardwareSerial using any available UART/USART. 11 | * Nucleo boards have UART/USART pins that are used by the ST-LINK interface (unless using solder bridging). 12 | * 13 | * Tested on Nucleo-F401RE and Nucleo-F072RB (PA9=D8 PA10=D2 on the Arduino pins) 14 | * 15 | * Code by midilab contact@midilab.co 16 | * Example modified by Jackson Devices contact@jacksondevices.com 17 | */ 18 | #include 19 | 20 | // MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards. 21 | #define MIDI_CLOCK 0xF8 22 | #define MIDI_START 0xFA 23 | #define MIDI_STOP 0xFC 24 | 25 | HardwareSerial Serial1(PA10, PA9); 26 | 27 | uint8_t bpm_blink_timer = 1; 28 | void handle_bpm_led(uint32_t tick) 29 | { 30 | // BPM led indicator 31 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 32 | bpm_blink_timer = 8; 33 | digitalWrite(LED_BUILTIN, HIGH); 34 | } else if ( !(tick % (24)) ) { // each quarter led on 35 | bpm_blink_timer = 1; 36 | digitalWrite(LED_BUILTIN, HIGH); 37 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 38 | digitalWrite(LED_BUILTIN, LOW); 39 | } 40 | } 41 | 42 | // Internal clock handlers 43 | void onSync24Callback(uint32_t tick) { 44 | // Send MIDI_CLOCK to external gear 45 | Serial1.write(MIDI_CLOCK); 46 | handle_bpm_led(tick); 47 | } 48 | 49 | void onClockStart() { 50 | // Send MIDI_START to external gear 51 | Serial1.write(MIDI_START); 52 | } 53 | 54 | void onClockStop() { 55 | // Send MIDI_STOP to external gear 56 | Serial1.write(MIDI_STOP); 57 | } 58 | 59 | void setup() { 60 | // Initialize Serial1 communication at 31250 bits per second, the default MIDI Serial1 speed communication: 61 | Serial1.begin(31250); 62 | 63 | // An led to display BPM 64 | pinMode(LED_BUILTIN, OUTPUT); 65 | 66 | // Setup our clock system 67 | // Inits the clock 68 | uClock.init(); 69 | // Set the callback function for the clock output to send MIDI Sync message. 70 | uClock.setOnSync24(onSync24Callback); 71 | // Set the callback function for MIDI Start and Stop messages. 72 | uClock.setOnClockStart(onClockStart); 73 | uClock.setOnClockStop(onClockStop); 74 | // Set the clock BPM to 126 BPM 75 | uClock.setTempo(120); 76 | // Starts the clock, tick-tac-tick-tac... 77 | uClock.start(); 78 | } 79 | 80 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 81 | void loop() { 82 | 83 | } 84 | -------------------------------------------------------------------------------- /examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino: -------------------------------------------------------------------------------- 1 | /* USB MIDI Sync Box 2 | * 3 | * This example demonstrates how to change the USB MIDI 4 | * device name on Teensy LC, 3.x and 4.x. When creating more 5 | * that one MIDI device, custom names are much easier to 6 | * use when selecting each device in MIDI software on 7 | * your PC or Mac. The custom name is in the "name.c" tab. 8 | * 9 | * Windows and Macintosh systems often cache USB info. 10 | * After changing the name, you may need to test on a 11 | * different computer to observe the new name, or take 12 | * steps to get your operating system to "forget" the 13 | * cached info. (TODO: wanted... can anyone contribute 14 | * instructions for these systems) 15 | * 16 | * You must select MIDI from the "Tools > USB Type" menu 17 | * 18 | * This example code is in the public domain. 19 | */ 20 | 21 | #include 22 | 23 | uint8_t bpm_blink_timer = 1; 24 | void handle_bpm_led(uint32_t tick) 25 | { 26 | // BPM led indicator 27 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 28 | bpm_blink_timer = 8; 29 | digitalWrite(LED_BUILTIN, HIGH); 30 | } else if ( !(tick % (24)) ) { // each quarter led on 31 | digitalWrite(LED_BUILTIN, HIGH); 32 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 33 | digitalWrite(LED_BUILTIN, LOW); 34 | bpm_blink_timer = 1; 35 | } 36 | } 37 | 38 | // Internal clock handlers 39 | void onSync24Callback(uint32_t tick) { 40 | // Send MIDI_CLOCK to external gears 41 | usbMIDI.sendRealTime(usbMIDI.Clock); 42 | handle_bpm_led(tick); 43 | } 44 | 45 | void onClockStart() { 46 | usbMIDI.sendRealTime(usbMIDI.Start); 47 | } 48 | 49 | void onClockStop() { 50 | usbMIDI.sendRealTime(usbMIDI.Stop); 51 | } 52 | 53 | void setup() { 54 | // A led to count bpms 55 | pinMode(LED_BUILTIN, OUTPUT); 56 | 57 | // Setup our clock system 58 | // Inits the clock 59 | uClock.init(); 60 | // Set the callback function for the clock output to send MIDI Sync message. 61 | uClock.setOnSync24(onSync24Callback); 62 | // Set the callback function for MIDI Start and Stop messages. 63 | uClock.setOnClockStart(onClockStart); 64 | uClock.setOnClockStop(onClockStop); 65 | // Set the clock BPM to 126 BPM 66 | uClock.setTempo(126); 67 | // Starts the clock, tick-tac-tick-tac... 68 | uClock.start(); 69 | } 70 | 71 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 72 | void loop() { 73 | 74 | } 75 | -------------------------------------------------------------------------------- /examples/TeensyUsbMasterMidiClock/name.c: -------------------------------------------------------------------------------- 1 | // To give your project a unique name, this code must be 2 | // placed into a .c file (its own tab). It can not be in 3 | // a .cpp file or your main sketch (the .ino file). 4 | 5 | #include "usb_names.h" 6 | 7 | // Edit these lines to create your own name. The length must 8 | // match the number of characters in your custom name. 9 | 10 | #define MIDI_NAME {'u','c','l','o','c','k','_','1'} 11 | #define MIDI_NAME_LEN 8 12 | 13 | // Do not change this part. This exact format is required by USB. 14 | 15 | struct usb_string_descriptor_struct usb_string_product_name = { 16 | 2 + MIDI_NAME_LEN * 2, 17 | 3, 18 | MIDI_NAME 19 | }; 20 | -------------------------------------------------------------------------------- /examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino: -------------------------------------------------------------------------------- 1 | /* USB MIDI Sync Slave Box 2 | * 3 | * This example demonstrates how to change the USB MIDI 4 | * device name on Teensy LC, 3.x and 4.x. When creating more 5 | * that one MIDI device, custom names are much easier to 6 | * use when selecting each device in MIDI software on 7 | * your PC or Mac. The custom name is in the "name.c" tab. 8 | * 9 | * Windows and Macintosh systems often cache USB info. 10 | * After changing the name, you may need to test on a 11 | * different computer to observe the new name, or take 12 | * steps to get your operating system to "forget" the 13 | * cached info. (TODO: wanted... can anyone contribute 14 | * instructions for these systems) 15 | * 16 | * You must select MIDI from the "Tools > USB Type" menu 17 | * 18 | * This example code is in the public domain. 19 | */ 20 | 21 | #include 22 | 23 | uint8_t bpm_blink_timer = 1; 24 | void handle_bpm_led(uint32_t tick) 25 | { 26 | // BPM led indicator 27 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 28 | bpm_blink_timer = 8; 29 | digitalWrite(LED_BUILTIN, HIGH); 30 | } else if ( !(tick % (24)) ) { // each quarter led on 31 | digitalWrite(LED_BUILTIN, HIGH); 32 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 33 | digitalWrite(LED_BUILTIN, LOW); 34 | bpm_blink_timer = 1; 35 | } 36 | } 37 | 38 | // Internal clock handlers 39 | void onSync24Callback(uint32_t tick) { 40 | // Send MIDI_CLOCK to external gears on other port? 41 | //usbMIDI.sendRealTime(usbMIDI.Clock); 42 | handle_bpm_led(tick); 43 | } 44 | 45 | void onClockStart() { 46 | //usbMIDI.sendRealTime(usbMIDI.Start); 47 | } 48 | 49 | void onClockStop() { 50 | //usbMIDI.sendRealTime(usbMIDI.Stop); 51 | } 52 | 53 | // External clock handlers 54 | void onExternalClock() 55 | { 56 | uClock.clockMe(); 57 | } 58 | 59 | void onExternalStart() 60 | { 61 | uClock.start(); 62 | } 63 | 64 | void onExternalStop() 65 | { 66 | uClock.stop(); 67 | } 68 | 69 | void setup() { 70 | // A led to count bpms 71 | pinMode(LED_BUILTIN, OUTPUT); 72 | 73 | // Setup realtime midi event handlers 74 | usbMIDI.setHandleClock(onExternalClock); 75 | usbMIDI.setHandleStart(onExternalStart); 76 | usbMIDI.setHandleStop(onExternalStop); 77 | 78 | // Setup our clock system 79 | // Inits the clock 80 | uClock.init(); 81 | // Set the callback function for the clock output to send MIDI Sync message. 82 | uClock.setOnSync24(onSync24Callback); 83 | // Set the callback function for MIDI Start and Stop messages. 84 | uClock.setOnClockStart(onClockStart); 85 | uClock.setOnClockStop(onClockStop); 86 | // set to external sync mode 87 | uClock.setMode(1); 88 | } 89 | 90 | void loop() { 91 | // Grab all midi data as fast as we can! 92 | while (usbMIDI.read()) {} 93 | } 94 | -------------------------------------------------------------------------------- /examples/TeensyUsbSlaveMidiClock/name.c: -------------------------------------------------------------------------------- 1 | // To give your project a unique name, this code must be 2 | // placed into a .c file (its own tab). It can not be in 3 | // a .cpp file or your main sketch (the .ino file). 4 | 5 | #include "usb_names.h" 6 | 7 | // Edit these lines to create your own name. The length must 8 | // match the number of characters in your custom name. 9 | 10 | #define MIDI_NAME {'u','c','l','o','c','k','_','1'} 11 | #define MIDI_NAME_LEN 8 12 | 13 | // Do not change this part. This exact format is required by USB. 14 | 15 | struct usb_string_descriptor_struct usb_string_product_name = { 16 | 2 + MIDI_NAME_LEN * 2, 17 | 3, 18 | MIDI_NAME 19 | }; 20 | -------------------------------------------------------------------------------- /examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino: -------------------------------------------------------------------------------- 1 | /* USB MIDI Sync Slave Box Monitor 2 | * 3 | * This example demonstrates how to create a 4 | * MIDI hid compilant slave clock box using 5 | * Teensy LC, 3.x and 4.x with 6 | * monitor support using oled displays 7 | * 8 | * Making use of a 250 usceconds timer to 9 | * handle MIDI input to avoid jitter on clock 10 | * 11 | * You need the following libraries to make it work 12 | * - u8g2 13 | * - uClock 14 | * 15 | * This example code is in the public domain. 16 | */ 17 | 18 | #include 19 | 20 | // 21 | // BPM Clock support 22 | // 23 | #include 24 | 25 | U8X8 * u8x8; 26 | IntervalTimer teensyTimer; 27 | 28 | // MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. 29 | #define MIDI_CLOCK 0xF8 30 | #define MIDI_START 0xFA 31 | #define MIDI_STOP 0xFC 32 | 33 | float bpm = 126; 34 | uint8_t bpm_blink_timer = 1; 35 | uint8_t clock_state = 1; 36 | uint8_t clock_mode = 0; 37 | 38 | void handle_bpm_led(uint32_t tick) 39 | { 40 | // BPM led indicator 41 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 42 | bpm_blink_timer = 8; 43 | digitalWrite(LED_BUILTIN, HIGH); 44 | } else if ( !(tick % (24)) ) { // each quarter led on 45 | digitalWrite(LED_BUILTIN, HIGH); 46 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 47 | digitalWrite(LED_BUILTIN, LOW); 48 | bpm_blink_timer = 1; 49 | } 50 | } 51 | 52 | // Internal clock handlers 53 | void onSync24Callback(uint32_t tick) { 54 | // Send MIDI_CLOCK to external gears 55 | usbMIDI.sendRealTime(MIDI_CLOCK); 56 | handle_bpm_led(tick); 57 | } 58 | 59 | void onClockStart() { 60 | usbMIDI.sendRealTime(MIDI_START); 61 | } 62 | 63 | void onClockStop() { 64 | usbMIDI.sendRealTime(MIDI_STOP); 65 | digitalWrite(LED_BUILTIN, LOW); 66 | } 67 | 68 | // External clock handlers 69 | void onExternalClock() 70 | { 71 | uClock.clockMe(); 72 | } 73 | 74 | void onExternalStart() 75 | { 76 | uClock.start(); 77 | } 78 | 79 | void onExternalStop() 80 | { 81 | uClock.stop(); 82 | } 83 | 84 | void setup() { 85 | // A led to count bpms 86 | pinMode(LED_BUILTIN, OUTPUT); 87 | 88 | // 89 | // MIDI setup 90 | // 91 | usbMIDI.begin(); 92 | usbMIDI.setHandleClock(onExternalClock); 93 | usbMIDI.setHandleStart(onExternalStart); 94 | usbMIDI.setHandleStop(onExternalStop); 95 | 96 | // 97 | // OLED setup 98 | // Please check you oled model to correctly init him 99 | // 100 | //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 101 | u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE); 102 | u8x8->begin(); 103 | u8x8->setFont(u8x8_font_pressstart2p_r); 104 | u8x8->clear(); 105 | u8x8->setFlipMode(true); 106 | u8x8->drawUTF8(0, 0, "uClock"); 107 | 108 | // 109 | // uClock Setup 110 | // 111 | // Setup our clock system 112 | uClock.init(); 113 | uClock.setOnSync24(onSync24Callback); 114 | // For MIDI Sync Start and Stop 115 | uClock.setOnClockStart(onClockStart); 116 | uClock.setOnClockStop(onClockStop); 117 | uClock.setMode(uClock.EXTERNAL_CLOCK); 118 | // make use of 250us timer to handle midi input sync 119 | teensyTimer.begin(handleMidiInput, 250); 120 | teensyTimer.priority(80); 121 | } 122 | 123 | void handleMidiInput() { 124 | while (usbMIDI.read()) { 125 | } 126 | } 127 | 128 | void loop() { 129 | if (bpm != uClock.getTempo()) { 130 | bpm = uClock.getTempo(); 131 | u8x8->drawUTF8(8, 7, String(bpm, 1).c_str()); 132 | u8x8->drawUTF8(8+5, 7, "BPM"); 133 | // clear display ghost number for 2 digit 134 | // coming from 3 digit bpm changes 135 | if (bpm < 100) { 136 | u8x8->drawUTF8(8+4, 7, " "); 137 | } 138 | } 139 | if (clock_state != uClock.state) { 140 | clock_state = uClock.state; 141 | if (clock_state >= 1) { 142 | u8x8->drawUTF8(0, 7, "Playing"); 143 | } else { 144 | u8x8->drawUTF8(0, 7, "Stopped"); 145 | } 146 | } 147 | if (clock_mode != uClock.getMode()) { 148 | clock_mode = uClock.getMode(); 149 | if (clock_mode == uClock.EXTERNAL_CLOCK) { 150 | u8x8->drawUTF8(10, 0, "Slave "); 151 | } else { 152 | u8x8->drawUTF8(10, 0, "Master"); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /examples/TeensyUsbSlaveMidiClockMonitor/name.c: -------------------------------------------------------------------------------- 1 | // To give your project a unique name, this code must be 2 | // placed into a .c file (its own tab). It can not be in 3 | // a .cpp file or your main sketch (the .ino file). 4 | 5 | #include "usb_names.h" 6 | 7 | // Edit these lines to create your own name. The length must 8 | // match the number of characters in your custom name. 9 | 10 | #define MIDI_NAME {'u','c','l','o','c','k','_','1'} 11 | #define MIDI_NAME_LEN 8 12 | 13 | // Do not change this part. This exact format is required by USB. 14 | 15 | struct usb_string_descriptor_struct usb_string_product_name = { 16 | 2 + MIDI_NAME_LEN * 2, 17 | 3, 18 | MIDI_NAME 19 | }; 20 | -------------------------------------------------------------------------------- /examples/XiaoUsbMasterMidiClock/XiaoUsbMasterMidiClock.ino: -------------------------------------------------------------------------------- 1 | /* USB MIDI Sync Box 2 | * 3 | * This example demonstrates how to change the USB MIDI 4 | * device name on Seeedstudio XIAO M0. 5 | * 6 | * This example code is in the public domain. 7 | * 8 | * Tested with Adafruit TinyUSB version 0.10.5 9 | * 10 | */ 11 | #include 12 | #include 13 | 14 | Adafruit_USBD_MIDI usb_midi; 15 | MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); 16 | 17 | //MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); 18 | #include 19 | 20 | uint8_t bpm_blink_timer = 1; 21 | void handle_bpm_led(uint32_t tick) 22 | { 23 | // BPM led indicator 24 | if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer 25 | bpm_blink_timer = 8; 26 | digitalWrite(LED_BUILTIN, LOW); 27 | } else if ( !(tick % (24)) ) { // each quarter led on 28 | bpm_blink_timer = 1; 29 | digitalWrite(LED_BUILTIN, LOW); 30 | } else if ( !(tick % bpm_blink_timer) ) { // get led off 31 | digitalWrite(LED_BUILTIN, HIGH); 32 | } 33 | } 34 | 35 | // Internal clock handlers 36 | void onSync24Callback(uint32_t tick) { 37 | // Send MIDI_CLOCK to external gears 38 | MIDI_USB.sendRealTime(midi::Clock); 39 | handle_bpm_led(tick); 40 | } 41 | 42 | void onClockStart() { 43 | MIDI_USB.sendRealTime(midi::Start); 44 | } 45 | 46 | void onClockStop() { 47 | MIDI_USB.sendRealTime(midi::Stop); 48 | } 49 | 50 | void setup() { 51 | MIDI_USB.begin(MIDI_CHANNEL_OMNI); 52 | 53 | // A led to count bpms 54 | pinMode(LED_BUILTIN, OUTPUT); 55 | 56 | // Setup our clock system 57 | // Inits the clock 58 | uClock.init(); 59 | // Set the callback function for the clock output to send MIDI Sync message. 60 | uClock.setOnSync24(onSync24Callback); 61 | // Set the callback function for MIDI Start and Stop messages. 62 | uClock.setOnClockStart(onClockStart); 63 | uClock.setOnClockStop(onClockStop); 64 | // Set the clock BPM to 126 BPM 65 | uClock.setTempo(126); 66 | // Starts the clock, tick-tac-tick-tac... 67 | uClock.start(); 68 | } 69 | 70 | // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... 71 | void loop() { 72 | 73 | } 74 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uClock", 3 | "version": "2.2.1", 4 | "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)", 5 | "keywords": "bpm, clock, timing, tick, music, generator", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/midilab/uClock.git" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "Romulo Silva", 15 | "email": "contact@midilab.co", 16 | "url": "https://midilab.co", 17 | "maintainer": true 18 | } 19 | ], 20 | "license": "MIT", 21 | "homepage": "https://midilab.co/umodular/", 22 | "headers": "uClock.h", 23 | "dependencies": {}, 24 | "frameworks": "Arduino", 25 | "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam,raspberrypi" 26 | } 27 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=uClock 2 | version=2.2.1 3 | author=Romulo Silva 4 | maintainer=Romulo Silva 5 | sentence=BPM clock generator for Arduino platform. 6 | paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040) 7 | category=Timing 8 | url=https://github.com/midilab/uClock 9 | architectures=avr,arm,samd,stm32,esp32,rp2040 10 | includes=uClock.h 11 | -------------------------------------------------------------------------------- /src/platforms/avr.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 4 | 5 | // want a different avr clock support? 6 | // TODO: we should do this using macro guards for avrs different clocks freqeuncy setup at compile time 7 | #define AVR_CLOCK_FREQ 16000000 8 | 9 | // forward declaration of uClockHandler 10 | void uClockHandler(); 11 | 12 | // AVR ISR Entrypoint 13 | ISR(TIMER1_COMPA_vect) 14 | { 15 | uClockHandler(); 16 | } 17 | 18 | void initTimer(uint32_t init_clock) 19 | { 20 | ATOMIC( 21 | // 16bits Timer1 init 22 | // begin at 120bpm (48.0007680122882 Hz) 23 | TCCR1A = 0; // set entire TCCR1A register to 0 24 | TCCR1B = 0; // same for TCCR1B 25 | TCNT1 = 0; // initialize counter value to 0 26 | // set compare match register for 48.0007680122882 Hz increments 27 | OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536) 28 | // turn on CTC mode 29 | TCCR1B |= (1 << WGM12); 30 | // Set CS12, CS11 and CS10 bits for 8 prescaler 31 | TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10); 32 | // enable timer compare interrupt 33 | TIMSK1 |= (1 << OCIE1A); 34 | ) 35 | } 36 | 37 | void setTimer(uint32_t us_interval) 38 | { 39 | float tick_hertz_interval = 1/((float)us_interval/1000000); 40 | 41 | uint32_t ocr; 42 | uint8_t tccr = 0; 43 | 44 | // 16bits avr timer setup 45 | if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) { 46 | // Set CS12, CS11 and CS10 bits for 1 prescaler 47 | tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10); 48 | } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) { 49 | // Set CS12, CS11 and CS10 bits for 8 prescaler 50 | tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10); 51 | } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) { 52 | // Set CS12, CS11 and CS10 bits for 64 prescaler 53 | tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10); 54 | } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) { 55 | // Set CS12, CS11 and CS10 bits for 256 prescaler 56 | tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10); 57 | } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) { 58 | // Set CS12, CS11 and CS10 bits for 1024 prescaler 59 | tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10); 60 | } else { 61 | // tempo not achiavable 62 | return; 63 | } 64 | 65 | ATOMIC( 66 | TCCR1B = 0; 67 | OCR1A = ocr-1; 68 | TCCR1B |= (1 << WGM12); 69 | TCCR1B |= tccr; 70 | ) 71 | } -------------------------------------------------------------------------------- /src/platforms/esp32-nofrertos.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TIMER_ID 0 4 | 5 | hw_timer_t * _uclockTimer = NULL; 6 | portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; 7 | #define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux); 8 | 9 | // forward declaration of uClockHandler 10 | void uClockHandler(); 11 | 12 | // ISR handler 13 | void ARDUINO_ISR_ATTR handlerISR(void) 14 | { 15 | uClockHandler(); 16 | } 17 | 18 | void initTimer(uint32_t init_clock) 19 | { 20 | _uclockTimer = timerBegin(init_clock); 21 | 22 | // attach to generic uclock ISR 23 | timerAttachInterrupt(_uclockTimer, &handlerISR); 24 | 25 | // init clock tick time 26 | timerAlarm(_uclockTimer, init_clock, true, 0); 27 | } 28 | 29 | void setTimer(uint32_t us_interval) 30 | { 31 | timerAlarmWrite(_uclockTimer, us_interval, true); 32 | } -------------------------------------------------------------------------------- /src/platforms/esp32.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // esp32-specific timer 6 | hw_timer_t * _uclockTimer = NULL; 7 | 8 | // FreeRTOS main clock task size in bytes 9 | #define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size 10 | TaskHandle_t taskHandle; 11 | // mutex to protect the shared resource 12 | SemaphoreHandle_t _mutex; 13 | // mutex control for task 14 | #define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex); 15 | 16 | // forward declaration of uClockHandler 17 | void uClockHandler(); 18 | 19 | // ISR handler 20 | void ARDUINO_ISR_ATTR handlerISR(void) 21 | { 22 | BaseType_t xHigherPriorityTaskWoken = pdFALSE; 23 | // Send a notification to task1 24 | vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken); 25 | portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 26 | } 27 | 28 | // task for user clock process 29 | void clockTask(void *pvParameters) 30 | { 31 | while (1) { 32 | // wait for a notification from ISR 33 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 34 | uClockHandler(); 35 | } 36 | } 37 | 38 | void initTimer(uint32_t init_clock) 39 | { 40 | // initialize the mutex for shared resource access 41 | _mutex = xSemaphoreCreateMutex(); 42 | 43 | // create the clockTask 44 | xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); 45 | 46 | _uclockTimer = timerBegin(init_clock); 47 | 48 | // attach to generic uclock ISR 49 | timerAttachInterrupt(_uclockTimer, &handlerISR); 50 | 51 | // init clock tick time 52 | timerAlarm(_uclockTimer, init_clock, true, 0); 53 | } 54 | 55 | void setTimer(uint32_t us_interval) 56 | { 57 | timerAlarm(_uclockTimer, us_interval, true, 0); 58 | } -------------------------------------------------------------------------------- /src/platforms/rp2040.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/sync.h" 3 | 4 | // RPi-specific timer 5 | struct repeating_timer timer; 6 | 7 | #define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } 8 | 9 | // forward declaration of uClockHandler 10 | void uClockHandler(); 11 | 12 | // ISR handler -- called when tick happens 13 | bool handlerISR(repeating_timer *timer) 14 | { 15 | uClockHandler(); 16 | 17 | return true; 18 | } 19 | 20 | void initTimer(uint32_t init_clock) { 21 | // set up RPi interrupt timer 22 | // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! 23 | add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); 24 | } 25 | 26 | void setTimer(uint32_t us_interval) { 27 | cancel_repeating_timer(&timer); 28 | // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! 29 | add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); 30 | } -------------------------------------------------------------------------------- /src/platforms/samd.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 24 bits timer 4 | #include 5 | // uses TimerTcc0 6 | // 16 bits timer 7 | //#include 8 | // uses TimerTc3 9 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 10 | 11 | // forward declaration of ISR 12 | void uClockHandler(); 13 | 14 | void initTimer(uint32_t init_clock) 15 | { 16 | TimerTcc0.initialize(init_clock); 17 | 18 | // attach to generic uclock ISR 19 | TimerTcc0.attachInterrupt(uClockHandler); 20 | } 21 | 22 | void setTimer(uint32_t us_interval) 23 | { 24 | TimerTcc0.setPeriod(us_interval); 25 | } -------------------------------------------------------------------------------- /src/platforms/software.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | Generic fallback approach that doesn't rely on any particular MCU's interrupts or RTOS threads etc. 5 | Simply checks micros() and compares last time tick happened and interval size to determine when a tick is due. 6 | requires calling softwareTimerHandler(micros()); inside loop() in order to trigger tick processing. 7 | function signature: void softwareTimerHandler(uint32_t micros_time); 8 | 9 | @author Doctea 10 | */ 11 | 12 | #define ATOMIC(X) X; 13 | 14 | // forward declaration of ISR 15 | void uClockHandler(); 16 | 17 | uint32_t uclock_last_time_ticked; 18 | uint32_t uclock_us_interval; 19 | 20 | // call this as often as possible to tick the uClock 21 | void softwareTimerHandler(uint32_t micros_time) { 22 | if (micros_time - uclock_last_time_ticked >= uclock_us_interval) { 23 | uclock_last_time_ticked = micros_time; 24 | uClockHandler(); 25 | } 26 | } 27 | 28 | void initTimer(uint32_t init_clock) 29 | { 30 | // basically nothing to do for software-implemented version..? 31 | uclock_last_time_ticked = micros(); 32 | } 33 | 34 | void setTimer(uint32_t us_interval) 35 | { 36 | uclock_us_interval = us_interval; 37 | } -------------------------------------------------------------------------------- /src/platforms/stm32.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION < 0x01090000) 4 | #error "Due to API change, this library is compatible with STM32_CORE_VERSION >= 0x01090000. Please update/install stm32duino core." 5 | #endif 6 | 7 | #if defined(TIM1) 8 | TIM_TypeDef * TimerInstance = TIM1; 9 | #else 10 | TIM_TypeDef * TimerInstance = TIM2; 11 | #endif 12 | 13 | // instantiate HardwareTimer object 14 | HardwareTimer * _uclockTimer = new HardwareTimer(TimerInstance); 15 | 16 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 17 | 18 | // forward declaration of ISR 19 | void uClockHandler(); 20 | 21 | void initTimer(uint32_t us_interval) 22 | { 23 | _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT); 24 | _uclockTimer->attachInterrupt(uClockHandler); 25 | _uclockTimer->resume(); 26 | } 27 | 28 | void setTimer(uint32_t us_interval) 29 | { 30 | _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT); 31 | _uclockTimer->refresh(); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/platforms/teensy.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ATOMIC(X) noInterrupts(); X; interrupts(); 4 | 5 | IntervalTimer _uclockTimer; 6 | 7 | // forward declaration of ISR 8 | void uClockHandler(); 9 | 10 | void initTimer(uint32_t init_clock) 11 | { 12 | _uclockTimer.begin(uClockHandler, init_clock); 13 | 14 | // Set the interrupt priority level, controlling which other interrupts 15 | // this timer is allowed to interrupt. Lower numbers are higher priority, 16 | // with 0 the highest and 255 the lowest. Most other interrupts default to 128. 17 | // As a general guideline, interrupt routines that run longer should be given 18 | // lower priority (higher numerical values). 19 | _uclockTimer.priority(80); 20 | } 21 | 22 | void setTimer(uint32_t us_interval) 23 | { 24 | _uclockTimer.update(us_interval); 25 | } -------------------------------------------------------------------------------- /src/uClock.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file uClock.cpp 3 | * Project BPM clock generator for Arduino 4 | * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) 5 | * @version 2.2.1 6 | * @author Romulo Silva 7 | * @date 10/06/2017 8 | * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a 11 | * copy of this software and associated documentation files (the "Software"), 12 | * to deal in the Software without restriction, including without limitation 13 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | * and/or sell copies of the Software, and to permit persons to whom the 15 | * Software is furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included 18 | * in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | * DEALINGS IN THE SOFTWARE. 27 | */ 28 | #include "uClock.h" 29 | 30 | // no hardware timer clock? use USE_UCLOCK_SOFTWARE_TIMER 31 | #if !defined(USE_UCLOCK_SOFTWARE_TIMER) 32 | // 33 | // General Arduino AVRs port 34 | // 35 | #if defined(ARDUINO_ARCH_AVR) 36 | #include "platforms/avr.h" 37 | #define UCLOCK_PLATFORM_FOUND 38 | #endif 39 | // 40 | // Teensyduino ARMs port 41 | // 42 | #if defined(TEENSYDUINO) 43 | #include "platforms/teensy.h" 44 | #define UCLOCK_PLATFORM_FOUND 45 | #endif 46 | // 47 | // Seedstudio XIAO M0 port 48 | // 49 | #if defined(SEEED_XIAO_M0) 50 | #include "platforms/samd.h" 51 | #define UCLOCK_PLATFORM_FOUND 52 | #endif 53 | // 54 | // ESP32 family 55 | // 56 | #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) 57 | #include "platforms/esp32.h" 58 | #define UCLOCK_PLATFORM_FOUND 59 | #endif 60 | // 61 | // STM32XX family 62 | // 63 | #if defined(ARDUINO_ARCH_STM32) 64 | #include "platforms/stm32.h" 65 | #define UCLOCK_PLATFORM_FOUND 66 | #endif 67 | // 68 | // RP2040 (Raspberry Pico) family 69 | // 70 | #if defined(ARDUINO_ARCH_RP2040) 71 | #include "platforms/rp2040.h" 72 | #define UCLOCK_PLATFORM_FOUND 73 | #endif 74 | #endif 75 | 76 | // 77 | // Software Timer for generic, board-agnostic, not-accurate, no-interrupt, software-only port 78 | // 79 | #if !defined(UCLOCK_PLATFORM_FOUND) 80 | #pragma message ("NOTE: uClock is using the 'software timer' approach instead of specific board interrupted support, because board is not supported or because of USE_UCLOCK_SOFTWARE_TIMER build flag. Remember to call uClock.run() inside your loop().") 81 | #include "platforms/software.h" 82 | #endif 83 | 84 | 85 | // 86 | // Platform specific timer setup/control 87 | // 88 | // initTimer(uint32_t us_interval) and setTimer(uint32_t us_interval) 89 | // are called from architecture specific module included at the 90 | // header of this file 91 | void uclockInitTimer() 92 | { 93 | // begin at 120bpm 94 | initTimer(uClock.bpmToMicroSeconds(120.00)); 95 | } 96 | 97 | void setTimerTempo(float bpm) 98 | { 99 | setTimer(uClock.bpmToMicroSeconds(bpm)); 100 | } 101 | 102 | namespace umodular { namespace clock { 103 | 104 | static inline uint32_t phase_mult(uint32_t val) 105 | { 106 | return (val * PHASE_FACTOR) >> 8; 107 | } 108 | 109 | static inline uint32_t clock_diff(uint32_t old_clock, uint32_t new_clock) 110 | { 111 | if (new_clock >= old_clock) { 112 | return new_clock - old_clock; 113 | } else { 114 | return new_clock + (4294967295 - old_clock); 115 | } 116 | } 117 | 118 | uClockClass::uClockClass() 119 | { 120 | tempo = 120; 121 | start_timer = 0; 122 | last_interval = 0; 123 | sync_interval = 0; 124 | clock_state = PAUSED; 125 | clock_mode = INTERNAL_CLOCK; 126 | resetCounters(); 127 | 128 | onOutputPPQNCallback = nullptr; 129 | onSync1Callback = nullptr; 130 | onSync2Callback = nullptr; 131 | onSync4Callback = nullptr; 132 | onSync8Callback = nullptr; 133 | onSync12Callback = nullptr; 134 | onSync24Callback = nullptr; 135 | onSync48Callback = nullptr; 136 | onStepCallback = nullptr; 137 | onClockStartCallback = nullptr; 138 | onClockStopCallback = nullptr; 139 | // initialize reference data 140 | calculateReferencedata(); 141 | } 142 | 143 | void uClockClass::init() 144 | { 145 | uclockInitTimer(); 146 | // first interval calculus 147 | setTempo(tempo); 148 | } 149 | 150 | uint32_t uClockClass::bpmToMicroSeconds(float bpm) 151 | { 152 | return (60000000.0f / (float)output_ppqn / bpm); 153 | } 154 | 155 | void uClockClass::calculateReferencedata() 156 | { 157 | mod_clock_ref = output_ppqn / input_ppqn; 158 | mod_sync1_ref = output_ppqn / PPQN_1; 159 | mod_sync2_ref = output_ppqn / PPQN_2; 160 | mod_sync4_ref = output_ppqn / PPQN_4; 161 | mod_sync8_ref = output_ppqn / PPQN_8; 162 | mod_sync12_ref = output_ppqn / PPQN_12; 163 | mod_sync24_ref = output_ppqn / PPQN_24; 164 | mod_sync48_ref = output_ppqn / PPQN_48; 165 | mod_step_ref = output_ppqn / 4; 166 | } 167 | 168 | void uClockClass::setOutputPPQN(PPQNResolution resolution) 169 | { 170 | // dont allow PPQN lower than PPQN_4 for output clock (to avoid problems with mod_step_ref) 171 | if (resolution < PPQN_4) 172 | return; 173 | 174 | ATOMIC( 175 | output_ppqn = resolution; 176 | calculateReferencedata(); 177 | ) 178 | } 179 | 180 | void uClockClass::setInputPPQN(PPQNResolution resolution) 181 | { 182 | ATOMIC( 183 | input_ppqn = resolution; 184 | calculateReferencedata(); 185 | ) 186 | } 187 | 188 | void uClockClass::start() 189 | { 190 | resetCounters(); 191 | start_timer = millis(); 192 | 193 | if (onClockStartCallback) { 194 | onClockStartCallback(); 195 | } 196 | 197 | if (clock_mode == INTERNAL_CLOCK) { 198 | clock_state = STARTED; 199 | } else { 200 | clock_state = STARTING; 201 | } 202 | } 203 | 204 | void uClockClass::stop() 205 | { 206 | clock_state = PAUSED; 207 | start_timer = 0; 208 | resetCounters(); 209 | if (onClockStopCallback) { 210 | onClockStopCallback(); 211 | } 212 | } 213 | 214 | void uClockClass::pause() 215 | { 216 | if (clock_mode == INTERNAL_CLOCK) { 217 | if (clock_state == PAUSED) { 218 | start(); 219 | } else { 220 | stop(); 221 | } 222 | } 223 | } 224 | 225 | void uClockClass::setTempo(float bpm) 226 | { 227 | if (clock_mode == EXTERNAL_CLOCK) { 228 | return; 229 | } 230 | 231 | if (bpm < MIN_BPM || bpm > MAX_BPM) { 232 | return; 233 | } 234 | 235 | ATOMIC( 236 | tempo = bpm 237 | ) 238 | 239 | setTimerTempo(bpm); 240 | } 241 | 242 | float uClockClass::getTempo() 243 | { 244 | if (clock_mode == EXTERNAL_CLOCK) { 245 | uint32_t acc = 0; 246 | // wait the buffer to get full 247 | if (ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE-1] == 0) { 248 | return tempo; 249 | } 250 | for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) { 251 | acc += ext_interval_buffer[i]; 252 | } 253 | if (acc != 0) { 254 | return freqToBpm(acc / EXT_INTERVAL_BUFFER_SIZE); 255 | } 256 | } 257 | return tempo; 258 | } 259 | 260 | // for software timer implementation(fallback for no board support) 261 | void uClockClass::run() 262 | { 263 | #if !defined(UCLOCK_PLATFORM_FOUND) 264 | // call software timer implementation of software 265 | softwareTimerHandler(micros()); 266 | #endif 267 | } 268 | 269 | float inline uClockClass::freqToBpm(uint32_t freq) 270 | { 271 | float usecs = 1/((float)freq/1000000.0); 272 | return (float)((float)(usecs/(float)input_ppqn) * 60.0); 273 | } 274 | 275 | void uClockClass::setClockMode(ClockMode tempo_mode) 276 | { 277 | clock_mode = tempo_mode; 278 | } 279 | 280 | uClockClass::ClockMode uClockClass::getClockMode() 281 | { 282 | return clock_mode; 283 | } 284 | 285 | void uClockClass::clockMe() 286 | { 287 | if (clock_mode == EXTERNAL_CLOCK) { 288 | ATOMIC( 289 | handleExternalClock() 290 | ) 291 | } 292 | } 293 | 294 | void uClockClass::resetCounters() 295 | { 296 | tick = 0; 297 | int_clock_tick = 0; 298 | mod_clock_counter = 0; 299 | mod_step_counter = 0; 300 | step_counter = 0; 301 | ext_clock_tick = 0; 302 | ext_clock_us = 0; 303 | ext_interval_idx = 0; 304 | // sync output counters 305 | mod_sync1_counter = 0; 306 | sync1_tick = 0; 307 | mod_sync2_counter = 0; 308 | sync2_tick = 0; 309 | mod_sync4_counter = 0; 310 | sync4_tick = 0; 311 | mod_sync8_counter = 0; 312 | sync8_tick = 0; 313 | mod_sync12_counter = 0; 314 | sync12_tick = 0; 315 | mod_sync24_counter = 0; 316 | sync24_tick = 0; 317 | mod_sync48_counter = 0; 318 | sync48_tick = 0; 319 | 320 | for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) { 321 | ext_interval_buffer[i] = 0; 322 | } 323 | } 324 | 325 | void uClockClass::tap() 326 | { 327 | // we can make use of mod_sync1_ref for tap 328 | //uint8_t mod_tap_ref = output_ppqn / PPQN_1; 329 | // we only set tap if ClockMode is INTERNAL_CLOCK 330 | } 331 | 332 | void uClockClass::setShuffle(bool active) 333 | { 334 | ATOMIC(shuffle.active = active) 335 | } 336 | 337 | bool uClockClass::isShuffled() 338 | { 339 | return shuffle.active; 340 | } 341 | 342 | void uClockClass::setShuffleSize(uint8_t size) 343 | { 344 | if (size > MAX_SHUFFLE_TEMPLATE_SIZE) 345 | size = MAX_SHUFFLE_TEMPLATE_SIZE; 346 | ATOMIC(shuffle.size = size) 347 | } 348 | 349 | void uClockClass::setShuffleData(uint8_t step, int8_t tick) 350 | { 351 | if (step >= MAX_SHUFFLE_TEMPLATE_SIZE) 352 | return; 353 | ATOMIC(shuffle.step[step] = tick) 354 | } 355 | 356 | void uClockClass::setShuffleTemplate(int8_t * shuff, uint8_t size) 357 | { 358 | //uint8_t size = sizeof(shuff) / sizeof(shuff[0]); 359 | if (size > MAX_SHUFFLE_TEMPLATE_SIZE) 360 | size = MAX_SHUFFLE_TEMPLATE_SIZE; 361 | ATOMIC(shuffle.size = size) 362 | for (uint8_t i=0; i < size; i++) { 363 | setShuffleData(i, shuff[i]); 364 | } 365 | } 366 | 367 | int8_t uClockClass::getShuffleLength() 368 | { 369 | return shuffle_length_ctrl; 370 | } 371 | 372 | bool inline uClockClass::processShuffle() 373 | { 374 | if (!shuffle.active) { 375 | return mod_step_counter == 0; 376 | } 377 | 378 | int8_t mod_shuffle = 0; 379 | 380 | // check shuffle template of current 381 | int8_t shff = shuffle.step[step_counter%shuffle.size]; 382 | 383 | if (shuffle_shoot_ctrl == false && mod_step_counter == 0) 384 | shuffle_shoot_ctrl = true; 385 | 386 | //if (mod_step_counter == mod_step_ref-1) 387 | 388 | if (shff >= 0) { 389 | mod_shuffle = mod_step_counter - shff; 390 | // any late shuffle? we should skip next mod_step_counter == 0 391 | if (last_shff < 0 && mod_step_counter != 1) 392 | return false; 393 | } else if (shff < 0) { 394 | mod_shuffle = mod_step_counter - (mod_step_ref + shff); 395 | //if (last_shff < 0 && mod_step_counter != 1) 396 | // return false; 397 | shuffle_shoot_ctrl = true; 398 | } 399 | 400 | last_shff = shff; 401 | 402 | // shuffle_shoot_ctrl helps keep track if we have shoot or not a note for the step space of output_ppqn/4 pulses 403 | if (mod_shuffle == 0 && shuffle_shoot_ctrl == true) { 404 | // keep track of next note shuffle for current note lenght control 405 | shuffle_length_ctrl = shuffle.step[(step_counter+1)%shuffle.size]; 406 | if (shff > 0) 407 | shuffle_length_ctrl -= shff; 408 | if (shff < 0) 409 | shuffle_length_ctrl += shff; 410 | shuffle_shoot_ctrl = false; 411 | return true; 412 | } 413 | 414 | return false; 415 | } 416 | 417 | void uClockClass::handleExternalClock() 418 | { 419 | switch (clock_state) { 420 | case PAUSED: 421 | break; 422 | 423 | case STARTING: 424 | clock_state = STARTED; 425 | ext_clock_us = micros(); 426 | break; 427 | 428 | case STARTED: 429 | uint32_t now_clock_us = micros(); 430 | last_interval = clock_diff(ext_clock_us, now_clock_us); 431 | ext_clock_us = now_clock_us; 432 | 433 | // external clock tick me! 434 | ext_clock_tick++; 435 | 436 | // accumulate interval incomming ticks data for getTempo() smooth reads on slave clock_mode 437 | if(++ext_interval_idx >= EXT_INTERVAL_BUFFER_SIZE) { 438 | ext_interval_idx = 0; 439 | } 440 | ext_interval_buffer[ext_interval_idx] = last_interval; 441 | 442 | if (ext_clock_tick == 1) { 443 | ext_interval = last_interval; 444 | } else { 445 | ext_interval = (((uint32_t)ext_interval * (uint32_t)PLL_X) + (uint32_t)(256 - PLL_X) * (uint32_t)last_interval) >> 8; 446 | } 447 | break; 448 | } 449 | } 450 | 451 | void uClockClass::handleTimerInt() 452 | { 453 | // track main input clock counter 454 | if (mod_clock_counter == mod_clock_ref) 455 | mod_clock_counter = 0; 456 | 457 | // process sync signals first please... 458 | if (mod_clock_counter == 0) { 459 | 460 | if (clock_mode == EXTERNAL_CLOCK) { 461 | // sync tick position with external tick clock 462 | if ((int_clock_tick < ext_clock_tick) || (int_clock_tick > (ext_clock_tick + 1))) { 463 | int_clock_tick = ext_clock_tick; 464 | tick = int_clock_tick * mod_clock_ref; 465 | mod_clock_counter = tick % mod_clock_ref; 466 | mod_step_counter = tick % mod_step_ref; 467 | } 468 | 469 | uint32_t counter = ext_interval; 470 | uint32_t now_clock_us = micros(); 471 | sync_interval = clock_diff(ext_clock_us, now_clock_us); 472 | 473 | if (int_clock_tick <= ext_clock_tick) { 474 | counter -= phase_mult(sync_interval); 475 | } else { 476 | if (counter > sync_interval) { 477 | counter += phase_mult(counter - sync_interval); 478 | } 479 | } 480 | 481 | // update internal clock timer frequency 482 | float bpm = freqToBpm(counter); 483 | if (bpm != tempo) { 484 | if (bpm >= MIN_BPM && bpm <= MAX_BPM) { 485 | tempo = bpm; 486 | setTimerTempo(bpm); 487 | } 488 | } 489 | } 490 | 491 | // internal clock tick me! 492 | ++int_clock_tick; 493 | } 494 | ++mod_clock_counter; 495 | 496 | // ALL OUTPUT SYNC CALLBACKS 497 | // Sync1 callback 498 | if (onSync1Callback) { 499 | if (mod_sync1_counter == mod_sync1_ref) 500 | mod_sync1_counter = 0; 501 | if (mod_sync1_counter == 0) { 502 | onSync1Callback(sync1_tick); 503 | ++sync1_tick; 504 | } 505 | ++mod_sync1_counter; 506 | } 507 | 508 | // Sync2 callback 509 | if (onSync2Callback) { 510 | if (mod_sync2_counter == mod_sync2_ref) 511 | mod_sync2_counter = 0; 512 | if (mod_sync2_counter == 0) { 513 | onSync2Callback(sync2_tick); 514 | ++sync2_tick; 515 | } 516 | ++mod_sync2_counter; 517 | } 518 | 519 | // Sync4 callback 520 | if (onSync4Callback) { 521 | if (mod_sync4_counter == mod_sync4_ref) 522 | mod_sync4_counter = 0; 523 | if (mod_sync4_counter == 0) { 524 | onSync4Callback(sync4_tick); 525 | ++sync4_tick; 526 | } 527 | ++mod_sync4_counter; 528 | } 529 | 530 | // Sync8 callback 531 | if (onSync8Callback) { 532 | if (mod_sync8_counter == mod_sync8_ref) 533 | mod_sync8_counter = 0; 534 | if (mod_sync8_counter == 0) { 535 | onSync8Callback(sync8_tick); 536 | ++sync8_tick; 537 | } 538 | ++mod_sync8_counter; 539 | } 540 | 541 | // Sync12 callback 542 | if (onSync12Callback) { 543 | if (mod_sync12_counter == mod_sync12_ref) 544 | mod_sync12_counter = 0; 545 | if (mod_sync12_counter == 0) { 546 | onSync12Callback(sync12_tick); 547 | ++sync12_tick; 548 | } 549 | ++mod_sync12_counter; 550 | } 551 | 552 | // Sync24 callback 553 | if (onSync24Callback) { 554 | if (mod_sync24_counter == mod_sync24_ref) 555 | mod_sync24_counter = 0; 556 | if (mod_sync24_counter == 0) { 557 | onSync24Callback(sync24_tick); 558 | ++sync24_tick; 559 | } 560 | ++mod_sync24_counter; 561 | } 562 | 563 | // Sync48 callback 564 | if (onSync48Callback) { 565 | if (mod_sync48_counter == mod_sync48_ref) 566 | mod_sync48_counter = 0; 567 | if (mod_sync48_counter == 0) { 568 | onSync48Callback(sync48_tick); 569 | ++sync48_tick; 570 | } 571 | ++mod_sync48_counter; 572 | } 573 | 574 | // main PPQNCallback 575 | if (onOutputPPQNCallback) { 576 | onOutputPPQNCallback(tick); 577 | ++tick; 578 | } 579 | 580 | // step callback to support 16th old school style sequencers 581 | // with builtin shuffle for this callback only 582 | if (onStepCallback) { 583 | if (mod_step_counter == mod_step_ref) 584 | mod_step_counter = 0; 585 | // processShufle make use of mod_step_counter == 0 logic too 586 | if (processShuffle()) { 587 | onStepCallback(step_counter); 588 | // going forward to the next step call 589 | ++step_counter; 590 | } 591 | ++mod_step_counter; 592 | } 593 | } 594 | 595 | // elapsed time support 596 | uint8_t uClockClass::getNumberOfSeconds(uint32_t time) 597 | { 598 | if ( time == 0 ) { 599 | return time; 600 | } 601 | return ((_millis - time) / 1000) % SECS_PER_MIN; 602 | } 603 | 604 | uint8_t uClockClass::getNumberOfMinutes(uint32_t time) 605 | { 606 | if ( time == 0 ) { 607 | return time; 608 | } 609 | return (((_millis - time) / 1000) / SECS_PER_MIN) % SECS_PER_MIN; 610 | } 611 | 612 | uint8_t uClockClass::getNumberOfHours(uint32_t time) 613 | { 614 | if ( time == 0 ) { 615 | return time; 616 | } 617 | return (((_millis - time) / 1000) % SECS_PER_DAY) / SECS_PER_HOUR; 618 | } 619 | 620 | uint8_t uClockClass::getNumberOfDays(uint32_t time) 621 | { 622 | if ( time == 0 ) { 623 | return time; 624 | } 625 | return ((_millis - time) / 1000) / SECS_PER_DAY; 626 | } 627 | 628 | uint32_t uClockClass::getNowTimer() 629 | { 630 | return _millis; 631 | } 632 | 633 | uint32_t uClockClass::getPlayTime() 634 | { 635 | return start_timer; 636 | } 637 | 638 | } } // end namespace umodular::clock 639 | 640 | umodular::clock::uClockClass uClock; 641 | 642 | volatile uint32_t _millis = 0; 643 | 644 | // 645 | // TIMER HANDLER 646 | // 647 | void uClockHandler() 648 | { 649 | // global timer counter 650 | _millis = millis(); 651 | 652 | if (uClock.clock_state == uClock.STARTED) { 653 | uClock.handleTimerInt(); 654 | } 655 | } 656 | -------------------------------------------------------------------------------- /src/uClock.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file uClock.h 3 | * Project BPM clock generator for Arduino 4 | * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) 5 | * @version 2.2.1 6 | * @author Romulo Silva 7 | * @date 10/06/2017 8 | * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a 11 | * copy of this software and associated documentation files (the "Software"), 12 | * to deal in the Software without restriction, including without limitation 13 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | * and/or sell copies of the Software, and to permit persons to whom the 15 | * Software is furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included 18 | * in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | * DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | #ifndef __U_CLOCK_H__ 30 | #define __U_CLOCK_H__ 31 | 32 | #include 33 | #include 34 | 35 | namespace umodular { namespace clock { 36 | 37 | // Shuffle templates are specific for each PPQN output resolution 38 | // min: -(output_ppqn/4)-1 ticks 39 | // max: (output_ppqn/4)-1 ticks 40 | // adjust the size of you template if more than 16 shuffle step info needed 41 | #define MAX_SHUFFLE_TEMPLATE_SIZE 16 42 | typedef struct { 43 | bool active = false; 44 | uint8_t size = MAX_SHUFFLE_TEMPLATE_SIZE; 45 | int8_t step[MAX_SHUFFLE_TEMPLATE_SIZE] = {0}; 46 | } SHUFFLE_TEMPLATE; 47 | 48 | // for smooth slave tempo calculate display you should raise this value 49 | // in between 64 to 128. 50 | // note: this doesn't impact on sync time, only display time getTempo() 51 | // if you dont want to use it, set it to 1 for memory save 52 | #define EXT_INTERVAL_BUFFER_SIZE 128 53 | 54 | #define MIN_BPM 1 55 | #define MAX_BPM 400 56 | 57 | #define PHASE_FACTOR 16 58 | #define PLL_X 220 59 | 60 | #define SECS_PER_MIN (60UL) 61 | #define SECS_PER_HOUR (3600UL) 62 | #define SECS_PER_DAY (SECS_PER_HOUR * 24L) 63 | 64 | class uClockClass { 65 | 66 | public: 67 | enum ClockMode { 68 | INTERNAL_CLOCK = 0, 69 | EXTERNAL_CLOCK 70 | }; 71 | 72 | enum ClockState { 73 | PAUSED = 0, 74 | STARTING, 75 | STARTED 76 | }; 77 | 78 | enum PPQNResolution { 79 | PPQN_1 = 1, 80 | PPQN_2 = 2, 81 | PPQN_4 = 4, 82 | PPQN_8 = 8, 83 | PPQN_12 = 12, 84 | PPQN_24 = 24, 85 | PPQN_48 = 48, 86 | PPQN_96 = 96, 87 | PPQN_384 = 384, 88 | PPQN_480 = 480, 89 | PPQN_960 = 960 90 | }; 91 | 92 | ClockState clock_state; 93 | 94 | uClockClass(); 95 | 96 | void setOnOutputPPQN(void (*callback)(uint32_t tick)) { 97 | onOutputPPQNCallback = callback; 98 | } 99 | 100 | void setOnStep(void (*callback)(uint32_t step)) { 101 | onStepCallback = callback; 102 | } 103 | 104 | // multiple output clock signatures 105 | void setOnSync1(void (*callback)(uint32_t tick)) { 106 | onSync1Callback = callback; 107 | } 108 | 109 | void setOnSync2(void (*callback)(uint32_t tick)) { 110 | onSync2Callback = callback; 111 | } 112 | 113 | void setOnSync4(void (*callback)(uint32_t tick)) { 114 | onSync4Callback = callback; 115 | } 116 | 117 | void setOnSync8(void (*callback)(uint32_t tick)) { 118 | onSync8Callback = callback; 119 | } 120 | 121 | void setOnSync12(void (*callback)(uint32_t tick)) { 122 | onSync12Callback = callback; 123 | } 124 | 125 | void setOnSync24(void (*callback)(uint32_t tick)) { 126 | onSync24Callback = callback; 127 | } 128 | 129 | void setOnSync48(void (*callback)(uint32_t tick)) { 130 | onSync48Callback = callback; 131 | } 132 | 133 | void setOnClockStart(void (*callback)()) { 134 | onClockStartCallback = callback; 135 | } 136 | 137 | void setOnClockStop(void (*callback)()) { 138 | onClockStopCallback = callback; 139 | } 140 | 141 | void init(); 142 | void setOutputPPQN(PPQNResolution resolution); 143 | void setInputPPQN(PPQNResolution resolution); 144 | 145 | void handleTimerInt(); 146 | void handleExternalClock(); 147 | void resetCounters(); 148 | 149 | // external class control 150 | void start(); 151 | void stop(); 152 | void pause(); 153 | void setTempo(float bpm); 154 | float getTempo(); 155 | 156 | // for software timer implementation(fallback for no board support) 157 | void run(); 158 | 159 | // external timming control 160 | void setClockMode(ClockMode tempo_mode); 161 | ClockMode getClockMode(); 162 | void clockMe(); 163 | 164 | // shuffle 165 | void setShuffle(bool active); 166 | bool isShuffled(); 167 | void setShuffleSize(uint8_t size); 168 | void setShuffleData(uint8_t step, int8_t tick); 169 | void setShuffleTemplate(int8_t * shuff, uint8_t size); 170 | // use this to know how many positive or negative ticks to add to current note length 171 | int8_t getShuffleLength(); 172 | 173 | // todo! 174 | void tap(); 175 | 176 | // elapsed time support 177 | uint8_t getNumberOfSeconds(uint32_t time); 178 | uint8_t getNumberOfMinutes(uint32_t time); 179 | uint8_t getNumberOfHours(uint32_t time); 180 | uint8_t getNumberOfDays(uint32_t time); 181 | uint32_t getNowTimer(); 182 | uint32_t getPlayTime(); 183 | 184 | uint32_t bpmToMicroSeconds(float bpm); 185 | 186 | private: 187 | float inline freqToBpm(uint32_t freq); 188 | void calculateReferencedata(); 189 | 190 | // shuffle 191 | bool inline processShuffle(); 192 | 193 | void (*onOutputPPQNCallback)(uint32_t tick); 194 | void (*onStepCallback)(uint32_t step); 195 | void (*onSync1Callback)(uint32_t tick); 196 | void (*onSync2Callback)(uint32_t tick); 197 | void (*onSync4Callback)(uint32_t tick); 198 | void (*onSync8Callback)(uint32_t tick); 199 | void (*onSync12Callback)(uint32_t tick); 200 | void (*onSync24Callback)(uint32_t tick); 201 | void (*onSync48Callback)(uint32_t tick); 202 | void (*onClockStartCallback)(); 203 | void (*onClockStopCallback)(); 204 | 205 | // clock input/output control 206 | PPQNResolution output_ppqn = PPQN_96; 207 | PPQNResolution input_ppqn = PPQN_24; 208 | // output and internal counters, ticks and references 209 | uint32_t tick; 210 | uint32_t int_clock_tick; 211 | uint8_t mod_clock_counter; 212 | uint16_t mod_clock_ref; 213 | uint8_t mod_step_counter; 214 | uint8_t mod_step_ref; 215 | uint32_t step_counter; 216 | uint8_t mod_sync1_counter; 217 | uint16_t mod_sync1_ref; 218 | uint32_t sync1_tick; 219 | uint8_t mod_sync2_counter; 220 | uint16_t mod_sync2_ref; 221 | uint32_t sync2_tick; 222 | uint8_t mod_sync4_counter; 223 | uint16_t mod_sync4_ref; 224 | uint32_t sync4_tick; 225 | uint8_t mod_sync8_counter; 226 | uint16_t mod_sync8_ref; 227 | uint32_t sync8_tick; 228 | uint8_t mod_sync12_counter; 229 | uint16_t mod_sync12_ref; 230 | uint32_t sync12_tick; 231 | uint8_t mod_sync24_counter; 232 | uint16_t mod_sync24_ref; 233 | uint32_t sync24_tick; 234 | uint8_t mod_sync48_counter; 235 | uint16_t mod_sync48_ref; 236 | uint32_t sync48_tick; 237 | 238 | // external clock control 239 | volatile uint32_t ext_clock_us; 240 | volatile uint32_t ext_clock_tick; 241 | volatile uint32_t ext_interval; 242 | uint32_t last_interval; 243 | uint32_t sync_interval; 244 | 245 | float tempo; 246 | uint32_t start_timer; 247 | ClockMode clock_mode; 248 | 249 | volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE]; 250 | uint16_t ext_interval_idx; 251 | 252 | // shuffle implementation 253 | volatile SHUFFLE_TEMPLATE shuffle; 254 | int8_t last_shff = 0; 255 | bool shuffle_shoot_ctrl = true; 256 | volatile int8_t shuffle_length_ctrl = 0; 257 | }; 258 | 259 | } } // end namespace umodular::clock 260 | 261 | extern umodular::clock::uClockClass uClock; 262 | 263 | extern "C" { 264 | extern volatile uint32_t _millis; 265 | } 266 | 267 | #endif /* __U_CLOCK_H__ */ 268 | 269 | --------------------------------------------------------------------------------