Making music with Clojure and JFugue

Mike Travers and Ziva Travers

feedback to mt(at)hyperphor.com

Introduction & Disclaimer

This is a learning exercise on several levels. Neither author is a Clojure expert -- we’re learning as we go. We also are relative novices at algorithmic composition, so learning that on the way as well.

You need to be able to use a terminal (shell) to follow this, and be able to edit text files.

The things you should type are in bold.

Note: the code here is also available on github.

Lesson 1: Getting Started

Get a terminal prompt (on the Mac, run Applications/Utilities/Terminal, on other operating systems you’ll have to figure out how to do something similar).

1) Install leiningen if it isn’t already on your system

2) Create an application:

bash$ lein new app mymusic

This will create a new directory called mymusic

3) Add JFugue to your app

Edit the project.clj file in your new directory so it looks like this (add the part in bold:

(defproject mymusic "0.1.0-SNAPSHOT"

  :description "FIXME: write description"

  :url "http://example.com/FIXME"

  :license {:name "Eclipse Public License"

            :url "http://www.eclipse.org/legal/epl-v10.html"}

  :dependencies [[org.clojure/clojure "1.8.0"]

                 [org.clojars.jmeeks/jfugue "4.0.3"]]

4) Start Clojure:

bash-3.2$ cd mymusic

bash-3.2$ lein repl

...prints a bunch of stuff…

Clojure 1.8.0

mymusic.core>

5) Save some typing. This bit of magic lets us refer to JFugue objects by shorter names.

mymusic.core=> (import '(org.jfugue Player Note Pattern MusicStringParser))

6) Make a player object:

mymusic.core=> (def player (new Player))

#'mymusic.core/player

This will open up a Java application window and may change your focus, so click back on your terminal window.

To break this down: def means we are defining a name for something.  In this case, the name is player and the something is a Player object.  The expression (new …) creates a new object of a particular class, in this case Player from the JFugue library.

7) Play a scale:

mymusic.core=> (.play player "C D E F G A B")

nil

What’s going here is that we are taking the Player object we created in step 6) and calling a method on it, which is how you get Java objects to do things for you. In this case the method is play, and we are including in the call an argument of "C D E F G A B".

8) Play something more interesting, and learn some more of JFugue's syntax.

mymusic.core=> (.play player "T120 I[Cello] G3q G3q G3q Eb3i Bb3i G3q Eb3i Bb3i G3h")

nil

Try it again, replacing the “T120” with “T240”; and/or the “Cello” with “Marimba”.


Lesson 2: Patterns

Here we will learn about pattern objects.  A pattern is a stored fragment of music that can be reused and combined to form a song.  Note that this and some other examples are adapted directly from the JFugue documentation: http://www.jfugue.org/examples.html

1) Define some patterns (you don’t have to type the comments, which are the lines starting with semicolons).  

;; Frere Jacques

(def pattern1 (new Pattern "C5q D5q E5q C5q"))

;; "Dormez-vous?"

(def pattern2 (new Pattern "E5q F5q G5h"))

;; "Sonnez les matines"

(def pattern3 (new Pattern "G5i A5i G5i F5i E5q C5q"))

;; "Ding ding dong"

(def pattern4 (new Pattern "C5q G4q C5h"))

2) Combine them into a song

(def song (new Pattern))

(.add song pattern1 2)  ; Adds 'pattern1' to 'song' twice

(.add song pattern2 2)  ; Adds 'pattern2' to 'song' twice

(.add song pattern3 2)  ; Adds 'pattern3' to 'song' twice

(.add song pattern4 2)  ; Adds 'pattern4' to 'song' twice

3) Play it:

(.play player song)


Lesson 3: Notes and functions

1) Make a note object

(def note (new Note (byte 40) 0.1))

40 is the pitch (Eb1) and 0.1 is the duration in seconds.  Don’t worry about the byte just now.

2) Add the note to a pattern and play it

        

