Making Music with Clojure – An Introduction to MIDI

Introduction

This post takes a break from Functional JavaScript and has a little fun making music. We’re going to be using Clojure, a Lisp language for the JVM, so that we can utilize the JVM’s MIDI implementation. No experience with music or MIDI is required though a familiarity with Clojure or any other Lisp is helpful.

I’m using Clojure for its functional similarities to JavaScript—the syntax of the languages are different but the underlying programming philosophies are similar. For this post I’m assuming you already have Clojure and Leiningen installed on your system. See Clojure Quick Start for everything you need to get Clojure and Leiningen installed and running on your system.

Once you have everything installed you can create a new midi-sequencer project by executing

lein new app midi-sequencer

Once your project is created all your source will be contained in the file core.clj. To run your program execute

lein run args

where we’ll be passing the instrument number, 0-127, as an argument.

Pro tip: you can execute

lein repl

to load your code into a REPL for ad-hoc programming and exploring.

Alright! Now you have everything you need to get started so let’s go!

MIDI Concepts

We need to get briefly acquainted with the following MIDI concepts:

synthesizer
think of it as your instrument, this is what we use to make music
channel
our synthesizer has 16 channels, 0-15, each acting as it’s own instrument
instrument
each channel may have one of 128 instruments, 0-127
note
the pitch to play, there’s 128 available, 0-127, with middle C at 60 and the 440 Hz middle A at 69
duration
how long to play a pitch, measured here in milliseconds
volume
how loud to play the pitch, from 0 to 127

The basic idea then is to obtain and initialize the synthesizer, select the desired channel to use, select an instrument into that channel and then use that channel to start playing notes for a specified duration and volume.

Obtaining and Initializing the Synthesizer

Let’s look at the code:

(defn getSynthPlayer [channelNbr instrumentNbr]
  "Initialize synthesizer and return play function"
  (let [synth (javax.sound.midi.MidiSystem/getSynthesizer)]
    (do
      (.open synth) ; Open synth before using
        (let [channels (.getChannels synth)
              instruments (.. synth getDefaultSoundbank getInstruments)]
          (do
            (let [channel (nth channels channelNbr)
                  instrument (nth instruments instrumentNbr)]
              (println "Instrument" instrumentNbr "is" (.getName instrument))
              (.loadInstrument synth instrument)
              (.programChange channel instrumentNbr) ; Lots of blogs never mentioned this!
              (fn [volume note duration] ; play function
                (do
                  (.noteOn channel note volume)
                  (Thread/sleep duration)
                  (.noteOff channel note)))))))))

We start off getting the synthesizer using javax.sound.midi.MidiSystem/getSynthesizer and then immediately open it. Next we get the set of channels provided by the synthesizer from which we can select our desired channel and we do likewise for the instrument (the synthesizer provides a getDefaultSoundbank which we use to get the available instruments). We load the instrument into the synthesizer and then, most importantly, invoke programChange on the channel which activates the instrument for that channel. If you omit this step the channel will use the default instrument rather than the instrument you had intended to use.

Lines 14–18 then comprise the play function we’re returning, getSynthPlayer returns a function allowing us to play music using the specified instrument on the selected channel of the synthesizer. This play function takes 3 parameters:

  • volume
  • note
  • duration

as discussed in the MIDI Concepts section above.

The implementation of the play function is simple: invoke the channel’s noteOn method specifying the note and volume, sleep for the desired duration and then invoke the channel’s noteOff method for the specified note. We need to specify the note to noteOff because it’s possible for the channel to play several notes simultaneously, though I won’t be demonstrating that capability in this post.

Playing Music

Here’s the code we need to play music:

(defn play [synthPlayer accVolume seqNotes]
  "Play note sequence at specified starting volume on player obtained via getSynthPlayer"
  (if (or (nil? seqNotes) (empty? seqNotes)) nil
    (let [[note duration {:keys [vol_adj] :or {vol_adj 0}}] (first seqNotes)
          volume (+ accVolume vol_adj)]
      (do
        (synthPlayer volume note duration) ; invoke function returned from getSynthPlayer
        (recur synthPlayer volume (rest seqNotes))))))

The music is stored in a list of vectors containing the note, duration, and an optional map specifying the volume adjustment. For example the chromatic scale is define as:

