Arduino, Sensors, and MIDI

Now that you’re up to speed on using Arduino’s inputs and outputs, this Instructable will give you everything you need to get started using sensors to trigger MIDI notes from Arduino. This post is the last installment in a series of workshops I led at Women’s Audio Mission. The first two classes are Intro to Arduino and Working with Arduino Inputs and Outputs.

Arduino, Sensors, and MIDI

Parts List:

(1x) Arduino Uno Amazon or you can pick one up at a local Radioshack

(1x) usb cable Amazon

(1x) breadboard Amazon

(1x) jumper wires Amazon

(1x) 220Ohm resistors Digikey CF14JT220RCT-ND

(1x) led Digikey C503B-RCN-CW0Z0AA1-ND

(1x) 10kOhm resistor Digikey CF14JT10K0CT-ND

(1x) tact button Digikey 450-1650-ND

(1x) tilt switch Adafruit 173

(1x) 10kOhm potentiometer Digikey PDB181-K420K-103B-ND

(1x) light sensitive resistor Digikey PDV-P8103-ND

(1x) 33kOhm resistor Digikey 33KQBK-ND

(1x) 1MOhm resistor Digikey 1.0MQBK-ND

(1x) piezo sensor Sparkfun SEN-10293

Step 1: Serial to MIDI converter

In this class we’ll be using the Ardiuno’s USB connection to send Serial messages to you computer, then we’ll run an app like Hairless MIDI to convert this the Serial messages to MIDI and route them to other applications on your computer (Ableton, Garageband, etc). I chose this software solution because it is easiest and cheapest to setup for an entire class, you could also use a 5 pin MIDI plug and a MIDI cable to plug directly into other MIDI instruments. There are a few things you will need to be aware of with this setup:

Be sure that the baud rate you specify in Serial.begin() in your Arduino sketch is the same number selected under Hairless MIDI >> Preferences >> Baud Rate (I used 9600 so I used the command Serial.begin(9600) in all example Arduino sketches, see the first two images above). If you choose to wire up a 5 pin MIDI plug you have to set the baud rate to 31250, but if you’re connecting via USB to a Serial to MIDI application, you can use whatever baud rate you like.

To use Hairless MIDI you will need to select your board (something like usbmodemfd121) from the Serial Port menu and select the MIDI channel that you would like to send or receive MIDI to/from. Make sure you have the same MIDI channel selected in the preferences of whatever other MIDI applications you are running on your computer. I sent my MIDI to IAC Driver Bus 1, and then setup Garage Band or Ableton to receive MIDI on this same channel. If you do not see any MIDI output options in Hairless MIDI, scroll down to the FAQ and troubleshoot your setup.

You cannot program the Arduino while it is connected to Hairless MIDI, because the two applications are competing for the same port (see the error in the second image). A quick way to bypass this without needing to quit Hairless MIDI each time you want to change your code is to select a different Serial Port from the Hairless MIDI interface, upload your new Arduino code, and then set the Serial Port in Hairless MIDI back to the correct one.

Step 2: MIDI Protocol

MIDI messages are comprised of two components: commands and data bytes. The command byte tells the MIDI instrument what type of message is being sent and on which MIDI channel, and the subsequent data byte(s) store the actual data. For example: a command byte might tell a MIDI instrument that it has information about a note, and the following data bytes will describe which note and how loud. A command byte could also tell a MIDI instrument that it going to send information about pitchbend, then the following data bytes would describe how much pitchbend. A command byte and the data bytes following it make up one “MIDI message”.

A byte is a data type (other data types we’ve seen so far are int, boolean, and long). Bytes store positive integers between 0 and 255. MIDI messages are made up of a series of bytes, and they can be decoded based on their value to understand what they mean.

Here is a list of common command bytes in their decimal (base ten) form:

Note Off = 128

Note On = 144

Pitchbend = 224

Command bytes are always greater than 127 and data bytes are always between 0 and 127, in fact, that’s how a MIDI instrument can tell the difference between a command byte and a data byte. Here’s how we would send a MIDI message to turn on Middle C with high volume:

144, 60, 127

The first number, 144, is the command byte, it tells the MIDI instrument that this MIDI message is a Note On message. The second number, 60, is a data byte. The first data byte in a Note On MIDI message is “note” – this Note On command turns on MIDI note 60 (Middle C, you can find a list of note/MIDI conversions here). The last number is also a data byte, the second data byte in a Note On MIDI message is “velocity”, which is used to control the loudness of a note. Since data bytes are between 0 and 127, 127 is the max volume for a note.

