In this instructable I will show you how to make a basic MIDI instrument using an Arduino Uno. This instrument plays three notes (a triad) in a row. Using the different potentiometers, you can move up and down the notes as well as up and down a scale. You can also control the delay between each note. I feel like I learned a some interesting things about MIDI controllers from this project and I hope you will too.
This instrument is a really basic version of an arpeggiator. Here is a video of a fancy one in action. Arpeggiators exploit the natural musicality of chords to make a lot of nice sounding noise for not that much effort. They often come as part of synths where you just hold down the chords you want to be arpeggiated. In this example, we have only have one button that trigger the arpeggio and three buttons to control what the starting pitch is and its position on the scale.
For the arduino Uno, the easist way to use the MIDI protocol is over the Serial port. It takes some configuration for this to work on Windows, which I will go over. I used the Ardunio MIDI library by FortySevenEffects. I also followed these excellent youtube tutorials: MIDI for the Arduino, How to construct a MIDI controller with an Arduino. I based the code for controlling the buttons and the potentiometers from the code from the second video series. It is really well documented at part of an entire free course on making MIDI controllers.
In this project I used the Grove Starter Kit Plus for the RGB backlit LCD screen. This is not strictly necessary, but it is nice to see what notes you are playing.
Supplies
- 4 potentiometers (I used whatever I had lying around)
- 1 Button
- 1 1k resistor for button
- 1 Arduino Uno
- 1 Grove Starter kit/ RGB backlight LCD (optional)
- Wires for connecting pieces
For the computer
- DAW (I am just using a trial version of Ableton right now. Cakewalk is free)
- Hairless MIDI (A serial bridge– something that converts your serial output to MIDI input)
- Loop MIDI (Creates virtual loopback ports for your computer. )
- Arduino IDE
Step 1: Setting Up the Board
Below is a diagram of the layout that I used without the LCD. The button will be used as a trigger to start the arpeggio. Three of the potetiometers will control different aspects of the arpeggio and the last potetiometer will serve as a MIDI control.
In my actual circut layout, I used the Grove board (which connects on top of the Arduino Uno). It came with a button and a decent potetiometer, which you can see in the above picture plugged in to their respective ports. The LCD screen was very easy to connect with this, you just connect one of the cables that come with the Grove kit from the LCD screen to the I2C port.
Step 2: Understand MIDI, Ever So Slightly
MIDI is a communication protocol that was purposely built for musical instruments created in the early 80’s. With the software setup we have, we could use MIDI for input as well as output to our arduino but just to keep things simple we will focus on output. Specifically we will use two different channel voice messages: Note On and Controller Change. Note On allows us to tell the DAW or whatever we are using to turn on or off a note, and at what velocity (MIDI’s strange way of saying volume.) Controller Change tells the DAW that a value of a controller has changed. You can imagine this as twisting a dial on an amplifier, wheter it is for volume or some effect like distortion or reverb. There are tons of other message types, and things can get really elaborate, really quickly. The MIDI library we are using has a bunch of functions to support these different message types.
Interested in finding out more? I would first refer you to the youtube links that I put at the beginning of the instructable. Then I would search around to see what people have done with MIDI to get an idea of the possibilities.
For Note On, this is a link to a list of pitches and their assigned MIDI values. For example, C4 has a midi value of 60. Each number maps to a semi-tone, so it is really easy to use modulo division to figure out what note you have. Given C4=60, I know that C5 = 72, C6 = 84, without having to look at a table because I know the octaves are spaced 12 semitones apart. This will come in handy later when we are converting the MIDI values to notes to display on the LCD screen. Also, if we save the Cmaj scale MIDI values to a list, we can use mod7 division to move around values in a scale.
Step 3: Code
If you do not want/need the LCD I attached a version where I commented out the LCD components. I heavily commented in the code, but here I will point some things out, starting from the top.
Initializing variables:
- The variable “debounce_delay” for the button and the variables “Timeout” and “var_threshold” can be used to reduce some of the noise and false readings from the button and the potentiometer. If you are having problems, that would be a good place to start. The potentiometers can get a little testy between boundaries because we are converting a continous range to discrete integers. If the potetiometers are flickering between states too much, there are several ways to address it. One solution that I did not implement here is to take several readings and average them before you do any further processing.
- The list “notes” is what sequence of notes we are going to play in the arpeggio. I have it set to play the starting note, and notes 2 notes and 4 notes a way in the major scale. This will make basic major, minor, and diminished chords. If you want something a little more interesting, you can add more notes to the list “notes”. Just don’t forget to change the constant “CHORD_LENGTH”. For example, you can do this:
const int CHORD_LENGTH = 6; //number of notes in chord int midi_vals[CHORD_LENGTH] = {}; //array to store midi-output to play int notes[CHORD_LENGTH] = {0,2,4,2, 5,7}; //1st 3rd, and 5th note on scale, plus the 7th. This is more jazzy - By creating the list “Cmaj_scale” I have created a template that I can move up and down to create the chords. If I want to change the key, all I need to do is change “midi_c_state[2]” using inputs from the 3rd potetiometer. If I just want to move around the major scale from a given starting note I just change “midi_c_state[0]” from the first potetiometer.
Setup:
- You need to have the correct Baud rate for Hairless MIDI to read your serial input correctly. Otherwise you will get errors in Hairless MIDI like “got unexpected data byte”
- The LCD screen comes with 16 column digits and 2 rows. I
Loop and associated Functions
- The Function “pause_length ” controls the delay between notes being played.
- “starting_scale_position” allows you to move up and down the major scale without changing key. “Starting note” allows you to shift everything up or down a half-tone value, allowing you to change key. Both functions call “move_chord” that remaps the notes you have selected to MIDI values.
- Changing the delay, starting scale position, MIDI control or note will cause the LCD to refresh.
//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 |
constint N_BUTTONS = 1; //* total numbers of buttons |
constint 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 |
unsignedlong last_debounce_time[N_BUTTONS] = {0}; // the last time the output pin was toggled |
unsignedlong debounce_delay = 50; //* the debounce time; increase if the output flickers |
//Potentiometers |
constint N_POTS=4; |
constint 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 |
constint TIMEOUT = 300; //* Amount of time the potentiometer will be read after it exceeds the var_threshold |
constint var_threshold = 10; //* Threshold for the potentiometer signal variation |
boolean pot_moving= true; // If the potentiometer is moving |
unsignedlong PTime[N_POTS] = {0,0,0,0}; // Previously stored time |
unsignedlong 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 “}; //for 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 |
constint 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; |
//LCD |
rgb_lcd lcd; //create LCD instance |
constint colorR = 255; //set background colors |
constint colorG = 0; |
constint colorB = 100; |
voidsetup() { |
// put your setup code here, to run once: |
Serial.begin(115200); |
for (int i = 0; i < N_BUTTONS; i++) { |
pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP); |
//LCD |
lcd.begin(16,2); //rows and columns of LCD |
lcd.setRGB(colorR, colorG, colorB); //starts background color |
lcd.print(“Get Ready to Jam!”); |
} |
} |
voidloop() { |
//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 |
voidarp_trigger() { |
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 |
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 |
MIDI.sendNoteOn(midi_vals[i], 0, midi_ch); |
} |
} |
button_p_state[0] = button_c_state[0]; //saves current state as past state |
} |
} |
} |
voidmove_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 |
voidstarting_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 |
} |
} |
} |
voidpause_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 |
} |
} |
} |
voidstarting_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 |
} |
} |
} |
voidpot_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 |
voidupdate_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); |
} |
Step 4: Setting Up the PC
For this to work, you need to download Hairless MIDI, a DAW, and Loop MIDI. The links to them are at the beginning.
First off, open up LoopMIDI and click on the plus button in the bottom left corner. This will create a loopback port called “loopMIDI port”. If you then open up Hairless MIDI, you should see it as an option in the drop down menus for “Serial Port” and “MIDI Out”. You can ignore “MIDI In” for this example. Note: If you have a lot of inputs from your controller, for example if a potentiometer is making a lot of noise, your computer will shut down the port and you will see it in LoopMIDI. You should try to make your potentiometers less noisy, then delete the port on LoopMIDI, using the “Minus” button on the bottom left of the screen. Then create a new port using the plus button and try again.
In Hairless MIDI, if you did it correctly, when you click on “Debug MIDI messages” and you trigger your MIDI controller, you should see what kind of MIDI message it is, as pictured. If there is a problem with your Baud rate, you will get an error like “Warning: got a status byte…”. You just need to go into preferences and select the correct Baud rate. I included a screenshot of what worked for mine.
Now all you need to do is choose your favorite DAW and set it up so it can take input from LoopMIDI. This video which is part of one of the series that I linked to in the beginning, shows how it is done for Ableton, but different DAWs may vary.
Source: Basic Arduino MIDI Arppegiator