Sampling
In addition to its powerful library of oscillators and sound generators, SuperCollider also gives us the ability to use recorded sounds in our synths. In SuperCollider, a recorded sound is stored in a buffer object on the server. A buffer is effectively an array of numbers which can be of any length and can have one or more channels. It's possible to load a sound into a buffer from your hard drive, but it's also possible to record the output of a synth or UGen into a buffer directly on the server. A buffer can be played back normally, or you can control its rate and the playhead position with a UGen.
1 Loading Sounds
To load a sound from the drive, use the buffer-read
function, like so:
(buffer-read "/path/to/your/file.wav")
This should be pretty self-explanatory; the only required parameter to buffer-read
is the path to the file you want to load. SuperCollider has support for wav and aiff files, of any sample rate and any number of channels.
2 Playing Sounds
Once you've loaded a sound, you'll probably want to play it. Here's an example SynthDef which illustrates the UGens you'd use to do that:
(defsynth :sample ((buffer 0) (rate 1) (start 0) (amp 0.5) (out 0)) (let ((sig (play-buf.ar 2 buffer (* rate (buf-rate-scale.ir buffer)) :start-pos (* start (buf-frames.ir buffer)) :act :free))) (out.ar out (* amp sig))))
Typically, to tell the synth what buffer we want it to play, we would usually add a buffer
or bufnum
parameter to the synthdef. These are fairly standard names for buffer parameters in synths, and some libraries may expect a buffer to be passed to a synth via a parameter with one of these names. So in general, it's probably a good idea to get in the habit of using one of the two in your buffer-playing synthdefs. For this synth, we've added a buffer
parameter.
One thing to note is that we don't actually send the buffer object itself to the synth. In SuperCollider, each buffer has a ID number. The server will only understand these IDs, and any other names or objects will likely result in an error. Fortunately, cl-collider (as well as the official SuperCollider client) automatically translates buffer objects to buffer IDs in most functions, so you usually don't have to do it manually. If you do, however, keep in mind that cl-collider's bufnum
function is what is used to get a buffer's ID number.
After the buffer
parameter, we provide another for the playback rate. rate
is the number to multiply the buffer's original speed by. So 1
would play the buffer back at its normal speed, 2
would play it twice as fast (and one octave higher), and 0.5
would play it half as fast (one octave lower).
In this SynthDef, we also include a start
parameter, which lets us pick where in the sample to start playing from. In the SynthDef, a start
value of 0
means start from the beginning, while 0.5
means start playing halfway through the buffer. Most SuperCollider UGens that use buffers take the playback position as a frame number, but it's often more convenient to be able to specify the relative position in the file regardless of its sample rate, so we multiply start
by the number of frames in the sample to make this possible.
The body of the SynthDef is fairly simple; the main UGen is play-buf
which, as the name suggests, plays the buffer. Its first argument is the number of channels the buffer has. This is required by SuperCollider, and can't be modified while the synth is playing, or used as a parameter. It has to be a static number when the synth is compiled.
The second argument to play-buf
is the buffer number. After that, the playback rate. Here we multiply our rate
parameter by (buf-rate-scale.ir buffer)
. buf-rate-scale
is a UGen that outputs a number that we can multiply by in order to play a sample at its original speed regardless of the original file's sampling rate. If we just supplied our rate
parameter directly, any sample that you load would play at the wrong speed if it doesn't match the sampling rate of the server itself.
You might also notice the .ir
on the end of buf-rate-scale
. .ir
specifies that the UGen should "initialization rate": processed only once, when the synth is started. This frees up CPU usage, but it also gives us the disadvantage of not being able to change the buffer that the synth is playing while it's playing. Typically it's not necessary to change the buffer of an already-playing synth, but if you want that ability, you can just change the .ir
to a .kr
.
start-pos
is the argument play-buf
takes to specify where to start playing the sample from. Since play-buf
expects a frame number for that parameter, we multiply our start
parameter by the buf-frames
UGen, which gives the total number of frames of the buffer. This is another .ir
UGen for the same reason as above.
Finally, the act
parameter specifies what action should be taken when play-buf
finishes playing the sound. In our case, we provide :free
so that the synth will stop playing and be freed. Without this, the synth would continue running in the background and continue using CPU resources even though it's making no sound.
Of course, you may want to have the sound loop instead, in which case act
would be left to its default value, so no special action is taken when the sound reaches its end. You would also have to provide play-buf
's loop
parameter a value of 1
.
To actually play the sample
synth, we'll want to make sure that we've saved the reference to the file we opened with buffer-read
to a variable:
(defvar *file* (buffer-read "/path/to/your/file.wav"))
Then, we can start the sample
synth like so:
(synth 'sample :buffer *file*)
As mentioned above, since play-buf
's :act
argument is set to :free
, the synth will automatically be freed when the sample finishes playing. Of course, if we had set the sample to loop, then it would continue looping indefinitely, or until we stop it manually. Because there is no :gate
argument, the release
function would have no effect, so we would have to call stop
instead, to force it to stop.