## Wednesday, October 13, 2010

### Installing

BBCut2 is a nifty library for doing break beat cutting. To use it, you must first install it. It is not available as a quark, alas. To get the library, download it from http://www.cogs.susx.ac.uk/users/nc81/bbcut2.html. Then unzip it. Inside, you will find several directories.

1. Move the "bbcut2 classes" directory to ~/Library/Application\ Support/SuperCollider/Extensions . That tilda represents your home directory, so if your user name is nicole, you would put the file in /Users/nicole/Library/Application\ Support/SuperCollider/Extensions . If the Extensions directory does not exist on your system, then create it.
2. Put the "bbcut2 help" directory inside the classes directory that you just moved.
3. Put the "bbcut2 ugens" directory in ~/Library/Application\ Support/SuperCollider/Extensions/sc3-plugins . If this directory does not exist, then create it.
4. Take the contents of the "bbcut2 sounds" directory and put them in the sounds folder with your SuperCollider application. so if you have SuperCollider in /Applications, you would put the contents of "bbcut2 sounds" in /Applications/SuperCollider/sounds

Then, re-start SuperCollider. Depending on what version of SC you have, you may have duplicate classes. If you do, there will be errors in the post window. If you see that this is a problem for you, go find the files in the BBCut classes and delete them, making sure to keep the other copy. The error message will tell you where to find the files and which ones they are.

### The Clock

BBCut relies on a clock. When I'm coding, I usually base the clock off the default clock:

 TempoClock.default.tempo_(180/60);
clock = ExternalClock(TempoClock.default);
clock.play;


The tempo is defined as beats per second. That's beats per minute, divided by 60 seconds. In the above example, the clock rate is 180 bpm, which is then divided by 60 to set the tempo. If you wanted a clock that was 60 bpm, you would set tempo_(60/60), or for 103 bpm, it would be tempo_(103/60)

BBCut uses an ExternalClock, which uses a TempoClock, so in the above example, I give it the default TempoClock. I don't have to use the default one, but could declare a new one if I wanted: clock = ExternalClock(TempoClock(182/60));

The next step is to the clock to play. If you forget this step (which I often do), nothing happens later on. Telling the clock to play is important. BBCut relies on this clock.

### Working with Buffers

There is a special sort of buffer used by BBCut, called a BBCutBuffer. The constructor for this takes two arguments. The first is a string which should contain the path and file name of the file. The second argument is the number of beats in the file. For example, we could open one of the sound files that came with BBCut:

 sf= BBCutBuffer("sounds/break",8);


We need to wait for the Buffer to load before we can start using it. One way to do that is to put the code that relies on the Buffer into a Routine. And then, we can tell the Routine to wait until the server is ready to carry on.

 sf= BBCutBuffer("sounds/break",8);

Routine.run({
s.sync; // this tells the task to wait
// below here, we know all out Buffers are loaded
. . .
})


Now we can tell BBCut that we want to cut up a buffer and get it to start doing that.

  cut = BBCut2(CutBuf3(sf)).play(clock);


BBCut2 is the class that runs everything, so we make a new one of these. Inside, we pass a CutBuf, which is a class that handles Buffer cutting. We tell the BBCut2 object to play, using the clock. This starts something going.

Cutting is much more interesting if it can jump around in the buffer a bit:

  cut = BBCut2(CutBuf3(sf, 0.4)).play(clock);


We can specify the chances of a random cut. 0.0 means a 0% chance and 1.0 is a 100% chance. We can set the chances at any numbers between and including 0.0 to 1.0. If we want a 40% chance of a random jump, we would use 0.4.

### Cut Procedures

We can tell BBCut to use one of several cut procedures. The original one is called BBCutProc11.

  cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11.new).play(clock);


It can take several arguments, which are: sdiv, barlength, phrasebars, numrepeats, stutterchance, stutterspeed, stutterarea

• sdiv - is subdivision. 8 subdivsions gives quaver (eighthnote) resolution.
• barlength - is normally set to 4 for 4/4 bars. If you give it 3, you get 3/4
• phrasebars - the length of the current phrase is barlength * phrasebars
• numrepeats - Total number of repeats for normal cuts. So 2 corresponds to a particular size cut at one offset plus one exact repetition.
• stutterchance - the tail of a phrase has this chance of becoming a repeating one unit cell stutter (0.0 to 1.0)

