Full Code of midilab/uClock for AI

main 6cf39cc242e1 cached
33 files
111.8 KB
31.8k tokens
33 symbols
1 requests
Download .txt
Repository: midilab/uClock
Branch: main
Commit: 6cf39cc242e1
Files: 33
Total size: 111.8 KB

Directory structure:
gitextract_wxko6giz/

├── LICENSE
├── README.md
├── examples/
│   ├── AVRUartSlaveMidiClockMonitor/
│   │   └── AVRUartSlaveMidiClockMonitor.ino
│   ├── AcidStepSequencer/
│   │   ├── AcidStepSequencer.ino
│   │   ├── DefaultUserInterface.ino
│   │   ├── HardwareInterface.ino
│   │   └── README.md
│   ├── 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

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 midilab

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# uClock - BPM Clock Generator Library

A professional-grade BPM clock generator library for Arduino and PlatformIO, designed for musicians, artists, and engineers creating sequencers, sync boxes, and real-time musical devices. It is built to be multi-architecture, portable, and easy to use within the open-source ecosystem.

## Overview

**uClock** delivers precise, hardware-interrupt-driven clock timing for music and media applications. Whether you're building a MIDI sequencer, modular synthesizer clock, or synchronizing multiple devices, uClock provides the rock-solid timing foundation your project needs.

The library leverages hardware timer interrupts to ensure accurate BPM generation and synchronization, making it suitable for professional music production, live performance, and creative installations.

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.

## Supported Platforms

- **AVR**: ATmega168/328, ATmega16u4/32u4, ATmega2560
- **ARM**: Teensy (all versions), STM32XX, Seeed Studio XIAO M0
- **ESP32**: All ESP32 family boards
- **RP2040**: Raspberry Pi Pico and compatible boards

## Why uClock?

Open-source platforms like Arduino and PlatformIO traditionally lack the real-time capabilities required for professional music and video applications. uClock bridges this gap by providing:

- **Precise timing** through hardware interrupts
- **Flexible clock resolutions** from 1 to 960 PPQN
- **External sync support** for external sync configurations
- **Shuffle and groove** capabilities for humanized timing
- **Multi-track sequencing** with independent shuffle per track
- **Multiple sync outputs** for different device standards

## Installation

### PlatformIO
1. Open platformio.ini, a project configuration file located in the root of PlatformIO project.
2. Add the following line to the lib_deps option of [env:] section: midilab/uClock@^2.3.0
```ini
[env:...]
lib_deps =
    midilab/uClock@^2.3.0
```
3. Build a project, PlatformIO will automatically install dependencies.

### Arduino IDE
1. Open your Arduino IDE
2. Select the Library Manager Tab on left side
3. Type "uclock" at the search box
4. Click Install for latest version

## Core Concepts

### Clock Resolutions (PPQN)

PPQN (Pulses Per Quarter Note) determines the timing resolution of your clock:

- **PPQN_1, 2, 4, 8, 12**: Modular synthesis sync standards
- **PPQN_24**: Standard MIDI sync (24 pulses per beat)
- **PPQN_48**: Common in vintage drum machines
- **PPQN_96**: High-resolution internal clock (default)
- **PPQN_384, 480, 960**: Ultra-high resolution for modern sequencing and DAWs

### Callback Architecture

uClock operates through a callback system that triggers your code at precise intervals:

| Callback | Purpose | Use Case |
|----------|---------|----------|
| `setOnOutputPPQN()` | Main clock pulse | Drive sequencers, process MIDI |
| `setOnStep()` | 16th note intervals | Step sequencers, drum patterns |
| `setOnSync()` | Custom sync outputs | Multiple device sync, modular CV |
| `setOnClockStart()` | Clock start event | Initialize sequences, send MIDI start |
| `setOnClockStop()` | Clock stop event | Reset states, send MIDI stop |
| `setOnClockPause()` | Clock pause event | Pause handling |
| `setOnClockContinue()` | Clock continue event | Resume from pause |

## Quick Start

### Basic 96 PPQN Clock

```cpp
#include <uClock.h>

void onPPQNCallback(uint32_t tick) {
    // Called at each clock pulse (default 96 PPQN)
    // Drive your sequencer logic here
}

void setup() {
    // set main clock rate for output(sequencer resolution)
    uClock.setOutputPPQN(uClock.PPQN_96);
    // Configure callbacks
    uClock.setOnOutputPPQN(onPPQNCallback);

    // Set tempo
    uClock.setTempo(120.0);

    // Initialize and start
    uClock.init();
    uClock.start();
}

void loop() {
    // Your main code here
}
```

### Basic MIDI Clock box with External Sync
Send clock message and sync to external midi clock.

```cpp
#include <uClock.h>

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

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

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

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

void setup() {

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

  // set main clock rate for output(sequencer resolution)
  uClock.setOutputPPQN(uClock.PPQN_96);
  // set main clock rate for input(expected sync signal rate)
  uClock.setInputPPQN(uClock.PPQN_24);
  
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // set external clock mode
  uClock.setClockMode(uClock.EXTERNAL_CLOCK);

  // Set the clock BPM to 126 BPM
  //uClock.setTempo(126);
  //uClock.start();
}

void loop() {
  // call clockMe() each time you receive an external clock pulse
  // in this example we set inputPPQN to 24 PPQN
  // wich expects a signal clock rate comming from MIDI device
  // PS: Idealy you should do midi sync with another interruption schema
  // the more code on loop() the less accuracy the MIDI_CLOCK signal reads
  if (Serial.available() > 0) {
    uint8_t midi_byte = Serial.read();
    
    switch (midi_byte) {
        case MIDI_CLOCK:
            uClock.clockMe();
            break;
    
        case MIDI_START:
            uClock.start(); 
            break;
    
        case MIDI_STOP:
            uClock.stop();
            break;
    }
  }
}
```

## Advanced Features

### Multiple Sync Outputs

Generate different clock resolutions simultaneously for various devices:

```cpp
#include <uClock.h>

#define SYNC_OUT_PIN 8
#define MIDI_CLOCK_BYTE 0xF8

void onSync1(uint32_t tick) {
    // Send modular sync (1 pulse per quarter note)
    triggerModularPulse();
}

void onSync2(uint32_t tick) {
    // Send Pocket Operators sync (2 pulse per quarter note)
    triggerPocketOperatorsPulse();
}

void onSync24(uint32_t tick) {
    // Send MIDI clock (24 PPQN)
    Serial.write(MIDI_CLOCK_BYTE);
}

void onSync48(uint32_t tick) {
    // Send 48 PPQN for vintage gear like Korg DIN Sync 48
    digitalWrite(SYNC_OUT_PIN, !digitalRead(SYNC_OUT_PIN));
}

void setup() {    
    // set main clock rate for output(sequencer resolution)
    uClock.setOutputPPQN(uClock.PPQN_96);
    // set sync callbacks
    uClock.setOnSync(uClock.PPQN_1, onSync1);
    uClock.setOnSync(uClock.PPQN_2, onSync2);
    uClock.setOnSync(uClock.PPQN_24, onSync24);
    uClock.setOnSync(uClock.PPQN_48, onSync48);
    // do only init after all setup is done
    uClock.init();
    
    // Set the clock BPM to 126 BPM
    uClock.setTempo(126);
    uClock.start();
}

void loop() {
    // Your main code here
}
```

**Available Sync Resolutions**: All possible Clock Resolutions (PPQN) where setOnSync(resolution) <= setOutputPPQN(resolution)

### Step Sequencer Extension

uClock includes a built-in extension specifically designed for creating step sequencers for synthesizers and drum machines. The extension provides multi-track support with independent per-track control, making it ideal for building complete rhythm machines and melodic sequencers.

#### Features

**Current Features**:
- ✅ **16th note orientation**: Natural step sequencer workflow
- ✅ **Multi-track support**: Independent sequences with individual callbacks
- ✅ **Per-track shuffle**: Each track can have its own groove templatexs

**Roadmap**:
- 🔄 **Per-track shift**: Offset patterns in time (coming soon)
- 🔄 **Per-track direction**: Forward, reverse, ping-pong playback (coming soon)

```cpp
#include <uClock.h>

#define MAX_STEPS 16
//#define TRACK_NUMBER 8

// a pattern example for drums mainly
uint8_t pattern[MAX_STEPS] = {1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0};

// Called every 16th note
// not dependent on internal or external clock resolution
// single track callback(doesn't mean you can't code a multirack sequencer here)
void onStepCallback(uint32_t step) {
    if (pattern[step % MAX_STEPS])
        playNote();
}

// the multirack callback 
//void onStepCallback(uint32_t step, uint8_t track) {
//}

void setup() {
    // Configure callbacks
    uClock.setOnStep(onStepCallback);
    // with multitrack support?
    //uClock.setOnStep(onStepCallback, TRACK_NUMBER);
    
    uClock.init();
    
    // Set the clock BPM to 126 BPM
    uClock.setTempo(126);
    uClock.start();
}

void loop() {
    // Your main code here
}
```

### Shuffle and Groove

Add humanization and swing to your sequences with shuffle support. Re-create timeless groove signatures of legends like MPC60 or TR-909, or experiment with your own custom groove templates.

#### How Shuffle Works

