Friday, 20 February 2009

JMusic and Clojure

JMusic is a Java library for music composition. I thought it'd be fun to play with music as with images, so I'm looking at trying some algorithmic compositions.

Since it's a Java library, it's easy to use JMusic with Clojure. It's simple as downloading the JAR file (see here) and then making sure the JAR is on your class path when you start Clojure.

You'll need to import a few classes to get started. Clojure doesn't support importing all members from a namespace, so it can be a little tedious. (as a side note, In Java I'm so used to IntelliJ auto-importing that I'd forgotten how much crud you have to import these days).

Here's my basic imports for a simple program which mirrors the basic one in the tutorial.

(ns jmusic
(:use [clojure.contrib.import-static :only (import-static)])
(:import jm.JMC)
(:import (jm.util Write))
(:import ( Note Score Part Phrase)))

(import-static jm.JMC

import-static is a very handy function - it does exactly what it says on the tin!

JMusic has a simple composite model for music.

  • A Note is the building block of any composition
  • A Phrase is 1 or more notes
  • A Part is a collection of phrases played with a given Instrument
  • A Score is a collection of parts

Writing functions to composes phrases of notes, and parts of phrases is very tedious, so we'll use a macro to help avoid duplication. Here's a few helper functions to build the various music domain objects in JMusic. Note that this is just for my "hello world" style application, they are incredibly inflexible functions at the moment!

(defmacro jm-add-children
[m obj parts]
`(let [obj# ~obj]
(doseq [p# ~parts]
(doto obj#
(~m p#)))

(defn make-score
[name parts]
(let [sc (Score. name)]
(jm-add-children .addPart sc parts)))

(defn make-phrase
[name notes]
(let [p (Phrase. name)]
(jm-add-children .addNote p notes)))

(defn make-part
[name instrument phrases]
(let [part (Part. name instrument)]
(jm-add-children .addPhrase part phrases)))

(defn make-note
[freq rhythm]
(Note. freq rhythm))

Once you've got a score together, you need to be able to save it.

(defn save [score output]
(Write/midi score output))

OK, with all those helper functions out of the way we can now write some music. Taking the first example (chromatic scale) from the JMusic tutorial, we get:

(defn make-noise []
(let [notes (map (fn [y] (make-note (+ C4 y) CROTCHET)) (range 0 12))
phrase (make-phrase "Phrase1" notes)
part (make-part "Part" FLUTE (list phrase))
score (make-score "Score" (list part))]
(save score "chromatic-scale.mid")))

Running this at your REPL should get:

jmusic> (make-noise)
----------------------------- Writing MIDI File ------------------------------
Converting to SMF data structure...
Part 0 'Part' to SMF Track on Ch. 0: Phrase 0:............
MIDI file 'chromatic-scale.mid' written from score 'Score' in 0.001 seconds.

And result in a chromatic scale midi file being output. Next, to find something funkier to do!