Lab: MIDI Output using an Arduino

Introduction

This lab covers only the details of MIDI communication on the Arduino module. For a more general introduction to MIDI on a microprocessor, see the the MIDI notes.

MIDI, the Musical Instrument Digital Interface, is a useful protocol for controlling synthesizers, sequencers, and other musical devices. MIDI devices are generally grouped in to two broad classes: controllers (i.e. devices that generate MIDI signals based on human actions) and synthesizers (including samplers, sequencers, and so forth). The latter take MIDI data in and make sound, light, or some other effect.

What You’ll Need to Know

To get the most out of this lab, you should be familiar with the following concepts. You can check how to do so in the links below:

Things You’ll Need

A short solderless breadboard with two rows of holes along each side. There are no components mounted on the board. The board is oriented sideways so that the long rows of holes are on the top and bottom of the image.
A short solderless breadboard.
Three short pieces of hookup wire: one is clad in red insulation, one in blue, and one in black. All three have exposed ends approximately 5mm long.
22 AWG hookup wire
An Arduino Uno. The USB connector is facing to the left, so that the digital pins are on the top of the image, and the analog pins are on the bottom.
An Arduino Uno.
Photo of a 5-pin MIDI socket. Three wires protrude from the back.
A 5-pin MIDI socket
Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
Resistors. Shown here are 220-ohm resistors.  You will need both 220 ohm and 10-kilohm resistors for this lab.
Force-sensing resistor (FSR). These sensors have a resistive rubber inside that changes its resistance depending on the force with which you press on the sensor. The one shown is a flat disc about 5cm in diameter
Force-sensing resistor
Photo of a toggle switch. This is a panel-mount switch, meant to be mounted in an instrument panel. It is about 0.5 in (2cm) long and has two wires protruding from it.
A switch

Prepare the breadboard

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino module, use the 5V and any of the ground connections:

An Arduino Uno on the left connected to a solderless breadboard, right. The Uno's 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno's ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.
An Arduino Uno on the left connected to a solderless breadboard, right.

Made with Fritzing

Connect the sensors

Connect an analog sensor to analog pins 0 like you did in the analog lab. Connect a switch to digital pin 10 like you did in the digital lab.

Schematic view of an Arduino connected to a voltage divider and a switch. On the left side, a variable resistor is connected to +5 Volts on one side, and to analog input 0 on the other. A fixed 10-kilohm resistor is also connected to analog input 0, and its other side connects to ground. On the right side, a switch is connected to +5 Volts, with its other side connected to digital pin 10. A 10-kilohm resistor also connects from digital pin 10, and its other end connects to ground.
Schematic view of an Arduino connected to a voltage divider and a switch.
Breadboard view of an Arduino connected to a voltage divider and a switch. The Arduino Uno, left, has red and black wires that connect its +5 volt and ground pins to the left side bus rows of the breadboard. The red wire connects to the outside left row, forming the left voltage bus, and the black wire connects to the inside left row, forming the left ground bus. A red wire connects the left side voltage bus to the inner row on the right side of the breadboard, thus forming the right side voltage bus. Similarly, a black wire connects the left side ground bus to the outer row on the right side, forming the right side ground bus. A 10-kilohm resistor connects the left side ground bus to row ten in the left center section. A pushbutton straddles the center divide of the breadboard, mounted in rows ten and twelve. A red wire connects row twelve in the left center section to the voltage bus on the left side. A blue wire connects tow ten, where the resistor and the pushbutton meet, to digital pin 10 on the Arduino. Another 10-kilohm resistor connects the left side ground bus to row 24 in the left center section. A force sensing resistor is also connected to that row. The other side of the FSR is connected to row is also connected to row 25. A red wire connects row 25 in the left center section to the voltage bus on the left side. A blue wire connects row 24, where the fixed resistor and the FSR meet, to analog input 0 on the Arduino.
Breadboard view of an Arduino connected to a voltage divider and a switch.

 Build the MIDI Circuit

Add the MIDI out jack and a 220-ohm resistor to digital pin 3:

 Schematic view of an Arduino connected to a voltage divider and a switch. The switch and voltage divider are connected as shown above. the MIDI connector's pin 2 is connected to a 220-ohm resistor, and the other side of the resistor is connected to +5 volts. Pin 3 of the MIDI connector is connected to ground. Pin 4 of the MIDI connector is connected to the Arduino's digital pin 3.
Schematic view of an Arduino connected to a voltage divider and a switch, with a MIDI connector as well.
Breadboard view of an Arduino connected to a voltage divider, a switch, and a MIDI connector. This view builds on the breadboard view above. The voltage divider and the switch are connected to analog pin 0 and digital pin 10 as described above. The MIDI connector's pin 2 is connected row 19 in the right center section of the breadboard. A red wire connects it to row 21. From there, a 220-ohm resistor connects to the right side voltage bus. The MIDI connector's pin 3 connects to the right side ground bus. The connector's pin 4 connects to row 14 in the right center section, and a blue wire connects from there to digital pin 3 on the Arduino.
Breadboard view of an Arduino connected to a voltage divider, a switch, and a MIDI connector.

