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: Twitter.
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!