// Acid StepSequencer, a Roland TB303 step sequencer engine clone // author: midilab contact@midilab.co // under MIT license #include "Arduino.h" #include // 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 onPPQNCallback(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 // Inits the clock uClock.init(); // Set the callback function for the clock output to send MIDI Sync message. uClock.setOnPPQN(onPPQNCallback); // for MIDI sync uClock.setOnSync24(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); // 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(); }