In our previous article, we used what we’d learned about the I2C bus and about manipulating analogue voltages to build an Arduino wavetable synthesizer that we can control using the Raspberry Pi’s I2C bus.
This primitive configuration works. But it offers a limited range of notes, and you need to type out Python code to play them. This is not a great aid to musical inspiration.
Wouldn’t it be much better to have every note available, and to sequence them visually?
It turns out that it’s easy to do all of that by connecting existing software to our synthesizer using the Musical Instrument Digital Interface, or MIDI protocol.
In the early days of electronic music, there was no standard way for electronic music devices to send and receive detailed instructions to each other. Different manufacturers were developing their own systems, which really restricted musicians in how they could use them together.
In 1981, Dave Smith and Chet Wood of Sequential Circuits proposed the Universal Synthesizer Interface as an industry standard that any manufacturer could use. Representatives from Korg, Roland, Yamaha and Kawai all suggested modifications, which led to the MIDI standard in 1983.
MIDI doesn’t send any audio or sound recordings. It’s a lot more like sheet music. Instead, it sends instructions for what notes are to be played, for how long, at what intensity and so on. The instrument uses these instructions to produce the audio.
MIDI specifies a number of standard hardware connections, which we won’t use here, though they are of obvious interest to device builders.
For this project, we can handle our MIDI messages “in the box” with software.
Raspberry Pi OS handles MIDI messages using the Advanced Linux Sound Architecture, or ALSA. This is part of the Linux kernel, and lets you route MIDI messages between programs and devices.
To make use of this, we need to install two things: a sequencer program, which generates the MIDI messages, and a Python module to receive them.
For the sequencer we can use Seq24, a simple and lightweight MIDI sequencer which we can install from the repositories. First, update your operating system:
1 |
sudo apt update && sudo apt upgrade -y |
Then install Seq24.
1 |
sudo apt install seq24 -y |
That’s it!
For a Python module to handle MIDI ports and messages, there’s Mido. To connect this to ALSA, we also need the python-rtmidi back end. Let’s install them.
1 |
sudo pip3 install mido python-rtmidi |
Now open the python interpreter and import Mido.
1 2 |
python3 import mido |
To open a MIDI input port for our Arduino synthesizer, use this command:
1 |
port = mido.open_input(‘Arduino’, virtual=True) |
A MIDI port named Arduino will now be available in ALSA.
Click on the Raspberry icon in the top left of the desktop environment to open the application menu and then select Seq24.
You’ll see a window full of bracketed areas. These are sequence containers. Right click on one of them and click New to open a sequence editor.
Click on the button on the left and you can select the Arduino.
Now switch back to the Python interpreter and type a function to display the MIDI messages as they arrive.
1 2 |
for message in port: print(message) |
Now return to the sequence editor and click on some of the piano keys. You should see note_on and note_off messages appear on the screen, with a number to indicate which note.
Press Control+C to exit this loop when you’ve played around enough.
Can you see where we’re going here? Instead of printing these messages out, we want to write a byte to the I2C bus to tell the Arduino to play the note.
If you need only the notes our synthesizer is already configured to play, you could use the sketch from our last lesson. However, this is a very limited range.
It’s easy though to calculate a frequency from a MIDI note number, using this formula.
The range of MIDI notes goes from 0 to 127. We can pass integers between 0 and 255 in I2C byte, so if we use half of these for “note on” messages, we have 128 to 255 left for control messages.
Let’s use 128 for a “note off” message. If we were making a polyphonic synth, we’d need to specify which note. Our monophonic synth will never have more than one note playing at a time, so we only need the one instruction.
Putting this all together, we can rewrite the readInstruction function like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void readInstruction(int bitstream) { byte option = Wire.read(); switch (option) { case 128: edgar.setFrequency(0, 0.0); // Play nothing edgar.trigger(0); break; case 0 … 127: float pitch = 440.0 * pow( 2.0, ((float)option – 69.0) / 12.0); // Calculate frequency edgar.setFrequency(0, pitch); edgar.trigger(0); break; } } |
Save this sketch and upload it to your Arduino Uno.
Now switch back to the python interpreter and open the I2C bus, just as we did before.
1 2 3 |
from smbus import SMBus arduino = 0x8 i2cbus = SMBu2(1) |
Now try writing some values to the I2C bus to see if you can make some noise.
1 2 3 4 5 |
i2cbus.write_byte(arduino, 52) i2cbus.write_byte(arduino, 55) i2cbus.write_byte(arduino, 57) i2cbus.write_byte(arduino, 58) i2cbus.write_byte(arduino, 128) |
Are you hearing the notes? Great.
Now write a loop, similar to the one before, that writes bytes to the I2C bus instead of printing messages.
1 2 3 4 5 |
for message in port: if message.type == ‘note_on’: i2cbus.write_byte(arduino, message.note) elif message.type == ‘note_off’: i2cbus.write_byte(arduino, 128) |
Now run this loop and try clicking the piano keys again. You should hear the synthesizer play.
You can now get sequencing!
If you need help figuring out Seq24, check out this tutorial.
There’s a lot more to the MIDI protocol than note on and note off messages. If you want to know all about it, this video series by Andrew Kilpatrick gets right into it:
Be sure to check back here soon, because we’ll be covering how to add a simple analogue filter to this synthesizer, to get a much more varied timbre.