Clojure Sound 3 - Hello MIDI Controller

Need help with your custom Clojure software? I'm open to (selected) contract work.

June 30, 2022

Please share: .

These books fund my work! Please check them out.

You may not know that music instrument were connected devices even in the Stone Age, that is, many decades ago. In the 80's that even resulted in the standard that facilitated connectivity of heterogeneous devices. Typically, you'd have a keyboard, a synthesizer, some external knobs, maybe effect boxes, and many of them could be connected by cable and talk to each other, even though that synth might be 27 years old, and the keyboard 2 years old.

That's the power of standard. MIDI might not be technically impressive. Especially for 2022, its transfer rate is unbelievably slow. On the other hand, a random device that you'd pick up at the store, on a yard sale, e-bay, or even at the dumpster, is almost guaranteed to have a MIDI connector.

So, even though you'd have a hard time connecting your iPhone with your Linux laptop, my guitar can talk with my Arch Linux desktop just fine (I'm not kidding, it really does!).

Making that guitar send a meaningful data to my computer is another pair of shoes. MIDI is very basic, and it's up to the user program to make sense of the data it receives. It is likely that you'll want to use a random exotic device in a very specific way; after all, music and art are all about originality. But, to walk on the Moon, we first have to get up from our bed. That's today's objective: taking input from a MIDI device, and controlling something on our computer by twisting knobs.

The software

As for the software, we need nothing more than Clojure Sound(), of course!

You can read how to prepare a fairly basic Clojure project in the first article in Clojure Sound tutorial series.

(require '[uncomplicate.commons.core :refer [close! info]]
         '[uncomplicate.clojure-sound
           [core :refer :all]
           [midi :refer :all]
           [sampled :refer :all]])

The hardware

It is unlikely that you'll have the same controller that I do, but so what? Any MIDI device that has a button or a knob on it will do!

At this moment, I'm using Faderfox MX12, not because it's a great device (which it is!), but because it conveniently sat next to my table. Also, being a newer device, it supports MIDI-over-USB connectivity (otherwise, I'd have to use an old-school MIDI cable, and plug it into an external USB sound card that also has a MIDI jack, which I do have, but you likely do not).

faderfox-mx12.jpg

If you have any device at home that is remotely related to music, please check it for MIDI support. If you don't, and you'll like to follow along, you may find a cheap old controller, any working MIDI device will do, as long as you can connect it and as long as it has at least one area to press or stick to twist!

What I would like

In the past several months, I've been learning to play guitar, which includes some basic of music theory. There is a nice ear training exercise where you listen to a chord (several notes played at the same time) and try to guess the exact chord that you've heard. If you have a training buddy, that is easy: one of you can play the chord, and the other could try to guess. Most of us do this alone, though. If I play the chord, I spoil the guessing part, which is the whole point of the exercise.

So, I thought of a solution. What if I could record a bunch of chords, and then somehow randomly play them? I would like this. There is a challenge, though: to record chords, I'd have to move fingers away from my guitar, and put them on my keyboard. Then, when guessing, I'd have to press keys for start, repeat, or end. But ear training likely requires that I actively strum my guitar to help my guessing by comparing with the recorded sample. That leaves the keyboard out.

Ideally, I'd use a foot controller, such as this Roland FC-300 (short of Foot Controller, I suppose with 300 feet :)

fc_300_angle_gal.jpg

I would press one of stomps 1-5 to record 5 different chord examples, and then press buttons 1 and 2 to play and stop random samples, pressing stomps 2-6 to enter my answers. The software, Clojure program running in REPL, might display results of my guessing, or accumulate them for later scoring. There are many possibilities, but the first step that we need to solve is: how does our program talk with these controllers in the first place?

Roland FC-300 has an additional obstacle of not supporting USB, so I'd have to use additional MIDI-to-USB sound card. I'll leave that for later, and in this article we will talk to the Faderfox.

MIDI device

First, thing, we make sure the device is connected to our computer. Then, we list all MIDI devices that our computer know of with (device-info).

(device-info)
:description Software MIDI Synthesizer :name Gervill :vendor OpenJDK :version 1.0
:description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0
:description Scarlett 2i4 USB, USB MIDI, Scarlett 2i4 USB :name USB [hw:2,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1
:description Faderfox MX12, USB MIDI, Faderfox MX12 :name MX12 [hw:3,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1
:description Scarlett 2i4 USB, USB MIDI, Scarlett 2i4 USB :name USB [hw:2,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1
:description Faderfox MX12, USB MIDI, Faderfox MX12 :name MX12 [hw:3,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1

We see two devices that we already recognize from earlier articles, two appearances of my Scarlett 2i4 external audio card, and two appearance of Faderfox MX12. Why do these appear twice?

(def mx12 (map device (filter #(clojure.string/includes? (info % :name) "MX12") (device-info))))
#'user/mx12

Here's the first one.

(first mx12)
:class MidiOutDevice :status :closed :micro-position -1 :description Faderfox MX12, USB MIDI, Faderfox MX12 :name MX12 [hw:3,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1

Note the :class MidiOutDevice. This instance can only send MIDI messages from computer to the device.

(second mx12)
:class MidiInDevice :status :open :micro-position 13220527818 :description Faderfox MX12, USB MIDI, Faderfox MX12 :name MX12 [hw:3,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1

The second one is MidiInDevice, which receives data, but doesn't know anything about sending.

In this case, the system sees the device through these two lenses. In other cases, one object might be capable of both sending and receiving, as in the case of the "Real Time Sequencer".

But how would our program know this, without us deciphering this from the :class output? The program could check for transmitter and receiver capabilities by calling appropriate predicates, and filter devices that match our requirements. In this case, we're interested in detecting button presses and knob turnings from the device, so we are looking for the transmitter.

(def mx12in (first (filter transmitter? mx12)))
#'user/mx12in
mx12in
:class MidiInDevice :status :open :micro-position 13220813146 :description Faderfox MX12, USB MIDI, Faderfox MX12 :name MX12 [hw:3,0,0] :vendor ALSA (http://www.alsa-project.org) :version 5.18.7-arch1-1

Receiver

All right, we have a device object that sends MIDI messages. But how do we access these messages? We need to implement a receiver to our liking (or use an existing one in unlikely case that someone already implemented our custom creative requirements; remember, this is music, not accounting).

Usually, it's a PITA. Clojure Sound makes it easy by:

  • Decoding raw byte MIDI data into nice Clojure maps and keywords, and integers, and floats.
  • Helping plain functions to listen for MIDI events.

We'll create a trivial listener that just prints the messages to the REPL output, and connect it to mx12in. First, we have to open! the device, or it will refuse connections.

(open! mx12in)
(connect! mx12in (receiver (partial println "Hello Faderfox")))
{:class "MidiInDevice", :status :open, :micro-position 13220961811, :description "Faderfox MX12, USB MIDI, Faderfox MX12", :name "MX12 [hw:3,0,0]", :vendor "ALSA (http://www.alsa-project.org)", :version "5.18.7-arch1-1"}
{:class "MidiInTransmitter", :id 2000879663}

Here's what I get when I start turning a knob.

Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 1, :control :effect-control-2} 12732693869
Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 1, :control :effect-control-2} 12732693869
Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 2, :control :effect-control-2} 12732704461
Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 2, :control :effect-control-2} 12732704461
Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 3, :control :effect-control-2} 12732718154
Hello Faderfox {:channel 0, :command :cc, :controller 13, :value 3, :control :effect-control-2} 12732718154

Nice! My computer understands my controller!

As this article is getting big, and it's 2 AM, I'll consider this a milestone, publish it, play guitar for a short time, and go to sleep. I deserve sleep, too :) In the next article, we'll create a more useful receiver that will use the information from these messages to control these chords I've been singing praises for!

Clojure Sound 3 - Hello MIDI Controller - June 30, 2022 - Dragan Djuric