Each MIDI note starts with a Note On message and ends with a Note Off message. Some percussive instruments will sound like they’ve turned off if you hold them for a long time, but the won’t actually be off until you send a note off message. It’s important to remember to turn a note off before you try to turn it on again to avoid inconsistent results. There are two ways to turn a MIDI note off, this first is using a Note Off command:

128, 60, 0

This command will turn note 60 off, it starts with the command byte for Note off, sets note = 60, and velocity = 0 (velocity is usually not very noticeable for Note Off, whatever number you want to pick is fine). You can also turn a note off by sending a Note On message with velocity = 0:

144, 60, 0

This is a more common approach in MIDI (from my experience) so it’s how we’ll be dealing with Note Off in this class.

If you’re interested in learning more about MIDI protocol, binary, and bits, check out this article and this table.

Step 3: Generating MIDI with Arduino

Upload the following code onto the Arduino, it turns MIDI note 60 (middle C) on, waits for 300ms, then turns it off and waits for another 200ms.

byte noteON = 144;//note on command

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

void loop() {
  MIDImessage(noteON, 60, 100);//turn note on
  delay(300);//hold note for 300ms
  MIDImessage(noteON, 60, 0);//turn note off (note on with velocity 0)
  delay(200);//wait 200ms until triggering next note
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

In this sketch I created a helper function called MIDImessage that accepts a command the two data bytes and sends them out the Arduino’s USB connection using Serial.write(). Serial.write is like Serial.print, but it converts whatever’s inside it to binary before sending it out.

Try rewriting the sketch to play a series of notes, cycling through MIDI notes 50-79, turning each note on and then off:

byte noteON = 144;//note on command

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

void loop() {
  for (byte note=50;note<80;note++) {//from note 50 (D3) to note 79 (G5)
    MIDImessage(noteON, note, 100);//turn note on
    delay(300);//hold note for 300ms
    MIDImessage(noteON, note, 0);//turn note off (note on with velocity 0)
    delay(200);//wait 200ms until triggering next note
  }
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

Step 4: Arduino Analog Inputs and MIDI

Let’s wire up a potentiometer to analog pin 0 and use the data from it to control the pitch of a MIDI note:

byte noteON = 144;//note on command
int potPin = A0;

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

void loop() {

  int potVal = analogRead(potPin);//read data from potentiometer

  //we have to scale the potentiometer data to fit between 0 and 127 (this is the range of MIDI notes)
  byte note = map(potVal, 0, 1023, 0, 127);

  MIDImessage(noteON, note, 100);//turn note on
  delay(300);//hold note for 300ms
  MIDImessage(noteON, note, 0);//turn note off (note on with velocity 0)
  delay(200);//wait 200ms until triggering next note
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

Now try using the potentiometer to control the velocity of a MIDI note:

byte noteON = 144;//note on command
int potPin = A0;

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

void loop() {

  int potVal = analogRead(potPin);//read data from potentiometer

  //we have to scale the potentiometer data to fit between 0 and 127 (this is the range of MIDI notes)
  byte velocity = map(potVal, 0, 1023, 0, 127);
  byte note = 60;

  MIDImessage(noteON, note, velocity);//turn note on
  delay(300);//hold note for 300ms
  MIDImessage(noteON, note, 0);//turn note off (note on with velocity 0)
  delay(200);//wait 200ms until triggering next note
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

This will sound different depending on what type of instrument you have your MIDI hooked up to, but generally it should sound louder. The sound may also be sharper, as if you were striking a key hard, or blowing into a wind instrument with a lot of force.

Step 5: Trigger a MIDI note with a button

Using the simple button debounce code from the first class (you could also use this debounce code that uses millis() to keep time), wire up a button to digital pin 7 and use it to trigger a MIDI note:

byte noteON = 144;//note on command

int buttonPin = 7;
boolean currentState = LOW;//stroage for current button state
boolean lastState = LOW;//storage for last button state

void setup(){
  pinMode(buttonPin, INPUT);//this time we will set the pin as INPUT
  Serial.begin(9600);//initialize Serial connection
}

void loop(){
  currentState = digitalRead(buttonPin);
  if (currentState == HIGH && lastState == LOW){//if button has just been pressed
    MIDImessage(noteON, 60, 127);//turn note 60 on with 127 velocity
    delay(2);//crude form of button debouncing
  } else if(currentState == LOW && lastState == HIGH){
    MIDImessage(noteON, 60, 0);//turn note 60 off
    delay(2);//crude form of button debouncing
  }
  lastState = currentState;
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

In this sketch, each time the button is pressed we send a Note On message, and each time the button is released, we send a Note Off message. Now try adding in the potentiometer (connected to A0) to control the pitch of the note:

byte noteON = 144;//note on command
byte note;//storage for currently playing note

int buttonPin = 7;
int potPin = A0;
boolean currentState = LOW;//stroage for current button state
boolean lastState = LOW;//storage for last button state

void setup(){
  pinMode(buttonPin, INPUT);//this time we will set the pin as INPUT
  Serial.begin(9600);//initialize Serial connection
}

void loop(){
  currentState = digitalRead(buttonPin);

  if (currentState == HIGH && lastState == LOW){//if button has just been pressed

    int currentPotVal = analogRead(potPin);
    note = map(currentPotVal, 0, 1023, 0, 127);

    MIDImessage(noteON, note, 127);//turn note on with 127 velocity
    delay(2);//crude form of button debouncing
  } else if(currentState == LOW && lastState == HIGH){
    MIDImessage(noteON, note, 0);//turn note off
    delay(2);//crude form of button debouncing
  }
  lastState = currentState;
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

Now we can add a few more notes into the mix, this sketch plays a three note phrase on each button press. You can change the base note with the potentiometer:

byte noteON = 144;//note on command
byte note;//storage for currently playing note

int buttonPin = 7;
int potPin = A0;
boolean currentState = LOW;//stroage for current button state
boolean lastState = LOW;//storage for last button state

void setup(){
  pinMode(buttonPin, INPUT);//this time we will set the pin as INPUT
  Serial.begin(9600);//initialize Serial connection
}

void loop(){
  currentState = digitalRead(buttonPin);

  if (currentState == HIGH && lastState == LOW){//if button has just been pressed

    int currentPotVal = analogRead(potPin);
    note = map(currentPotVal, 0, 1023, 0, 127);
    int noteLength = 200;
    byte noteVelocity = 127;

    MIDImessage(noteON, note, noteVelocity);//base note
    delay(noteLength);
    MIDImessage(noteON, note, 0);//turn note off
    MIDImessage(noteON, note+7, noteVelocity);//fifth
    delay(noteLength);
    MIDImessage(noteON, note+7, 0);//turn note off
    MIDImessage(noteON, note+12, noteVelocity);//octave
    delay(noteLength);
    MIDImessage(noteON, note+12, 0);
  }

  lastState = currentState;
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

Finally, we can use a while loop to continue the arpeggio as long as we press the button. While loops are basically loopable if statements. If the argument inside the while loop’s parentheses evaluated to true, then the commands inside the while loop are executed. At the end of the while loop, the Arduino checks to see if the argument inside the while loops’s parentheses is true again. If it is still true, the while loop executes all the commands in the culy braces again, this continues forever until the argument inside the while loop’s parentheses evaluates to false. In the example below, I use the while loop to keep playing the three note phrase for as long as digitalRead(buttonPin) == HIGH.

byte noteON = 144;//note on command
byte note;//storage for currently playing note

int buttonPin = 7;
int potPin = A0;
boolean currentState = LOW;//stroage for current button state
boolean lastState = LOW;//storage for last button state

void setup(){
  pinMode(buttonPin, INPUT);//this time we will set the pin as INPUT
  Serial.begin(9600);//initialize Serial connection
}

void loop(){
  currentState = digitalRead(buttonPin);

  if (currentState == HIGH && lastState == LOW){//if button has just been pressed

    int currentPotVal = analogRead(potPin);
    note = map(currentPotVal, 0, 1023, 0, 127);
    int noteLength = 200;
    byte noteVelocity = 127;

    while(digitalRead(buttonPin) == HIGH){//as long as the button is pressed, repeat the arpeggio
      MIDImessage(noteON, note, noteVelocity);//base note
      delay(noteLength);
      MIDImessage(noteON, note, 0);//turn note off
      MIDImessage(noteON, note+7, noteVelocity);//fifth
      delay(noteLength);
      MIDImessage(noteON, note+7, 0);//turn note off
      MIDImessage(noteON, note+12, noteVelocity);//octave
      delay(noteLength);
      MIDImessage(noteON, note+12, 0);
    }
  }

  lastState = currentState;
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

Step 6: Arduino and Tilt Switch

A tilt switch is a mechanical switch that is open when you hold it in one direction and closed when you flip it upsidedown. It has a tiny metal ball on the inside of it that can roll around, when you hold it in a certain orientation, the ball creates an electrical connection between two sides of the switch.

The tilt switch has two leads coming out of it, you can pull the push button out of your circuit and replace it with a tilt switch, orientation of the switch in the circuit (long vs short lead) doesn’t matter.

Step 7: Arduino and Light Sensitive Resistors

A light sensitive resistor (LSR) is a type of variable resistor that responds to light. The LSRs I got for this class have a range of 16-33kOhms of resistance, so in total darkness they have a resistance of 33kOhms and in light they have a resistance of 16kOhms. The circuit that measures the LSR requires another regular resistor, the resistor acts as a benchmark to determine how the LSR is changing. Whenever you’re measuring variable resistance of a component (flex sensor, pressure sensors, and many others) you want to pair it with a regular resistor that has a resistance that is about equal to the max resistance of your variable resistance component. Since I’m using a 16-33kOhm LSR, I’ll use a 33kOhm resistor in my circuit.

The circuit looks like this:

5V -> 33kOhm resistor -> light sensitive resistor -> Ground (see image above)

and the Arduino analog pin (I’m using A0) connects to the junction between the LSR and the resistor. The LSR has no polarity, so orientation of the component in the circuit does not matter.

Run the following code to get a sense of the range of the LSR:

int analogPin = A0;//junction between LSR and resistor attached to pin A0

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

void loop(){
  int lsr = analogRead(analogPin);
  Serial.println(lsr);
}

I found that my resistor readings ranged from about 0 in full light to about 900 in darkness. Now map this to note, notice where I threw in the 0-900 range

Arduino, Sensors, and MIDI circuit

byte noteON = 144;//note on command
int analogPin = A0;

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

void loop() {

  int analogVal = analogRead(analogPin);//read data

  //we have to scale the lsr data to fit between 0 and 127 (this is the range of MIDI notes)
  byte note = map(analogVal, 0, 900, 0, 127);//use the 0-900 range I measured

  MIDImessage(noteON, note, 100);//turn note on
  delay(300);//hold note for 300ms
  MIDImessage(noteON, note, 0);//turn note off (note on with velocity 0)
  delay(200);//wait 200ms until triggering next note
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

This setup creates notes with higher pitch in darkness and low pitch in light. If I wanted to reverse the relation ship between light and pitch I would just reverse my range in the map function:

byte note = map(analogVal, 900, 0, 0, 127);

Notice how I’ve reversed my range from 0-900 to 900-0, this makes more light create higher pitched notes and low light create low pitched notes.

This sketch is spanning a lot of notes right now, if I wanted to narrow the scope of notes I can play with the circuit, I can use the following line:

byte note = map(analogVal, 900, 0, 40, 90);

Now the full range of resistor light sensitivity will return a range of notes between 40 and 90.

Step 8: Arduino and Pressure Sensor

I made a few pressure sensors from conductive foam, copper tape, stranded wire, and electrical tape. As the conductive foam is compressed it becomes less resistive, making it a pressure sensitive variable resistor.

Any variable resistor can be wire up according to the schematic from the previous step. I used a multimeter to measure the max resistance of my pressure sensors at about 1MOhm, so I wired up a 1MOhm resistor in series with the pressure sensor (I used the exact same schematic as the light sensitive resistor, replacing the light sensitive resistor with the pressure sensor, and the 33kOhm resistor with the 1MOhm resistor). Then I used the code from the last step to measure the range of readings form the pressure sensors at about 50-500. From there you can run the same code to change the pitch of a note with the pressure sensor, but change the line that maps the sensor measurement to note to account for the range 50-500:

byte note = map(analogVal, 50, 500, 0, 127);

Step 9: Arduino and Flex sensor

Flex sensors are another type of variable resistor, they can be wired up in exactly the same way as the light sensitive and pressure sensitive resistors. The flex sensors I used in class have a resistance between 10kOhm-20kOhm, so they should be paired with a resistor on approximately the same value. The code from the light sensitive resistor step will work with a flex sensor, but remember to adjust the 0, 900 in the line:

byte note = map(analogVal, 0, 900, 0, 127);

to whatever range you measure for your flex sensor.

 

For more detail: Arduino, Sensors, and MIDI


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

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

Scroll to Top