(def p2 (new Pattern))

(.addElement p2 note)

(.play player p2)

3) Make it easier on ourselves by defining a procedure.  

It’s kind of a pain to have to type that long thing in step 1) every time we want to make a note.  Let’s say we know we want to make a lot of notes with the same duration, and we don’t want to type that each time.  We can make life simpler by defining a procedure of our own, called make-note.

(defn make-note [pitch]

   (new Note (byte pitch) 0.1))

(def note (make-note 40))

We just did the same thing we did in step 1), but by using a procedure we can make it easier on ourselves if we want to make more notes.

(.addElement p2 (make-note 42))

(.addElement p2 (make-note 37))

(.play player p2)

Some things to note (excuse the expression):

Lesson 3.5: Sequences and mapping

Try this:

$ (range 30 40)

(30 31 32 33 34 35 36 37 38 39)

range is a Clojure form that returns a sequence of numbers. Try giving it different arguments, or a third argument like this:

(range 40 70 3)

And this:

$ (map make-note (range 30 40))

(#object[org.jfugue.Note 0x366aac49 "org.jfugue.Note@366aac49"]

 #object[org.jfugue.Note 0x10db5d4f "org.jfugue.Note@10db5d4f"]

...

What’s going on here is kind of interesting. Map is a function that takes two arguments: the first is another function (in this case, the make-note we defined earlier) and a sequence, and calls the function on each element of the sequence. And it returns a new sequence of the results.

Map is an example of what we call a higher-order function, that is a function that can operate on other functions. This is pretty advanced stuff, so pat yourself on the back.

To make this useful, we’ll define a convenience function:

(defn notes->pattern [notes]

  (let [pattern (new Pattern)]

    (doseq [note notes]

      (.addElement pattern note))

    pattern))

This just takes a sequence of notes and returns a Pattern that contains them. Don’t worry about the detail of how it works. But it means we can use the above tools to generate music.

Here’s a musical application of iteration:

(.play player (notes->pattern (map make-note (range 40 60))))

        

        

Look, we’ve made music according to a very simple mathematical rule, or algorithm!


Lesson 4:  Algorithmic composition

The real reason to use a programming language to create music is that you can compose music algorithmically, rather than having to add each note one by one.  Here’s some simple illustrations of what that means.

First lets make a new function just to make our lives easier:

(defn play-sequence [seq]

  (.play player (notes->pattern (map make-note seq))))

So now we can easily play with variants of our ascending or descending sequences:

;;; Ascending

(play-sequence (range 40 60))

::: Descending

(play-sequence (range 60 40 -1)

;;; Ascending by Intervals

(play-sequence 40 70 4)   

In the last one, we are ascending by 4 semitone pitches rather than one. This interval is called a minor third.

Lesson 5: Random compositions

In this lesson we will use some math functions to generate some aleatoric (random) music.  

First, let’s learn about the function rand-int, which returns a random integer in a given range:

mymusic.core> (rand-int 100)

13

mymusic.core> (rand-int 100)

77

mymusic.core> (rand-int 100)

90

Here’s how you can use some other functions called take and repeatedly to generate a sequence of random numbers. Let’s not worry about the details now.

(take 20 (repeatedly #(rand-int 100)))

(95 46 98 53 39 8 19 6 92 11 1 83 51 86 94 62 3 96 88 22)

Except you should know that repeatedly generates an infinite sequence, which is cool but if you try to print it you will have problems. The take function takes the first 20 elements from a sequence so you can print them safely.

So now you can play random music.

(play-sequence (take 20 (repeatedly #(rand-int 100)))

This will pick pitches between 0 and 99. Can you think how you might change it to use a different range, say, from 50 to 120?

Lesson 6: Not quite as random

Now, let’s try to define a function that generates a pattern where each note varies from the previous by a random interval.

This function is sort of like rand-int, but it will produce negative as well as positive numbers

(defn rand-updn [n]

  (- (rand-int (- (* 2 n) 1)) (- n 1)))

(rand-updn 6)

3

(rand-updn 6)

-5

Now let’s use that to make a sequence of pitches on what’s knows as a random walk (or drunkard’s walk). This means that instead of choosing each pitch as a completely separate random number, each one is going to be a random step away from the previous one.

To do this we’ll need to learn a couple of more things. First, iterate is a high-order function (like map) that takes as arguments a function and a value. It generates an infinite sequence by applying the function to the value, and then again to the result of that, and again, and so on. In math notation the sequence would be:

v, f(v), f(f(v)), f(f(f(v)...

So if we didn’t already know about range, we could write something like it this way:

(defn add1 [n] (+ 1 n))

(take 20 (iterate add1 0))

(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)

Another piece of Clojure to learn is fn. This is not a function, but it’s a way of defining functions. It’s sort of like defn but you can use it inline. This lets us write the last one more simply as

(take 20 (iterate (fn [pitch] (+ pitch 1)) 0)

OK, with that in mind we can make our random walk.

(defn drunk-walk [from interval]

  (iterate (fn [pitch] (+ pitch (rand-updn interval))) from))

(play-sequence (take 30 (drunk-walk 40 6)))

That sounds a bit more musical, doesn’t it?  But we still don’t have much musical knowledge in our program.

Lesson 7: Smarter Randomness

Our drunk composer could be improved if it chose its notes a bit more carefully.  Here’s one way to do that. First, let’s explore and implement a little music theory (this assumes you know the basics of scales and intervals).

JFugue and MIDI use small integers to indicate pitch. You can see a more standard musical name for these pitches like this:

(Note/getStringForNote 60)

“C5”

There are 12 pitches in an octave (in the standard European scale that we are using). This means that if you go 12 steps up from a C, you get to another C:

(Note/getStringForNote (+ 60 12))

"C6"

Or a smaller number goes up by an interval. A major 3rd, for example, is 5 pitches up:

mymusic.core=> (Note/getStringForNote (+ 5 60))

"F5"

Given this, we can define a key as a root and a set of intervals. The major and minor keys are very similar, they have one different interval.

(def major-key [0 2 4 5 7 9 11])

(def minor-key [0 2 3 5 7 8 10])

;;; Given a PITCH, a KEY (as a sequence of intervals) and a root for the key, return true if pitch is in the key.

(defn in-key? [pitch key root]

  (in? key (mod (- pitch root) 12)))

With this in place, you can check that pitch is in a given key.  We define keys by their signature of intervals and a root, so for instance, to check if a pitch 67 (a G) is in c-major:

>> (in-key? 72 major-key 60)

true

Now that we’ve defined this, we need to use it to filter our random sequence. There is a built-in function called filter that we can use, which takes a function and a sequence, applies the function to all elements of the sequence and creates a new sequence consisting only of the elements for which the function returns true.

So, now let’s use these in a composition.

(play-sequence

   (take 30

      (filter (fn [pitch] (in-key? pitch major-key 48))

              (drunk-walk 48 6))))

This is like our previous drunk function, but it filters the notes so that only notes from the scale defined by key (and the root 60, which is built into the function, although it could be another argument).

This is getting yet more musical! Experiment with different parameters (larger or smaller intervals, replacing major-key with minor-key or some set of intervals you prefer).

The End

Congratulations! If you got this far, you have successfully been making music using some pretty advanced programming-language constructs.  We’ve been using the fact that Clojure has first-order procedures (aka functions) -- that is, procedures are objects that can be manipulated by programs, just like numbers or strings. We’ve also made use of some higher-order procedures (procedures that take other procedures as arguments, like filter and iterate). This is hard or impossible to do in many programming languages, but very easy and natural in Clojure.

Topics for further lessons (suggestions welcome):

- polyphony

- responding to Midi events

- input, process, and output Midi files