mirror of https://github.com/midilab/uClock
AcidStepSequencer: separation of code into main sequencer engine, user interface and hardware interface for better code reuse. Added ATOMIC() macro to access sequencer data in a atomic way(since uClock runs on timmer interrupt)
parent
b4e95eaa85
commit
db5682f08b
@ -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)); |
||||
} |
||||
} |
@ -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; |
||||
}
|
||||
} |
Loading…
Reference in new issue