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.

korg and mixer

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.

Run aconnect -d 36:0 32:0 to break the connection. The port names in amidi and 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 amidi and 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:2,0,0, 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:

synth :circuit do
	# name and sub_name as reported by the driver
	name "Circuit", "Circuit MIDI 1"
end

synth :ms20mini do
	name "MS-20 mini", "MS-20 mini MIDI 1"
end

synth :umone do
	name "UM-ONE", "UM-ONE MIDI 1"
end

synth_alias :korg => :ms20mini
synth_alias :nova => :umone

Print incoming midi data, pass thru midi as is or transpose notes before sending to the destination:

# print whatever the synth sends
plug :circuit do |n|
	n.pp
end
# midi passthru
plug :circuit => :korg 
# split and pass to two synths 
plug :korg => [:circuit, :nova]
# transpose notes up an octave and pass everything else 
plug :korg => :circuit do |n, circuit|
	n.pitch += 12 if n.note?
	circuit << n
end 

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:

plug :korg => [:circuit, :nova] do |n, circuit, nova|
	if n.note?
		circuit << n
		circuit << n.copy { |n| n.pitch += 7 }
		circuit << n.copy { |n| n.chan = 2 }
		circuit << n.copy { |n| n.pitch += 7; n.chan = 2 }
		nova << n.copy { |n| n.pitch -= 12 } 
		nova << n.copy { |n| n.pitch -= 12 + 7 } 
	end
end 

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:

patch_num = 0 # starting patch
plug :korg => :circuit do |n, circuit|
	if n.note_on?
		patch_num = patch_num >= 63 ? 0 : patch_num + 1 
		circuit << pgm(1, patch_num)
	end
end 

The Circuit is a drum machine, and can be used as midi sync master. It sends sync messages: 0xfa when it starts playing, 0xfc when stops and 0xf8 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:

# Poor Man's song mode
seq = Sequencer.new # initialize sequencer
plug :circuit => :circuit do |n, circuit|
	seq << n # feed the midi message to the sequencer to count the beats 
	seq.pp # print current position
	
	# switch to session 2 after 6 bars
	seq.when :bar => 6, :step => 16 do
		circuit << pgm(16, 65)
	end
	# play 4 bars and switch back to session 1 
	seq.when :bar => 10, :step => 16 do
		circuit << pgm(16, 64)
	end
end

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.

Fork me on GitHub