diff --git a/README.md b/README.md index cfadb9e..e3c496a 100755 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interruption. It is designed to be multi-architecture, portable, and easy to use within the open source community universe. -We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0)**. - -(See also the generic fallback mode) +We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)**. The absence of real-time features necessary for creating professional-level embedded devices for music and video on open source community-based platforms like Arduino led to the development of uClock. By leveraging the use of timer hardware interruptions, the library can schedule and manage real-time-like processing with safe shared resource access through its API. @@ -38,6 +36,20 @@ void loop() { } ``` +## Set your own resolution for your clock needs + +1. **PPQN_24** 24 Pulses Per Quarter Note +2. **PPQN_48** 48 Pulses Per Quarter Note +3. **PPQN_96** 96 Pulses Per Quarter Note +1. **PPQN_384** 384 Pulses Per Quarter Note +2. **PPQN_480** 480 Pulses Per Quarter Note +3. **PPQN_960** 960 Pulses Per Quarter Note + +To generate a MIDI sync signal and synchronize external MIDI devices, you can start working with the resolution of 24PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available in the market. By sending 24 pulses per quarter note interval, you can ensure effective synchronization among your MIDI devices. + +If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. + +Furthermore, it is possible to utilize all three resolutions simultaneously, allowing for flexibility based on your specific requirements and preferences. ## uClock v2.0 Breaking Changes @@ -54,29 +66,49 @@ If you are coming from uClock version < 2.0 versions, pay attention to the break If created a device using setClock16PPQNOutput only you just change the API call to setOnStep. If you were dependent on setClock96PPQNOutput you might need to review your tick counting system, depending on which PPQN clock resolution you choose to use. -## Set your own resolution for your clock needs +# Examples -1. **PPQN_24** 24 Pulses Per Quarter Note -2. **PPQN_48** 48 Pulses Per Quarter Note -3. **PPQN_96** 96 Pulses Per Quarter Note -1. **PPQN_384** 384 Pulses Per Quarter Note -2. **PPQN_480** 480 Pulses Per Quarter Note -3. **PPQN_960** 960 Pulses Per Quarter Note +You will find more complete examples on examples/ folder: -To generate a MIDI sync signal and synchronize external MIDI devices, you can start working with the resolution of 24PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available in the market. By sending 24 pulses per quarter note interval, you can ensure effective synchronization among your MIDI devices. +```c++ +#include -If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. +// external or internal sync? +bool _external_sync_on = false; -Furthermore, it is possible to utilize all three resolutions simultaneously, allowing for flexibility based on your specific requirements and preferences. +// the main uClock PPQN resolution ticking +void onPPQNCallback(uint32_t tick) { + // tick your sequencers or tickable devices... +} -### Example +void onStepCallback(uint32_t step) { + // triger step data for sequencer device... +} +<<<<<<< HEAD You will find more complete examples on examples/ folder. +======= +// The callback function called by uClock each Pulse of 24PPQN clock resolution. +void onSync24Callback(uint32_t tick) { + // send sync signal to... +} +>>>>>>> main -```c++ -#include +// The callback function called when clock starts by using uClock.start() method. +void onClockStartCallback() { + // send start signal to... +} + +// The callback function called when clock stops by using uClock.stop() method. +void onClockStopCallback() { + // send stop signal to... +} void setup() { + + // inits the clock library + uClock.init(); + // avaliable resolutions // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] // not mandatory to call, the default is 96PPQN if not set @@ -90,7 +122,25 @@ void setup() { uClock.setOnClockStart(onClockStartCallback); uClock.setOnClockStop(onClockStopCallback); - uClock.init(); + // set external sync mode? + if (_external_sync_on) { + uClock.setMode(uClock.EXTERNAL_CLOCK); + } + + // starts clock + uClock.start(); +} + +void loop() { + // do we need to external sync? + if (_external_sync_on) { + // watch for external sync signal income + bool signal_income = true; // your external input signal check will be this condition result + if (signal_income) { + // at each clockMe call uClock will process and handle external/internal syncronization + uClock.clockMe(); + } + } } ``` @@ -98,7 +148,7 @@ void setup() { Here a few examples on the usage of Clock library for MIDI devices, keep in mind the need to make your own MIDI interface, more details will be avaliable soon but until that, you can find good material over the net about the subject. -If you dont want to build a MIDI interface and you are going to use your arduino only with your PC, you can use a Serial-to-Midi bridge and connects your arduino via USB cable to your conputer to use it as a MIDI tool [like this one](http://projectgus.github.io/hairless-midiserial/). +If you don't have native USB/MIDI support on your microcontroller and don't want to build a MIDI interface and you are going to use your arduino only with your PC, you can use a Serial-to-Midi bridge and connects your arduino via USB cable to your conputer to use it as a MIDI tool [like this one](http://projectgus.github.io/hairless-midiserial/). ### A Simple MIDI Sync Box sketch example @@ -112,18 +162,18 @@ Here is an example on how to create a simple MIDI Sync Box on Arduino boards #define MIDI_START 0xFA #define MIDI_STOP 0xFC -// The callback function wich will be called by Clock each Pulse of 24PPQN clock resolution. +// 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 wich will be called when clock starts by using Clock.start() method. +// The callback function 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. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { Serial.write(MIDI_STOP); } @@ -159,18 +209,18 @@ An example on how to create a simple MIDI Sync Box on Teensy boards and USB Midi ```c++ #include -// The callback function wich will be called by Clock each Pulse of 96PPQN clock resolution. +// The callback function called by Clock each Pulse of 96PPQN clock resolution. void onSync24Callback(uint32_t tick) { // Send MIDI_CLOCK to external gears usbMIDI.sendRealTime(usbMIDI.Clock); } -// The callback function wich will be called when clock starts by using Clock.start() method. +// The callback function called when clock starts by using Clock.start() method. void onClockStart() { usbMIDI.sendRealTime(usbMIDI.Start); } -// The callback function wich will be called when clock stops by using Clock.stop() method. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { usbMIDI.sendRealTime(usbMIDI.Stop); } @@ -263,7 +313,7 @@ void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) Serial.write(byte2); } -// The callback function wich will be called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. +// The callback function called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. void onStepCallback(uint32_t tick) { uint16_t step; @@ -301,7 +351,7 @@ void onStepCallback(uint32_t tick) } } -// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. +// The callback function called by uClock each Pulse of 96PPQN clock resolution. void onPPQNCallback(uint32_t tick) { // Send MIDI_CLOCK to external hardware @@ -319,14 +369,14 @@ void onPPQNCallback(uint32_t tick) } } -// The callback function wich will be called when clock starts by using Clock.start() method. +// The callback function 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. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { Serial.write(MIDI_STOP); diff --git a/examples/AcidStepSequencer/AcidStepSequencer.ino b/examples/AcidStepSequencer/AcidStepSequencer.ino index 5531fa9..41f3928 100644 --- a/examples/AcidStepSequencer/AcidStepSequencer.ino +++ b/examples/AcidStepSequencer/AcidStepSequencer.ino @@ -80,7 +80,7 @@ void onStepCallback(uint32_t tick) ++step; step = step % _step_length; if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) { - length = NOTE_LENGTH + (i * 6); + length = NOTE_LENGTH + (i * 24); break; } else if ( _sequencer[step].rest == false ) { break; diff --git a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino new file mode 100644 index 0000000..366e938 --- /dev/null +++ b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino @@ -0,0 +1,67 @@ +#include + +// external or internal sync? +bool _external_sync_on = false; + +// the main uClock PPQN resolution ticking +void onPPQNCallback(uint32_t tick) { + // tick your sequencers or tickable devices... +} + +void onStepCallback(uint32_t step) { + // triger step data for sequencer device... +} + +// The callback function called by uClock each Pulse of 24PPQN clock resolution. +void onSync24Callback(uint32_t tick) { + // send sync signal to... +} + +// The callback function called when clock starts by using uClock.start() method. +void onClockStartCallback() { + // send start signal to... +} + +// The callback function called when clock stops by using uClock.stop() method. +void onClockStopCallback() { + // send stop signal to... +} + +void setup() { + + // inits the clock library + uClock.init(); + + // avaliable resolutions + // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] + // not mandatory to call, the default is 96PPQN if not set + uClock.setPPQN(uClock.PPQN_96); + + // you need to use at least one! + uClock.setOnPPQN(onPPQNCallback); + uClock.setOnStep(onStepCallback); + uClock.setOnSync24(onSync24Callback); + + uClock.setOnClockStart(onClockStartCallback); + uClock.setOnClockStop(onClockStopCallback); + + // set external sync mode? + if (_external_sync_on) { + uClock.setMode(uClock.EXTERNAL_CLOCK); + } + + // starts clock + uClock.start(); +} + +void loop() { + // do we need to external sync? + if (_external_sync_on) { + // watch for external sync signal income + bool signal_income = true; // your external input signal check will be this condition result + if (signal_income) { + // at each clockMe call uClock will process and handle external/internal syncronization + uClock.clockMe(); + } + } +} \ No newline at end of file diff --git a/examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino b/examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino new file mode 100644 index 0000000..e571da9 --- /dev/null +++ b/examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino @@ -0,0 +1,89 @@ +/* + * USB/Uart MIDI Sync Box + * + * This example code is in the public domain. + * + */ + +#include +#include + +#include + +// 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 + // Inits the clock + uClock.init(); + // Set the callback function for the clock output to send MIDI Sync message. + uClock.setOnSync24(onSync24Callback); + // Set the callback function for MIDI Start and Stop messages. + uClock.setOnClockStart(onClockStart); + uClock.setOnClockStop(onClockStop); + // Set the clock BPM to 126 BPM + uClock.setTempo(126); + // 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(); +} diff --git a/examples/RP2040UsbUartMasterClock/builtin_led.ino b/examples/RP2040UsbUartMasterClock/builtin_led.ino new file mode 100644 index 0000000..7a1eedf --- /dev/null +++ b/examples/RP2040UsbUartMasterClock/builtin_led.ino @@ -0,0 +1,37 @@ +#if defined(WS2812_BUILTIN_LED) +#include +#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 +} \ No newline at end of file diff --git a/library.json b/library.json index e64bcf2..20a3781 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "uClock", - "version": "2.0.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, Seedstudio XIAO M0. ESP32 and STM32)", + "version": "2.1.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": { @@ -22,5 +22,5 @@ "headers": "uClock.h", "dependencies": {}, "frameworks": "Arduino", - "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam" + "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam,raspberrypi" } diff --git a/library.properties b/library.properties index fa4b2e8..8911b8b 100755 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=uClock -version=2.0.0 +version=2.1.0 author=Romulo Silva maintainer=Romulo Silva 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, Seedstudio XIAO M0. ESP32 and STM32) +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 +architectures=avr,arm,samd,stm32,esp32,rp2040 includes=uClock.h diff --git a/src/platforms/esp32.h b/src/platforms/esp32.h index 1344923..22e80b0 100644 --- a/src/platforms/esp32.h +++ b/src/platforms/esp32.h @@ -2,6 +2,7 @@ #include #include +// esp32-specific timer #define TIMER_ID 0 hw_timer_t * _uclockTimer = NULL; // mutex control for ISR diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h new file mode 100644 index 0000000..eb918c2 --- /dev/null +++ b/src/platforms/rp2040.h @@ -0,0 +1,30 @@ +#include +#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); +} \ No newline at end of file diff --git a/src/uClock.cpp b/src/uClock.cpp index fd18740..ffa97a5 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -1,8 +1,8 @@ /*! * @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(Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.0.0 + * @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.1.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co @@ -72,6 +72,12 @@ #pragma message ("NOTE: uClock is using the 'generic' approach instead of specific board support, because board is not supported or because of USE_UCLOCK_GENERIC build flag.") #include "platforms/generic.h" #endif +// +// RP2040 (Raspberry Pico) family +// +#if defined(ARDUINO_ARCH_RP2040) + #include "platforms/rp2040.h" +#endif // diff --git a/src/uClock.h b/src/uClock.h index 722b385..9b438c2 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -1,8 +1,8 @@ /*! * @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(Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.0.0 + * @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.1.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co