For more on this, see the helpfile. In general, the cut procedures are very well documented. Here's an example of passing some arguments to BBCutProc11:

  cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2).play(clock)


We can tell the cutter to stop playing, or free it

  cut.stop;
cut.free;


Putting all of what we have so far together, we get:

(
var clock, sf, cut;

TempoClock.default.tempo_(180/60);
clock = ExternalClock(TempoClock.default);
clock.play;

sf= BBCutBuffer("sounds/break",8);

Routine.run({
s.sync; // this tells the task to wait

cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);

30.wait; //  // let things run for 30 seconds

cut.stop;
cut.free;
})
)


There are several other cut procedures, like WarpCutProc1 or SQPusher1 or SQPusher2. If you go look at the main helpfile, you can find which ones are available. This file is called BBCut2Wiki (and is found at ~/Library/Application\ Support/SuperCollider/Extensions/bbcut2\ classes/bbcut2\ help/BBCut2Wiki.help.rtf or by selecting the text BBCut2Wiki and typing apple-d )

### Polyphony

The clock keeps things in sync, so you can run two different cut procedures at the same time and have things line up in time.

  cut1 = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
cut2 = BBCut2(CutBuf3(sf, 0.2), WarpCutProc1.new).play(clock);


You can even mix and match sound files:

(
var clock, sf1, sf2, cut1, cut2, group;

TempoClock.default.tempo_(180/60);
clock = ExternalClock(TempoClock.default);
clock.play;

sf1= BBCutBuffer("sounds/break",8);
sf2= BBCutBuffer("sounds/break2",4);

Routine.run({
s.sync; // this tells the task to wait

cut1 = BBCut2(CutBuf3(sf1, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
cut2 = BBCut2(CutBuf3(sf2, 0.2), WarpCutProc1.new).play(clock);

15.wait;
cut1.stop;
cut2.stop;
})
)


If you want to also sync up a Pbind, you can use BBcut's clock via the playExt method:

Pbind.new(/*  . . . */ ).playExt(clock);


Or, if you want to play an Event, you can use the tempo clock associated with the external clock

Event.new.play(clock.tempoclock);


### Groups and FX

If we want to add fx to the chain, and take them back out, we can use a thing called a CutGroup:

  // make a group with a Buffer
group = CutGroup(CutBuf3(sf1, 0.4));
// then send it to get cut up
cut1 = BBCut2(group, BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
// then put some FX in the chain


The CutGroup acts like an array, which holds our CutBuf and also the fx. To get an idea of how this works, try running the following code, adapted from CutGroup's help file:

(
var sf, clock;

clock= ExternalClock(TempoClock(2.5));

clock.play;

Routine.run({

sf= BBCutBuffer("sounds/break",8);

s.sync; //this forces a wait for the Buffer to load

g=CutGroup(CutBuf1(sf));

BBCut2(g, WarpCutProc1.new).play(clock);
});

)

//run these one at a time

g.cutsynths.postln;

g.cutsynths.postln;

g.removeAt(2);  //remove comb

g.cutsynths.postln;


And also notice that you may get some errors in the post window when you remove fx: FAILURE /n_set Node not found. These are not usually a problem.

### Tying it all Together

Here's an example using everything covered so far:

(
var clock, sf1, sf2, cut1, cut2, group;

TempoClock.default.tempo_(180/60);
clock = ExternalClock(TempoClock.default);
clock.play;

sf1= BBCutBuffer("sounds/break",8);
sf2= BBCutBuffer("sounds/break2",4);

Routine.run({
s.sync; // this tells the task to wait

group = CutGroup(CutBuf3(sf1, 0.2));  // make a group with a Buffer
cut1 = BBCut2(group, BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);  // then cut it up

5.wait;

cut2 = BBCut2(CutBuf3(sf2, 0.2),
BBCutProc11(8, 4, 4, 2, 0.2)).play(clock); // start more drums from the other sound file

5.wait;

group.add(CutComb1.new); // put some FX on the drums in cut1

15.wait;

group.removeAt(2); // take the fx back off

1.wait;
cut2.pause;

4.wait;
cut1.stop;
})
)


#### Summary

• You must download BBCut2 from a website and install it by moving folders around.
• The main BBCut helpfile is BBCut2Wiki
• BBCut uses it's own clock, called ExternalClock, which relies on TempoClocks.
• You must remember to start the clock
• BBCut uses it's own buffer class called BBCutBuffer. The constructor takes two arguments: a string with the path and filename, and the number of beats in the sound file. If you get the number of beats wrong, your results may sound weird.
• There are several cut procedures, one of which is called BBCutProc11. To use it, (or any other cut procedure in it's place), you use the construction BBcut2(CutBuf1(sf), BBCutProc11.new).play(clock)
• The cut procedures all have good help files.
• Due to the magic of clocks, you can start two BBCuts going at the same time and if they have the same clock, they will line up.
• It is possible to add FX or remove FX from your chain by using CutGroup.

#### Problems

1. Get some of your own drum loops or rhythmic samples and try this out. You can find many loops at http://www.freesound.org/. Also, try some files with sustained tones.
2. Experiment with the arguments to BBCutProc11. Try your files with several different values.
3. Try out the different cut procedures. Look at their help files to find their arguments.
4. The FX are not as well documented, but they're located in ~/Library/Application\ Support/SuperCollider/Extensions/bbcut2\ classes/cutsynths/fx . Try several of them out.

### Waiting

Last time, we learned that the language does not wait for the server to finish loading buffers before it carries on with the programme, which could lead to a situation where we instruct the server to start playing a buffer that hasn't yet loaded. This will fail to play correctly. Fortunately, there are a few ways to make sure this doesn't happen.

If we use a Task (or a Routine), we can tell it to pause until the server has caught up.

s.boot;

(
var buf;

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)

s.sync; // wait for the server
Synth(\playBufMono, [\out, 0, \bufnum, buf.bufnum, \rate, 1])

}).play
)


The s.sync makes the Task wait for the server to get caught up with the language. The SynthDef, like the buffer, is also asynchronous, so the sync gives everything a chance to get caught up.

We can also give the buffer an action. This is a function that gets evaluated when the buffer has finished loading. This might be a good idea when we are going to load several buffers, but don't need to use all of them right away. We could use the action, for example, to set a flag so our programme knows it's ok to start using the buffer:

s.boot;

(

SynthDef(\playBuf, {| out = 0, bufnum = 0, rate = 1,
dur = 0.2, amp = 0.2, startPos = 0 |
var scaledRate, player, env;

env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
scaledRate = rate * BufRateScale.kr(bufnum);
player = PlayBuf.ar(1, bufnum, scaledRate,
startPos: startPos, loop:1);
Out.ar(out, player * env)

Pseq([
Pbind( // play this Pbind first
\scale,  Scale.gong,
\dur,   Pwhite(1, 3, inf) * 0.001,
\degree,  Prand([0, 2, 4, \rest], inf),
\amp,  0.2,
\test,  Pfunc({ bufloaded }) // when this is nil, this Pbind ends
),
Pbind( // next play this one
\instrument, \playBuf,
\bufnum,  Pfunc({buf.bufnum}), // use the buffer, now that we know it's ready
\dur,  Pwhite(0.01, 0.3, 20),
\startFrame, Pfunc({buf.numFrames.rand}),
\amp,  1
)
], 1). play

)



Recall that when a Pbind gets a nil, it stops playing and the pattern goes on to the next section, so setting the flag to nil, in this case, advances the piece. Because the second buffer has not yet loaded when the interpreter first evaluates the second Pbind, buf.numFrames will still be nil. Therefore, we put that into a Pfunc so that it gets evaluated when the Pbind plays, instead of when the interpretter first looks at the code.

In that example, we don't always start playing back at the start of the Buffer, but instead offset a random number of samples (called "Frames" here). Coupled with the short duration, this can be an interesting effect.

When you are writing your own actions, you can also do a more typical true / false flag or do anything else, as it's a function. For example, it's possible to combine the two approaches:

s.boot;

(
var buf1, syn, pink;

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

SynthDef(\playBufST, {| out = 0, bufnum = 0, rate = 1, loop = 1,
dur = 1, amp = 0.2 |
var scaledRate, player, env;

env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
scaledRate = rate * BufRateScale.kr(bufnum);
player = PlayBuf.ar(2, bufnum, scaledRate, loop:loop);
Out.ar(out, player * env)

s.sync; // wait for buf1
syn = Synth(\playBuf1, [\out, 0, \bufnum, buf1.bufnum,
\rate, 1, \loop, 1]);  // play buf1

action: {  // run this when pink loads
syn.set("loop", 0);
syn = Synth(\playBufST, [\out, 0, \bufnum, pink.bufnum,
\rate, 1, \loop, 1, \dur, 10]); // play pink
});

}).play
)


In the above example, we first wait for the allwlk01 buffer to load and then start playing it. While it's playing, we tell the SinedPink buffer to load also and give it in action. When it has loaded, the action will be evaluated. The action tells the first synth to stop looping and then starts playing a new Synth with the new buffer.

If we wanted to, we could use the action to set a flag or do any number of other things.

### Recording

We don't need to just play Buffers, we can also record to them. Let's start by allocating a new Buffer:

b = Buffer.alloc(s, s.sampleRate * 3, 1)


The first argument is the server. The second is the number of frames. I've allocated a 3 second long Buffer. The third argument is the number of channels.

Now, let's make a synth to record into it:

 SynthDef(\recBuf, {| in = 1, bufnum, loop = 1|

var input;
input = AudioIn.ar(in);
RecordBuf.ar(input, bufnum, recLevel: 0.7, preLevel: 0.4, loop:loop, doneAction: 2);


AudioIn.ar reads from our microphone or line in. Channels start at 1, for this UGen.

RecordBuf takes an input array, a bufnum and several other arguments. In this example, we're scaling the input by 0.7 and keeping previous data in the buffer, scaled by 0.4. We'll keep looping until instructed to stop.

We can mix this with a stuttering playback from earlier:

s.boot;

(
var buf, syn, pb;

SynthDef(\playBuf2, {| out = 0, bufnum = 0, rate = 1,
dur = 5, amp = 1, startPos = 0 |
var scaledRate, player, env;

env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
scaledRate = rate * BufRateScale.kr(bufnum);
player = PlayBuf.ar(1, bufnum, scaledRate,
startPos: startPos, loop:1);
Out.ar(out, player * env)

SynthDef(\recBuf, {| in = 1, bufnum, loop = 1|

var input;
input = AudioIn.ar(in);
RecordBuf.ar(input, bufnum, recLevel: 0.7, preLevel: 0.4, loop:loop, doneAction: 2);

buf = Buffer.alloc(s, s.sampleRate * 3, 1);

s.sync; // wait for the buffer

syn = Synth(\recBuf, [\in, 1, \bufnum, buf.bufnum, \loop, 1]); // start recording

2.wait; // let some stuff get recorded;

pb = Pbind( // play from the same buffer
\instrument, \playBuf2,
\bufnum,  buf.bufnum,
\dur,  Pwhite(0.01, 0.5, 2000),
\startFrame, Pwhite(0, buf.numFrames.rand, inf),
\amp,  1
).play;

5.wait;

syn.set("loop", 0);  // stop looping the recorder

3.wait;
pb.stop; // stop playing back
}).play
)


#### Summary

• Buffers load asynchronously and we can't count on them to be ready unless we wait for them.
• One way to wait is to call s.ync inside a Task or a Routine.
• We can start playing back a Buffer from any sample with the startFrame argument.
• Buffer.read has an action argument, to which we can pass a function that will be evaluated after the Buffer has been read on the server.
• We can allocate empty Buffers on the server with Buffer.alloc.
• AudioIn.ar starts with channel 1, even though most other input busses start at 0.
• RecordBuf.ar records to a Buffer.

#### Problems

1. Write a programme that opens and plays several Buffers. Make sure that every Buffer is ready before you play it. Spend the least possible amount of time waiting.
2. Write a programme that reads a Buffer, starts playing it back in some way and the starts recording to the same Buffer. You can use AudioIn.ar or check out InFeedback.ar. Note that, as the name implies, you may get feedback, especially if you are playing back in a linear manner at a rate of 1. Try other rates or ways of playing back to avoid this.
3. Find a short audio file that contains some beat-driven audio. If you know the number of beats in the file, could you make an array of startFrames? Write a Pbind that does something interesting with your file and your array.

## 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;


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)

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)
)


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)

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)

)

(
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