Last active
June 24, 2021 17:15
-
-
Save jot240/6a0111ba0896b2050fc2488f5481c7b3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Basic MIDI arpeggiator instrument for the Arduino UNO by Joseph Thompson. | |
//This plays triads that can be shifted in pitch and position on the scale | |
//Inspired by and uses the button/potentiometer checking from https://github.com/silveirago/DIY-Midi-Controller/blob/master/Code%20-%20c%C3%B3digo/en-DIY_midi_controller/en-DIY_midi_controller.ino | |
// uses Grove rgb_lcd, from grove starter kit. https://wiki.seeedstudio.com/Grove-LCD_RGB_Backlight/ This is not essential and can be removed | |
#include <rgb_lcd.h> | |
#include <MIDI.h> | |
MIDI_CREATE_DEFAULT_INSTANCE(); //Creates MIDI object to interact with Serial | |
// BUTTONS | |
//Only have 1 button to trigger the arpeggio | |
const int N_BUTTONS = 1; //* total numbers of buttons | |
const int BUTTON_ARDUINO_PIN[N_BUTTONS] = {3}; //* pins of each button connected straight to the Arduino | |
int button_c_state[N_BUTTONS] = {}; // stores the button current value | |
int button_p_state[N_BUTTONS] = {}; // stores the button previous value | |
// debounce | |
unsigned long last_debounce_time[N_BUTTONS] = {0}; // the last time the output pin was toggled | |
unsigned long debounce_delay = 50; //* the debounce time; increase if the output flickers | |
//Potentiometers | |
const int N_POTS=4; | |
const int POT_ARDUINO_PIN[N_POTS] = { A0, A1, A2, A3}; //* pins of each pot connected straight to the Arduino | |
int pot_c_state[N_POTS] = {0,0,0,0}; // Current state of the pot | |
int pot_p_state[N_POTS] = {0,0,0,0}; // Previous state of the pot | |
int pot_var = 0; // Difference between the current and previous state of the pot | |
/* midi_c_state[0] --> Position on scale | |
* midi_c_state[1] -->pause length | |
* midi_c_state[2] --> adjusts position on MIDI note map. i.e. C5 = 60, C5# = 61... | |
* midi_c_state[3] --> MIDI control | |
*/ | |
int midi_c_state[N_POTS] = {0,0,0,0}; // Current state of the midi value | |
int midi_p_state[N_POTS] = {0,0,0,0}; // Previous state of the midi value | |
const int TIMEOUT = 300; //* Amount of time the potentiometer will be read after it exceeds the var_threshold | |
const int var_threshold = 10; //* Threshold for the potentiometer signal variation | |
boolean pot_moving= true; // If the potentiometer is moving | |
unsigned long PTime[N_POTS] = {0,0,0,0}; // Previously stored time | |
unsigned long timer[N_POTS] = {0,0,0,0}; // Stores the time that has elapsed since the timer was reset | |
// MIDI | |
byte midi_ch = 1; //* MIDI channel to be used | |
byte cc = 1; //* Lowest MIDI CC to be used | |
//Scale info | |
String note_names[] = {"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B "}; //we will use this to map our midi values to notes to display on the LCD | |
int Cmaj_scale[] = {60,62,64,65,67,69,71,72}; //MIDI code for C Major scale. In midi, each number is a semi-tone | |
int scale_start = 0; //starting position in scale | |
const int CHORD_LENGTH = 3; //number of notes in chord | |
int midi_vals[CHORD_LENGTH] = {}; //array to store midi-output to play | |
int notes[CHORD_LENGTH] = {0,2,4}; //1st 3rd, and 5th note on scale, makes basic triad | |
int note_delay = 100; //time between notes in arpeggio | |
//LCD | |
rgb_lcd lcd; //create LCD instance | |
const int colorR = 255; //set background colors | |
const int colorG = 0; | |
const int colorB = 100; | |
void setup() { | |
//Set the baud rate so Hairless MIDI can use it. y | |
Serial.begin(115200); | |
//initialize button | |
for (int i = 0; i < N_BUTTONS; i++) { | |
pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP); | |
//LCD | |
lcd.begin(16,2); //columns and rows of LCD | |
lcd.setRGB(colorR, colorG, colorB); //starts background color | |
lcd.print("Get Ready to Jam!"); | |
} | |
} | |
void loop() { | |
//buttons | |
arp_trigger(); //scale_starts the arpegio | |
//pots | |
starting_note(); //adjusts starting note | |
starting_scale_position(); //adjusts scale position | |
pause_length(); //adjust time between notes in arppegio | |
pot_midi(); //adjusts MIDI control pots | |
} | |
// BUTTONS | |
void arp_trigger() { | |
/* This function reads the button and triggers the arpeggio*/ | |
button_c_state[0] = digitalRead(BUTTON_ARDUINO_PIN[0]); // read pin from arduino | |
if ((millis() - last_debounce_time[0]) > debounce_delay) {//Stops multiple triggers in too short of a timespan(debouncing) | |
if (button_p_state[0] != button_c_state[0]) { //Checks if something changed | |
last_debounce_time[0] = millis(); | |
if (button_c_state[0] == LOW) { //Plays the arpegio | |
for( int i =0; i<CHORD_LENGTH; i++){ | |
MIDI.sendNoteOn(midi_vals[i], 127, midi_ch);// note, velocity, channel | |
delay(note_delay); //delay controlled by potentiometer 1 | |
} | |
} | |
else { //stops playing arpegio | |
for( int i =0; i<CHORD_LENGTH; i++){ | |
MIDI.sendNoteOn(midi_vals[i], 0, midi_ch); | |
} | |
} | |
button_p_state[0] = button_c_state[0]; //saves current state as past state | |
} | |
} | |
} | |
void move_chord(){ | |
/* helper function to shift chord around the scale. midi_c_state[0] is controlled by potentiometer 0 */ | |
int note; | |
scale_start = midi_c_state[0]; | |
for (int i =0; i< CHORD_LENGTH ; i++){ //Go through each note in arpeggio. Here it is the basic triads in major scale | |
note = notes[i]; | |
if(scale_start+ note > 7){//This allows us to play notes in next octave | |
midi_vals[i] = Cmaj_scale[(scale_start+note)%7] + 12 + midi_c_state[2]; //converts note to default scale, adds twelve semitones to move it up an octave | |
}else{ | |
midi_vals[i] = Cmaj_scale[scale_start+note]+ midi_c_state[2]; //note is in current octave, we can store it normally | |
} | |
} | |
} | |
// POTENTIOMETERS | |
void starting_scale_position() { | |
/* This function determines where on scale you start. This is controlled by potentiometer 0 (0 is start of scale, 7 is the octave) */ | |
pot_c_state[0] = analogRead(POT_ARDUINO_PIN[0]); // reads the pins from arduino | |
midi_c_state[0] = map(pot_c_state[0], 0, 1023, 0, 7); // Maps the reading of thepot_c_state to a value that we will add to the starting midi value | |
pot_var = abs(pot_c_state[0] - pot_p_state[0]); // Calculates the absolute value between the difference between the current and previous state of the pot | |
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold | |
PTime[0] = millis(); // Stores the previous time | |
} | |
timer[0] = millis() - PTime[0]; // Resets the timer 11000 - 11000 = 0ms | |
if (timer[0] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving | |
pot_moving= true; | |
} | |
else { | |
pot_moving= false; | |
} | |
if (pot_moving== true) { // If the potentiometer is still moving, send the change control | |
if (midi_p_state[0] != midi_c_state[0]) { | |
move_chord(); //Calls move chord function. This shifts the chord to the values mapped to the potentiometers | |
update_lcd(); //updates LCD to reflect changes | |
pot_p_state[0] =pot_c_state[0]; // Stores the current reading of the potentiometer to compare with the next | |
midi_p_state[0] = midi_c_state[0]; //Stores current mapping | |
} | |
} | |
} | |
void pause_length() { | |
/* This function determines length of pause. Controlled by potentiometer 1 */ | |
pot_c_state[1] = analogRead(POT_ARDUINO_PIN[1]); // reads the pins from arduino | |
midi_c_state[1] = map(pot_c_state[1], 0, 1023, 0, 1000); // Maps the reading of thepot_c_state to a delay value between 0 and 1 second | |
pot_var = abs(pot_c_state[1] - pot_p_state[1]); // Calculates the absolute value between the difference between the current and previous state of the pot | |
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold | |
PTime[1] = millis(); // Stores the previous time | |
} | |
timer[1] = millis() - PTime[1]; // Resets the timer 11000 - 11000 = 0ms | |
if (timer[1] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving | |
pot_moving= true; | |
} | |
else { | |
pot_moving= false; | |
} | |
if (pot_moving== true) { // If the potentiometer is still moving, send the change control | |
if (midi_p_state[1] != midi_c_state[1]) { | |
note_delay = midi_c_state[1]; | |
update_lcd(); //updates LCD to display new info | |
pot_p_state[1] =pot_c_state[1]; // Stores the current reading of the potentiometer to compare with the next | |
midi_p_state[1] = midi_c_state[1]; //Stores current mapping | |
} | |
} | |
} | |
void starting_note() { | |
/* This function determines absolute starting location. Controlled by potentiometer 2 */ | |
pot_c_state[2] = analogRead(POT_ARDUINO_PIN[2]); // reads the pins from arduino | |
midi_c_state[2] = map(pot_c_state[2], 0, 1023, -10, 30); // Maps the reading of thepot_c_state to a delay value | |
pot_var = abs(pot_c_state[2] - pot_p_state[2]); // Calculates the absolute value between the difference between the current and previous state of the pot | |
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold | |
PTime[2] = millis(); // Stores the previous time | |
} | |
timer[2] = millis() - PTime[2]; // Resets the timer 11000 - 11000 = 0ms | |
if (timer[2] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving | |
pot_moving= true; | |
} | |
else { | |
pot_moving= false; | |
} | |
if (pot_moving== true) { // If the potentiometer is still moving, send the change control | |
if (pot_p_state[2] != pot_c_state[2]) { | |
move_chord(); //moves the new chord | |
update_lcd(); //updates LCD to display new info | |
pot_p_state[2] =pot_c_state[2]; // Stores the current reading of the potentiometer to compare with the next | |
} | |
} | |
} | |
void pot_midi() { | |
//this function sends the MIDI control values from the pot to the computer. You can use this to control effects, volume, etc. in DAW | |
pot_c_state[3] = analogRead(POT_ARDUINO_PIN[3]); // reads the pins from arduino | |
midi_c_state[3] = map(pot_c_state[3], 0, 1023, 0, 127); // Maps the reading of thepot_c_state to a value usable in midi | |
pot_var = abs(pot_c_state[3] - pot_p_state[3]); // Calculates the absolute value between the difference between the current and previous state of the pot | |
if (pot_var > var_threshold) { // Opens the gate if the potentiometer variation is greater than the threshold | |
PTime[3] = millis(); // Stores the previous time | |
} | |
timer[3] = millis() - PTime[3]; // Resets the timer 11000 - 11000 = 0ms | |
if (timer[3] < TIMEOUT) { // If the timer is less than the maximum allowed time it means that the potentiometer is still moving | |
pot_moving= true; | |
} | |
else { | |
pot_moving= false; | |
} | |
if (pot_moving== true) { // If the potentiometer is still moving, send the change control | |
if (midi_p_state[3] != midi_c_state[3]) { | |
MIDI.sendControlChange(cc + 3, midi_c_state[3], midi_ch); // cc number, cc value, midi channel | |
update_lcd(); | |
pot_p_state[3] =pot_c_state[3]; // Stores the current reading of the potentiometer to compare with the next | |
midi_p_state[3] = midi_c_state[3]; | |
} | |
} | |
} | |
//LCD | |
void update_lcd(){ | |
/*This function builds the two lines to display on the LCD screen */ | |
String first_line = "notes:"; | |
String second_line = "ms:"; | |
String note_name; | |
for(int i = 0; i< CHORD_LENGTH; i++){ //building string to display the notes we play | |
note_name = note_names[midi_vals[i]%12]; //Using modulo division to map midi number to name of note | |
first_line = first_line + note_name; | |
first_line = first_line + " "; | |
} | |
second_line = second_line + note_delay; | |
second_line = second_line + " MIDI:"; | |
second_line = second_line + pot_c_state[3]; // | |
lcd.clear(); //Erase what was previously written on LCD screen | |
lcd.setCursor(0,0); //Move cursor to begginning of first row | |
lcd.print(first_line); | |
lcd.setCursor(0,1); //move cursor to second row | |
lcd.noCursor(); //removes cursor so user does not see it | |
lcd.print(second_line); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment