diff --git a/README.md b/README.md index 323a912..fc5b438 100755 --- a/README.md +++ b/README.md @@ -83,20 +83,32 @@ A clone of Roland TB303 step sequencer main engine, here is a example with no us ```c++ // Roland TB303 Step Sequencer engine clone. // No interface here, just the engine as example. +// 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 SEQUENCER_MIN_BPM 50 -#define SEQUENCER_MAX_BPM 177 #define NOTE_LENGTH 4 // min: 1 max: 5 DO NOT EDIT BEYOND!!! #define NOTE_VELOCITY 90 #define ACCENT_VELOCITY 127 -#define NOTE_STACK_SIZE 3 // 1 for no glide note, other 2 for overlap glide notes -// MIDI config +// 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 @@ -107,26 +119,25 @@ typedef struct bool rest; } SEQUENCER_STEP_DATA; -SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE]; - 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]; - -bool _playing = false; -uint16_t _step, _step_edit = 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 +// make sure all above sequencer data are modified atomicly only +// eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7); +uint8_t _tmpSREG; +#define ATOMIC(X) _tmpSREG = SREG; cli(); X; SREG = _tmpSREG; + +// 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) { @@ -137,8 +148,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 here. +// 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) { uint16_t step, length; @@ -148,9 +158,7 @@ void ClockOut16PPQN(uint32_t * tick) // send note on only if this step are not in rest mode if ( _sequencer[_step].rest == false ) { - // send note on - sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); - + // check for glide event ahead of _step step = _step; for ( uint16_t i = 1; i < _step_length; i++ ) { @@ -170,6 +178,8 @@ void ClockOut16PPQN(uint32_t * tick) 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; } } @@ -192,6 +202,9 @@ void ClockOut96PPQN(uint32_t * tick) } } } + + // user feedback about sequence time events + tempoInterface(tick); } // The callback function wich will be called when clock starts by using Clock.start() method. @@ -205,6 +218,7 @@ void onClockStart() 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; @@ -215,8 +229,14 @@ void onClockStop() 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(); @@ -236,7 +256,7 @@ void setup() // initing sequencer data for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { - _sequencer[i].note = 36; + _sequencer[i].note = 48; _sequencer[i].accent = false; _sequencer[i].glide = false; _sequencer[i].rest = false; diff --git a/examples/AcidStepSequencer/AcidStepSequencer.ino b/examples/AcidStepSequencer/AcidStepSequencer.ino index 67f293f..24ab29b 100644 --- a/examples/AcidStepSequencer/AcidStepSequencer.ino +++ b/examples/AcidStepSequencer/AcidStepSequencer.ino @@ -6,42 +6,24 @@ // Sequencer config #define STEP_MAX_SIZE 16 -#define SEQUENCER_MIN_BPM 50 -#define SEQUENCER_MAX_BPM 177 #define NOTE_LENGTH 4 // min: 1 max: 5 DO NOT EDIT BEYOND!!! #define NOTE_VELOCITY 90 #define ACCENT_VELOCITY 127 -// do not edit this! -#define NOTE_STACK_SIZE 3 - -// Ui config -#define LOCK_POT_SENSTIVITY 3 - // MIDI modes #define MIDI_CHANNEL 0 // 0 = channel 1 #define MIDI_MODE //#define SERIAL_MODE -// 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 +// do not edit from here! +#define NOTE_STACK_SIZE 3 -#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 +// 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 @@ -52,36 +34,25 @@ typedef struct bool rest; } SEQUENCER_STEP_DATA; -SEQUENCER_STEP_DATA _sequencer[STEP_MAX_SIZE]; - 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]; - -bool _playing = false; -uint16_t _step, _step_edit = 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 +// make sure all above sequencer data are modified atomicly only +// eg. ATOMIC(_sequencer[0].accent = true); ATOMIC(_step_length = 7); +uint8_t _tmpSREG; +#define ATOMIC(X) _tmpSREG = SREG; cli(); X; SREG = _tmpSREG; -// User Interface data -// 6 buttons to keep last value track -// 4 10k potentiometers to keep lasta value track -uint8_t _button_state[6] = {1}; -uint16_t _pot_state[4] = {0}; -bool _lock_pot[4] = {true}; -uint8_t _last_octave = 3; -uint8_t _last_note = 0; -uint8_t _bpm_blink_timer = 1; +// 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) { @@ -92,8 +63,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 here. +// 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) { uint16_t step, length; @@ -103,9 +73,7 @@ void ClockOut16PPQN(uint32_t * tick) // send note on only if this step are not in rest mode if ( _sequencer[_step].rest == false ) { - // send note on - sendMidiMessage(NOTE_ON, _sequencer[_step].note, _sequencer[_step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY); - + // check for glide event ahead of _step step = _step; for ( uint16_t i = 1; i < _step_length; i++ ) { @@ -125,12 +93,11 @@ void ClockOut16PPQN(uint32_t * tick) 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; } } - - // if we reach at this point means we could not find a free note stack for this note... so lets send note off to avoid ghost notes in the air - sendMidiMessage(NOTE_OFF, _sequencer[_step].note, 0); } } @@ -151,23 +118,14 @@ void ClockOut96PPQN(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; - } + // 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); - digitalWrite(PLAY_STOP_LED_PIN , LOW); _playing = true; } @@ -175,6 +133,7 @@ void onClockStart() 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; @@ -182,33 +141,6 @@ void onClockStop() _playing = false; } -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); -} - void setup() { // Initialize serial communication @@ -239,7 +171,7 @@ void setup() // initing sequencer data for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { - _sequencer[i].note = 36; + _sequencer[i].note = 48; _sequencer[i].accent = false; _sequencer[i].glide = false; _sequencer[i].rest = false; @@ -253,278 +185,10 @@ void setup() // pins, buttons, leds and pots config configureInterface(); - - acidRandomize(); -} - -void acidRandomize() -{ - // ramdom it all - for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) { - _sequencer[i].note = random(36, 70); // octave 2 to 4. octave 3 to 5 (40 - 83) - _sequencer[i].accent = random(0, 2); - _sequencer[i].glide = random(0, 2); - _sequencer[i].rest = random(0, 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 here 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 lockPotsState(bool state) -{ - for ( uint8_t i = 0; i < 4; i++ ) { - _lock_pot[i] = state; - } -} - -bool pressed(uint8_t button_pin) -{ - uint8_t value; - uint8_t * last_value; - - switch(button_pin) { - case PREVIOUS_STEP_BUTTON_PIN: - last_value = &_button_state[0]; - break; - case NEXT_STEP_BUTTON_PIN: - last_value = &_button_state[1]; - break; - case REST_BUTTON_PIN: - last_value = &_button_state[2]; - break; - case GLIDE_BUTTON_PIN: - last_value = &_button_state[3]; - break; - case ACCENT_BUTTON_PIN: - last_value = &_button_state[4]; - break; - case PLAY_STOP_BUTTON_PIN: - last_value = &_button_state[5]; - 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; - uint16_t * last_value; - bool * lock_pot; - uint8_t pot_sensitivity = 1; - - switch(pot_pin) { - case OCTAVE_POT_PIN: - last_value = &_pot_state[0]; - lock_pot = &_lock_pot[0]; - break; - case NOTE_POT_PIN: - last_value = &_pot_state[1]; - lock_pot = &_lock_pot[1]; - break; - case STEP_LENGTH_POT_PIN: - last_value = &_pot_state[2]; - lock_pot = &_lock_pot[2]; - break; - case TEMPO_POT_PIN: - last_value = &_pot_state[3]; - lock_pot = &_lock_pot[3]; - break; - default: - return -1; - } - - // range our value - value = (analogRead(pot_pin) / (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) - if ( *lock_pot == true ) { - pot_sensitivity = LOCK_POT_SENSTIVITY; - } - - if ( abs(value - *last_value) >= pot_sensitivity ) { - *last_value = value; - if ( *lock_pot == true ) { - *lock_pot = false; - } - return value; - } else { - return -1; - } -} - -void processPots() -{ - int8_t octave, note, step_note; - 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 ) { - _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 ) { - _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(); - } - } - - // 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; - } - 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) ) { - _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) ) { - _sequencer[_step_edit].glide = !_sequencer[_step_edit].glide; - } - - // step accent - if ( pressed(ACCENT_BUTTON_PIN) ) { - _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); - } } // User interaction goes here void loop() { - processButtons(); - processLeds(); - processPots(); + processInterface(); } diff --git a/examples/AcidStepSequencer/DefaultUserInterface.ino b/examples/AcidStepSequencer/DefaultUserInterface.ino new file mode 100644 index 0000000..904d651 --- /dev/null +++ b/examples/AcidStepSequencer/DefaultUserInterface.ino @@ -0,0 +1,276 @@ + +#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)); + } +} diff --git a/examples/AcidStepSequencer/HardwareInterface.ino b/examples/AcidStepSequencer/HardwareInterface.ino new file mode 100644 index 0000000..e4762b8 --- /dev/null +++ b/examples/AcidStepSequencer/HardwareInterface.ino @@ -0,0 +1,120 @@ + +#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; + } +}