This is a Literate Haskell file; you can view the source here
Don’t let the
LANGUAGE pragmas scare you – if you’re an average Vivid user you can just pop these two lines at the top of your file and you’re ready to go.
Up and Running
You’ll need vivid (at least version 0.5.1) and SuperCollider.
Boot the SuperCollider server (either on the command line or by hitting Ctrl-b/Command-b in the SC IDE), then load this file into GHCi.
Making a Sound
In Vivid / SuperCollider, you make sounds by creating
Synths are created using Synth Definitions, or
We’ll use a
SynthDef adapted from one on sccode.org, which is a great place to explore what’s possible with SuperCollider.
At first we’ll just look at the type signature – we’ll explore the definition later:
This type signature might look a little unusual, but you can learn it by using it. Basically we’re saying that
"t60", etc are arguments to the SynthDef, almost like arguments to a function.
If everything’s set up, that should sound like this:
So it’s like we’re passing 60 as the
"note" argument to
bell, 6 for
"t60", etc. The arguments can come in any order, and notice that not all the ones
bell supports are required: they’re all optional because they all have default values.
(You can remember “I” as “initial value”)
synth is a normal Haskell function, so we can use our Haskell powers to create abstractions:
Which sounds like:
wait just waits a given number of seconds.
We’ve switched from
IO () to a more general type signature. This isn’t required but it lets us do a lot of cool things, which we’ll learn more about below.
We changed a few parameters, and even though they’re both bells they sound fairly different.
Another thing to notice is that we throw away the result of
synth. If we held onto it, we could use it to stop and restart the running Synth, change its parameters while it was running, and more.
Okay so how is this
bell sound actually defined?
Here’s the first line of our SynthDef. If you squint, it’s a little like the left hand side of a function.
sd is short for SynthDef.
1, 1, 1, 0.1, and 1 are the default values for the SynthDef arguments.
In other words, if the above were a function in a language with default arguments, it might look like:
bell (fs = 1, t60 = 1, pitchy = 1, amp = 0.1, gate = 1) = …
~* is just multiplication for signals. It’s a "*" plus a little sound wave “~”. There are similar operators
percGen are Unit Generators or
UGens. These are the things which actually create sounds. SynthDefs are graphs of UGens.
releaseSecs_ are a way of having named arguments for the UGens (in this case, for
percGen creates an envelope, a way of shaping sound. We’ll see (and explain) “V::V” again below.
V::V "t60" is the way we reference the current value of the
I "t60" argument above. You can remember “V” as “value of”
klank is a bit complicated as far as
UGens go, but whenever you have questions there’s plenty of documentation.
midiCPS takes a nice-to-work-with 0-127 MIDI note number and converts it to cycles per second, which is what’s usually needed for frequencies in
People in the SuperCollider world use the “s = foo ; s = bar(s)” etc idiom of continually redefining a single variable name. This can be pretty useful, e.g. when you want to comment out part of a filter chain.
If the bell has stopped ringing, remove the Synth.
And voila, send it out to the speakers! If you’re listening with two speakers (like with headphones), the left speaker is the first element of the array, the right is the second. If you’ve got an 8-speaker surround sound setup, no problem: just make the list longer!
All Together Now
Let’s abstract out playing and
playNotesWithWaits :: VividAction m => [(I "note", Rational)] -> m () playNotesWithWaits notesNWaits = forM_ notesNWaits $ \(note, waitAmt) -> do playNote note wait waitAmt silentNight :: VividAction m => m () silentNight = do replicateM_ 2 $ do silentNightStart let part2 = [(67, 1), (67, 1/2), (64, 3/2), (65, 1), (65, 1/2), (60, 3/2)] part3 = [(62, 1), (62, 1/2), (65, 3/4), (64, 1/4), (62, 1/2), (60, 3/4), (62, 1/4), (60, 1/2), (57, 3/2)] part4 = [(67, 1), (67, 1/2), (70, 3/4), (67, 1/4), (64, 1/2), (65, 3/2), (69, 3/2)] part5 = [(65, 1/2), (60, 1/2), (57, 1/2), (60, 3/4), (58, 1/4), (55, 1/2), (53, 2)] playNotesWithWaits $ part2 ++ part3 ++ part3 ++ part4 ++ part5
While we played this in GHCi, using
IO (), there are other ways to use
VividActions. One way is to use
doScheduledAt to have precise musical timing. Another way is to write to a file, using Non-Real Time (NRT) mode. This is how easy it was to write out the final audio file for this blog post:
And here’s how it sounds:
If you want to swim further out into the sea of Vivid, here are a few more things to try
- Use freeAll or cmdPeriod to silence everything playing on the server.
- Use fork to play multiple patterns at once. Maybe a polyphonic “Silent Night”?
- Use set to change the parameters of a running
- Use free to stop a single
Synthwhile it’s playing.
:iin GHCi to get the type of a
UGen. You’ll get a type signature that starts with
Args. The first list after
Argsis the required arguments; the second is the list of optional ones (the ones with default values).
However and whatever you celebrate, wishing you a happy holiday season and an incredible New Year!