Sorry, you need to enable JavaScript to visit this website.

Arduino and MIDI out

Level: 
Tools: 

Arduino.

Note! We have received some concern about connecting "directly" from the Arduino to the MIDI in port of a HW synth. Although we have done this successfully with several different HW setups, note that LMP does not take any responsibility for any catastrophic results of this tutorial. In the next part of this tutorial, "Arduino and MIDI in", we will introduce the optocoupler.

In this tutorial we will learn to use the Arduino micro-controller as a tool to control musical instruments through MIDI. We will assume you have some basic understanding of what a program is, as well as functions and variables. If not:

  • A program is a collection of statements and functions that tells the computer (the Arduino) what to do.
  • A function is a way of grouping and naming a couple of lines of code so that you can reuse them. Arduino has two special functions that must be present in all projects. The first is called setup and is called once when the program starts. The seconds is called loop. The Arduino will run the loop function continously until it is powered off.
  • A variable is a box that can hold different values. It is given a name so that you can change and read the value.

This tutorial also assumes you have a computer running the Arduino IDE, as well as an Arduino connected with an USB cable. Instructions on how to install the Arduino IDE.

You must also have a GM-compatible synth (as most are, and if it is not, you will still be able to do most of the tutorial) and a MIDI cable. For this tutorial I have used an old (and small!) Yamaha QY70 MIDI workstation.

1. MIDI out

MIDI is a serial protocol for talking to synths. As the Arduino is already very capable of talking serially, making a MIDI out interface is very easy.

You will need:

  • a MIDI DIN contact
  • a 220 Ohm resistor

Both of these can be bought at your local electronics store or on Ebay.

You solder the DIN contact like this:

MIDI DIN contact.

and connect the parts like this:

Arduino connected to MIDI interface.

You can test the interface using this simple code which outputs a short sequence of notes:

Download midi_out.ino.

Make sure to put the source code in a folder named "midi_out".

Note

As you can see in the code

// note lengths
const int bpm = 128; // tempo, BPM (beats per minute), a value that makes t16 an integer
const int t1 = 1024 * (bpm/64); // 1 whole beat = 512
const int t2 = t1/2; // 256
const int t4 = t2/2; // 128
const int t8 = t4/2; // 64
const int t16 = t8/2; // 32
 

we are using a BPM value of 128. For our purposes (and for the code to work properly later on in the tutorial) it is a good idea to keep the BPM as a value that will divide nicely, so that the calculations of the following constants will not create any rounding errors.

Exercise

Try changing the note values and the note lengths. Can you make it play a melody?

2. Program control

If you test and change the code and re-upload new sketches (as programs for the Arduino are called), notes will probably be sounding as the program is interrupted between a NOTE ON and a NOTE OFF message.

To get rid of this, we will add a short loop in the setup function that sends the ALL NOTES OFF and ALL SOUNDS OFF on all 16 MIDI channels.

// all sounds off on all 16 channels (can be synth-specific)
for (int i = 0; i < 16; i++) {
    midiData2((0xB0 | i), 0x78, 0);
    delay(midiSendDelay);
    midiData1((0xB0 | i), 0x7B);
    delay(midiSendDelay);
}
 

Now we will introduce dynamic control of the MIDI commands. Let’s start by panning the note sequence. We will use  a variable to control the pan. In MIDI, the pan value can go from 0 (extreme left) to 127 (extreme right), so we will let the variable step through these values and send out a MIDI pan message for each value.

And why not dynamically control the pitch too? We use a variable for the MIDI note value and let it increase by 3 for each iteration.

 // pan
pan = pan + 5;
if (pan > 127)
{
  pan = 0;
}
midiData2((0xB0 | voiceMidiChannel), 10, pan);
 
note = note + 3;
if (note > 90)
{
  note = 24;
}
midiNote(midiNoteOn + voiceMidiChannel, note, midiVelocity);
delay(t8);
midiNote(midiNoteOff + voiceMidiChannel, note, midiVelocity);
 

Download midi_out_pan.ino and try it out.

Exercise

Introduce other controllers and step through them, for example MIDI cutoff and resonance.

3. User control