![Ableton Shuffle Example](https://raw.githubusercontent.com/midilab/uclock/main/doc/shuflle-example.gif)

Shuffle operates by shifting individual steps earlier or later in time, along with adjusting note lengths to maintain musical coherence. As shown in the Ableton example above:

- **Positive shuffle values**: Delay the note trigger and shorten note length (to avoid overlapping with the next note)
- **Negative shuffle values**: Trigger the note earlier and extend note length (filling the time gap)
- **Zero values**: Play straight with no shuffle effect

This is the fundamental principle behind groove templating in step sequencers. Each 16th note step can be individually time-shifted to create swing, shuffle, or completely custom rhythmic feels.

#### Technical Specifications

**Shuffle templates are PPQN-dependent**: The range of shuffle values depends on your output resolution:

```cpp
// Shuffle range calculation - in ticks
min_shuffle = -(output_ppqn/4) - 1
max_shuffle = +(output_ppqn/4) - 1

// Example with PPQN_96 (default)
// Range: -23 to +23 ticks per step
```

**Template Size Configuration**: Adjust in `uClock.h` if needed:

```cpp
#define MAX_SHUFFLE_TEMPLATE_SIZE   16
```

Modify this value to support longer patterns (memory permitting) or reduce it to save memory if you only need simpler grooves.

MPC60 Groove implmentation example based on [Roger Linn Groove Magic Interview](https://www.attackmagazine.com/features/interview/roger-linn-swing-groove-magic-mpc-timing/)

```cpp
#include <uClock.h>

#define TRACKS_SIZE 8

// The shuffle effect is applied transparently for shuffled steps
void onStepCallback(uint32_t step, uint8_t track) {
    // get the length diff to apply to current shuffled step length
    // if the step is not shuffled, then shuffle_len == 0
    int8_t shuffle_len = uClock.getShuffleLength(track);
    // sequencer procesing...
    // ...
    uint8_t step_len = getNoteLength(step, track);
    step_len += shuffle_len;
    // use step_len as the new length of note for this step
    // ...
}

// no multirack support gives you a global shuffle flavor
//void onStepCallback(uint32_t step) {
//}

void setup() {
    // MPC60 groove signature (Same used for TR909)
    // Based on Roger Linn Groove Magic Interview
    // Internal clock: 96 PPQN
    // Each step is 24 ticks long
    uint8_t current_shuffle = 0;
    int8_t shuffle_50[2] = {0, 0};
    int8_t shuffle_54[2] = {0, 2};
    int8_t shuffle_58[2] = {0, 4};
    int8_t shuffle_62[2] = {0, 6};
    int8_t shuffle_66[2] = {0, 8};
    int8_t shuffle_71[2] = {0, 10};
    int8_t shuffle_75[2] = {0, 12};
    String shuffle_name[7] = {"50%", "54%", "58%", "62%", "66%", "71%", "75%"};
    int8_t* shuffle_templates[7] = {shuffle_50, shuffle_54, shuffle_58, shuffle_62, shuffle_66, shuffle_71, shuffle_75};
    
    // set main clock rate for output(sequencer resolution) and input(expected sync signal)
    uClock.setOutputPPQN(uClock.PPQN_96);
    // the second argument of setOnStep is used to request multitrack support
    // multitrack individual shuffle support for this example
    uClock.setOnStep(onStepCallback, TRACKS_SIZE);
    // global shuffle? no need to multitrack handle of step sequencer extension
    // set the callback without second param
    //uClock.setOnStep(onStepCallback);
    
    uClock.init();
    
    // set a template for shuffle for specific tracks
    uClock.setShuffleTemplate(shuffle_templates[current_shuffle], 0);
    uClock.setShuffleTemplate(shuffle_templates[current_shuffle], 1);
    uClock.setShuffleTemplate(shuffle_templates[current_shuffle], 2);
    uClock.setShuffleTemplate(shuffle_templates[current_shuffle], 3);
    // if no multirack support set on setOnStep() then the suffle is global
    //uClock.setShuffleTemplate(shuffle_templates[current_shuffle]);
    // enable/disable shuffle for specific tracks
    uClock.setShuffle(true, 0);
    uClock.setShuffle(true, 1);
    uClock.setShuffle(true, 2);
    uClock.setShuffle(true, 3);
    
    uClock.start();
}
```

#### Shuffle API Reference

```cpp
// Enable/disable shuffle for a track
void setShuffle(bool active, uint8_t track = 0);

// Check if shuffle is active
bool isShuffled(uint8_t track = 0);

// Set shuffle pattern size (max 16 steps)
void setShuffleSize(uint8_t size, uint8_t track = 0);

// Set individual shuffle values
void setShuffleData(uint8_t step, int8_t tick, uint8_t track = 0);

// Set complete shuffle template
void setShuffleTemplate(int8_t* shuff, uint8_t size, uint8_t track = 0);

// Get shuffle offset for note length compensation
int8_t getShuffleLength(uint8_t track = 0);
```

**Shuffle Values**:
- Range: `-(output_ppqn/4)-1` to `+(output_ppqn/4)-1` ticks
- Negative values = play earlier
- Positive values = delay
- Zero = no shuffle (straight timing)

### External Sync and Phase Lock

Fine-tune synchronization with external clocks:

```cpp
void setup() {
    // sets external clock
    uClock.setClockMode(uClock.EXTERNAL_CLOCK);
    // get back to internal clock
    //uClock.setClockMode(uClock.INTERNAL_CLOCK);

    // Lock phase every N quarters (default: 1)
    // Higher values = looser sync but more stable
    // Lower values = tighter sync but may jitter
    uClock.setPhaseLockQuartersCount(1);

    // Smooth tempo reading for uClock.getTempo()
    // Buffer size: 1-254 (larger = smoother but slower lock time response)
    // The higher the value the longer it takes to sync getTempo() value to actual tempo
    // PS: the tempo taken to bpm sync here it is for getTempo() value only
    // it does not affect the main clock.
    uClock.setExtIntervalBuffer(64);
}
```

## Configuration API

### Clock Control

```cpp
// Set internal tempo (1-500 BPM)
void setTempo(float bpm);

// Get current tempo (works with external sync too)
float getTempo();

// Clock transport control
void start();        // Start clock
void stop();         // Stop clock
void pause();        // Toggle pause/continue

// Clock mode
void setClockMode(ClockMode mode);
ClockMode getClockMode();
// Modes: INTERNAL_CLOCK, EXTERNAL_CLOCK
```

### Resolution Configuration

```cpp
// Set main output resolution
void setOutputPPQN(PPQNResolution resolution);

// Set expected input resolution (external sync)
void setInputPPQN(PPQNResolution resolution);

// Available resolutions:
// PPQN_1, PPQN_2, PPQN_4, PPQN_8, PPQN_12, PPQN_24
// PPQN_48, PPQN_96, PPQN_384, PPQN_480, PPQN_960
```

**Important**: `inputPPQN` must be ≤ `outputPPQN`

### Timing Utilities

```cpp
// Convert BPM to microseconds
uint32_t bpmToMicroSeconds(float bpm);

// Elapsed time functions
uint8_t getNumberOfSeconds(uint32_t time);
uint8_t getNumberOfMinutes(uint32_t time);
uint8_t getNumberOfHours(uint32_t time);
uint8_t getNumberOfDays(uint32_t time);

// Timers
uint32_t getNowTimer();
uint32_t getPlayTime();
```

## Software Timer Mode

For unsupported boards or to avoid interrupt conflicts, uClock can run in software mode:

**Automatic Fallback**: Activates automatically on unsupported boards

**Manual Activation**: Define `USE_UCLOCK_SOFTWARE_TIMER` build flag

```cpp
void loop() {
    // REQUIRED in software timer mode
    uClock.run();

    // Part of your code here
    // ...
    
    // Call run() frequently for best accuracy
    uClock.run();

    // Other parts of your code here
    // ...

    uClock.run();
}
```

⚠️ **Note**: Software timer mode provides less accurate timing than hardware interrupts.

## Migration Guide (v1.x → v2.3)

### Breaking Changes

| Old API (v1.x) | New API (v2.3+) |
|----------------|-----------------|
| `setClock96PPQNOutput()` | `setOnOutputPPQN()` |
| `setClock16PPQNOutput()` | `setOnStep()` |
| `setOnClockStartOutput()` | `setOnClockStart()` |
| `setOnClockStopOutput()` | `setOnClockStop()` |
| `setOnSync24()` | `setOnSync(uClock.PPQN_24, onSync24)` |
| `setOnSync48()` | `setOnSync(uClock.PPQN_48, onSync48)` |
| `setOnSyncXX()` | `setOnSync(uClock.PPQN_XX, onSyncXX)` |

### Resolution Changes

v2.0 introduces flexible PPQN configuration. If you relied on the old 96 PPQN default:

```cpp
// Old (implicit 96 PPQN)
uClock.setClock96PPQNOutput(callback);

// New (explicit configuration)
uClock.setOutputPPQN(uClock.PPQN_96);
uClock.setOnOutputPPQN(callback);
```

### Tick Counting

If you used `setClock16PPQNOutput()` (16th notes), simply replace with `setOnStep()` - no other changes needed.

If you relied on specific tick values from `setClock96PPQNOutput()`, verify your timing calculations match your chosen PPQN resolution.

## Examples

Complete examples are available in the `examples/` directory:

- **BasicClock**: Simple internal clock setup
- **ExternalSync**: Slave to external clock
- **MidiClockSync**: Full MIDI sync box implementation
- **AcidStepSequencer**: TB-303 style sequencer engine
- **MultiTrackSequencer**: Independent track sequencing with shuffle

## Technical Details

### Concurrency Safety

uClock operates at interruption or thread(when avaliable) level, wich means your microcontroller will be executing uClock callbacks processing detached from your main `loop()` code, that creates a concurrency programming context where you need to properly protect read and write access to any shared resource between uClock callback code and `loop()` code.

All clock operations that involve shared variables or resources use atomic operations to ensure concurrency-safe access between interrupt/threads contexts and your main `loop()` code.

* interruption/thread: Depending on micrcontroller platform we levarage a FreeRTOS implementation when multi core avaliable. In this scenario, a threaded context will be used for callbacks instead of interruption.

#### Understanding Shared Resources

**What are shared variables or resources?**

Any data that is accessed both inside uClock callbacks (which run in interrupt/thread context) and inside your `loop()` function (which runs in the main program context) is considered a shared resource.

**Examples of shared resources:**
- Pattern data arrays modified in `loop()` and read in `onStepCallback()`
- Sequencer state variables (mute, arp, etc.) modified in `loop()` and read in `onStepCallback()`
- MIDI buffers or output queues used in `loop()` and `onStepCallback()`

#### Safe Modification Using ATOMIC

When modifying shared variables from your main code, always use the `ATOMIC()` macro:
```cpp
void onStepCallback(uint32_t step) {
    // Read pattern state in interrupt context
    if (!sequencer.mute) {
        playNote();
    }
}

// Example: Safely updating sequencer state from user input
void loop() {
    if (buttonPressed()) {
        uint8_t mute = readButton();
        
        ATOMIC(
            sequencer.mute = mute;
        )
    }
}
```
```cpp
// Example: Safely modifying pattern data
uint8_t pattern[16] = {1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0};

void onStepCallback(uint32_t step) {
    // Read pattern in interrupt context
    if (pattern[step % 16]) {
        playNote();
    }
}

void loop() {
    if (userEditedPattern()) {
        // Safely modify from main loop
        ATOMIC(
            pattern[4] = 1;
            pattern[8] = 0;
        )
    }
}
```

#### When to Use ATOMIC

**You MUST use ATOMIC when:**
- Modifying variables at `loop()` that are readed inside uClock callbacks
- Updating multiple related variables that must change together atomically at `loop()`
- Writing or Reading variables that are `volatile` (often used with interrupts/threads) at `loop()`

**You DON'T need ATOMIC when:**
- Coding inside any uClock callback
- Using uClock's built-in methods like `setTempo()`, `start()`, `stop()` (all uClock public API are already protected)

#### Built-in Thread Safety

uClock's API methods are already interrupt/thread-safe and handle atomic operations internally:
```cpp
// These are SAFE to call from loop() without ATOMIC
uClock.setTempo(120.0);
uClock.start();
uClock.stop();
uClock.pause();
uClock.setShuffle(true, 0);
uClock.setShuffleTemplate(template, 16, 0);
```

#### Common Pitfalls

**❌ Wrong - Race condition:**
```cpp
uint8_t stepCount = 0;

void onStepCallback(uint32_t step) {
    stepCount++;  // Modified in interrupt/thread context
}

void loop() {
    if (stepCount >= 16) {  // Read in main loop - NOT ATOMIC!
        stepCount = 0;      // Modified in main loop - DANGER!
        doSomething();
    }
}
```

**✅ Correct - Atomic access:**
```cpp
volatile uint8_t stepCount = 0;

void onStepCallback(uint32_t step) {
    stepCount++;  // Modified in interrupt/thread context
}

void loop() {
    uint8_t currentCount;
    
    ATOMIC(
        currentCount = stepCount;  // Atomic read
    )
    
    if (currentCount >= 16) {
        ATOMIC(
            stepCount = 0;  // Atomic write
        )
        doSomething();
    }
}
```

#### Advanced: Platform-Specific Implementation

For AVR platforms, `ATOMIC()` is defined as:
```cpp
#define ATOMIC(X) noInterrupts(); X; interrupts();
```

For some ARM's and other platforms with multi-core architecture, uClock uses platform-appropriate atomic mechanisms. 

From user perspective the ATOMIC() macro is transparent and safe to use when you need, independently from you micro-controller architecture.

**Note:** Keep atomic sections as short as possible to minimize interrupt latency. Only include the actual shared variable access inside the `ATOMIC()` block:
```cpp
// Good - minimal atomic section
ATOMIC(pattern = newPattern;)
updateDisplay(pattern);  // Outside atomic section

// Less ideal - unnecessary code in atomic section
ATOMIC(
    pattern = newPattern;
    updateDisplay(pattern);  // This doesn't need to be atomic!
)
```

## License

MIT License - Copyright (c) 2025 Romulo Silva

## Support & Community

- **Documentation**: [GitHub Repository](https://github.com/midilab/uClock)
- **Issues**: Report bugs via GitHub Issues
- **Contact**: contact@midilab.co

---

**uClock** - Precision timing for creative minds.


================================================
FILE: examples/AVRUartSlaveMidiClockMonitor/AVRUartSlaveMidiClockMonitor.ino
================================================
/* Uart MIDI Sync Slave Box Monitor
 *
 * This example demonstrates how to create a
 * MIDI slave clock box with
 * monitor support using oled display
 *
 * MIDI in must be provided via an opto-isolator to pin RX/D0
 * Tested on an Arduino Uno.
 *
 * You need the following libraries to make it work
 * - Midi Library
 * - u8g2
 * - uClock
 * This example code is in the public domain.
 *
 * Code by midilab contact@midilab.co
 * Example modified by Jackson Devices contact@jacksondevices.com
 */
#include <MIDI.h>
#include <U8x8lib.h>

//
// BPM Clock support
//
#include <uClock.h>

MIDI_CREATE_DEFAULT_INSTANCE();
U8X8 * u8x8;

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

float bpm = 126.0;
uint8_t bpm_blink_timer = 1;
uint8_t clock_state = 1;
uint8_t clock_mode = 0;

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  MIDI.sendRealTime(MIDI_CLOCK);
  handle_bpm_led(tick);
}

void onClockStart() {
  MIDI.sendRealTime(MIDI_START);
}

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

// External clock handlers
void onExternalClock() {
  uClock.clockMe();
}

void onExternalStart() {
  uClock.start();
}

void onExternalStop() {
  uClock.stop();
  digitalWrite(LED_BUILTIN, LOW);
}

void setup() {
  //
  // MIDI setup
  //
  // Initiate MIDI communications, listen to all channels, disable soft MIDI thru
  MIDI.begin();
  MIDI.turnThruOff();
  MIDI.setHandleClock(onExternalClock);
  MIDI.setHandleStart(onExternalStart);
  MIDI.setHandleStop(onExternalStop);

  // An led to display MIDI reception
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  //
  // OLED setup
  // Please check you oled model to correctly init him
  // The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
  //
  //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8->begin();
  u8x8->setFont(u8x8_font_pressstart2p_r);
  u8x8->clear();
  u8x8->setFlipMode(true);
  u8x8->drawUTF8(0, 0, "uClock");

  //
  // uClock Setup
  //
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // For MIDI Sync Start and Stop
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);
  uClock.setClockMode(uClock.EXTERNAL_CLOCK);
  // for smooth slave tempo calculate display you should raise the
  // buffer_size of ext_interval_buffer in between 64 to 128. 254 max size.
  // note: this doesn't impact on sync time, only display time getTempo()
  // if you dont want to use it, it is default set it to 1 for memory save
  uClock.setExtIntervalBuffer(128);

  // inits uClock
  uClock.init();

  //uClock.setTempo(136.5);
  //uClock.start();
}

void loop() {
  while(MIDI.read()) {}
  // DO NOT ADD MORE PROCESS HERE AT THE COST OF LOSING CLOCK SYNC
  // Since arduino make use of Serial RX interruption we need to
  // read Serial as fast as we can on the loop
  if (bpm != uClock.getTempo()) {
    bpm = uClock.getTempo();
    u8x8->drawUTF8(8, 7, String(bpm, 1).c_str());
    u8x8->drawUTF8(8 + 5, 7, "BPM");
    // clear display ghost number for 2 digit
    // coming from 3 digit bpm changes
    if (bpm < 100) {
      u8x8->drawUTF8(8 + 4, 7, " ");
    }
  }
  if (clock_state != uClock.clock_state) {
    clock_state = uClock.clock_state;
    if (clock_state >= 1) {
      u8x8->drawUTF8(0, 7, "Playing");
    } else {
      u8x8->drawUTF8(0, 7, "Stopped ");
    }
  }
  if (clock_mode != uClock.getClockMode()) {
    clock_mode = uClock.getClockMode();
    if (clock_mode == uClock.EXTERNAL_CLOCK) {
      u8x8->drawUTF8(10, 0, "Slave ");
    } else {
      u8x8->drawUTF8(10, 0, "Master");
    }
  }
}


================================================
FILE: examples/AcidStepSequencer/AcidStepSequencer.ino
================================================
// Acid StepSequencer, a Roland TB303 step sequencer engine clone
// author: midilab contact@midilab.co
// under MIT license
#include "Arduino.h"
#include <uClock.h>

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

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

// do not edit from here!
#define NOTE_STACK_SIZE    3

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

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

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

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

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

// shared data to be used for user interface feedback
bool _playing = false;
uint16_t _step = 0;

void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2)
{
  // send midi message
  command = command | (uint8_t)MIDI_CHANNEL;
  Serial.write(command);
  Serial.write(byte1);
  Serial.write(byte2);
}

// Each call represents exactly one step.
void onStepCallback(uint32_t tick)
{
  uint16_t step;
  uint16_t length = NOTE_LENGTH;

  // get actual step.
  _step = tick % _step_length;

  // send note on only if this step are not in rest mode
  if ( _sequencer[_step].rest == false ) {

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

    // find a free note stack to fit in
    for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
      if ( _note_stack[i].length == -1 ) {
        _note_stack[i].note = _sequencer[_step].note;
        _note_stack[i].length = length;
        // send note on
        sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY);
        return;
      }
    }
  }
}

// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution.
void onOutputPPQNCallback(uint32_t tick)
{
  // handle note on stack
  for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
    if ( _note_stack[i].length != -1 ) {
      --_note_stack[i].length;
      if ( _note_stack[i].length == 0 ) {
        sendMidiMessage(NOTE_OFF, _note_stack[i].note, 0);
        _note_stack[i].length = -1;
      }
    }
  }

  // user feedback about sequence time events
  tempoInterface(tick);
}

void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  Serial.write(MIDI_CLOCK);
}

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

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

void setup()
{
  // Initialize serial communication
#ifdef MIDI_MODE
  // the default MIDI serial speed communication at 31250 bits per second
  Serial.begin(31250);
#endif
#ifdef SERIAL_MODE
  // for usage with a PC with a serial to MIDI bridge
  Serial.begin(115200);
#endif

  // Set the callback function for the clock output to send MIDI Sync message.
  uClock.setOnOutputPPQN(onOutputPPQNCallback);

  // for MIDI sync
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);

  // Set the callback function for the step sequencer on 16ppqn
  uClock.setOnStep(onStepCallback);

  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);

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

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

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

// User interaction goes here
void loop()
{
  processInterface();
}


================================================
FILE: examples/AcidStepSequencer/DefaultUserInterface.ino
================================================

#define SEQUENCER_MIN_BPM  50
#define SEQUENCER_MAX_BPM  177

// Ui config
#define LOCK_POT_SENSTIVITY 3

// hardware setup to fit different kinda of setups and arduino models
#define OCTAVE_POT_PIN            A3
#define NOTE_POT_PIN              A2
#define STEP_LENGTH_POT_PIN       A1
#define TEMPO_POT_PIN             A0

#define PREVIOUS_STEP_BUTTON_PIN  2
#define NEXT_STEP_BUTTON_PIN      3
#define REST_BUTTON_PIN           4
#define GLIDE_BUTTON_PIN          5
#define ACCENT_BUTTON_PIN         6
#define PLAY_STOP_BUTTON_PIN      7

#define PREVIOUS_STEP_LED_PIN     8
#define NEXT_STEP_LED_PIN         9
#define REST_LED_PIN              10
#define GLIDE_LED_PIN             11
#define ACCENT_LED_PIN            12
#define PLAY_STOP_LED_PIN         13

// User Interface data
uint16_t _step_edit = 0;
uint8_t _last_octave = 3;
uint8_t _last_note = 0;

uint8_t _bpm_blink_timer = 1;

void configureInterface()
{
  // Buttons config
  // use internal pullup for buttons
  pinMode(PREVIOUS_STEP_BUTTON_PIN, INPUT_PULLUP);
  pinMode(NEXT_STEP_BUTTON_PIN, INPUT_PULLUP);
  pinMode(REST_BUTTON_PIN, INPUT_PULLUP);
  pinMode(GLIDE_BUTTON_PIN, INPUT_PULLUP);
  pinMode(ACCENT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(PLAY_STOP_BUTTON_PIN, INPUT_PULLUP);

  // Leds config
  pinMode(PREVIOUS_STEP_LED_PIN, OUTPUT);
  pinMode(NEXT_STEP_LED_PIN, OUTPUT);
  pinMode(REST_LED_PIN, OUTPUT);
  pinMode(GLIDE_LED_PIN, OUTPUT);
  pinMode(ACCENT_LED_PIN, OUTPUT);
  pinMode(PLAY_STOP_LED_PIN, OUTPUT);

  digitalWrite(PREVIOUS_STEP_LED_PIN, LOW);
  digitalWrite(NEXT_STEP_LED_PIN, LOW);
  digitalWrite(REST_LED_PIN, LOW);
  digitalWrite(GLIDE_LED_PIN, LOW);
  digitalWrite(ACCENT_LED_PIN, LOW);
  digitalWrite(PLAY_STOP_LED_PIN, LOW);  

  // getting first value state
  pressed(PREVIOUS_STEP_BUTTON_PIN);
  pressed(NEXT_STEP_BUTTON_PIN);
  pressed(REST_BUTTON_PIN);
  pressed(GLIDE_BUTTON_PIN);
  pressed(ACCENT_BUTTON_PIN);
  pressed(PLAY_STOP_BUTTON_PIN);

  // getting first values
  getPotChanges(OCTAVE_POT_PIN, 0, 10);
  getPotChanges(NOTE_POT_PIN, 0, 11);
  getPotChanges(STEP_LENGTH_POT_PIN, 1, STEP_MAX_SIZE);
  getPotChanges(TEMPO_POT_PIN, SEQUENCER_MIN_BPM, SEQUENCER_MAX_BPM);
  
  lockPotsState(true);

  //acidRandomize();
}

void processInterface()
{
  processButtons();
  processLeds();
  processPots();  
}

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

void sendPreviewNote(uint16_t step)
{
  unsigned long milliTime, preMilliTime;
  
  sendMidiMessage(NOTE_ON, _sequencer[step].note, _sequencer[step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY);

  // avoid delay() call because of uClock timmer1 usage
  //delay(200);
  preMilliTime = millis();
  while ( true ) {
    milliTime = millis();
    if (abs(milliTime - preMilliTime) >= 200) {
      break;
    }
  }
  
  sendMidiMessage(NOTE_OFF, _sequencer[step].note, 0);
}

void processPots()
{
  static int8_t octave, note, step_note;
  static int16_t tempo, step_length;

  octave = getPotChanges(OCTAVE_POT_PIN, 0, 10);
  if ( octave != -1 ) {  
    _last_octave = octave;
  }

  note = getPotChanges(NOTE_POT_PIN, 0, 11);
  if ( note != -1 ) { 
    _last_note = note;
  }

  // changes on octave or note pot?
  if ( octave != -1 || note != -1 ) {
    ATOMIC(_sequencer[_step_edit].note = (_last_octave * 8) + _last_note);
    if ( _playing == false && _sequencer[_step_edit].rest == false ) {
      sendPreviewNote(_step_edit);
    }
  }

  step_length = getPotChanges(STEP_LENGTH_POT_PIN, 1, STEP_MAX_SIZE);
  if ( step_length != -1 ) {  
    ATOMIC(_step_length = step_length);
    if ( _step_edit >= _step_length ) {
      _step_edit = _step_length-1;
    }
  }

  tempo = getPotChanges(TEMPO_POT_PIN, SEQUENCER_MIN_BPM, SEQUENCER_MAX_BPM);
  if ( tempo != -1 ) {   
    //uClock.setTempo(tempo);
  }
}
  
void processButtons()
{
  // play/stop
  if ( pressed(PLAY_STOP_BUTTON_PIN) ) {
    if ( _playing == false ) {
      // Starts the clock, tick-tac-tick-tac...
      uClock.start();
    } else {
      // stop the clock
      uClock.stop();
    }
  }

  // ramdom test
  //if ( pressed(PREVIOUS_STEP_BUTTON_PIN) && pressed(NEXT_STEP_BUTTON_PIN) ) {
    //acidRandomize();
    //return;
  //}

  // previous step edit
  if ( pressed(PREVIOUS_STEP_BUTTON_PIN) ) {
    if ( _step_edit != 0 ) {
      // add a lock here for octave and note to not mess with edit mode when moving steps around 
      lockPotsState(true);   
      --_step_edit;
    } else { // TODO: just for tests.. take this guy off here and put it on second page
      acidRandomize();
    }
    if ( _playing == false && _sequencer[_step_edit].rest == false ) {
      sendPreviewNote(_step_edit);
    }
  }

  // next step edit
  if ( pressed(NEXT_STEP_BUTTON_PIN) ) {
    if ( _step_edit < _step_length-1 ) {
      // add a lock here for octave and note to not mess with edit mode when moving steps around
      lockPotsState(true);     
      ++_step_edit;
    }
    if ( _playing == false && _sequencer[_step_edit].rest == false ) {
      sendPreviewNote(_step_edit);
    }    
  }

  // step rest
  if ( pressed(REST_BUTTON_PIN) ) {
    ATOMIC(_sequencer[_step_edit].rest = !_sequencer[_step_edit].rest);
    if ( _playing == false && _sequencer[_step_edit].rest == false ) {
      sendPreviewNote(_step_edit);
    }
  }

  // step glide
  if ( pressed(GLIDE_BUTTON_PIN) ) {
    ATOMIC(_sequencer[_step_edit].glide = !_sequencer[_step_edit].glide);
  }

  // step accent
  if ( pressed(ACCENT_BUTTON_PIN) ) {
    ATOMIC(_sequencer[_step_edit].accent = !_sequencer[_step_edit].accent);
    if ( _playing == false && _sequencer[_step_edit].rest == false ) {
      sendPreviewNote(_step_edit);
    }       
  }     
}
  
void processLeds()
{   
  // Editing First Step? 
  if ( _step_edit == 0 ) {
    digitalWrite(PREVIOUS_STEP_LED_PIN , HIGH);
  } else {
    digitalWrite(PREVIOUS_STEP_LED_PIN , LOW);
  }  

  // Editing Last Step? 
  if ( _step_edit == _step_length-1 ) {
    digitalWrite(NEXT_STEP_LED_PIN , HIGH);
  } else {
    digitalWrite(NEXT_STEP_LED_PIN , LOW);
  }  
  
  // Rest 
  if ( _sequencer[_step_edit].rest == true ) {
    digitalWrite(REST_LED_PIN , HIGH);
  } else {
    digitalWrite(REST_LED_PIN , LOW);
  }

  // Glide 
  if ( _sequencer[_step_edit].glide == true ) {
    digitalWrite(GLIDE_LED_PIN , HIGH);
  } else {
    digitalWrite(GLIDE_LED_PIN , LOW);
  }  

  // Accent 
  if ( _sequencer[_step_edit].accent == true ) {
    digitalWrite(ACCENT_LED_PIN , HIGH);
  } else {
    digitalWrite(ACCENT_LED_PIN , LOW);
  } 

  // shut down play led if we are stoped
  if ( _playing == false ) {
    digitalWrite(PLAY_STOP_LED_PIN , LOW);
  }
}

void acidRandomize() 
{
  // ramdom it all
  for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) {
    ATOMIC(_sequencer[i].note = random(36, 70)); // octave 2 to 4. octave 3 to 5 (40 - 83)
    ATOMIC(_sequencer[i].accent = random(0, 2));
    ATOMIC(_sequencer[i].glide = random(0, 2));
    ATOMIC(_sequencer[i].rest = random(0, 1));
  }
}


================================================
FILE: examples/AcidStepSequencer/HardwareInterface.ino
================================================

#define POT_NUMBER    4
#define BUTTON_NUMBER 6

// pot data
typedef struct
{
  uint8_t pin;
  uint16_t state;
  bool lock;
} POT_DATA;

// button data
typedef struct
{
  uint8_t pin;
  bool state;
} BUTTON_DATA;

POT_DATA _pot[POT_NUMBER];
BUTTON_DATA _button[BUTTON_NUMBER];

void lockPotsState(bool lock)
{
  for ( uint8_t i = 0; i < POT_NUMBER; i++ ) {
    _pot[i].lock = lock;
  }
}

bool pressed(uint8_t button_pin)
{
  bool value;
  bool * last_value;

  switch(button_pin) {
    case PREVIOUS_STEP_BUTTON_PIN:
      last_value = &_button[0].state;
      break;
    case NEXT_STEP_BUTTON_PIN:
      last_value = &_button[1].state;
      break;
    case REST_BUTTON_PIN:
      last_value = &_button[2].state;
      break;   
    case GLIDE_BUTTON_PIN:
      last_value = &_button[3].state;
      break;
    case ACCENT_BUTTON_PIN:
      last_value = &_button[4].state;
      break;
    case PLAY_STOP_BUTTON_PIN:
      last_value = &_button[5].state;
      break;    
    default:
      return false;                    
  }
  
  value = digitalRead(button_pin);
  
  // check, using pullup pressed button goes LOW
  if ( value != *last_value && value == LOW ) {
    *last_value = value; 
    return true;    
  } else {
    *last_value = value; 
    return false;
  }
   
}

int16_t getPotChanges(uint8_t pot_pin, uint16_t min_value, uint16_t max_value)
{
  uint16_t value, value_ranged, last_value_ranged;
  uint16_t * last_value;
  bool * lock_pot;
  uint8_t pot_sensitivity = 1;

  switch(pot_pin) {
    case OCTAVE_POT_PIN:
      last_value = &_pot[0].state;
      lock_pot = &_pot[0].lock;
      break;
    case NOTE_POT_PIN:
      last_value = &_pot[1].state;
      lock_pot = &_pot[1].lock;
      break;
    case STEP_LENGTH_POT_PIN:
      last_value = &_pot[2].state;
      lock_pot = &_pot[2].lock;
      break;   
    case TEMPO_POT_PIN:
      last_value = &_pot[3].state;
      lock_pot = &_pot[3].lock;
      break;
    default:
      return -1;
  }

  // get absolute value
  value = analogRead(pot_pin);
    
  // range that value and our last_value
  value_ranged = (value / (1024 / ((max_value - min_value) + 1))) + min_value;
  last_value_ranged = (*last_value / (1024 / ((max_value - min_value) + 1))) + min_value; 

  // 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!)
  if ( *lock_pot == true ) {
      pot_sensitivity = LOCK_POT_SENSTIVITY;
  }
  
  if ( abs(value_ranged - last_value_ranged) >= pot_sensitivity ) {
    *last_value = value;
    if ( *lock_pot == true ) {
      *lock_pot = false;
    }
    return value_ranged;    
  } else {
    return -1;
  }  
}


================================================
FILE: examples/AcidStepSequencer/README.md
================================================
# TB303 Step Sequencer engine clone

With some user interface changes for cheap construction of a functional TB303 engine we present you the interface:

![Image of uMODULAR ctrl16 pcb top view](https://raw.githubusercontent.com/midilab/uClock/development/examples/AcidStepSequencer/acid_step_sequencer-protoboard-v001.png)

## Interface from left to rigth

POT1: Octave
POT2: Note
POT3: Sequence Length
POT4: Sequencer BPM Tempo

Button1: Prev Step
Button2: Next Step
Button3: Rest
Button4: Glide
Button5: Accent
Buttons6: Play/Stop

================================================
FILE: examples/ESP32UartMasterMidiClock/ESP32UartMasterMidiClock.ino
================================================
/* Uart MIDI Sync Box
 *
 * This example demonstrates how to change the Uart MIDI
 * device name on ESP32 family.
 *
 * This example code is in the public domain.
 *
 * ...
 *
 */
#include <uClock.h>

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

// the blue led
#define LED_BUILTIN    2

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  Serial.write(MIDI_CLOCK);
  handle_bpm_led(tick);
}

void onClockStart() {
  Serial.write(MIDI_START);
}

void onClockStop() {
  Serial.write(MIDI_STOP);
}

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

  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);
  // Starts the clock, tick-tac-tick-tac...
  uClock.start();
}

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

}