This circuit doesn’t actually match the MIDI specification, but it works with all the MIDI devices we’ve tried it with. This circuit includes an analog and a digital sensor to allow for physical interactivity, but those aren’t necessary to send MIDI data.

Play Notes

Once you’re connected, sending MIDI is just a matter of sending the appropriate bytes. In the code below, you’ll use the SoftwareSerial library to send data on digital pin 3, so that you can keep the hardware serial available for debugging purposes.

The bytes have to be sent as binary values, but you can format them in your code as decimal or hexadecimal values. The example below uses hexadecimal format for any fixed values, and a variable for changing values. All values are sent serially as raw binary values, using the BYTE modifier to .print() (Many MIDI tables give the command values in hex, so this was done in hex for the sake of convenience):

#include <SoftwareSerial.h>

 // Variables:
  byte note = 0;            // The MIDI note value to be played

 //software serial
 SoftwareSerial midiSerial(2, 3); // digital pins that we'll use for soft serial RX & TX

  void setup() {
    //  Set MIDI baud rate:
    Serial.begin(9600);
    midiSerial.begin(31250);
  }

  void loop() {
    // play notes from F#-0 (30) to F#-5 (90):
    for (note = 30; note < 90; note ++) {
      //Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
      noteOn(0x90, note, 0x45);
      delay(100);
      //Note on channel 1 (0x90), some note value (note), silent velocity (0x00):
      noteOn(0x90, note, 0x00);
      delay(100);
    }
  }

  //  plays a MIDI note.  Doesn't check to see that
  //  cmd is greater than 127, or that data values are  less than 127:
  void noteOn(byte cmd, byte data1, byte data2) {
    midiSerial.write(cmd);
    midiSerial.write(data1);
    midiSerial.write(data2);

     //prints the values in the serial monitor so we can see what note we're playing
    Serial.print("cmd: ");
    Serial.print(cmd);
    Serial.print(", data1: ");
    Serial.print(data1);
    Serial.print(", data2: ");
    Serial.println(data2);
  }

Alternatives to SoftwareSerial

There are two alternatives to SoftwareSerial, but they are only available on boards other than the Uno. You could use the second hardware serial port if you have one. Or, if you’re using one of the MKR series boards or an M0-based derivative board, you can use MIDIUSB.

Second Hardware Serial Port

If you’re using any board that has two hardware serial ports, you don’t have to use SoftwareSerial. That includes almost any of the official Arduino boards except the Uno: the Due, the Mega, the MKR series, the Leonardo, the Micro and the 101 all have two hardware serial ports. You could also use any M0- or 32U4-based derivative board like Adafruit’s Feather and Trinket boards. If you are using one of those boards, move the MIDI connector’s pin 4 to the TX1 pin of your board. Then change your code as follows. First, remove the first line that includes the SoftwareSerial library. Then remove the line that initializes SoftwareSerial. Then change midiSerial.begin() to Serial1.begin(). Then change all midiSerial calls to Serial1 calls. Here’s the changed code:

 // Variables:
  byte note = 0;            // The MIDI note value to be played

  void setup() {
    //  Set MIDI baud rate:
    Serial.begin(9600);
    Serial1.begin(31250);
  }

  void loop() {
    // play notes from F#-0 (30) to F#-5 (90):
    for (note = 30; note < 90; note ++) {
      //Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
      noteOn(0x90, note, 0x45);
      delay(100);
      //Note on channel 1 (0x90), some note value (note), silent velocity (0x00):
      noteOn(0x90, note, 0x00);
      delay(100);
    }
  }

  //  plays a MIDI note.  Doesn't check to see that
  //  cmd is greater than 127, or that data values are  less than 127:
  void noteOn(byte cmd, byte data1, byte data2) {
    Serial1.write(cmd);
    Serial1.write(data1);
    Serial1.write(data2);

     //prints the values in the serial monitor so we can see what note we're playing
    Serial.print("cmd: ");
    Serial.print(cmd);
    Serial.print(", data1: ");
    Serial.print(data1);
    Serial.print(", data2: ");
    Serial.println(data2);
  }

Using MIDIUSB

If you’re using an ARM board like the MKR series, or the Due or any of the M0-based derivatives like the Adafruit Feather M0 boards, you can also use MIDIUSB. When you do this, your microcontroller shows up to your computer like a USB MIDI device. This is handy for when you’re connecting to a laptop. It’s less handy for connecting to dedicated synthesizers or samplers.

