Added CV and Gate outputs via 2x i2c MCP4725 modules
This commit is contained in:
278
AcidStepSequencer.ino
Normal file
278
AcidStepSequencer.ino
Normal file
@ -0,0 +1,278 @@
|
||||
// Acid StepSequencer, a Roland TB303 step sequencer engine clone
|
||||
// author: midilab contact@midilab.co
|
||||
// under MIT license
|
||||
#include "uClock.h"
|
||||
|
||||
#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
|
||||
#define MIDI_CC 0xB0
|
||||
|
||||
// sequencer data
|
||||
typedef struct
|
||||
{
|
||||
uint8_t note;
|
||||
int16_t length;
|
||||
} STACK_NOTE_DATA;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t note:7;
|
||||
uint8_t accent:1;
|
||||
uint8_t glide:1;
|
||||
uint8_t rest:1;
|
||||
uint8_t tie:1;
|
||||
uint8_t reserved:5;
|
||||
} SEQUENCER_STEP_DATA;
|
||||
// 2 bytes per step
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SEQUENCER_STEP_DATA step[STEP_MAX_SIZE];
|
||||
int8_t step_init_point;
|
||||
uint8_t step_length;
|
||||
} SEQUENCER_TRACK_DATA;
|
||||
// 32 bytes per 16 step + 2 bytes config = 34 bytes [STEP_MAX_SIZE=16]
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SEQUENCER_TRACK_DATA data;
|
||||
uint8_t step_location;
|
||||
uint8_t channel;
|
||||
bool mute;
|
||||
STACK_NOTE_DATA stack[NOTE_STACK_SIZE];
|
||||
} SEQUENCER_TRACK;
|
||||
|
||||
// main sequencer data is constantly change inside uClock 16PPQN and 96PPQN ISR callbacks, so volatile him!
|
||||
SEQUENCER_TRACK volatile _sequencer[TRACK_NUMBER];
|
||||
|
||||
uint8_t _selected_track = 0;
|
||||
uint8_t _selected_pattern = 0;
|
||||
|
||||
// make sure all above sequencer data are modified atomicly only
|
||||
// eg. ATOMIC(_sequencer[track]data.step[0].accent = 1); ATOMIC(_sequencer[track].data.step_length = 7);
|
||||
|
||||
// shared data to be used for user interface interaction and feedback
|
||||
bool _playing = false;
|
||||
uint8_t _harmonize = 0;
|
||||
uint16_t _step_edit = 0;
|
||||
uint8_t _last_octave = 3;
|
||||
uint8_t _last_note = 0;
|
||||
int8_t _transpose = 0; // zero is centered C
|
||||
uint8_t _selected_mode = 0;
|
||||
|
||||
void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2, uint8_t channel)
|
||||
{
|
||||
// send midi message
|
||||
command = command | (uint8_t)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.
|
||||
void ClockOut16PPQN(uint32_t * tick)
|
||||
{
|
||||
uint8_t step, next_step;
|
||||
uint16_t length;
|
||||
int8_t note;
|
||||
|
||||
for ( uint8_t track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
if ( _sequencer[track].mute == true ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
length = NOTE_LENGTH;
|
||||
|
||||
// get actual step location.
|
||||
_sequencer[track].step_location = uint32_t(*tick + _sequencer[track].data.step_init_point) % _sequencer[track].data.step_length;
|
||||
|
||||
// send note on only if this step are not in rest mode
|
||||
if ( _sequencer[track].data.step[_sequencer[track].step_location].rest == 0 ) {
|
||||
|
||||
// check for slide or tie event ahead of _sequencer[track].step_location
|
||||
step = _sequencer[track].step_location;
|
||||
next_step = step;
|
||||
for ( uint8_t i = 1; i < _sequencer[track].data.step_length; i++ ) {
|
||||
next_step = ++next_step % _sequencer[track].data.step_length;
|
||||
if (_sequencer[track].data.step[step].glide == 1 && _sequencer[track].data.step[next_step].rest == 0) {
|
||||
length = NOTE_LENGTH + 5;
|
||||
break;
|
||||
} else if (_sequencer[track].data.step[next_step].tie == 1 && _sequencer[track].data.step[next_step].rest == 1) {
|
||||
length = NOTE_LENGTH + (i * 6);
|
||||
} else if ( _sequencer[track].data.step[next_step].rest == 0 || _sequencer[track].data.step[next_step].tie == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find a free note stack to fit in
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
if ( _sequencer[track].stack[i].length == -1 ) {
|
||||
if ( _harmonize == 1 ) {
|
||||
note = harmonizer(_sequencer[track].data.step[_sequencer[track].step_location].note);
|
||||
} else {
|
||||
note = _sequencer[track].data.step[_sequencer[track].step_location].note;
|
||||
}
|
||||
note += _transpose;
|
||||
// in case transpose push note away from the lower or higher midi note range barrier do not play it
|
||||
if ( note < 0 || note > 127 ) {
|
||||
break;
|
||||
}
|
||||
_sequencer[track].stack[i].note = note;
|
||||
_sequencer[track].stack[i].length = length;
|
||||
// send note on
|
||||
sendMidiMessage(NOTE_ON, note, _sequencer[track].data.step[_sequencer[track].step_location].accent ? ACCENT_VELOCITY : NOTE_VELOCITY, _sequencer[track].channel);
|
||||
sendCVNote(note, track); //add accent here
|
||||
sendGateOn(track);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void clearStackNote(int8_t track = -1)
|
||||
{
|
||||
if ( track <= -1 ) {
|
||||
// clear all tracks stack note
|
||||
for ( uint8_t i = 0; i < TRACK_NUMBER; i++ ) {
|
||||
// clear and send any note off
|
||||
for ( uint8_t j = 0; j < NOTE_STACK_SIZE; j++ ) {
|
||||
ATOMIC(
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[i].stack[j].note, 0, _sequencer[i].channel);
|
||||
_sequencer[i].stack[j].length = -1;
|
||||
sendGateOff(i);
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear and send any note off
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
ATOMIC(
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[track].stack[i].note, 0, _sequencer[track].channel);
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
sendGateOff(track);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution.
|
||||
void ClockOut96PPQN(uint32_t * tick)
|
||||
{
|
||||
uint8_t track;
|
||||
|
||||
// Send MIDI_CLOCK to external hardware
|
||||
Serial.write(MIDI_CLOCK);
|
||||
|
||||
for ( track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
// handle note on stack
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
if ( _sequencer[track].stack[i].length != -1 ) {
|
||||
--_sequencer[track].stack[i].length;
|
||||
if ( _sequencer[track].stack[i].length == 0 ) {
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[track].stack[i].note, 0, _sequencer[track].channel);
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
sendGateOff(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// user feedback about sequence time events
|
||||
tempoInterface(tick);
|
||||
}
|
||||
|
||||
// The callback function wich will be called when clock starts by using Clock.start() method.
|
||||
void onClockStart()
|
||||
{
|
||||
Serial.write(MIDI_START);
|
||||
_playing = 1;
|
||||
}
|
||||
|
||||
// The callback function wich will be called when clock stops by using Clock.stop() method.
|
||||
void onClockStop()
|
||||
{
|
||||
Serial.write(MIDI_STOP);
|
||||
|
||||
// clear all tracks stack note
|
||||
clearStackNote();
|
||||
|
||||
_playing = 0;
|
||||
}
|
||||
|
||||
void setTrackChannel(uint8_t track, uint8_t channel)
|
||||
{
|
||||
--track;
|
||||
--channel;
|
||||
ATOMIC(_sequencer[track].channel = channel);
|
||||
}
|
||||
|
||||
void initAcidStepSequencer(uint8_t mode)
|
||||
{
|
||||
uint8_t track;
|
||||
|
||||
// Initialize serial communication
|
||||
if ( mode == 0 ) {
|
||||
// the default MIDI serial speed communication at 31250 bits per second
|
||||
Serial.begin(31250);
|
||||
} else if ( mode == 1 ) {
|
||||
// for usage with a PC with a serial to MIDI bridge
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
// 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 memory data
|
||||
for ( track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
_sequencer[track].channel = track;
|
||||
_sequencer[track].data.step_init_point = 0;
|
||||
_sequencer[track].data.step_length = STEP_MAX_SIZE;
|
||||
_sequencer[track].step_location = 0;
|
||||
_sequencer[track].mute = false;
|
||||
|
||||
// initing note data
|
||||
for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) {
|
||||
_sequencer[track].data.step[i].note = 48;
|
||||
_sequencer[track].data.step[i].accent = 0;
|
||||
_sequencer[track].data.step[i].glide = 0;
|
||||
_sequencer[track].data.step[i].tie = 0;
|
||||
_sequencer[track].data.step[i].rest = 0;
|
||||
}
|
||||
|
||||
// initing note stack data
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
_sequencer[track].stack[i].note = 0;
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user