Plugging a synthesizer into a PC usually implies using a DAW, a Tracker or some other intimidating GUI application with lots of menus, buttons and windows. In this post i’m trying to explore what you could do without a DAW, using only a bare-bones linux installation, a few system tools and some scripting.
Topics covered include but are not limited to:
- Turning any obsolete linux-capable pc or single-board computer into a midi breakout box
- Midi routing and filtering
- Layering sounds from two or more hardware synths
- Implementing a song mode on the Novation Circuit by making it sequence itself.
MIDI on linux
The Advanced Linux Sound Architecture (ALSA) provides audio and MIDI functionality to the Linux operating system.
ALSA is the audio and midi heart of Linux. It consists of a framework to build kernel drivers for sound and midi cards to handle the low level stuff, a C api (alsa-lib) to access the drivers in a uniform way, and alsa-utils - a collection of command line utils to manage audio devices (set volume, play sound files, list connected sound cards etc).
amidi, part of alsa-utils, is the util to read and write to RawMIDI ports.
You can use it to list available midi devices, peek into the incoming midi
stream and do other stuff like sending SysEx *.syx files with patches
to a synthesizer.
You can see that i have 3 midi devices connected to my linux workstation, a USB to DIN dongle, a Novation Circuit, a Korg MS20 mini, and pushed a few keys on the korg:
alex@mogwai ~ $ amidi -l # list devices Dir Device Name IO hw:2,0,0 UM-ONE MIDI 1 IO hw:4,0,0 Circuit MIDI 1 IO hw:5,0,0 MS-20 mini MIDI 1 alex@mogwai ~ $ amidi -d -p hw:5,0,0 # connect to the ms mini 90 3C 40 80 3C 40 90 40 40 80 40 40 90 43 40 80 43 40
90 is a note on on channel 1,
80 is note off, the second
byte is the notes pitch and third byte is velocity. The keys on
MS20mini is not velocity sensitive, so it is always fixed at 0x40.
MIDI is a very simple protocol, a midi message consist of one status
byte and 0 to 2 data bytes, with the exception of a SysEx message,
which can be of arbitrary length and does have a status byte at
the beginning and another status byte at the end of the message.
Here is a nice summary
from midi.org, or a more in-depth guide.
aconnect is the virtual patchcord, with it you can patch any midi
in to any midi out:
alex@mogwai ~ $ aconnect -i # list ports client 0: 'System' [type=kernel] 0 'Timer ' 1 'Announce ' client 14: 'Midi Through' [type=kernel] 0 'Midi Through Port-0' client 24: 'UM-ONE' [type=kernel,card=2] 0 'UM-ONE MIDI 1 ' client 32: 'Circuit' [type=kernel,card=4] 0 'Circuit MIDI 1 ' client 36: 'MS-20 mini' [type=kernel,card=5] 0 'MS-20 mini MIDI 1' alex@mogwai ~ $ aconnect 36:0 32:0 # connect korgy to the circuit
Now when i play the keyboard on the korg, both korg and the circuit make sound in unison. I found that i can get very nice sound layering - for example a bass patch on the korg and the organ patch on the circuit layered together make a warm full bodied church organ. Make sure to select chromatic scale on the circuit, or it might produce some very weird harmonies.
aconnect -d 36:0 32:0 to break the connection. The port names in
aconnect are different, because amidi uses raw midi ports
and aconnect goes thru the built in alsa sequencer, but it does not
matter at this point this point.
So with just
aconnect from alsa-utils you could turn any cheap
linux-capable computer into a USB Midi breakout box, but you are limited
to routing midi as is from one device to another, you can’t modify it
in transit. To do that, some coding is required.
Using Ruby to make music
My favorite scripting language is Ruby, it is not the greatest or fastest language, but it has a unique quality that with some magic you can turn ruby syntax into a domain specific language and express problems in the chosen application domain in a much more clearer and readable way. This quality has been primarily used to build web frameworks and database ORMs, but there are also projects that use it for algorithmic composition - Archaeopteryx (abandoned) and live music coding performance - the awesome Sonic Pi app (alive and well).
There is also Patchmaster, that looks surprisingly close to what I have in mind but appears to be abandoned as well and does not work on a modern linux distro because of the broken alsa-rawmidi rubygem dependency (i’ll get to that in an a moment).
So, my linux pc is successful at recognizing my synthesizers when I plug them in and I can connect to them with alsa-utils. Now i need a way to pipe the data from a midi port into a ruby script for processing, pipe it back out to another midi port and maybe invent a nice syntax for midi tinkering.
Yak Shaving (technical stuff)
Yak Shaving - Any apparently useless activity which, by allowing you to overcome intermediate difficulties, allows you to solve a larger problem.
Ruby does not come with alsa bindings, so i googled to see if there is rubygem I could use. A brief visit to rubygems.org revealed that all midi-related ruby libraries use unimidi, a cross platform midi lib, which on linux in turn uses alsa-rawmidi by the same author. I tried it and it crashed with a segmentation fault.
There a two ways you can use a C library from ruby, libffi where you write only ruby code to bridge the two languages, the other way is to write a ruby extension in C. The problem with ffi is that you have to bring a lot of C guts into ruby, declare all of the C structs and function signatures in ruby. That makes the ffi bindings very fragile, if something changes in the c library ABI you have to track down the changes and fix it in ruby code, where with C you might be able to get away with a simple recompile.
alsa-rawmidi lib uses ffi and was not updated since 2014, so my best guess is that the alsa-lib ABI changed and that’s the cause of the segfault. But it might be more complicated than that, i tried to fix it by reimplementing the bindings with ffi and failed. My code crashed just like the original lib roughly in the same spot. The remaining option was to write a native ruby extension. I think I will save the gory details of writing ALSA Ruby bindings for another post.
FFWD >>>> to Smoke Test
After about a week of googling and copypasting bits of code from different sources I had MIDI IO in a ruby script working. The bindings implement just enough of the API to get midi in and out. It is time to try it out and see what I can do with it and maybe come up with a clean and readable syntax.
ALSA raw midi port names look like
hw:4,0,1. When you plug in a
synth, the driver assigns is to a port and there are no guarantees that the
port will be the same every time. I have to tell the script how to look up a
device by name and connect to the correct port:
Print incoming midi data, pass thru midi as is or transpose notes before sending to the destination:
That wasn’t all too exciting, apart from transposition, you could do it with alsa-utils. Now lets try something more advanced. Take a note from the mini, play unison and a fifth on channel 1 and 2 on the circuit and also play the note one octave down on a third synth:
Midi is not limited to notes, the Circuit MIDI Parameters Guide has an extensive list of what you can do to it with a midi message. For example, you can tell it to switch patches with a Program Change message.
I’ll start a simple arpeggio on the circuit and cycle through the synth patches with a keypress on the ms mini. Each keypress moves to the next patch, cycles back to patch one when it reaches the last patch:
The Circuit is a drum machine, and can be used as midi sync master. It sends
0xfa when it starts playing,
0xfc when stops and
clock pulse, the clock can be used to build a sequencer.
Per midi spec there are 24 pulses per quarter note. That makes 6 pulses for a 16th
and 96 pulses in a bar. If we start counting right after play, we will know exactly where we
are in the song at any given moment and be able to trigger actions.
The manual does not list a midi command to switch patterns within a
session, but there is a way to switch sessions by sending a
PGM message on
channel 16. Patch the clock from the circuit, use it to step the sequencer
and send back commands on certain beats:
That’s it for now, i hope it was fun, though a bit more technical than i anticipated.
The source code is available on github.com.