Maple Leaf Rag with Clojure Sound
Need help with your custom Clojure software? I'm open to (selected) contract work.June 25, 2022
Please share: Twitter.
These books fund my work! Please check them out.
I've just noticed that it's been one year since the last post on this blog. It's not because I've been lazy. I had just finished two books (check them out!) and I probably suffered a bit from the writer's exhaustion afterwards. I'm joking. I wasn't that exhausted; I've been busy with programming. Thanks to generous support from Clojurists Together, and thanks to people who bought or subscribed to my books, I've been able to take on some bigger chunks of time to work on Uncomplicate libraries, old and new. OK, enough talking; you're not here to read my reports, but to see some nice Clojure code.
A couple of months ago I released a new library - Clojure Sound(), but I haven't had time to write about it. Now that it's time to revive my blog, is there a better way to start than with music?
How to access Clojure Sound
First I'll show you a basic code example and then I'll tell you what Clojure Sound is some other time.
It's a programming library, available from Clojars. Include Clojure Sound in your project.clj
by adding
[org.uncomplicate/clojure-sound "0.1.0"]
to :dependencies
. Start the REPL and connect your
preferred Clojure editor.
First, we require the appropriate namespaces. In this example, we're using functions from core
and midi
.
(require '[uncomplicate.clojure-sound [core :refer :all] [midi :refer :all]])
Playing a nice song with virtual piano
We're now going to instruct a virtual piano on our computer to play something. But, instead of playing a few random notes, we would like to hear a wonderful song. I've chosen a cheerful Maple Leaf Rag. You might think: "What song is that?", but I can bet that you'll instantly recognize it as soon as you hear your REPL sing (I hope that you follow along by typing the code yourself).
There's plenty of ways to specify the notes that our virtual piano should play. Since I'm lazy, I'll take a score that already specifies all details in a MIDI file. Since the song is 123 years old, it's in public domain, so there are many free MIDI transcriptions. Please find a version on the Internet, rename the file to maple.mid, and put it in the classpath of your project. If you're not feeling adventurous, you can use the one I'm using.
The data that should be played is stored in sequences. Not Clojure sequences, but sound sequences
(sorry about this name duplication, but that's a general domain-specific term). We can make a sequence
in several ways, from adding each note programmatically, to loading the sequence stored in a MIDI file
that someone else prepared. I access the file as a Clojure resource, and give it to Clojure Sound sequence
function.
(def maple (sequence (clojure.java.io/resource "maple.mid"))) maple
#'user/maple |
{:ticks 110592, :id 1336053642} |
Now, we have a sequence object that contains more than 100.000 ticks (basically, things that our piano has to do).
But, the music sheet is not going to play itself. It needs someone to see the actual instructions, and to
press the right places at the instrument. This is what sequencer is for. Sequencer as just like a player.
When a real person plays the instrument, that real person is the sequencer. In Clojure Sound, the sequencer
function creates that player. In this example, I'll use the default sequencer, which already has its default
instruments collection.
(def sqcr (sequencer))
#'user/sqcr
While sequencer plays music, it has to access the sound card on your computer, and produce side-effects, the actual sounds that you enjoy. That requires careful opening and closing protocols, so it interferes with other multimedia software on your computer as little as possible. Before we use our sequencer, we have to open it.
(open! sqcr)
:class | RealTimeSequencer | :status | :open | :micro-position | 0 | :description | Software sequencer | :name | Real Time Sequencer | :vendor | Oracle Corporation | :version | Version 1.0 |
Now, we tell our sequencer to take the music sheet (the maple
sequence) that it's going to perform.
(sequence! sqcr maple)
:class | RealTimeSequencer | :status | :open | :micro-position | 0 | :description | Software sequencer | :name | Real Time Sequencer | :vendor | Oracle Corporation | :version | Version 1.0 |
Finally, we're ready to rock rag!
(start! sqcr)
:class | RealTimeSequencer | :status | :open | :micro-position | 0 | :description | Software sequencer | :name | Real Time Sequencer | :vendor | Oracle Corporation | :version | Version 1.0 |
Normally, the Maple's gonna rag for 2-3 minutes. If you'd like to stop it, here's how to do it.
(stop! sqcr)
:class | RealTimeSequencer | :status | :open | :micro-position | 145833 | :description | Software sequencer | :name | Real Time Sequencer | :vendor | Oracle Corporation | :version | Version 1.0 |
Finally, when you're tired from dancing, you can tell the player to go get some rest, too:
(require '[uncomplicate.commons.core :refer [close!]]) (close! sqcr)
:class | RealTimeSequencer | :status | :closed | :micro-position | 0 | :description | Software sequencer | :name | Real Time Sequencer | :vendor | Oracle Corporation | :version | Version 1.0 |
That's it, I hope you're enjoyed this little demonstration. I'll be back with more music! :)