================================================
FILE: examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino
================================================
#include <uClock.h>

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

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

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

// The callback function called by uClock each Pulse of 1PPQN clock resolution.
void onSync1Callback(uint32_t tick) {
  // send sync signal to...
}

// The callback function called by uClock each Pulse of 2PPQN clock resolution.
void onSync2Callback(uint32_t tick) {
  // send sync signal to...
}

// The callback function called by uClock each Pulse of 4PPQN clock resolution.
void onSync4Callback(uint32_t tick) {
  // send sync signal to...
}

// The callback function called by uClock each Pulse of 24PPQN clock resolution.
void onSync24Callback(uint32_t tick) {
  // send sync signal to...
}

// The callback function called by uClock each Pulse of 48PPQN clock resolution.
void onSync48Callback(uint32_t tick) {
  // send sync signal to...
}

// The callback function called when clock starts by using uClock.start() method.
void onClockStartCallback() {
  // send start signal to...
}

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

void setup() {
  // setup clock library
  // avaliable output resolutions
  // [ uClock.PPQN_4, uClock.PPQN_8, uClock.PPQN_12, uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ]
  // not mandatory to call, the default is 96PPQN if not set
  uClock.setOutputPPQN(uClock.PPQN_96);

  // you need to use at least one!
  uClock.setOnOutputPPQN(onOutputPPQNCallback);
  uClock.setOnStep(onStepCallback);
  // multi sync output signatures avaliable
  // normaly used by eurorack modular modules
  uClock.setOnSync(uClock.PPQN_1, onSync1Callback);
  uClock.setOnSync(uClock.PPQN_2, onSync2Callback);
  uClock.setOnSync(uClock.PPQN_4, onSync4Callback);
  // midi sync standard
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // some korg machines does 48ppqn
  uClock.setOnSync(uClock.PPQN_48, onSync48Callback);

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

  // set external sync mode?
  if (_external_sync_on) {
    uClock.setClockMode(uClock.EXTERNAL_CLOCK);
    // what is the clock of incomming signal to sync with?
    // not mandatory to call, the default is 24PPQN if not set
    // avaliable input resolutions -  should be always InputPPQN <= OutputPPQN
    // [ uClock.PPQN_1, uClock.PPQN_2, uClock.PPQN_4, uClock.PPQN_8, uClock.PPQN_12, uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ]
    uClock.setInputPPQN(uClock.PPQN_24);
  }

  // inits the clock library
  uClock.init();

  // starts clock
  uClock.start();
}

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


================================================
FILE: examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino
================================================
/* USB MIDI Sync Slave Box Monitor
 *
 * This example demonstrates how to create a
 * MIDI hid compilant slave clock box with
 * monitor support using oled displays
 *
 * You need the following libraries to make it work
 * - Midi Library
 * - USB-MIDI and MIDIUSB
 * - u8g2
 * - uClock
 * This example code is in the public domain.
 */

#include <USB-MIDI.h>
#include <U8x8lib.h>

//
// BPM Clock support
//
#include <uClock.h>

USBMIDI_CREATE_DEFAULT_INSTANCE();
U8X8 * u8x8;

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

float bpm = 126.0;
uint8_t bpm_blink_timer = 1;
uint8_t clock_state = 1;
uint8_t clock_mode = 0;

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  MIDI.sendRealTime(MIDI_CLOCK);
  handle_bpm_led(tick);
}

void onClockStart() {
  MIDI.sendRealTime(MIDI_START);
}

void onClockStop() {
  MIDI.sendRealTime(MIDI_STOP);
}

// External clock handlers
void onExternalClock()
{
  uClock.clockMe();
}

void onExternalStart()
{
  uClock.start();
}

void onExternalStop()
{
  uClock.stop();
}

void setup() {
  //
  // MIDI setup
  //
  MIDI.begin();
  MIDI.setHandleClock(onExternalClock);
  MIDI.setHandleStart(onExternalStart);
  MIDI.setHandleStop(onExternalStop);

  //
  // OLED setup
  // Please check you oled model to correctly init him
  //
  //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8->begin();
  u8x8->setFont(u8x8_font_pressstart2p_r);
  u8x8->clear();
  u8x8->setFlipMode(true);
  u8x8->drawUTF8(0, 0, "uClock");

  //
  // uClock Setup
  //
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // set main clock rate for input(expected sync signal rate) MIDI 24PPQN clock based
  uClock.setInputPPQN(uClock.PPQN_24);
  // For MIDI Sync Start and Stop
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);
  uClock.setClockMode(uClock.EXTERNAL_CLOCK);
  // for smooth slave tempo calculate display you should raise the
  // buffer_size of ext_interval_buffer in between 64 to 128. 254 max size.
  // note: this doesn't impact on sync time, only display time getTempo()
  // if you dont want to use it, it is default set it to 1 for memory save
  uClock.setExtIntervalBuffer(128);

  // init uClock
  uClock.init();

  //uClock.setTempo(136.5);
  //uClock.start();
}

void loop() {
  while(MIDI.read()) {}
  // DO NOT ADD MORE PROCESS HERE AT THE COST OF LOSING CLOCK SYNC
  // Since arduino make use of Serial RX interruption we need to
  // read Serial as fast as we can on the loop
  if (bpm != uClock.getTempo()) {
    bpm = uClock.getTempo();
    u8x8->drawUTF8(8, 7, String(bpm, 1).c_str());
    u8x8->drawUTF8(8+5, 7, "BPM");
    // clear display ghost number for 2 digit
    // coming from 3 digit bpm changes
    if (bpm < 100) {
      u8x8->drawUTF8(8+4, 7, " ");
    }
  }
  if (clock_state != uClock.clock_state) {
    clock_state = uClock.clock_state;
    if (clock_state >= 1) {
      u8x8->drawUTF8(0, 7, "Playing");
    } else {
      u8x8->drawUTF8(0, 7, "Stopped");
    }
  }
  if (clock_mode != uClock.getClockMode()) {
    clock_mode = uClock.getClockMode();
    if (clock_mode == uClock.EXTERNAL_CLOCK) {
      u8x8->drawUTF8(10, 0, "Slave ");
    } else {
      u8x8->drawUTF8(10, 0, "Master");
    }
  }
}


================================================
FILE: examples/MidiClock/MidiClock.ino
================================================
#include "Arduino.h"
#include <uClock.h>

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

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

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

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

void setup()
{

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

  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);

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

}

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

}


================================================
FILE: examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino
================================================
/*
 * USB/Uart MIDI Sync Box
 *
 * This example code is in the public domain.
 *
 */

#include <Adafruit_TinyUSB.h>
#include <MIDI.h>

#include <uClock.h>

// Instantiate the MIDI interfaces
Adafruit_USBD_MIDI usb_midi;
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

// Do your rpi 2040 has a ws2812 RGB LED? set the pin!
// otherwise keep it commented for normal LED_BUILTIN led blinking
#define WS2812_BUILTIN_LED  16

uint8_t bpm_blink_timer = 1;
void handle_bpm_led(uint32_t tick)
{
  // BPM led indicator
  if ( !(tick % (96)) || (tick == 1) ) {  // first of 4 quarter pulse will flash longer
    bpm_blink_timer = 8;
    ledOn();
  } else if ( !(tick % (24)) ) {   // each quarter led on
    bpm_blink_timer = 1;
    ledOn();
  } else if ( !(tick % bpm_blink_timer) ) { // get led off
    ledOff();
  }
}

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  MIDI.sendRealTime(midi::Clock);
  MIDI_USB.sendRealTime(midi::Clock);
  // blink tempo
  handle_bpm_led(tick);
}

void onClockStart() {
  MIDI.sendRealTime(midi::Start);
  MIDI_USB.sendRealTime(midi::Start);
}

void onClockStop() {
  MIDI.sendRealTime(midi::Stop);
  MIDI_USB.sendRealTime(midi::Stop);
}

void setup() {
#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
  // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040
  TinyUSB_Device_Init(0);
#endif

  // Initialize USB midi stack
  MIDI_USB.begin(MIDI_CHANNEL_OMNI);
  // Initialize UART midi stack
  MIDI.begin(MIDI_CHANNEL_OMNI);

  // Initialize builtin led for clock timer blinking
  initBlinkLed();

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);
  // Starts the clock, tick-tac-tick-tac..
  uClock.start();
}

// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment...
void loop() {
  // handle midi input?
  MIDI.read();
  MIDI_USB.read();
}


================================================
FILE: examples/RP2040UsbUartMasterClock/builtin_led.ino
================================================
#if defined(WS2812_BUILTIN_LED)
#include <Adafruit_NeoPixel.h>
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, WS2812_BUILTIN_LED, NEO_GRB + NEO_KHZ800);
#endif

// check the pinage for BUILTIN LED of your model in case LED_BUILTIN wont ligth up
// this is valid only if you're not using rgb version ws2812 (WS2812_BUILTIN_LED)
//#define LED_BUILTIN PIN_LED_B

void initBlinkLed() {
#if defined(WS2812_BUILTIN_LED)
  // use adafruit neo pixel 
  pixels.begin();
#else
  // normal led pin
  pinMode(LED_BUILTIN, OUTPUT);
#endif
}

void ledOn() {
#if defined(WS2812_BUILTIN_LED)
  pixels.setPixelColor(0, pixels.Color(0, 0, 20));
  pixels.show();  // turn the LED on (HIGH is the voltage level)
#else
  digitalWrite(LED_BUILTIN, LOW);
#endif
}

void ledOff() {
#if defined(WS2812_BUILTIN_LED)
  pixels.setPixelColor(0, pixels.Color(0, 0, 0));
  pixels.show();
#else
  digitalWrite(LED_BUILTIN, HIGH);
#endif
}

================================================
FILE: examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino
================================================
/* Uart MIDI out
 *
 * This example demonstrates how to send MIDI data via Uart
 * interface on STM32 family.
 *
 * This example code is in the public domain.
 *
 * Requires STM32Duino board manager to be installed.
 *
 * Define HardwareSerial using any available UART/USART.
 * Nucleo boards have UART/USART pins that are used by the ST-LINK interface (unless using solder bridging).
 *
 * Tested on Nucleo-F401RE and Nucleo-F072RB (PA9=D8 PA10=D2 on the Arduino pins)
 *
 * Code by midilab contact@midilab.co
 * Example modified by Jackson Devices contact@jacksondevices.com
 */
#include <uClock.h>

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

HardwareSerial Serial1(PA10, PA9);

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gear
  Serial1.write(MIDI_CLOCK);
  handle_bpm_led(tick);
}

void onClockStart() {
    // Send MIDI_START to external gear
  Serial1.write(MIDI_START);
}

void onClockStop() {
    // Send MIDI_STOP to external gear
  Serial1.write(MIDI_STOP);
}

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

  // An led to display BPM
  pinMode(LED_BUILTIN, OUTPUT);

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(120);
  // Starts the clock, tick-tac-tick-tac...
  uClock.start();
}

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

}


================================================
FILE: examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino
================================================
/* USB MIDI Sync Box
 *
 * This example demonstrates how to change the USB MIDI
 * device name on Teensy LC, 3.x and 4.x.  When creating more
 * that one MIDI device, custom names are much easier to
 * use when selecting each device in MIDI software on
 * your PC or Mac.  The custom name is in the "name.c" tab.
 *
 * Windows and Macintosh systems often cache USB info.
 * After changing the name, you may need to test on a
 * different computer to observe the new name, or take
 * steps to get your operating system to "forget" the
 * cached info.  (TODO: wanted... can anyone contribute
 * instructions for these systems)
 *
 * You must select MIDI from the "Tools > USB Type" menu
 *
 * This example code is in the public domain.
 */

#include <uClock.h>

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  usbMIDI.sendRealTime(usbMIDI.Clock);
  handle_bpm_led(tick);
}

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

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

void setup() {
  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);
  // Starts the clock, tick-tac-tick-tac...
  uClock.start();
}

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

}


================================================
FILE: examples/TeensyUsbMasterMidiClock/name.c
================================================
// To give your project a unique name, this code must be
// placed into a .c file (its own tab).  It can not be in
// a .cpp file or your main sketch (the .ino file).

#include "usb_names.h"

// Edit these lines to create your own name.  The length must
// match the number of characters in your custom name.

#define MIDI_NAME   {'u','c','l','o','c','k','_','1'}
#define MIDI_NAME_LEN  8

// Do not change this part.  This exact format is required by USB.

struct usb_string_descriptor_struct usb_string_product_name = {
        2 + MIDI_NAME_LEN * 2,
        3,
        MIDI_NAME
};


================================================
FILE: examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino
================================================
/* USB MIDI Sync Slave Box
 *
 * This example demonstrates how to change the USB MIDI
 * device name on Teensy LC, 3.x and 4.x.  When creating more
 * that one MIDI device, custom names are much easier to
 * use when selecting each device in MIDI software on
 * your PC or Mac.  The custom name is in the "name.c" tab.
 *
 * Windows and Macintosh systems often cache USB info.
 * After changing the name, you may need to test on a
 * different computer to observe the new name, or take
 * steps to get your operating system to "forget" the
 * cached info.  (TODO: wanted... can anyone contribute
 * instructions for these systems)
 *
 * You must select MIDI from the "Tools > USB Type" menu
 *
 * This example code is in the public domain.
 */

#include <uClock.h>

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears on other port?
  //usbMIDI.sendRealTime(usbMIDI.Clock);
  handle_bpm_led(tick);
}

void onClockStart() {
  //usbMIDI.sendRealTime(usbMIDI.Start);
}

void onClockStop() {
  //usbMIDI.sendRealTime(usbMIDI.Stop);
}

// External clock handlers
void onExternalClock()
{
  uClock.clockMe();
}

