Sunday, October 03, 2010

Working with Buffers

Let's say you want to open a short audio file and play it back. You can do this with a Buffer.

 s.boot;
 b = Buffer.read(s, "sounds/a11wlk01.wav");

Don't run both of those lines at once. Wait for the server to finish booting before reading the Buffer. The reason you need to wit is because the Buffer actually lives with the server. The server plays the audio, so the server needs to have the audio in it's memory.

The first argument, is therefore the server that will hold the buffer. The second is the path to the buffer on your computer.

Because the Buffer lives with the server, the command to read it is asynchronous. We tell the server to load the Buffer and then carry on, without waiting to hear back if it's been loaded or not. For large files, it may take a few moments for it to fully load. One way to deal with tis is to load buffers into global variables (like b, above) and then manually wait to evaluate the next section.

PlayBuf

To play the Buffer, you can use the UGen PlayBuf:

(
 SynthDef(\playBuf, {| out = 0, bufnum = 0 |
  var scaledRate, player;
  scaledRate = BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum]);
)

When Buffers are allocated on the server, they're given a unique ID number, so we can reference them later. This number is called the bufnum. It's the second argument to the SynthDef. This way, we know which buffer we're supposed to play.

Some Buffers may have unusual sample rates or otherwise be unusual. To make sure that they play at the speed we expect, we use a UGen to scale the rate. If we wanted to play at half speed, we could change the SynthDef:

(
 SynthDef(\playBuf, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 0.5]);
)

Note that when we play at half speed, it takes twice as long to play the sample and pitches are all an octave lower. This is just like working with magnetic tape. If we change the rate to 2, it will play back at half the speed of the original and be an octave higher. If you try a rate of -1, it will play backwards at the normal rate.

The PlayBuf UGen is the one that actually plays the buffer. The first argument is the number of channels. Our buffer is mono, so that number is 1. If it were stereo, we would change it to 2. You cannot pass in this number as an argument or change it while the Synth is running. The SynthDef must be defined with the right number of channels. If you give a stereo buffer to a mono PlayBuf, or vice versa, it will not play. If you have both mono and stereo buffers in one piece, you will need to have two SynthDefs:

(
 SynthDef(\playBufMono, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 SynthDef(\playBufStereo, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;
)

PlayBuf can handle N-channel files, so if you have some three channel or 8 channel files, you can also play those if you define synthdefs with the right number of channels.

To figure out which SynthDef to use, you can send the message numChannels to a buffer object:

(
 if((b.numChannels == 1), {
  Synth(\playBufMono, [\out, 0, \bufnum, b.bufnum, \rate, 1])
 }, {
  if((b.numChannels == 2), {
   Synth(\playBufStereo, [\out, 0, \bufnum, b.bufnum, \rate, 1])
  })
 });
)

The second argument to PlayBuf is the bufnum. This tells it which Buffer to play.

The third argument is the rate. As mentioned above, it's a good idea to scale this rate with BufRateScale.kr

Buffers, like envelopes, can have a doneAction. 2 means to deallocate the synth from the server when the Buffer stops playing.

If you want to loop your buffer, that's also a possible argument:

(
 SynthDef(\playBufMonoLoop, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1]);
)

1 means do loop and 0 means don't loop. the default is 0. You can change the loop value while the Synth is running, so it's a good idea to still have a doneAction.

You can trigger a PlayBuf to jump back to the start:

(
 SynthDef(\playBufMonoStutter, {| out = 0, bufnum = 0, rate = 1 |
  var trigs, scaledRate, player;
  trigs = Dust.ar(2);
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, trigs, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1]);
)

Dust.ar returns impulses at random intervals. It's argument is the average number of impulses per second.

You can also tell the PlayBuf to start someplace else than the first sample:

(
 SynthDef(\playBufMonoLoopStart, {| out = 0, bufnum = 0, rate = 1, startPos |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, startPos: startPos, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1, \startPos, b.numFrames.rand]);
)

startPos is the number of the sample to start from. If you have a one second long buffer with 44100 samples in it, and you want to start in the middle, you would set use 22050 for the startPos.

Dialogs

If you don't want to have to pick an audio file ahead of time, you can use Buffer.loadDialog:


s.boot;
 
(
 SynthDef(\playBufMono, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 SynthDef(\playBufStereo, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 b = Buffer.loadDialog(s);
)

(
 if((b.numChannels == 1), {
  Synth(\playBufMono, [\out, 0, \bufnum, b.bufnum, \rate, 1])
 }, {
  if((b.numChannels == 2), {
   Synth(\playBufStereo, [\out, 0, \bufnum, b.bufnum, \rate, 1])
  })
 });
)

Note that you cannot load a compressed audio format like mp3 or ogg directly into a Buffer.

More on Buffers to come!

Summary

  • Buffers live on the server
  • Every Buffer has a unique bufnum
  • You can change the playback rate of a Buffer. This should be scaled with BufRateScale
  • SynthDefs must be sent to the server already knowing how many channels PlayBuf.ar will play.
  • PlayBuf.ar will optionally take a doneAction
  • You can loop a Buffer, stuter it and tell it where to start
  • You can open a Buffer with a Dialog box
  • You cannot read an mp3 or ogg directly into a Buffer

1 comment:

Anonymous said...

I'm really glad I've came across this informative post, this really helped me out quite a bit, by answering all my questions properly, hopefully the site will grow bigger and more popular as ever it was. Keep up the good work and I'm sure I'll visit you up someday soon for more great articles. You may find great articles and music production tools at: http://www.lucidsamples.com by the way!