Clojure Sound 2 - A better piano

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

June 28, 2022

Please share: .

These books fund my work! Please check them out.

You might not be too thrilled with the sound richness of the default piano that played Maple Leaf Rag in the previous article. We can use a much better one, of course! Here we broaden our understanding of the basic building blocks that generate music in Clojure Sound]](): sequencers, synthesizers, and instruments.

If you followed along by trying out the code in your favorite Clojure REPL tool, you can continue hacking on the same project.

The namespaces

We use the same namespaces as before: clojure-sound 's core and midi, and a couple general functions from uncomplicate commons library.

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

The sequence and the sequencer

As we did before, we use the music score of Scott Joplin's Maple Leaf Rag, encoded as a series of music events (mostly notes to play) encoded in a midi file. As there are more than 100.000 events even in this short song, this is much more practical than invoking functions that produce these same effects by hand.

The score is only a writing on the paper. Someone need to read these instructions, and invoke them at precisely exact moments, as the song progresses. This is done by a sequencer, which is basically our player, same as before. By invoking the sequencer function, we get the default sequencer.

(def maple (sequence (clojure.java.io/resource "maple.mid")))
(def sqcr (sequencer))
#'user/maple
#'user/sqcr

Synthesizer

Now we come to something new: the instrument that the player plays. In the last article, the sequencer used the defaults, so we didn't even know what was producing the sounds that we heard. The sounds are generated by a synthesizer, which has the ability to produce actual sounds, by any imaginable and unimaginable technique. The synthesizer function instantiate the default implementation of a soft synthesizer, which we'll use here. Of course, we could have accessed other software implementations, or even hardware synthesizers, if we had one, and if it was connected to our computer, registered, and made accessible through the drivers in our operating system. The default one is guaranteed to be present on your computer if you have Java installed, which you surely do since you're happily using Clojure.

(def synth (synthesizer))
#'user/synth

Instrument collections

The default synthesizer comes with default instruments. In my opinion, even the default piano that comes with OpenJDK is not bad at all, but music connoisseurs will know better sounds. We'll better acquire better instruments for our orchestra! Instruments for our software synthesizer come as sound fonts, similarly to text fonts. There are countless collections of quality sound fonts, free and commercial, so you'll likely find something that matches your taste. Here I use FluidR3, a great free sound font that comes with MuseScore. Download it, and put it somewhere on the classpath of your project.

(def fluid (soundbank (clojure.java.io/resource "FluidR3_GM.sf2")))
#'user/fluid

Of course, other implementations of the Synthesizer interface may use other technologies for software synthesis, and may or may not use sound fonts. Hardware synthesizers will typically have yet other methods of working. What is important to us is that we can use them all from Clojure Sound by invoking the same functions.

Once we reserve the synth by invoking open!, we load the fluid soundbank, and replace the old orchestra with new, hopefully better one.

(open! synth)
(load! synth fluid)
{:class "SoftSynthesizer", :status :open, :micro-position 0, :description "Software MIDI Synthesizer", :name "Gervill", :vendor "OpenJDK", :version "1.0"}
true

We can invoke the info function to see whether this had any effect of our synth.

(info fluid)
:class SF2Soundbank :description Licensed under the MIT License. :name Fluid R3 GM :vendor Frank Wen :version 2.1

We can get all instruments in a nice Clojure sequence. As Maple Leaf Rag is performed on one instrument, this is not too useful right now, but might be if more complex settings. We won't waste this sequence, though; let's check how many instruments this synthesizer can simulate now.

(count (instruments synth))
189

Connecting sequencer and synthesizer

Now that we have a shiny new synthesizer, we have to connect it to the sequencer. The polymorphic connect! function connect various things, among others, sequencers and synthesizers.

(connect! sqcr synth)
:class SequencerTransmitter :id 769064251

We should not forget to instruct the sequencer what to play.

(open! sqcr)
(sequence! sqcr maple)
{:class "RealTimeSequencer", :status :open, :micro-position 0, :description "Software sequencer", :name "Real Time Sequencer", :vendor "Oracle Corporation", :version "Version 1.0"}
{:class "RealTimeSequencer", :status :open, :micro-position 0, :description "Software sequencer", :name "Real Time Sequencer", :vendor "Oracle Corporation", :version "Version 1.0"}

After all this, we're ready to play!

Play it again, Sam

(start! sqcr)
:class RealTimeSequencer :status :open :micro-position 0 :description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0

I hope you've noticed how richer the sound is now compared to the one played on the default piano.

If you wanted to enjoy this song again, you might have noticed that repeated start! call results in silence.

(start! sqcr)
:class RealTimeSequencer :status :open :micro-position 139322 :description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0

Once the sequencer performs the score, it stops at the end. We can see where it is by calling tick-position to get the current position in ticks, or micro-position to determine the time in microseconds after the beginning of the score.

(micro-position sqcr)
278645

We can rewind the score, or point it anywhere, by invoking micro-position!, or tick-position!

(micro-position! sqcr 0)
:class RealTimeSequencer :status :open :micro-position 0 :description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0

Now it's ready to play again.

(start! sqcr)
:class RealTimeSequencer :status :open :micro-position 140625 :description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0
(stop! sqcr)
:class RealTimeSequencer :status :open :micro-position 286458 :description Software sequencer :name Real Time Sequencer :vendor Oracle Corporation :version Version 1.0
(close! synth)
(close! sqcr)
{:class "SoftSynthesizer", :status :closed, :micro-position 0, :description "Software MIDI Synthesizer", :name "Gervill", :vendor "OpenJDK", :version "1.0"}
{:class "RealTimeSequencer", :status :closed, :micro-position 0, :description "Software sequencer", :name "Real Time Sequencer", :vendor "Oracle Corporation", :version "Version 1.0"}

I like this song!

Clojure Sound 2 - A better piano - June 28, 2022 - Dragan Djuric