void onExternalStart()
{
  uClock.start();
}

void onExternalStop()
{
  uClock.stop();
}

void setup() {
  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);

  // Setup realtime midi event handlers
  usbMIDI.setHandleClock(onExternalClock);
  usbMIDI.setHandleStart(onExternalStart);
  usbMIDI.setHandleStop(onExternalStop);

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // set main clock rate for input(expected sync signal rate) MIDI 24PPQN clock based
  uClock.setInputPPQN(uClock.PPQN_24);
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // set to external sync mode
  uClock.setClockMode(uClock.EXTERNAL_CLOCK);

  // Inits the clock
  uClock.init();
}

void loop() {
  // Grab all midi data as fast as we can!
  while (usbMIDI.read()) {}
}


================================================
FILE: examples/TeensyUsbSlaveMidiClock/name.c
================================================
// To give your project a unique name, this code must be
// placed into a .c file (its own tab).  It can not be in
// a .cpp file or your main sketch (the .ino file).

#include "usb_names.h"

// Edit these lines to create your own name.  The length must
// match the number of characters in your custom name.

#define MIDI_NAME   {'u','c','l','o','c','k','_','1'}
#define MIDI_NAME_LEN  8

// Do not change this part.  This exact format is required by USB.

struct usb_string_descriptor_struct usb_string_product_name = {
        2 + MIDI_NAME_LEN * 2,
        3,
        MIDI_NAME
};


================================================
FILE: examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino
================================================
/* USB MIDI Sync Slave Box Monitor
 *
 * This example demonstrates how to create a
 * MIDI hid compilant slave clock box using
 * Teensy LC, 3.x and 4.x with
 * monitor support using oled displays
 *
 * Making use of a 250 usceconds timer to
 * handle MIDI input to avoid jitter on clock
 *
 * You need the following libraries to make it work
 * - u8g2
 * - uClock
 *
 * This example code is in the public domain.
 */

#include <U8x8lib.h>

//
// BPM Clock support
//
#include <uClock.h>

U8X8 * u8x8;
IntervalTimer teensyTimer;

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

float bpm = 126;
uint8_t bpm_blink_timer = 1;
uint8_t clock_state = 1;
uint8_t clock_mode = 0;

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

// Internal clock handlers
void onSync24Callback(uint32_t tick) {
  // Send MIDI_CLOCK to external gears
  usbMIDI.sendRealTime(MIDI_CLOCK);
  handle_bpm_led(tick);
}

void onClockStart() {
  usbMIDI.sendRealTime(MIDI_START);
}

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

// External clock handlers
void onExternalClock()
{
  uClock.clockMe();
}

void onExternalStart()
{
  uClock.start();
}

void onExternalStop()
{
  uClock.stop();
}

void setup() {
  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);

  //
  // MIDI setup
  //
  usbMIDI.begin();
  usbMIDI.setHandleClock(onExternalClock);
  usbMIDI.setHandleStart(onExternalStart);
  usbMIDI.setHandleStop(onExternalStop);

  //
  // OLED setup
  // Please check you oled model to correctly init him
  //
  //u8x8 = new U8X8_SH1106_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8 = new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE);
  u8x8->begin();
  u8x8->setFont(u8x8_font_pressstart2p_r);
  u8x8->clear();
  u8x8->setFlipMode(true);
  u8x8->drawUTF8(0, 0, "uClock");

  //
  // uClock Setup
  //
  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  // set main clock rate for input(expected sync signal rate) MIDI 24PPQN clock based
  uClock.setInputPPQN(uClock.PPQN_24);
  // For MIDI Sync Start and Stop
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);
  uClock.setClockMode(uClock.EXTERNAL_CLOCK);
  // for smooth slave tempo calculate display you should raise the
  // buffer_size of ext_interval_buffer in between 64 to 128. 254 max size.
  // note: this doesn't impact on sync time, only display time getTempo()
  // if you dont want to use it, it is default set it to 1 for memory save
  uClock.setExtIntervalBuffer(128);

  // inits uClock
  uClock.init();

  // make use of 250us timer to handle midi input sync
  teensyTimer.begin(handleMidiInput, 250);
  teensyTimer.priority(80);
}

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

void loop() {
  if (bpm != uClock.getTempo()) {
    bpm = uClock.getTempo();
    u8x8->drawUTF8(8, 7, String(bpm, 1).c_str());
    u8x8->drawUTF8(8+5, 7, "BPM");
    // clear display ghost number for 2 digit
    // coming from 3 digit bpm changes
    if (bpm < 100) {
      u8x8->drawUTF8(8+4, 7, " ");
    }
  }
  if (clock_state != uClock.clock_state) {
    clock_state = uClock.clock_state;
    if (clock_state >= 1) {
      u8x8->drawUTF8(0, 7, "Playing");
    } else {
      u8x8->drawUTF8(0, 7, "Stopped");
    }
  }
  if (clock_mode != uClock.getClockMode()) {
    clock_mode = uClock.getClockMode();
    if (clock_mode == uClock.EXTERNAL_CLOCK) {
      u8x8->drawUTF8(10, 0, "Slave ");
    } else {
      u8x8->drawUTF8(10, 0, "Master");
    }
  }
}


================================================
FILE: examples/TeensyUsbSlaveMidiClockMonitor/name.c
================================================
// To give your project a unique name, this code must be
// placed into a .c file (its own tab).  It can not be in
// a .cpp file or your main sketch (the .ino file).

#include "usb_names.h"

// Edit these lines to create your own name.  The length must
// match the number of characters in your custom name.

#define MIDI_NAME   {'u','c','l','o','c','k','_','1'}
#define MIDI_NAME_LEN  8

// Do not change this part.  This exact format is required by USB.

struct usb_string_descriptor_struct usb_string_product_name = {
        2 + MIDI_NAME_LEN * 2,
        3,
        MIDI_NAME
};


================================================
FILE: examples/XiaoUsbMasterMidiClock/XiaoUsbMasterMidiClock.ino
================================================
/* USB MIDI Sync Box
 *
 * This example demonstrates how to change the USB MIDI
 * device name on Seeedstudio XIAO M0.
 *
 * This example code is in the public domain.
 *
 * Tested with Adafruit TinyUSB version 0.10.5
 *
 */
#include <Adafruit_TinyUSB.h>
#include <MIDI.h>

Adafruit_USBD_MIDI usb_midi;
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB);

//MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
#include <uClock.h>

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

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

void onClockStart() {
  MIDI_USB.sendRealTime(midi::Start);
}

void onClockStop() {
  MIDI_USB.sendRealTime(midi::Stop);
}

void setup() {
  MIDI_USB.begin(MIDI_CHANNEL_OMNI);

  // A led to count bpms
  pinMode(LED_BUILTIN, OUTPUT);

  // Setup our clock system
  // Set the callback function for the clock output to send MIDI Sync message based on 24PPQN
  uClock.setOnSync(uClock.PPQN_24, onSync24Callback);
  
  // Set the callback function for MIDI Start and Stop messages.
  uClock.setOnClockStart(onClockStart);
  uClock.setOnClockStop(onClockStop);

  // Inits the clock
  uClock.init();

  // Set the clock BPM to 126 BPM
  uClock.setTempo(126);
  // Starts the clock, tick-tac-tick-tac...
  uClock.start();
}

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

}


================================================
FILE: library.json
================================================
{
  "name": "uClock",
  "version": "2.3.0",
  "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)",
  "keywords": "bpm, clock, timing, tick, music, generator",
  "repository": {
    "type": "git",
    "url": "https://github.com/midilab/uClock.git"
  },
  "authors": [
    {
      "name": "Romulo Silva",
      "email": "contact@midilab.co",
      "url": "https://midilab.co",
      "maintainer": true
    }
  ],
  "license": "MIT",
  "homepage": "https://midilab.co/umodular/",
  "headers": "uClock.h",
  "dependencies": {},
  "frameworks": "Arduino",
  "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam,raspberrypi"
}


================================================
FILE: library.properties
================================================
name=uClock
version=2.3.0
author=Romulo Silva <contact@midilab.co>
maintainer=Romulo Silva <contact@midilab.co>
sentence=BPM clock generator for Arduino platform.
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)
category=Timing
url=https://github.com/midilab/uClock
architectures=avr,arm,samd,stm32,esp32,rp2040
includes=uClock.h


================================================
FILE: src/platforms/avr.h
================================================
#include <Arduino.h>

#define ATOMIC(X) noInterrupts(); X; interrupts();

// want a different avr clock support?
// TODO: we should do this using macro guards for avrs different clocks freqeuncy setup at compile time
#define AVR_CLOCK_FREQ	16000000

// forward declaration of uClockHandler
void uClockHandler();

// AVR ISR Entrypoint
ISR(TIMER1_COMPA_vect)
{
    uClockHandler();
}

void initTimer(uint32_t init_clock)
{
    ATOMIC(
        // 16bits Timer1 init
        // begin at 120bpm (48.0007680122882 Hz)
        TCCR1A = 0; // set entire TCCR1A register to 0
        TCCR1B = 0; // same for TCCR1B
        TCNT1  = 0; // initialize counter value to 0
        // set compare match register for 48.0007680122882 Hz increments
        OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536)
        // turn on CTC mode
        TCCR1B |= (1 << WGM12);
        // Set CS12, CS11 and CS10 bits for 8 prescaler
        TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
        // enable timer compare interrupt
        TIMSK1 |= (1 << OCIE1A);
    )
}

void setTimer(uint32_t us_interval)
{
    float tick_hertz_interval = 1/((float)us_interval/1000000);

    uint32_t ocr;
    uint8_t tccr = 0;

    // 16bits avr timer setup
    if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) {
        // Set CS12, CS11 and CS10 bits for 1 prescaler
        tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10);
    } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) {
        // Set CS12, CS11 and CS10 bits for 8 prescaler
        tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10);
    } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) {
        // Set CS12, CS11 and CS10 bits for 64 prescaler
        tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10);
    } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) {
        // Set CS12, CS11 and CS10 bits for 256 prescaler
        tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10);
    } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) {
        // Set CS12, CS11 and CS10 bits for 1024 prescaler
        tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10);
    } else {
        // tempo not achiavable
        return;
    }

    ATOMIC(
        TCCR1B = 0;
        OCR1A = ocr-1;
        TCCR1B |= (1 << WGM12);
        TCCR1B |= tccr;
    )
}


================================================
FILE: src/platforms/esp32-nofrertos.h
================================================
#include <Arduino.h>

#define TIMER_ID	0

hw_timer_t * _uclockTimer = NULL;
portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED;
#define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux);

// forward declaration of uClockHandler
void uClockHandler();

// ISR handler
void ARDUINO_ISR_ATTR handlerISR(void)
{
    uClockHandler();
}

void initTimer(uint32_t init_clock)
{
    _uclockTimer = timerBegin(init_clock);

    // attach to generic uclock ISR
    timerAttachInterrupt(_uclockTimer, &handlerISR);

    // init clock tick time
    timerAlarm(_uclockTimer, init_clock, true, 0); 
}

void setTimer(uint32_t us_interval)
{
    timerAlarmWrite(_uclockTimer, us_interval, true); 
}

================================================
FILE: src/platforms/esp32.h
================================================
#include <Arduino.h>
#include <freertos/task.h>
#include <freertos/semphr.h>

// esp32-specific timer
hw_timer_t * _uclockTimer = NULL;

// FreeRTOS main clock task size in bytes
#define CLOCK_STACK_SIZE    5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size
TaskHandle_t taskHandle;
// mutex to protect the shared resource
SemaphoreHandle_t _mutex;
// mutex control for task
#define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex);

// forward declaration of uClockHandler
void uClockHandler();

// ISR handler
void ARDUINO_ISR_ATTR handlerISR(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    // Send a notification to task1
    vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// task for user clock process
void clockTask(void *pvParameters)
{
    while (1) {
        // wait for a notification from ISR
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        uClockHandler();
    }
}

void initTimer(uint32_t init_clock)
{
    // initialize the mutex for shared resource access
    _mutex = xSemaphoreCreateMutex();

    // create the clockTask
    xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle);

    _uclockTimer = timerBegin(init_clock);

    // attach to generic uclock ISR
    timerAttachInterrupt(_uclockTimer, &handlerISR);

    // init clock tick time
    timerAlarm(_uclockTimer, init_clock, true, 0); 
}

void setTimer(uint32_t us_interval)
{
    timerAlarm(_uclockTimer, us_interval, true, 0); 
}

================================================
FILE: src/platforms/rp2040.h
================================================
#include <Arduino.h>
#include "pico/sync.h"

// RPi-specific timer
struct repeating_timer timer;

#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); }

// forward declaration of uClockHandler
void uClockHandler();

// ISR handler -- called when tick happens
bool handlerISR(repeating_timer *timer)
{
    uClockHandler();

    return true;
}

void initTimer(uint32_t init_clock) {
    // set up RPi interrupt timer
    // 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!
    add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer);
}

void setTimer(uint32_t us_interval) {
    cancel_repeating_timer(&timer);
    // 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!
    add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer);
}

================================================
FILE: src/platforms/samd.h
================================================
#include <Arduino.h>

// 24 bits timer
#include <TimerTCC0.h>
// uses TimerTcc0
// 16 bits timer
//#include <TimerTC3.h>
// uses TimerTc3
#define ATOMIC(X) noInterrupts(); X; interrupts();

// forward declaration of ISR
void uClockHandler();

void initTimer(uint32_t init_clock)
{
    TimerTcc0.initialize(init_clock);

    // attach to generic uclock ISR
    TimerTcc0.attachInterrupt(uClockHandler);
}

void setTimer(uint32_t us_interval)
{
    TimerTcc0.setPeriod(us_interval);
}

================================================
FILE: src/platforms/software.h
================================================
#include <Arduino.h>

/* 
    Generic fallback approach that doesn't rely on any particular MCU's interrupts or RTOS threads etc.
    Simply checks micros() and compares last time tick happened and interval size to determine when a tick is due.
    requires calling softwareTimerHandler(micros()); inside loop() in order to trigger tick processing. 
    function signature: void softwareTimerHandler(uint32_t micros_time);

    @author     Doctea
*/

#define ATOMIC(X) X;

// forward declaration of ISR
void uClockHandler();

uint32_t uclock_last_time_ticked;
uint32_t uclock_us_interval;

