From 698c7895141f412be565949f33f33f62e835a74e Mon Sep 17 00:00:00 2001 From: midilab Date: Fri, 9 Mar 2018 10:15:46 -0300 Subject: [PATCH] Roland TB303 main engine added as example for a step sequencer using uClock --- README.md | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 75d0f1e..cf283a1 100755 --- a/README.md +++ b/README.md @@ -75,11 +75,181 @@ void loop() { } ``` -### A Simple Step Sequencer -A simple step sequencer for MIDI devices: +### Acid Step Sequencer +A clone of Roland TB303 step sequencer main engine, the schematics for user interface are avaliable on example folder. ```c++ -Soon... +// Roland TB303 Step Sequencer engine clone. +// No interface here, just the engine as example. +#include "Arduino.h" +#include + +// Sequencer config +#define STEP_MAX_SIZE 16 +#define NOTE_LENGTH 4 // min: 1 max: 5 DO NOT EDIT BEYOND!!! +#define NOTE_VELOCITY 90 +#define ACCENT_VELOCITY 127 + +// MIDI modes +#define MIDI_CHANNEL 0 // 0 = channel 1 + +// Sequencer data +typedef struct +{ + uint8_t note; + bool accent; + bool glide; + bool rest; +} SEQUENCER_STEP_DATA; + +SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE]; + +typedef struct +{ + uint8_t note; + int8_t length; +} STACK_NOTE_DATA; + +STACK_NOTE_DATA _note_stack[2]; + +bool _playing = false; +uint16_t _step = 0; +uint16_t _step_length = STEP_MAX_SIZE; + +// MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards. +#define MIDI_CLOCK 0xF8 +#define MIDI_START 0xFA +#define MIDI_STOP 0xFC +#define NOTE_ON 0x90 +#define NOTE_OFF 0x80 + +void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) +{ + // send midi message + command = command | (uint8_t)MIDI_CHANNEL; + Serial.write(command); + Serial.write(byte1); + Serial.write(byte2); +} + +// The callback function wich will be called by uClock each Pulse of 16PPQN clock resolution. +// Each call represents exactly one step here. +void ClockOut16PPQN(uint32_t * tick) +{ + uint16_t step; + bool glide_ahead; + + // get actual step. + _step = *tick % _step_length; + + // send note on only if this step are not in rest mode + if ( _sequencer[_step].rest == false ) { + sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); + // do we have a glide ahead us? + step = _step; + for ( uint16_t i = 1; i < _step_length; i++ ) { + ++step; + step = step % _step_length; + if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) { + _note_stack[1].note = _sequencer[_step].note; + _note_stack[1].length = NOTE_LENGTH + (i * 6); + glide_ahead = true; + break; + } else if ( _sequencer[step].rest == false ) { + glide_ahead = false; + break; + } + } + if ( glide_ahead == false ) { + _note_stack[0].note = _sequencer[_step].note; + _note_stack[0].length = NOTE_LENGTH; + } + } +} + +// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. +void ClockOut96PPQN(uint32_t * tick) +{ + // Send MIDI_CLOCK to external hardware + Serial.write(MIDI_CLOCK); + + // handle note on stack + // [1] is notes to be glided, its in hold on mode until we reach the glided step + if ( _note_stack[1].length != -1 ) { + --_note_stack[1].length; + if ( _note_stack[1].length == 0 ) { + sendMidiMessage(NOTE_OFF, _note_stack[1].note, 0); + _note_stack[1].length = -1; + } + } + // [0] is the actual step note stack + if ( _note_stack[0].length != -1 ) { + --_note_stack[0].length; + if ( _note_stack[0].length == 0 ) { + sendMidiMessage(NOTE_OFF, _note_stack[0].note, 0); + _note_stack[0].length = -1; + } + } +} + +// The callback function wich will be called when clock starts by using Clock.start() method. +void onClockStart() +{ + Serial.write(MIDI_START); +} + +// The callback function wich will be called when clock stops by using Clock.stop() method. +void onClockStop() +{ + Serial.write(MIDI_STOP); + sendMidiMessage(NOTE_OFF, _note_stack[1].note, 0); + sendMidiMessage(NOTE_OFF, _note_stack[0].note, 0); +} + +void setup() +{ + // Initialize serial communication + // the default MIDI serial speed communication at 31250 bits per second + Serial.begin(31250); + + // Inits the clock + uClock.init(); + + // Set the callback function for the clock output to send MIDI Sync message. + uClock.setClock96PPQNOutput(ClockOut96PPQN); + + // Set the callback function for the step sequencer on 16ppqn + uClock.setClock16PPQNOutput(ClockOut16PPQN); + + // Set the callback function for MIDI Start and Stop messages. + uClock.setOnClockStartOutput(onClockStart); + uClock.setOnClockStopOutput(onClockStop); + + // Set the clock BPM to 126 BPM + uClock.setTempo(126); + + // initing sequencer data + for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { + _sequencer[i].note = 36; + _sequencer[i].accent = false; + _sequencer[i].glide = false; + _sequencer[i].rest = false; + } + + // starts the sequencer + uClock.start(); + + // pins, buttons, leds and pots config + //configureYourUserInterface(); +} + +// User interaction goes here +void loop() +{ + //processYourButtons(); + //processYourLeds(); + //processYourPots(); +} ```