To do this, dispose of the MIDI socket. You’ll be connecting through your USB connector, and yes, the serial connection to the Serial Monitor will still work as well. Change your code as follows. First include the MIDIUSB library at the top of your code. Then remove the Serial1.begin() line in the setup. Then replace the Serial1.write() lines in the noteOn function with the MIDI code shown below:

  #include <MIDIUSB.h>
  // Variables:
  byte note = 0;            // The MIDI note value to be played

  void setup() {
    Serial.begin(9600);
  }

  void loop() {
    // play notes from F#-0 (30) to F#-5 (90):
    for (note = 30; note < 90; note ++) { 
      //Note on channel 1 (0x90), some note value (note), middle velocity (0x45): 
      noteOn(0x90, note, 0x45); delay(100); 
      //Note on channel 1 (0x90), some note value (note), silent velocity (0x00): 
      noteOn(0x90, note, 0x00); delay(100); 
     } 
   } 

   // plays a MIDI note. Doesn't check to see that 
   // cmd is greater than 127, or that data values are less than 127: 
   void noteOn(byte cmd, byte data1, byte data2) { 
     /* First parameter is the event type (top 4 bits of the command byte). 
        Second parameter is command byte combined with the channel. 
        Third parameter is the first data byte 
        Fourth parameter second data byte, if there is one: 
    */ 
    midiEventPacket_t midiMsg = {cmd >> 4, cmd, data1, data2};
    MidiUSB.sendMIDI(midiMsg);
    
    //prints the values in the serial monitor so we can see what note we're playing
    Serial.print("cmd: ");
    Serial.print(cmd);
    Serial.print(", data1: ");
    Serial.print(data1);
    Serial.print(", data2: ");
    Serial.println(data2);
  }

Allow a Person to Play Notes

The previous example will just play notes, no interactivity. The example below uses an analog input to set the pitch, and a digital input (a switch) to start and stop the note:

 #include <SoftwareSerial.h>

 const int switchPin = 10;  // The switch is on Arduino pin 10
 const int LEDpin = 13;     //  Indicator LED

  // Variables:
  byte note = 0;              // The MIDI note value to be played
  int AnalogValue = 0;        // value from the analog input
  int lastNotePlayed = 0;     // note turned on when you press the switch
  int lastSwitchState = 0;    // state of the switch during previous time through the main loop
  int currentSwitchState = 0;

 //software serial
 SoftwareSerial midiSerial(2, 3); // digital pins that we'll use for soft serial RX & TX

  void setup() {
    //  set the states of the I/O pins:
    pinMode(switchPin, INPUT);
    pinMode(LEDpin, OUTPUT);
    //  Set MIDI baud rate:
   Serial.begin(9600);
    midiSerial.begin(31250);
  }

  void loop() {
    //  My potentiometer gave a range from 0 to 1023:
    AnalogValue = analogRead(0);
    //  convert to a range from 0 to 127:
    note = AnalogValue/8;
    currentSwitchState = digitalRead(switchPin);
    // Check to see that the switch is pressed:
    if (currentSwitchState == 1) {
      //  check to see that the switch wasn't pressed last time
      //  through the main loop:
      if (lastSwitchState == 0) {
        // set the note value based on the analog value, plus a couple octaves:
        // note = note + 60;
        // start a note playing:
        noteOn(0x90, note, 0x40);
        // save the note we played, so we can turn it off:
        lastNotePlayed = note;
        digitalWrite(LEDpin, HIGH);
      }
    }
    else {   // if the switch is not pressed:
      //  but the switch was pressed last time through the main loop:
      if (lastSwitchState == 1) {
        //  stop the last note played:
        noteOn(0x90, lastNotePlayed, 0x00);
        digitalWrite(LEDpin, LOW);
      }
    }

    //  save the state of the switch for next time
    //  through the main loop:
    lastSwitchState = currentSwitchState;
  }

  //  plays a MIDI note.  Doesn't check to see that
  //  cmd is greater than 127, or that data values are  less than 127:
  void noteOn(byte cmd, byte data1, byte  data2) {
    midiSerial.write(cmd);
    midiSerial.write(data1);
    midiSerial.write(data2);

   //prints the values in the serial monitor so we can see what note we're playing
    Serial.print("cmd: ");
    Serial.print(cmd);
    Serial.print(", data1: ");
    Serial.print(data1);
    Serial.print(", data2: ");
    Serial.println(data2);
  }

Make an Instrument

This is a suggestion. You can do any project you wish as long as it demonstrates your mastery of the lab exercises and good physical interaction. This is just one suggestion.

Now that you’ve got the basics, make a musical instrument. Consider a few things in designing your instrument:

  • Do you want to play discrete notes (like a piano), or sliding pitches (like a theremin)? How do you program to achieve these effects?
  • Do you want to control the tempo and duration of a note?
  • Do you want the same physical action to set both the pitch and the velocity (volume) of a note?
  • Do you want to be able to play more than one note at a time (e.g. chords)?

All of these questions, and many more, will affect what sensors you use, how you read them, and how you design both the physical interface and the software.

Originally written on August 23, 2014 by Benedetta Piantella Simeonidis
Last modified on August 21, 2018 by Tom Igoe