// call this as often as possible to tick the uClock
void softwareTimerHandler(uint32_t micros_time) {
    if (micros_time - uclock_last_time_ticked >= uclock_us_interval) {
        uclock_last_time_ticked = micros_time;
        uClockHandler();
    }
}

void initTimer(uint32_t init_clock)
{
    // basically nothing to do for software-implemented version..?
    uclock_last_time_ticked = micros();
}

void setTimer(uint32_t us_interval)
{
    uclock_us_interval = us_interval;
}

================================================
FILE: src/platforms/stm32.h
================================================
#include <Arduino.h>

#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION  < 0x01090000)
  #error "Due to API change, this library is compatible with STM32_CORE_VERSION >= 0x01090000. Please update/install stm32duino core."
#endif

#if defined(TIM1)
  TIM_TypeDef * TimerInstance = TIM1;
#else
  TIM_TypeDef * TimerInstance = TIM2;
#endif

// instantiate HardwareTimer object
HardwareTimer * _uclockTimer = new HardwareTimer(TimerInstance);

#define ATOMIC(X) noInterrupts(); X; interrupts();

// forward declaration of ISR
void uClockHandler();

void initTimer(uint32_t us_interval)
{
  _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT);
  _uclockTimer->attachInterrupt(uClockHandler);
  _uclockTimer->resume();
}

void setTimer(uint32_t us_interval)
{
  _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT);
  _uclockTimer->refresh();
}



================================================
FILE: src/platforms/teensy.h
================================================
#include <Arduino.h>

#define ATOMIC(X) noInterrupts(); X; interrupts();

IntervalTimer _uclockTimer;

// forward declaration of ISR
void uClockHandler();

void initTimer(uint32_t init_clock)
{
    _uclockTimer.begin(uClockHandler, init_clock);

    // Set the interrupt priority level, controlling which other interrupts
    // this timer is allowed to interrupt. Lower numbers are higher priority,
    // with 0 the highest and 255 the lowest. Most other interrupts default to 128.
    // As a general guideline, interrupt routines that run longer should be given
    // lower priority (higher numerical values).
    _uclockTimer.priority(80);
}

void setTimer(uint32_t us_interval)
{
    _uclockTimer.update(us_interval);
}


