Friday, June 04, 2010

Pbinds

If you've been using tasks to control rhythmic sounds, you may have noticed that sometimes the timing can wander a bit. If you have two separate tasks running at the same time, they can also get out of sync. Tasks don't guarantee exact timing. Also, remember that the language interpreter and the audio server are separate programs. There is a small delay between telling the server to play something and when it actually plays it. It's possible to compensate for this, by giving it instructions slightly ahead of time. Pbinds compensate automatically.

p = Pbind.new.play;

That will repeat forever until you stop it.

p.stop;

A Pbind is a sort of a pattern. Patterns use the information that you provide and make assumptions about the information that you don't. In the above example, we didn't specify anything, so it picked all default values. Let's change the frequency:

Pbind(\freq, 660).play

Pbind.new takes an even number of arguments, as many or as few as you would like. They come in pairs, where the first member is a symbol and the second is an expression. For example:

Pbind(\freq, 660, \dur, 0.2 + 0.3.rand).play

Note that the expressions are evaluated when the Pbind's constructor is called. Therefore, whatever value is returned by 0.2 + 0.3.rand will be the duration for every separate event.

Repeating the same note over and over is rather dull, so there are some patterns that can help.

Prand takes two arguments, an array and the number of times it should run. For example:

(
 Pbind(
  \freq, Prand([330, 440, 660], 6)
 ).play
)

When the Pbind is playing, Prand picks a frequency from the array and uses that for the event. The next time, Prand again picks one of the freqs. It does this 6 times. Then the Pbind stops playing.

You can change the number of repeats to any number you would like, including inf. If you have multiple patterns in your Pbind, it will stop with whichever one ends first:

(
 Pbind(
  \freq, Prand([330, 440, 660], 6),
  \dur, Prand([0.2, 0.4, 0.3], 5)
 ).play
)

That only plays five notes because the second Prand only will return a value 5 times.

You can also play through a list with Pseq:

(
 Pbind(
  \freq, Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440, 1),
  \dur, Prand([0.2, 0.4, 0.3], inf)
 ).play
)

Pseq takes three arguments: a list and the number of times that it should play through the entire list. The third argument is an optional offset. Note that in the example, we're multiplying the entire array by 440. Every single element is multiplied, which gives us frequencies in the audible range. Note you can do additional math this way also:

(
 Pbind(
  \freq, Pseq(([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440) + 10, 1),
  \dur, Prand([0.2, 0.4, 0.3], inf)
 ).play
)

The lists passed to patterns can contain other patterns:

(
 Pbind(
  \freq, Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440, inf),
  \dur, Pseq([
     Pseq([0.2, 0.4], 2), 
     Pseq([0.3, 0.3, 0.6], 1)
    ], 3)
 ).play
)

You can change what synthdef the Pbind is playing with \instrument. First, let's define a synthdef:

(
 SynthDef(\example9, {|out = 0, freq, amp, dur, pan = 0, mod = 50|
  
  var pm, modulator, env, panner;
  
  modulator = SinOsc.ar(mod, 0, 0.2);
  pm = SinOsc.ar(freq, modulator);
  
  env = EnvGen.kr(Env.perc(0.01, dur, amp), doneAction:2);
  panner = Pan2.ar(pm, pan, env);
  
  Out.ar(out, panner);
 }).store
)

Then, specify it:

(
 Pbind(
  \instrument, \example9,
  \freq,  Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440, inf),
  \dur,  Pseq([
      Pseq([0.2, 0.4], 2), 
      Pseq([0.3, 0.3, 0.6], 1)
     ], 3)
 ).play
)

We can set values for any of the synthdef's arguments:

(
 Pbind(
  \instrument, \example9,
  \mod,  Pwhite(20, 100, inf),
  \freq,  Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440, inf),
  \dur,  Pseq([
      Pseq([0.2, 0.4], 2), 
      Pseq([0.3, 0.3, 0.6], 1)
     ], 3)
 ).play
)

Pwhite takes three arguments, a low number, a high number and the number of times to run. It then picks random numbers between the low and high values. In the example, we use those to control the modulation frequency of our synthdef.

Some argument names for synthdefs are quite common, like: amp, freq, pan, and out. Pbinds expect to see those argument names and will provide reasonable values if you don't specify. If you have a more original name for an argument, it's a good idea to specify values for it in the Pbind.

(
 SynthDef(\example9a, {|out = 0, freq, amp, dur, pan = 0, mod = 50, modulation_depth = 0.2|
  
  var pm, modulator, env, panner;
  
  modulator = SinOsc.ar(mod, 0, modulation_depth);
  pm = SinOsc.ar(freq, modulator);
  
  env = EnvGen.kr(Env.perc(0.01, dur, amp), doneAction:2);
  panner = Pan2.ar(pm, pan, env);
  
  Out.ar(out, panner);
 }).store
)


(
 Pbind(
  \instrument, \example9a,
  \modulation_depth,
     0.3,
  \mod,  Pwhite(20, 100, inf),
  \freq,  Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ] * 440, inf),
  \dur,  Pseq([
      Pseq([0.2, 0.4], 2), 
      Pseq([0.3, 0.3, 0.6], 1)
     ], 3)
 ).play
)

Do not assume that your default values from your synthdef will be used. If you want it to always be a certain value, specify that value.

Summary

  • Pbinds are more accurate than Tasks for playing in time.
  • Pbinds assume several default values, which you can specify if you want to change them.
  • Prand(list, n) picks an item from the given list n times.
  • Pseq(list, n) plays the entire list n times.
  • If you do a math operation with a SimpleNumber and an array, it does the operation for each item of the array and stores the result in an array.
  • Pseqs can contain other patterns, including other Pseqs
  • You can specify what synthdef to play with \instrument
  • Pwhite(lo, hi, n) picks a number between low and high n times
  • Symbols in a Pbind can correspond to synthdef argument names
  • If you have an unusual argument name, it's a good idea to specify a value for it in the Pbind

3 comments:

Ceiba Groove said...

I wonder how to use silences inside the sequence, should I use a freq=0?

Charles Céleste Hutchins said...

If you set freq to \rest, the Pbind will pause for the duration of that note.

joesh said...

Just thought I'd leave a comment so that you know people are passing by. I bumped into your tutorial whilst looking up 'chords' in SC. I find you tutorials really helpful, thanks for posting them.

Best - Joe