Now we can control our synth with a program. But how about some user interaction? For this part you will need:

  • 2 x momentary push buttons (buttons that don't "get stuck" in a position)
  • 2 x 10 kOhm resistors
  • wires for connections
  • a breadboard to make all the connections

Add the push buttons

Arduino push buttons.

and connect them

Arduino push button schematics.

The push buttons are read by the Arduino on two different pins (inputs).

First we have to tell the Arduino what pins we are using and that they are inputs. We do that in the setup function.

pinMode(pinDown, INPUT);
pinMode(pinUp, INPUT);
 

The two (constant) variables pinDown and pinUp are declared at the start of the program. The const word means that their value can't be changed.

We read the values in our loop function and play two different notes depending on which button is pressed.

 // if user presses 1st button play a short note 
if (digitalRead(pinDown) == HIGH)
{
  midiNote(midiNoteOn + voiceMidiChannel, 48, midiVelocity);
  delay(t8);
  midiNote(midiNoteOff + voiceMidiChannel, 48, midiVelocity);
}
 
// if user presses 2nd button play a short note 
if (digitalRead(pinUp) == HIGH)
{
  midiNote(midiNoteOn + voiceMidiChannel, 51, midiVelocity);
  delay(t8);
  midiNote(midiNoteOff + voiceMidiChannel, 51, midiVelocity);
}
 

Download midi_out_control.ino and try it out.

Exercise

Use a light sensitive resistor instead of the push buttons to make a simple theremin type controller. Use the map function to map the input to a proper range.

4. A simple MIDI sequencer

 

And now for the grand finale in this tutorial: A MIDI sequencer with a simple remix functionality! Although this might sound more fancy than it is, this code can be a framework for all kinds of multitimbral sequence based projects.

First we will add some hardware: the LEDS. They will show which sequence we are currently playing. Use the buttons from the previous step and add the LEDs (and resistors). So for this part you will also need:

  • 3 x LEDs (one yellow, one green and one red, one for each kind of sequence: Intro/Outro, Verse and Chorus)
  • 3 x 220 ohm resistors
  • some more wires

Arduino schematics.

The buttons are connected as in the previous step. They will allow the user to cycle through a predetermined list of sequences (stored in the song array).

We will now control several voices at once, letting each one play in sync with the others.

Each voice will have one or more sequences that you can compare to a block in a sequencer program.

Each sequence contains a list of notes to play.

Which sequence to play is decided by a state variable (sequenceIndex), so that we can move between the blocks just like a true software sequencer plays a song.

For that, we will do some object oriented programming (OOP). The idea is to create an object for every voice. An object is a  collection of functions and variables, neatly packaged. Once you have written the code for an object you can create an arbitrary number of the object, each with slightly different behaviour depending on how you create it.

This object will contain some important properties of the voice:

byte voiceId; // unique id
byte midiChannel; // MIDI channel
note notes[maxNotes]; // the notes in the currently playing sequence
int numberOfNotes; // number of notes in notes[]
int currentNote; // note currently playing - index into notes[]
unsigned long currentNoteStart; // time in ms when current note started playing
 

as well as functions (called methods in OOP programming) that play the sequences:

void noteFill();
void noteStart();
void noteStop();
void initSynthVoice(byte, int, int, int, int);
 

The notes are stored in an array of structs. What does this mean?

A struct is a collection of variables, in our case the MIDI note value, the length and the velocity.

An array is an ordered list of values, in our case a sequence (of notes).

We will initialize all the voices (objects) in the setup function.

In the loop function we will tell the objects to play the next note (if it is time to do so = if the previous note has finished) as well as check if the user has pressed any buttons. As the loop executes quite fast, we have to introduce some timing, so that it is only possible to press a button once every half second (500 ms).

 // let the user control which sequence should be played
if ((digitalRead(pinDown) == HIGH) && (currentTime - pushTime > 500))
{
  sequenceIndex--;
  pushTime = millis();
  if (sequenceIndex < 0) {
   sequenceIndex = 0;
  }
  lightLeds(sequenceIndex);
}
 
if ((digitalRead(pinUp) == HIGH) && (currentTime - pushTime > 500))
{
  sequenceIndex++;
  pushTime = millis();
  if (sequenceIndex > maxSequences) {
   sequenceIndex = maxSequences;
  }
  lightLeds(sequenceIndex);
}
 

Download midi_sequencer.ino and try it out by pressing the buttons so the sequencer moves through the different sequences. The LEDs will show which sequence is playing.

Exercises

  • Use other controllers for selecting sequences.
  • Implement sound/patch selection using banks for non-GM synths (for example XG or other standards)

Memory

As the Arduino Uno only has 2K memory, the length of sequences and the number of voices will be a problem. We solve this by keeping the sequences short (max 16 notes) and the number of voices low.

Other possible solutions that you can explore would be:

  • to use the Arduino Mega which has 8K RAM.
  • to get an SD card reader for the Arduino and keep the sequences on a card, and only read them into RAM when you need them.
  • to create the sequences not as static arrays, but as programmatically created note values.

By Staffan Melin