================================================
FILE: src/uClock.cpp
================================================
/*!
 *  @file       uClock.cpp
 *  Project     BPM clock generator for Arduino
 *  @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)
 *  @version    2.3.0
 *  @author     Romulo Silva
 *  @date       10/06/2017
 *  @license    MIT - (c) 2025 - Romulo Silva - contact@midilab.co
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include "uClock.h"

//
// Compile time selection of Platform implementation of timer setup/control/handler
//
#if !defined(USE_UCLOCK_SOFTWARE_TIMER)
    //
    // General Arduino AVRs port
    //
    #if defined(ARDUINO_ARCH_AVR)
        #include "platforms/avr.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
    //
    // Teensyduino ARMs port
    //
    #if defined(TEENSYDUINO)
        #include "platforms/teensy.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
    //
    // Seedstudio XIAO M0 port
    //
    #if defined(SEEED_XIAO_M0)
        #include "platforms/samd.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
    //
    // ESP32 family
    //
    #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32)
        #include "platforms/esp32.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
    //
    // STM32XX family
    //
    #if defined(ARDUINO_ARCH_STM32)
        #include "platforms/stm32.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
    //
    // RP2040 (Raspberry Pico) family
    //
    #if defined(ARDUINO_ARCH_RP2040)
        #include "platforms/rp2040.h"
        #define UCLOCK_PLATFORM_FOUND
    #endif
#endif

//
// Software Timer for generic, board-agnostic, not-accurate, no-interrupt, software-only port
// No hardware timer support? fallback to USE_UCLOCK_SOFTWARE_TIMER
//
#if !defined(UCLOCK_PLATFORM_FOUND)
    #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().")
    #include "platforms/software.h"
    #if !defined(USE_UCLOCK_SOFTWARE_TIMER)
        #define USE_UCLOCK_SOFTWARE_TIMER
    #endif
#endif

//
// Platform specific timer handler/setup/control wrappers
//
// global timer counter
volatile uint32_t _millis = 0;

// called each tick genarated from platform specific timer
void uClockHandler()
{
    _millis = millis();
    uClock.handleInternalClock();
}

// initTimer(uint32_t us_interval) and setTimer(uint32_t us_interval)
// are defined at platform specific code and are platform dependent
void uClockInitTimer()
{
    // initialize at 120bpm as default
    initTimer(uClock.bpmToMicroSeconds(120.00));
}

void uClockSetTimerTempo(float bpm)
{
    setTimer(uClock.bpmToMicroSeconds(bpm));
}

namespace umodular { namespace clock {

static inline uint32_t clock_diff(uint32_t old_clock, uint32_t new_clock)
{
    if (new_clock >= old_clock) {
        return new_clock - old_clock;
    } else {
        return new_clock + (4294967295UL - old_clock);
    }
}

uClockClass::uClockClass()
{
    resetCounters();
}

uClockClass::~uClockClass()
{
    if (sync_callbacks)
        delete[] sync_callbacks;

    if (ext_interval_buffer)
        delete[] ext_interval_buffer;

    if (tracks)
        delete[] tracks;
}

void uClockClass::init()
{
    if (ext_interval_buffer == nullptr)
        setExtIntervalBuffer(1);

    // initialize reference data
    calculateReferencedata();
    // initialize hardware timer
    uClockInitTimer();
    // first interval calculus
    setTempo(tempo);
}

void uClockClass::handleInternalClock()
{
    static uint32_t counter = 0;
    static uint32_t sync_interval = 0;

    // for debug usage while developing any application under uClock
    ++int_overflow_counter;

    if (clock_state <= STARTING) // STOPED=0, PAUSED=1, STARTING=2, SYNCING=3, STARTED=4
        return;

    // tick phase lock and external tempo match for EXTERNAL_CLOCK mode
    if (clock_mode == EXTERNAL_CLOCK) {
        // Tick Phase-lock
        if (labs(int_clock_tick - ext_clock_tick) > 1) {
            // only update tick at a full quarter or phase_lock_quarters * a quarter
            // how many quarters to count until we phase-lock?
            if ((ext_clock_tick * mod_clock_ref) % (output_ppqn*phase_lock_quarters) == 0) {
                tick = ext_clock_tick * mod_clock_ref;
                int_clock_tick = ext_clock_tick;
                // update any counter reference to lock with int_clock_tick
                for (uint8_t track=0; track < track_slots_size; track++) {
                    tracks[track].step_counter = tick/mod_step_ref;
                    tracks[track].mod_step_counter = 0;
                }
                // update counter reference for sync callbacks
                for (uint8_t i = 0; i < sync_callback_size; i++) {
                    if (sync_callbacks[i].callback) {
                        sync_callbacks[i].tick = tick/sync_callbacks[i].sync_ref;
                        sync_callbacks[i].mod_counter = 0;
                    }
                }
            }
        }

        // any external interval avaliable to start sync timer?
        if (ext_interval > 0) {
            counter = ext_interval;
            sync_interval = clock_diff(ext_clock_us, micros());

            // phase-multiplier interval
            if (int_clock_tick <= ext_clock_tick) {
                counter -= (sync_interval * PHASE_FACTOR) >> 8;
            } else {
                if (counter > sync_interval) {
                    counter += ((counter - sync_interval) * PHASE_FACTOR) >> 8;
                }
            }

            external_tempo = constrainBpm(freqToBpm(counter));
            if (external_tempo != tempo) {
                tempo = external_tempo;
                uClockSetTimerTempo(tempo);
            }
        }
    }

    // main input clock counter control
    if (mod_clock_counter == mod_clock_ref)
        mod_clock_counter = 0;
    // process internal clock signal
    // int_clock_tick is the internal clock reference. mainly used for external clock phase lock
    if (mod_clock_counter == 0)
        ++int_clock_tick;
    ++mod_clock_counter;

    // sync callbacks
    for (uint8_t i = 0; i < sync_callback_size; i++) {
        if (sync_callbacks[i].mod_counter == sync_callbacks[i].sync_ref)
            sync_callbacks[i].mod_counter = 0;
        if (sync_callbacks[i].mod_counter == 0) {
            sync_callbacks[i].callback(sync_callbacks[i].tick);
            // tick sync callback
            ++sync_callbacks[i].tick;
        }
        ++sync_callbacks[i].mod_counter;
    }

    // StepSeq extension: step callback to support 16th old school style sequencers
    // with builtin shuffle - process onStepCallback()
    if (tracks)
        stepSeqTick();

    // main PPQNCallback
    if (onOutputPPQNCallback)
        onOutputPPQNCallback(tick);

    // internal ticking
    ++tick;

    // for debug usage while developing any application under uClock
    --int_overflow_counter;
}

void uClockClass::handleExternalClock()
{
    static uint32_t now_clock_us = 0;
    static uint8_t start_sync_counter = 0;

    // for debug usage while developing any application under uClock
    ++ext_overflow_counter;

    // calculate and store ext_interval
    now_clock_us = micros();
    if (ext_clock_us > 0)
        ext_interval = clock_diff(ext_clock_us, now_clock_us);
    ext_clock_us = now_clock_us;

    // external clock tick me!
    ext_clock_tick++;

    switch (clock_state) {
        case STARTING:
            clock_state = SYNCING;
            start_sync_counter = 4;
            break;
        case SYNCING:
            if (--start_sync_counter == 0)
                clock_state = STARTED;
            break;
        default:
            // accumulate interval incomming ticks data for getTempo() smooth reads on slave clock_mode
            if (ext_interval > 0) {
                ext_interval_buffer[ext_interval_idx] = ext_interval;
                if(++ext_interval_idx >= ext_interval_buffer_size)
                    ext_interval_idx = 0;
            }
            break;
    }

    // for debug usage while developing any application under uClock
    --ext_overflow_counter;
}

void uClockClass::clockMe()
{
    ATOMIC(handleExternalClock())
}

void uClockClass::start()
{
    ATOMIC(resetCounters())
    start_timer = millis();

    if (clock_mode == INTERNAL_CLOCK) {
        ATOMIC(clock_state = STARTED)
    } else {
        ATOMIC(clock_state = STARTING)
    }

    if (onClockStartCallback)
        onClockStartCallback();
}

void uClockClass::stop()
{
    ATOMIC(clock_state = STOPED)
    start_timer = 0;
    if (onClockStopCallback)
        onClockStopCallback();
}

void uClockClass::pause()
{
    if (clock_state == STARTED) {
        ATOMIC(clock_state = PAUSED)
        if (onClockPauseCallback)
            onClockPauseCallback();
    } else if (clock_state == PAUSED) {
        if (clock_mode == INTERNAL_CLOCK) {
            ATOMIC(clock_state = STARTED)
        } else if (clock_mode == EXTERNAL_CLOCK) {
            ATOMIC(clock_state = STARTING)
        }
        if (onClockContinueCallback)
            onClockContinueCallback();
    }
}

void uClockClass::setClockMode(ClockMode tempo_mode)
{
    ATOMIC(
        clock_mode = tempo_mode;
        // trying to set external clock while playing?
        if (clock_mode == EXTERNAL_CLOCK && clock_state == STARTED)
            clock_state = STARTING;
    )
}

uClockClass::ClockMode uClockClass::getClockMode()
{
    return clock_mode;
}

// for software timer implementation(fallback for no timer board support)
void uClockClass::run()
{
#if defined(USE_UCLOCK_SOFTWARE_TIMER)
    // call software timer implementation
    softwareTimerHandler(micros());
#endif
}

void uClockClass::stepSeqTick()
{
    for (uint8_t track=0; track < track_slots_size; track++) {
        bool stepProcess = false;
        if (tracks[track].mod_step_counter == mod_step_ref)
            tracks[track].mod_step_counter = 0;
        if (!tracks[track].shuffle.tmplt.active) {
            if (tracks[track].mod_step_counter == 0)
                stepProcess = true;
        } else if (processShuffle(track)) {
            stepProcess = true;
        }

        if (stepProcess) {
            if (onStepGlobalCallback)
                onStepGlobalCallback(tracks[track].step_counter);
            if (onStepMultiCallback)
                onStepMultiCallback(tracks[track].step_counter, track);

            // going forward to the next step call
            ++tracks[track].step_counter;
        }
        ++tracks[track].mod_step_counter;
    }
}

void uClockClass::setShuffle(bool active, uint8_t track)
{
    if (tracks == nullptr)
        return;

    ATOMIC(tracks[track].shuffle.tmplt.active = active)
}

bool uClockClass::isShuffled(uint8_t track)
{
    if (tracks == nullptr)
        return false;

    return tracks[track].shuffle.tmplt.active;
}

void uClockClass::setShuffleSize(uint8_t size, uint8_t track)
{
    if (tracks == nullptr)
        return;

    if (size > MAX_SHUFFLE_TEMPLATE_SIZE)
        size = MAX_SHUFFLE_TEMPLATE_SIZE;
    ATOMIC(tracks[track].shuffle.tmplt.size = size)
}

void uClockClass::setShuffleData(uint8_t step, int8_t tick, uint8_t track)
{
    if (tracks == nullptr)
        return;

    if (step >= MAX_SHUFFLE_TEMPLATE_SIZE)
        return;
    ATOMIC(tracks[track].shuffle.tmplt.step[step] = tick)
}

void uClockClass::setShuffleTemplate(int8_t * shuff, uint8_t size, uint8_t track)
{
    if (tracks == nullptr)
        return;

    //uint8_t size = sizeof(shuff) / sizeof(shuff[0]);
    if (size > MAX_SHUFFLE_TEMPLATE_SIZE)
        size = MAX_SHUFFLE_TEMPLATE_SIZE;
    ATOMIC(tracks[track].shuffle.tmplt.size = size)
    for (uint8_t i=0; i < size; i++) {
        setShuffleData(i, shuff[i], track);
    }
}

int8_t uClockClass::getShuffleLength(uint8_t track)
{
    if (tracks == nullptr)
        return 0;

    return tracks[track].shuffle.shuffle_length_ctrl;
}

bool inline uClockClass::processShuffle(uint8_t track)
{
    if (tracks == nullptr)
        return false;

    int8_t mod_shuffle = 0;

    // check shuffle template of current
    int8_t shff = tracks[track].shuffle.tmplt.step[tracks[track].step_counter%tracks[track].shuffle.tmplt.size];

    if (tracks[track].shuffle.shuffle_shoot_ctrl == false && tracks[track].mod_step_counter == 0)
        tracks[track].shuffle.shuffle_shoot_ctrl = true;

    if (shff >= 0) {
        mod_shuffle = tracks[track].mod_step_counter - shff;
        // any late shuffle? we should skip next tracks[track].mod_step_counter == 0
        if (tracks[track].shuffle.last_shff < 0 && tracks[track].mod_step_counter != 1) {
            if (tracks[track].shuffle.shuffle_shoot_ctrl == true)
                tracks[track].shuffle.shuffle_shoot_ctrl = false;
            return false;
        }
    } else if (shff < 0) {
        mod_shuffle = tracks[track].mod_step_counter - (mod_step_ref + shff);
        tracks[track].shuffle.shuffle_shoot_ctrl = true;
    }

    tracks[track].shuffle.last_shff = shff;

    // shuffle_shoot_ctrl helps keep track if we have shoot or not a note for the step space of output_ppqn/4 pulses
    if (mod_shuffle == 0 && tracks[track].shuffle.shuffle_shoot_ctrl == true) {
        // keep track of next note shuffle for current note lenght control
        tracks[track].shuffle.shuffle_length_ctrl = tracks[track].shuffle.tmplt.step[(tracks[track].step_counter+1)%tracks[track].shuffle.tmplt.size];
        if (shff > 0)
            tracks[track].shuffle.shuffle_length_ctrl -= shff;
        if (shff < 0)
            tracks[track].shuffle.shuffle_length_ctrl += shff;
        tracks[track].shuffle.shuffle_shoot_ctrl = false;
        return true;
    }

    return false;
}

uint32_t uClockClass::bpmToMicroSeconds(float bpm)
{
    return (60000000.0f / (float)output_ppqn / bpm);
}

void uClockClass::calculateReferencedata()
{
    mod_clock_ref = output_ppqn / input_ppqn;
    mod_step_ref = output_ppqn / 4;
    // sync callback references update
    for (uint8_t i = 0; i < sync_callback_size; i++)
        sync_callbacks[i].sync_ref = output_ppqn / sync_callbacks[i].resolution;
}

void uClockClass::setOutputPPQN(PPQNResolution resolution)
{
    // dont allow PPQN lower than PPQN_4 for output clock (to avoid problems with mod_step_ref)
    if (resolution < PPQN_4)
        return;

    // dont allow output_ppqn lower than input_ppqn
    if (resolution < input_ppqn)
        return;

    ATOMIC(
        output_ppqn = resolution;
        calculateReferencedata();
    )
}

void uClockClass::setInputPPQN(PPQNResolution resolution)
{
    // dont allow input_ppqn greater than output_ppqn
    if (resolution > output_ppqn)
        return;

    ATOMIC(
        input_ppqn = resolution;
        calculateReferencedata();
    )
}

void uClockClass::setOnSync(PPQNResolution resolution, void (*callback)(uint32_t tick)) {
    // sets sync callback only if the resolution is lower or equal main clock rate
    if (resolution > output_ppqn)
        return;

    // alloc once and forever policy!
   	// reallocate by creating a new array, copying data, and deleting the old one
   	SyncCallback * new_sync_callbacks = new SyncCallback[sync_callback_size+1];
   	if (sync_callbacks != nullptr) {
  		memcpy(new_sync_callbacks, sync_callbacks, sizeof(SyncCallback) * (sync_callback_size+1));
  		delete[] sync_callbacks;
   	}
    sync_callbacks = new_sync_callbacks;

    sync_callbacks[sync_callback_size].callback = callback;
    sync_callbacks[sync_callback_size].resolution = resolution;

    ++sync_callback_size;
}

void uClockClass::setTempo(float bpm)
{
    if (clock_mode == EXTERNAL_CLOCK)
        return;

    if (bpm < MIN_BPM || bpm > MAX_BPM)
        return;

    ATOMIC(tempo = bpm)

    uClockSetTimerTempo(bpm);
}

float uClockClass::getTempo()
{
    if (clock_mode == EXTERNAL_CLOCK) {
        uint64_t acc = 0;
        uint8_t valid_buffer_size = 0;
        for (uint8_t i=0; i < ext_interval_buffer_size; i++) {
            if (ext_interval_buffer[i] > 0) {
                ATOMIC(acc += ext_interval_buffer[i])
                ++valid_buffer_size;
            }
        }
        if (acc == 0)
            return tempo;
        return constrainBpm(freqToBpm(acc / valid_buffer_size));
    }
    return tempo;
}

float inline uClockClass::freqToBpm(uint32_t freq)
{
    float usecs = 1/((float)freq/1000000.0);
    return (float)((float)(usecs/(float)input_ppqn) * 60.0);
}

float inline uClockClass::constrainBpm(float bpm)
{
    return (bpm < MIN_BPM) ? MIN_BPM : ( bpm > MAX_BPM ? MAX_BPM : bpm );
}

void uClockClass::setExtIntervalBuffer(size_t buffer_size)
{
    if (ext_interval_buffer != nullptr)
        return;

    // alloc once and forever policy
    ext_interval_buffer_size = buffer_size;
    ext_interval_buffer = new uint32_t[ext_interval_buffer_size];

    for (uint8_t i=0; i < ext_interval_buffer_size; i++)
        ext_interval_buffer[i] = 0;
}

void uClockClass::setPhaseLockQuartersCount(uint8_t count)
{
    ATOMIC(phase_lock_quarters = count)
}

void uClockClass::resetCounters()
{
    tick = 0;
    mod_clock_counter = 0;
    int_clock_tick = 0;
    ext_clock_tick = 0;
    ext_clock_us = 0;
    ext_interval = 0;
    //ext_interval_idx = 0;

    // sync output counters
    for (uint8_t i = 0; i < sync_callback_size; i++) {
        sync_callbacks[i].mod_counter = 0;
        sync_callbacks[i].tick = 0;
    }

    // stepseq counters
    for (uint8_t track=0; track < track_slots_size; track++) {
        tracks[track].step_counter = 0;
        tracks[track].mod_step_counter = 0;
    }

    // external bpm read buffer
    //for (uint8_t i=0; i < ext_interval_buffer_size; i++)
    //    ext_interval_buffer[i] = 0;
}

void uClockClass::tap()
{
    // we can make use of mod_sync1_ref for tap
    //uint8_t mod_tap_ref = output_ppqn / PPQN_1;
    // we only set tap if ClockMode is INTERNAL_CLOCK
}

// elapsed time support
uint8_t uClockClass::getNumberOfSeconds(uint32_t time)
{
    if ( time == 0 ) {
        return time;
    }
    return ((_millis - time) / 1000) % SECS_PER_MIN;
}

uint8_t uClockClass::getNumberOfMinutes(uint32_t time)
{
    if ( time == 0 ) {
        return time;
    }
    return (((_millis - time) / 1000) / SECS_PER_MIN) % SECS_PER_MIN;
}

uint8_t uClockClass::getNumberOfHours(uint32_t time)
{
    if ( time == 0 ) {
        return time;
    }
    return (((_millis - time) / 1000) % SECS_PER_DAY) / SECS_PER_HOUR;
}

uint8_t uClockClass::getNumberOfDays(uint32_t time)
{
    if ( time == 0 ) {
        return time;
    }
    return ((_millis - time) / 1000) / SECS_PER_DAY;
}

uint32_t uClockClass::getNowTimer()
{
    return _millis;
}

uint32_t uClockClass::getPlayTime()
{
    return start_timer;
}

uint16_t uClockClass::getIntOverflowCounter()
{
    uint16_t counter = 0;
    ATOMIC(counter = int_overflow_counter)
    return counter;
}

uint16_t uClockClass::getExtOverflowCounter()
{
    uint16_t counter = 0;
    ATOMIC(counter = ext_overflow_counter)
    return counter;
}

} } // end namespace umodular::clock

umodular::clock::uClockClass uClock;


================================================
FILE: src/uClock.h
================================================
/*!
 *  @file       uClock.h
 *  Project     BPM clock generator for Arduino
 *  @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)
 *  @version    2.3.0
 *  @author     Romulo Silva
 *  @date       10/06/2017
 *  @license    MIT - (c) 2025 - Romulo Silva - contact@midilab.co
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#ifndef __U_CLOCK_H__
#define __U_CLOCK_H__

#include <Arduino.h>
#include <inttypes.h>

namespace umodular { namespace clock {

// Shuffle templates are specific for each PPQN output resolution
// min: -(output_ppqn/4)-1 ticks
// max: (output_ppqn/4)-1 ticks
// adjust the size of you template if more than 16 shuffle step info needed
#define MAX_SHUFFLE_TEMPLATE_SIZE   16

#define MIN_BPM	1
#define MAX_BPM	500

#define PHASE_FACTOR 16

#define MICROS_PER_MIN (60000000UL)
#define SECS_PER_MIN  (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY  (SECS_PER_HOUR * 24L)

class uClockClass {

    public:
        enum ClockMode {
            INTERNAL_CLOCK = 0,
            EXTERNAL_CLOCK
        };

        enum ClockState {
            STOPED = 0,
            PAUSED,
            STARTING,
            SYNCING,
            STARTED
        };

        enum PPQNResolution {
            PPQN_1 = 1,
            PPQN_2 = 2,
            PPQN_4 = 4,
            PPQN_8 = 8,
            PPQN_12 = 12,
            PPQN_24 = 24,
            PPQN_48 = 48,
            PPQN_96 = 96,
            PPQN_384 = 384,
            PPQN_480 = 480,
            PPQN_960 = 960
        };

        ClockState clock_state = STOPED;

        uClockClass();
        ~uClockClass();

        // set main input and output clock rates
        void setOutputPPQN(PPQNResolution resolution);
        void setInputPPQN(PPQNResolution resolution);
        
        // callbacks setup
        void setOnOutputPPQN(void (*callback)(uint32_t tick)) {
            onOutputPPQNCallback = callback;
        }

        // multiple output sync clock signatures callback register
        void setOnSync(PPQNResolution resolution, void (*callback)(uint32_t tick));

        void setOnClockStart(void (*callback)()) {
            onClockStartCallback = callback;
        }

        void setOnClockStop(void (*callback)()) {
            onClockStopCallback = callback;
        }

        void setOnClockPause(void (*callback)()) {
            onClockPauseCallback = callback;
        }

        void setOnClockContinue(void (*callback)()) {
            onClockContinueCallback = callback;
        }

        // Step Seq extension support?
        // Step Seq supports per track control:
        // shuffle: YES
        // shift: ROADMAP
        // direction: ROADMAP
        // keep API compatibility for setOnStep global track
        void setOnStep(void (*callback)(uint32_t step)) {
            if (tracks == nullptr) {
                track_slots_size = 1;
                // alloc once and forever policy
                tracks = new TRACK_SLOT[track_slots_size];
                onStepGlobalCallback = callback;
            }
        }
        // extended stepSeq multitrack support for setOnStep
        void setOnStep(void (*callback)(uint32_t step, uint8_t track), uint8_t track_number) {
            if (tracks == nullptr) {
                track_slots_size = track_number;
                // alloc once and forever policy
                tracks = new TRACK_SLOT[track_slots_size];
                onStepMultiCallback = callback;
            }
        }

        void init();

        // the main processing of internal and external tick system
        void handleInternalClock();
        void handleExternalClock();

        // external class control
        void start();
        void stop();
        void pause();
        void setTempo(float bpm);
        float getTempo();

        // Step Seq extension for global and multi track sequences control
        // void setShift(int8_t shift, uint8_t track = 0);
        // void setDirection(uint8_t direction, uint8_t track = 0);
        void setShuffle(bool active, uint8_t track = 0);
        bool isShuffled(uint8_t track = 0);
        void setShuffleSize(uint8_t size, uint8_t track = 0);
        void setShuffleData(uint8_t step, int8_t tick, uint8_t track = 0);
        void setShuffleTemplate(int8_t * shuff, uint8_t size, uint8_t track = 0);
        // use this to know how many positive or negative ticks to add to current note length
        int8_t getShuffleLength(uint8_t track = 0);

        // for software timer implementation(fallback for no board support)
        void run();

        // external timming control
        void setClockMode(ClockMode tempo_mode);
        ClockMode getClockMode();
        void clockMe();
        void setPhaseLockQuartersCount(uint8_t count);
        // for smooth slave tempo calculate display you should raise the
        // buffer_size of ext_interval_buffer in between 64 to 128. 254 max size.
        // note: this doesn't impact on sync time, only display time getTempo()
        // if you dont want to use it, it is default set it to 1 for memory save
        void setExtIntervalBuffer(size_t buffer_size);

        // todo!
        void tap();

        // elapsed time support
        uint8_t getNumberOfSeconds(uint32_t time);
        uint8_t getNumberOfMinutes(uint32_t time);
        uint8_t getNumberOfHours(uint32_t time);
        uint8_t getNumberOfDays(uint32_t time);
        uint32_t getNowTimer();
        uint32_t getPlayTime();

        // interrupt overflow debug
        uint16_t getIntOverflowCounter();
        uint16_t getExtOverflowCounter();

        uint32_t bpmToMicroSeconds(float bpm);
        
        void resetCounters();

    private:
        float inline freqToBpm(uint32_t freq);
        float inline constrainBpm(float bpm);
        void calculateReferencedata();

        // callbacks
        void (*onOutputPPQNCallback)(uint32_t tick) = nullptr;
        void (*onClockStartCallback)() = nullptr;
        void (*onClockStopCallback)() = nullptr;
        void (*onClockPauseCallback)() = nullptr;
        void (*onClockContinueCallback)() = nullptr;
        // step seq extension for global and multi track sequences control
        void (*onStepGlobalCallback)(uint32_t step) = nullptr;
        void (*onStepMultiCallback)(uint32_t step, uint8_t track) = nullptr;
        

        typedef struct {
            bool active = false;
            uint8_t size = MAX_SHUFFLE_TEMPLATE_SIZE;
            int8_t step[MAX_SHUFFLE_TEMPLATE_SIZE] = {0}; // int8 supports max PPQN_480 of internal clock resolution
        } SHUFFLE_TEMPLATE;
        
        typedef struct {
            volatile SHUFFLE_TEMPLATE tmplt;
            int8_t last_shff = 0; // int8 supports max PPQN_480 of internal clock resolution
            bool shuffle_shoot_ctrl = true;
            volatile int8_t shuffle_length_ctrl = 0;
        } SHUFFLE_DATA;
        
        typedef struct {
            SHUFFLE_DATA shuffle;
            //int8_t shift = 0;
            //uint8_t direction = 0;
            uint32_t step_counter = 0;
            uint8_t mod_step_counter;
        } TRACK_SLOT;
        
        // sync callback structure for dynamic multiple sync outputs support
        struct SyncCallback {
            void (*callback)(uint32_t tick) = nullptr;
            uint8_t mod_counter = 0;
            uint16_t sync_ref = 0;
            uint32_t tick = 0;
            PPQNResolution resolution;
        };
        
        // sync callback data
        SyncCallback * sync_callbacks = nullptr;
        uint8_t sync_callback_size = 0;

        // clock core
        // input/output tick resolution
        PPQNResolution output_ppqn = PPQN_96;
        PPQNResolution input_ppqn = PPQN_24;
        volatile float tempo = 120.0;
        volatile ClockMode clock_mode = INTERNAL_CLOCK;
        uint32_t start_timer = 0;

        // output and internal counters, ticks and references
        volatile uint32_t tick = 0;
        volatile uint32_t int_clock_tick = 0;
        uint8_t mod_step_ref = 0;
        uint8_t mod_clock_counter = 0;
        uint16_t mod_clock_ref = 0;

        // external clock control
        volatile uint32_t ext_clock_us = 0;
        volatile uint32_t ext_clock_tick = 0;
        volatile uint32_t ext_interval = 0;
        volatile float external_tempo = tempo;
        uint8_t phase_lock_quarters = 1;

        // debug interrupts overflow
        volatile uint16_t int_overflow_counter = 0;
        volatile uint16_t ext_overflow_counter = 0;

        // StepSeq extension
        // main stepseq tick processor
        void stepSeqTick();
        // stepseq shuffle processor
        bool inline processShuffle(uint8_t track = 0);
        TRACK_SLOT * tracks = nullptr;
        size_t track_slots_size = 0;

        // external clock bpm read calculus getTempo()
        volatile uint32_t * ext_interval_buffer = nullptr;
        size_t ext_interval_buffer_size = 0;
        uint16_t ext_interval_idx = 0;
};

} } // end namespace umodular::clock

extern umodular::clock::uClockClass uClock;

extern "C" {
    extern volatile uint32_t _millis;
}

#endif /* __U_CLOCK_H__ */
Download .txt
gitextract_wxko6giz/

├── LICENSE
├── README.md
├── examples/
│   ├── AVRUartSlaveMidiClockMonitor/
│   │   └── AVRUartSlaveMidiClockMonitor.ino
│   ├── AcidStepSequencer/
│   │   ├── AcidStepSequencer.ino
│   │   ├── DefaultUserInterface.ino
│   │   ├── HardwareInterface.ino
│   │   └── README.md
│   ├── 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
Download .txt
SYMBOL INDEX (33 symbols across 13 files)

FILE: examples/TeensyUsbMasterMidiClock/name.c
  type usb_string_descriptor_struct (line 15) | struct usb_string_descriptor_struct

FILE: examples/TeensyUsbSlaveMidiClock/name.c
  type usb_string_descriptor_struct (line 15) | struct usb_string_descriptor_struct

FILE: examples/TeensyUsbSlaveMidiClockMonitor/name.c
  type usb_string_descriptor_struct (line 15) | struct usb_string_descriptor_struct

FILE: src/platforms/avr.h
  function ISR (line 13) | ISR(TIMER1_COMPA_vect)
  function initTimer (line 18) | void initTimer(uint32_t init_clock)
  function setTimer (line 37) | void setTimer(uint32_t us_interval)

FILE: src/platforms/esp32-nofrertos.h
  function handlerISR (line 13) | handlerISR(void)
  function initTimer (line 18) | void initTimer(uint32_t init_clock)
  function setTimer (line 29) | void setTimer(uint32_t us_interval)

FILE: src/platforms/esp32.h
  function handlerISR (line 20) | handlerISR(void)
  function clockTask (line 29) | void clockTask(void *pvParameters)
  function initTimer (line 38) | void initTimer(uint32_t init_clock)
  function setTimer (line 55) | void setTimer(uint32_t us_interval)

FILE: src/platforms/rp2040.h
  type repeating_timer (line 5) | struct repeating_timer
  function handlerISR (line 13) | bool handlerISR(repeating_timer *timer)
  function initTimer (line 20) | void initTimer(uint32_t init_clock) {
  function setTimer (line 26) | void setTimer(uint32_t us_interval) {

FILE: src/platforms/samd.h
  function initTimer (line 14) | void initTimer(uint32_t init_clock)
  function setTimer (line 22) | void setTimer(uint32_t us_interval)

FILE: src/platforms/software.h
  function softwareTimerHandler (line 21) | void softwareTimerHandler(uint32_t micros_time) {
  function initTimer (line 28) | void initTimer(uint32_t init_clock)
  function setTimer (line 34) | void setTimer(uint32_t us_interval)

FILE: src/platforms/stm32.h
  function initTimer (line 21) | void initTimer(uint32_t us_interval)
  function setTimer (line 28) | void setTimer(uint32_t us_interval)

FILE: src/platforms/teensy.h
  function initTimer (line 10) | void initTimer(uint32_t init_clock)
  function setTimer (line 22) | void setTimer(uint32_t us_interval)

FILE: src/uClock.cpp
  function uClockHandler (line 97) | void uClockHandler()
  function uClockInitTimer (line 105) | void uClockInitTimer()
  function uClockSetTimerTempo (line 111) | void uClockSetTimerTempo(float bpm)
  type umodular (line 116) | namespace umodular { namespace clock {
    type clock (line 116) | namespace clock {
      function clock_diff (line 118) | static inline uint32_t clock_diff(uint32_t old_clock, uint32_t new_c...

FILE: src/uClock.h
  function namespace (line 35) | namespace umodular { namespace clock {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (121K chars).
[
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2017 midilab\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 23494,
    "preview": "# uClock - BPM Clock Generator Library\n\nA professional-grade BPM clock generator library for Arduino and PlatformIO, des"
  },
  {
    "path": "examples/AVRUartSlaveMidiClockMonitor/AVRUartSlaveMidiClockMonitor.ino",
    "chars": 4372,
    "preview": "/* Uart MIDI Sync Slave Box Monitor\n *\n * This example demonstrates how to create a\n * MIDI slave clock box with\n * moni"
  },
  {
    "path": "examples/AcidStepSequencer/AcidStepSequencer.ino",
    "chars": 5268,
    "preview": "// Acid StepSequencer, a Roland TB303 step sequencer engine clone\n// author: midilab contact@midilab.co\n// under MIT lic"
  },
  {
    "path": "examples/AcidStepSequencer/DefaultUserInterface.ino",
    "chars": 7344,
    "preview": "\n#define SEQUENCER_MIN_BPM  50\n#define SEQUENCER_MAX_BPM  177\n\n// Ui config\n#define LOCK_POT_SENSTIVITY 3\n\n// hardware s"
  },
  {
    "path": "examples/AcidStepSequencer/HardwareInterface.ino",
    "chars": 2682,
    "preview": "\n#define POT_NUMBER    4\n#define BUTTON_NUMBER 6\n\n// pot data\ntypedef struct\n{\n  uint8_t pin;\n  uint16_t state;\n  bool l"
  },
  {
    "path": "examples/AcidStepSequencer/README.md",
    "chars": 532,
    "preview": "# TB303 Step Sequencer engine clone\n\nWith some user interface changes for cheap construction of a functional TB303 engin"
  },
  {
    "path": "examples/ESP32UartMasterMidiClock/ESP32UartMasterMidiClock.ino",
    "chars": 1933,
    "preview": "/* Uart MIDI Sync Box\n *\n * This example demonstrates how to change the Uart MIDI\n * device name on ESP32 family.\n *\n * "
  },
  {
    "path": "examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino",
    "chars": 3293,
    "preview": "#include <uClock.h>\n\n// external or internal sync?\nbool _external_sync_on = false;\n\n// the main uClock PPQN resolution t"
  },
  {
    "path": "examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino",
    "chars": 3900,
    "preview": "/* USB MIDI Sync Slave Box Monitor\n *\n * This example demonstrates how to create a\n * MIDI hid compilant slave clock box"
  },
  {
    "path": "examples/MidiClock/MidiClock.ino",
    "chars": 1422,
    "preview": "#include \"Arduino.h\"\n#include <uClock.h>\n\n// MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards.\n"
  },
  {
    "path": "examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino",
    "chars": 2379,
    "preview": "/*\n * USB/Uart MIDI Sync Box\n *\n * This example code is in the public domain.\n *\n */\n\n#include <Adafruit_TinyUSB.h>\n#inc"
  },
  {
    "path": "examples/RP2040UsbUartMasterClock/builtin_led.ino",
    "chars": 915,
    "preview": "#if defined(WS2812_BUILTIN_LED)\n#include <Adafruit_NeoPixel.h>\n#define NUMPIXELS 1\nAdafruit_NeoPixel pixels(NUMPIXELS, W"
  },
  {
    "path": "examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino",
    "chars": 2414,
    "preview": "/* Uart MIDI out\n *\n * This example demonstrates how to send MIDI data via Uart\n * interface on STM32 family.\n *\n * This"
  },
  {
    "path": "examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino",
    "chars": 2200,
    "preview": "/* USB MIDI Sync Box\n *\n * This example demonstrates how to change the USB MIDI\n * device name on Teensy LC, 3.x and 4.x"
  },
  {
    "path": "examples/TeensyUsbMasterMidiClock/name.c",
    "chars": 585,
    "preview": "// To give your project a unique name, this code must be\n// placed into a .c file (its own tab).  It can not be in\n// a "
  },
  {
    "path": "examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino",
    "chars": 2596,
    "preview": "/* USB MIDI Sync Slave Box\n *\n * This example demonstrates how to change the USB MIDI\n * device name on Teensy LC, 3.x a"
  },
  {
    "path": "examples/TeensyUsbSlaveMidiClock/name.c",
    "chars": 585,
    "preview": "// To give your project a unique name, this code must be\n// placed into a .c file (its own tab).  It can not be in\n// a "
  },
  {
    "path": "examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino",
    "chars": 4100,
    "preview": "/* USB MIDI Sync Slave Box Monitor\n *\n * This example demonstrates how to create a\n * MIDI hid compilant slave clock box"
  },
  {
    "path": "examples/TeensyUsbSlaveMidiClockMonitor/name.c",
    "chars": 585,
    "preview": "// To give your project a unique name, this code must be\n// placed into a .c file (its own tab).  It can not be in\n// a "
  },
  {
    "path": "examples/XiaoUsbMasterMidiClock/XiaoUsbMasterMidiClock.ino",
    "chars": 1919,
    "preview": "/* USB MIDI Sync Box\n *\n * This example demonstrates how to change the USB MIDI\n * device name on Seeedstudio XIAO M0.\n "
  },
  {
    "path": "library.json",
    "chars": 846,
    "preview": "{\n  \"name\": \"uClock\",\n  \"version\": \"2.3.0\",\n  \"description\": \"A Library to implement BPM clock tick calls using hardware"
  },
  {
    "path": "library.properties",
    "chars": 532,
    "preview": "name=uClock\nversion=2.3.0\nauthor=Romulo Silva <contact@midilab.co>\nmaintainer=Romulo Silva <contact@midilab.co>\nsentence"
  },
  {
    "path": "src/platforms/avr.h",
    "chars": 2401,
    "preview": "#include <Arduino.h>\n\n#define ATOMIC(X) noInterrupts(); X; interrupts();\n\n// want a different avr clock support?\n// TODO"
  },
  {
    "path": "src/platforms/esp32-nofrertos.h",
    "chars": 734,
    "preview": "#include <Arduino.h>\n\n#define TIMER_ID\t0\n\nhw_timer_t * _uclockTimer = NULL;\nportMUX_TYPE _uclockTimerMux = portMUX_INITI"
  },
  {
    "path": "src/platforms/esp32.h",
    "chars": 1593,
    "preview": "#include <Arduino.h>\n#include <freertos/task.h>\n#include <freertos/semphr.h>\n\n// esp32-specific timer\nhw_timer_t * _uclo"
  },
  {
    "path": "src/platforms/rp2040.h",
    "chars": 1031,
    "preview": "#include <Arduino.h>\n#include \"pico/sync.h\"\n\n// RPi-specific timer\nstruct repeating_timer timer;\n\n#define ATOMIC(X) { ui"
  },
  {
    "path": "src/platforms/samd.h",
    "chars": 482,
    "preview": "#include <Arduino.h>\n\n// 24 bits timer\n#include <TimerTCC0.h>\n// uses TimerTcc0\n// 16 bits timer\n//#include <TimerTC3.h>"
  },
  {
    "path": "src/platforms/software.h",
    "chars": 1070,
    "preview": "#include <Arduino.h>\n\n/* \n    Generic fallback approach that doesn't rely on any particular MCU's interrupts or RTOS thr"
  },
  {
    "path": "src/platforms/stm32.h",
    "chars": 853,
    "preview": "#include <Arduino.h>\n\n#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION  < 0x01090000)\n  #error \"Due to API change"
  },
  {
    "path": "src/platforms/teensy.h",
    "chars": 727,
    "preview": "#include <Arduino.h>\n\n#define ATOMIC(X) noInterrupts(); X; interrupts();\n\nIntervalTimer _uclockTimer;\n\n// forward declar"
  },
  {
    "path": "src/uClock.cpp",
    "chars": 20299,
    "preview": "/*!\n *  @file       uClock.cpp\n *  Project     BPM clock generator for Arduino\n *  @brief      A Library to implement BP"
  },
  {
    "path": "src/uClock.h",
    "chars": 10331,
    "preview": "/*!\n *  @file       uClock.h\n *  Project     BPM clock generator for Arduino\n *  @brief      A Library to implement BPM "
  }
]

About this extraction

This page contains the full source code of the midilab/uClock GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (111.8 KB), approximately 31.8k tokens, and a symbol index with 33 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!