Skip to content

Instantly share code, notes, and snippets.

@jot240
Last active June 24, 2021 17:15
Show Gist options
  • Save jot240/6a0111ba0896b2050fc2488f5481c7b3 to your computer and use it in GitHub Desktop.
Save jot240/6a0111ba0896b2050fc2488f5481c7b3 to your computer and use it in GitHub Desktop.
//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