(def chromatic-scale '([60 250] [62 250 {:vol_adj +2}] [64 250 {:vol_adj +2}] [65 250 {:vol_adj +2}] [67 250 {:vol_adj +2}] [69 250 {:vol_adj +2}] [71 250 {:\
vol_adj +2}] [72 250 {:vol_adj +2}] [72 250] [71 250 {:vol_adj -2}] [69 250 {:vol_adj -2}] [67 250 {:vol_adj -2}] [65 250 {:vol_adj -2}] [64 250 {:vol_adj -2\
}] [62 250 {:vol_adj -2}] [60 5000 {:vol_adj -2}]))

The volume is continually incremented by 2 when ascending the scale and decremented by 2 when descending the scale. Also note the volume is an adjustment, it doesn’t specify the actual volume but adjusts the volume relative to the current volume setting when playing this note (for the musical types out there this is really the velocity adjustment, or the piano/forte adjustment).

The play function takes 3 parameters

  • synthPlayer
  • accVolume
  • seqNotes

The synthPlayer is the player returned from getSynthPlayer, accVolume is the current volume, and seqNotes is the sequence of notes to play as discussed above. The play function employs the standard idiom of recursing through the list, playing each note in succession, until the list is exhausted.

Take a look at line 4:
(let [[note duration {:keys [vol_adj] :or {vol_adj 0}}] (first seqNotes)

(first seqNotes) gets the first vector in the supplied notes sequence. Recall that vector has the following values: [note duration {:vol_adj vol_adj}]. This form assigns note to note, duration to duration, and vol_adj to the value mapped to the :vol_adj key, or 0 if none is supplied. This is an example of Clojure’s destructuring. Much has been written about destructuring so I won’t explore the topic any further. If it’s not obvious how the symbol bindings are being performed then search the literature.

Finally, line 8 is the recursive invocation of play. Note the recur form only requires us to specify the parameters as it’s known the current function is what’s being recursed. We recurse with (rest seqNotes) to obtain the set of notes excluding the first note, which we just played. In this manner we recurse through the sequence of notes playing each note in turn.

Finalizing the Implementation

We need a main function:

(defn -main
  "Right now we play the chromatic scale using the instrument provided"
  [instrument & args]
  (let [synthPlayer (getSynthPlayer 2 (Integer/parseInt instrument))]
    (play synthPlayer 80 chromatic-scale)))

This gives Leiningen an entry point from which to run. Your program can be invoked by:

lein run instrument#:0-127

Experiment with the different instruments and see which ones you like. On my particular system 0 is a grand piano, 40 is a violin, 50 is the strings section, and 60 is a French horn.

Conclusion

At this point you should have Clojure and Leiningen installed and running on your system and you know some of the basic concepts of MIDI and know how to play simple music. I’ve opened a can of worms—you can probably think of several improvements to this implementation and think of features to add. For example, we have no means for “playing” rests nor do we have a means for tying notes together. It’s also pretty bogus to specify notes by number rather than name and specifying duration by a millisecond value rather than eighth note, quarter note, half note and so forth.

Experiment and make it so! I may improve upon this implementation from time–to–time in future posts.

Bonus

I sort of have a tradition (it’s a long story) of playing synthesized Christmas holiday music. Here’s the sequence you need to play Joy To The World. Enjoy!

(def joy-to-the-world
'([77 660] [76 495] [74 165] [72 990] [70 330] [69 660] [67 660] [65 990]
[72 330] [74 990] [74 330] [76 990] [76 330] [77 990]
[77 330] [77 330] [76 330] [74 330] [72 330] [72 495] [70 165] [69 330] [77 330] [77 330] [76 330] [74 330] [72 330] [72 495] [70 165] [69 330]
[69 330] [69 330] [69 330] [69 330] [69 165] [70 165] [72 990]
[70 165] [69 165] [67 330] [67 330] [67 330] [67 165] [69 165] [70 990]
[69 165] [67 165] [65 330] [77 660] [74 330] [72 495] [70 165] [69 330] [70 330] [69 660] [67 660] [65 1980]))

Advertisements

About taylodl

I'm an enterprise architect/developer who enjoys programming, math, music, physics, martial arts and beer
This entry was posted in Clojure and tagged , . Bookmark the permalink.

4 Responses to Making Music with Clojure – An Introduction to MIDI

  1. Nomen Nescio says:

    Thank you, this is great! It was exactly what I needed!

    (Note that JVM sound may get better when you use a better sound bank.
    http://www.oracle.com/technetwork/java/soundbanks-135798.html )

  2. Nomen Nescio says:

    It will be interesting to check how far we can go without using e.g. Overtone. The problem seems to be that Thread.sleep(..) doesn’t give us guaranteed timings. (“sleep .. for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers”, says the API) As far as I understand, Overtone (or, rather SuperCollider) gives its best to guarantee timings. Only experiments can tell 🙂

  3. Nomen Nescio says:

    If we are at it: Windows users may want to get rid of a strange warning (.. WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs ..)

    You find here how:
    http://stackoverflow.com/questions/16428098/groovy-shell-warning-could-not-open-create-prefs-root-node

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s