Basic Arduino MIDI Arppegiator

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 ArduinoHow 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.

basicarpeggiator.ino

//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

Leave a Comment

Your email address will not be published. Required fields are marked *

*
= 4 + 2

(Spamcheck Enabled)

Scroll to Top