Friday, June 25, 2010

Dictionary

Let's take a break from Pbinds and their ilk to talk about a data type. A Dictionary is a data type like an array, except that instead of using integers for indexes, you can use anything that you want.

(
 var id;
 
 id = Dictionary.new;
 id.put(\foo, "bar");
 
 id[\foo].postln;
)

The indexes are called keys and the items associated with them are called values. In the above example, \foo is the key and "bar" is the value. As mentioned above, you can use any kind of object for keys. And, like arrays, any kind of object can be the value.

(
 var id;
 
 id = Dictionary.new;
 id.put(\foo, "bar");
 id.put(15, true);
 id.put("baz", 12.5);
 
 id[15].postln;
)

You can get a list of the keys with the message keys, and then use that to step through a do loop:

(
 var id, keys;
 
 id = Dictionary[
  \foo -> "bar",
  15 -> true,
  "baz" -> 12.5
 ];

 keys = id.keys;
 keys.postln;

 keys.do({|key|
  
  "key is %, value is %".format(key, id.at(key)).postln;
 });
)

In that example, we see another way of declaring a Dictionary, with square brackets and key, value pairs in the format key -> value

We also see a new way of formatting strings. If you want to print a string with some variables in it, you can use % to signify where the variable should go and then pass the variables as arguments to format. You can use as many variables as you would like: "string %, % and %".format(var1, var2, var3)

Notice that the keys are in a random order. If you want them in a particular order, you will have to sort them.

Here's another example:

(
 var triangle_spectrum, freqs;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 freqs = triangle_spectrum.keys;
 freqs = freqs.asArray;
 freqs = freqs.sort;
 
 freqs.do({|freq|
 
  "freq %\t amplitude %\n".postf(freq * 440, triangle_spectrum[freq]);
 })
)

This stores the (non-normalised) spectrum of a triangle wave. Note that when we declare the Dictionary, we need to put mathematical expressions inside parentheses.

Then, we get the keys from the dictionary. Then we need to convert that Set to an Array. We can then tell the Array to sort itself.

\t means tab and \n means newline. We use these for formatting, to make our output look nice. postf formats a string and then posts it. It does not include a newline, so we do it ourselves with a \n

Array.sort takes an optional argument, which is a sort function. Here's an example to do a reverse sort:

(
 var triangle_spectrum, freqs;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 freqs = triangle_spectrum.keys;
 freqs = freqs.asArray;
 freqs = freqs.sort({|a, b| a > b});
 
 freqs.do({|freq|
 
  "freq %\t amplitude %\n".postf(freq * 440, triangle_spectrum[freq]);
 })
)

The sort function takes two agruments and returns a boolean. In the above example, it compares two items and if the first one is bigger, it returns true. This will order the array so that larger items come first.

We can check to see if a Dictionary includes a particular key:

(
 var triangle_spectrum;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 triangle_spectrum.includesKey(4).postln;
)

We can also look for the key of a particular item:

(
 var id;
 
 id = Dictionary[
  \foo -> "bar",
  15 -> true,
  "baz" -> 12.5
 ];

 id.findKeyForValue(12.5).postln;
)

Summary

  • A Dictionary is made up of key, value pairs.
  • You can add items to a Dictionary with a message put(key, value)
  • Dictionaries can be declared with key -> value pairs in square brackets.
  • Dictionary.keys exports a set of the keys, in random order.
  • If you want to sort the keys, you must pass them the message .asArray and then pass the message sort to the result of that.
  • If you want to sort anyhting besides numbers from small ot large you have to write a sort function
  • A sort function takes two arguments and returns a boolean based on a comparison between them.
  • You can test is an Dictionary contains a particular key with includesKey
  • You can look up the key for a value with findKeyForValue

Thursday, June 24, 2010

Streams

There's a very useful helpfile set for Streams Patterns and Events, which is available by looking for the helpfile for Streams.

We know what a pattern is, after looking at Pseq and he other Pthings. But what is a stream? "A stream represents a lazy sequence of values," says the helpfile "Understanding Streams, Patterns and Events - Part 1," which you should go read. Streams respond to the message next. Here's an example:

(
 var stream;
 
 stream = Pseq([1, 2, 3], 1).asStream;
 
 3.do({
  stream.next.postln;
 })
)

Any kind of pattern can be converted to a stream by sending it the message asStream. This can be more useful than you might think. For example, if you have two threads stepping through the same data:

(

 var stream, task1, task2;
 
 stream = Pseq(["Mary", "had", "a", "little", "lamb", "whose", "fleece"], 1).asStream;
 
 task1 = Task({
 
  inf.do({
  
   "task1: ".post;
   stream.next.postln;
   1.wait;
  });
 });
 
 task2 = Task({
 
  inf.do({
  
   "task2: ".post;
   stream.next.postln;
   1.51.wait;
  });
 });
 
 task1.play;
 task2.play
)

The two tasks can strep through a shared list. However, note that the stream starts returning nil when it runs out of other data to return. You have to test for that:

(

 var stream, task;
 
 stream = Pseq(["Mary", "had", "a", "little", "lamb", "whose", "fleece"], 1).asStream;
 
 task = { |id, wait|
  var next;
  Task({
 
   next = stream.next;
   
   {next.notNil}.while({
  
    ("task" ++ id ++ ": " ++ next). postln;
    wait.wait;
    next = stream.next;
   });
  });
 };
 
 task.value(1, 1).play;
 task.value(2, 1.51).play
)

You can also do math on streams:

(
 var stream, squared;
 
 stream = Pseq([1, 2, 3], 1).asStream;
 squared = stream.squared;
 3.do({
  squared.next.postln;
 })
)

And you can do binary math operations on two streams:

(
 var stream1, stream2, sum;

 stream1 = Pseq([1, 2, 3], 1).asStream;
 stream2 = Pseq([4, 5, 6], 1).asStream;
 sum = stream1 + stream2;

 3.do({
  sum.next.postln;
 })
)

You can use streams with Pbinds, but you have to wrap them in Prout or Pfunc:

(
 var notes, pbind;
 
 notes = Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ].pyramid(1), 2).asStream;
 notes = notes * 440;
 
 pbind = { |dur = 0.5|
 
  Pbind(
   \dur, dur,
   \freq, Pfunc({notes.next})
  )
 };
 
 Ppar([pbind.value(0.5), pbind.value(0.751)]).play;
)

Note that when notes.next returns nil, the Pbind quits playing.

Summary

  • Streams can be create from patterns. The respond to the message next.
  • When streams run out of values, they start to return nil.
  • Multiple tasks can access the same stream.
  • You can do math on streams.
  • Streams can be used in Pbinds, provided a function or a routine calls next

Tuesday, June 08, 2010

Math with Patterns & Prout

Let's say that you want to pick random numbers? You could use Pwhite. But what if you wanted random multiples of 2? You would still use Pwhite:

(
 Pbind(
  \dur,  0.2,
  \note,  Pwhite(0, 6) * 2
 ).play
)

What if you want to square your random numbers?

(
 Pbind(
  \dur,  0.2,
  \note,  Pwhite(0, 5).squared
 ).play
)

You can do any math operation with a pattern as if it was a number.

(
 Pbind(
  \dur,  0.2, 
  \note,  Pseq([1, 2, 3], 15) * Pseq([1, 2, 3, 5, 4], inf)
 ).play
)

If you want to do math with a pattern and a variable that might change over time, you still need to use a Pfunc:

(
 var num;
 num = 5;
 
 Pbind(
  \dur,  0.2,
  \note, Pwhite(0, 5) * Pfunc({num})
 ).play;
 
 Task({
  5.wait;
  num = 2;
 }).play;
)

Let's say you want step through every item in a shuffled array. You could use Pseq:

Pseq([1, 3, 5, 7].scramble, 4)

Or Pshuf does the same thing:

Pshuf([1, 3, 5, 7], 4)

But what if you want the array to reshuffle after every tme through the loop? (As far as I know) there's not a pre-existing pattern for that. But you can write one with Prout.

(
 var arr, rout;
 
 arr = [1, 3, 5, 7];
 
 rout = Prout({ 
  5.do({
   arr.scramble.do({|item|
    item.yield;
  })})
 });

 
 Pbind(
  \dur, 0.2,
  \note, rout
 ).play
)

A Routine is a function that can pause while it's running. It can also return values more than once. Prout is a wrapper for a Routine. Everytime it gets to item.yield, it returns the item and then waits to be asked for it's next return value. Then it starts running again until it finds another yield or runs out of things to run.

Like Pfunc, Prout evaluates variables as it runs. In the above example, if arr had changed, the Prout would have used the new arr:

(
 var arr, rout;
 
 arr = [1, 3, 5, 7];
 
 rout = Prout({ 
  10.do({
   arr.scramble.do({|item|
    item.yield;
  })})
 });

 
 Pbind(
  \dur, 0.2,
  \note, rout
 ).play;
 
 Task({
  2.wait;
  arr = [12, 15, 19, 21];
  2.wait;
  arr = [-12, -13, -11];
 }).play
)

You could do something similar also, this way:

(
 var arr1, arr2, arr3, rout;
 
 arr1 = [1, 3, 5, 7];
 arr2 = [12, 15, 19, 21];
 arr3 = [-12, -13, -11];
 
 rout = Prout({ 
  3.do({
   arr1.scramble.do({|item|
    item.yield;
  })});
  3.do({
   arr2.scramble.do({|item|
    item.yield;
  })});
  3.do({
   arr3.scramble.do({|item|
    item.yield;
  })})
 });

 
 Pbind(
  \dur, 0.2,
  \note, rout
 ).play;
 
)

You can have as many yields as you want in a Routine - or a Prout.

If you want to pass in arguments, you can use a wrapper function:

(
 var rout;
 
 rout = {|arr, repeats = 5|
  Prout({ 
   repeats.do({
    arr.scramble.do({|item|
     item.yield;
   })})
  });
 };

 
 Pbind(
  \dur, 0.2,
  \note, rout.value([0, 2, 4, 6], 6)
 ).play
)

And, as with Pfunc, you can do math:

(
 var rout; 
 
 rout = {|arr, repeats = 5|
  Prout({ 
   repeats.do({
    arr.scramble.do({|item|
     item.yield;
   })})
  });
 };

 
 Pbind(
  \dur, 0.2,
  \note, rout.value([0, 2, 4, 5], 6) * Pseq([1, 2, 3], inf)
 ).play
)

Yielding only works inside Routines. If you try this in a Pfunc, it does not work:

(
 var func; 
 
 func = Pfunc({ 
  5.do({
   [1, 2, 5].scramble.do({|item|
    item.yield; // you can't do this in a Pfunc!!
  })})
 });
 

 
 Pbind(
  \dur, 0.2,
  \note, func
 ).play
)

Note that it does not create an error message, it just behaves in a bizarre way. Silent errors are the hardest to track down, so beware.

Summary

  • Patterns can behave like numbers for math operations.
  • A Routine is a function that can pause
  • A Prout is like a Pfunc, except that it can stop in the midst of things and resume.
  • Routines can return more than one item, at any point in their execution, whenever you yield a value.
  • You can only yield in a Routine or a Prout

Sunday, June 06, 2010

Pfunc

Let's say you want to control some part of your Pattern from, say, a task. For example, we want to execute some block of code, then wait 5 seconds and then casue the amplitude of a Pbind to fade towards zero. We can do this by using some variables:

(
 var db, pbind, task;
 
 db = -12;
 
 task = Task({
 
  // ...
  
  5.wait;
  
  60.do({
  
   db = db - 1;
   0.1.wait;
  });
  
  "done fading".postln;
 });
 
 // ...
)

db will hold our amplitude in decibels. We're using db because it's logarithmic and will sound right if we just use subtraction to change the value.

task will hold our task. That "..." near the top represents whatever we wanted to do before starting the countdown to the fade-out.

After that, we wait for 5 seconds, then we slowly drop 60 db.

Now for the pbind:

 // ...

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    db
 );
 
 // ...
  

In the line for degree, we see that we're sending the message pyramid to the array. It reorders the array in an interesting way. See the helpfile for Array for more information about it.

Pbinds understand the symbol \db and will convert to amp automatically, as needed. There's a message you can pass to numbers called dbamp. It converts from db to amps. -12.dbamp is 0.25118864315096. Or if you want to go the other way, there's a message ampdb. 0.25.ampdb is -12.041199826559. However, the Pbind will do this for you, so you don't need to convert ahead of time.

Now we can try running the task and the pbind simultaneously:

 // ...
 
 pbind.play;
 task.play;

And when you put this all together and try it, "done fading" prints out, without there having been any fade.

This is because the interpretter evaluates the expression db and then passes the result of that expression as an argument to the Pbind contructor. It is only evaluated that one time. In order to get the current value of db, we need a way to tell the pbind to re-evaluate it every time it comes up. We can do that with Pfunc

Pfunc is a wrapper for functions, so they can be used in patterns. It's constructor takes a function as an argument and then it evaluates that function every time it's asked for a value. Let's re-write our pbind:

 // ...

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({db});
 );
 
 // ...

Remember that functions return the value of their last line. Since we only have one line, it returns that. And, indeed, the amplitude fades away to quiet. However, the Pbind keeps running even after we can't hear it. We can use an if statement to tell it to stop:

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({
       if(db > -72, {
        db
       }, {
        nil
       })
      })
 );

Pbinds stop running as soon as any symbol is paired with nil. Therefore, when db gets to -72 and the Pfunc returns nil, the whole Pbind stops. If the Pbind were in a Pseq, it would go on to the next item.

You may have also noticed that we don't have semicolons everywhere they could be. This is because they separate lines of code, so it's ok to skip them on the last (or only) line in a block of code. It makes no difference to the interpretter if there is a semicolon after nil or not. If you find it easier to use them everywhere, then you should keep doing that.

We could also skip the false function for the if, since it will still return nil if we do:

Pfunc({ if(db > -72, { db }) })

The final version of this example, all together is:

(
 var db, pbind, task;
 
 db = -12;
 
 task = Task({
 
  // ...
  
  5.wait;
  
  60.do({
   db = db - 1;
   0.1.wait;
  });
  
  "done fading".postln;
 });
 
 
 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({ if(db > -72, { db }) })
 );
 
 
 pbind.play;
 task.play;
 
)

Summary

  • Decibels can be an easier system to use when computing amplitudes
  • Pbinds will convert between db and amp
  • The messages ampdb and dbamp can convert number values from amps to db or db to amps
  • pyramid is an interesting way of shuffling an array
  • You can put a function in a pattern with Pfunc
  • A Pfunc with get re-evaluated for every event
  • A Pbind stops when it gets a nil value

Problem

  1. Write a short piece of music where two separate Pbinds both access the same variable. They can be in the same Ppar or not.
  2. Write a programme such that one Pbind sets a flag that causes another Pbind to change in some way.

Saturday, June 05, 2010

Polyphony and Sequencing

Chords

Now that we can use Pbinds to make sure our sounds come on time, let's look about how to order them and play more than one note at once. The easiest way to do polyphony is with arrays:

Pbind(\note, [1, 3, 5]).play

The Pbind creates a separate synth for each item in the array.

\note determines pitch as a scale degree in an equal tempered scale. It computes the frequency based on the note number and then uses that for the synth's freq argument. \note can be used with any synthdef.

While note is always equally tempered, it need not be 12-tone ET:

(
 Pbind(
  \note,    [1, 4, 7],
  \stepsPerOctave,  10
 ).play
)

By pairing \stepsPerOctave with 10, the example above gives us 10-tone ET. We could do 24 for a quatertone scale, or use whatever number we'd like.

If, for some reason, we wanted to use MIDI note numbers we could do that. We don't need to use whole numbers, but can use Floats to change the tuning.

Pbind(\midinote, 60.05).play

60.05 is middle C, 5 cents sharp.

Rests

If we want to rest for a note, we can do that by pairing the symbol \rest to any of the symbols that control pitch:

(
 Pbind(
  \dur,   0.2,
  \note,    Prand([1, 4, 7, 9, \rest], 15),
  \stepsPerOctave,  10
 ).play
)

We could also pair rest with \freq, \midinote, or \degree - which is the degree in the scale.

Ppar

If we want to play two Pbinds at the same time, just telling one to play right after the other does not guarantee that they will line up perfectly. To make sure they start in sync, we can use Ppar.

Ppar takes two arguments, a list and the number of repeats. Here's an example from the helpfile:

(
 var a, b;
 a = Pbind(\note, Pseq([7, 4, 0], 4), \dur, Pseq([1, 0.5, 1.5], inf));
 b = Pbind(\note, Pseq([5, 10, 12], 4), \dur, 1);
 Ppar([a, b ]).play;
)

a and b play exactly in time with each other.

If we don't want all the patterns to start right away, we can use Ptpar, which is the same except that the list contains pairs of times and patterns. Each time is how long to delay before starting the pattern:

(
 var a, b;
 a = Pbind(\note, Pseq([7, 4, 0], 4), \dur, Pseq([1, 0.5, 1.5], inf));
 b = Pbind(\note, Pseq([5, 10, 12], 4), \dur, 1);
 Ptpar([ 0.0, a, 1.3, b ], 2).play;
)

Pbind a starts immediately, because it's time is 0. Pbind b starts after 1.3 seconds. The whole thing repeats twice, because of the second argument to Ptpar

If, instead of playing at the same time, we wanted to play one after the other, we can put the Pbinds into a Pseq:

(
 var a, b;
 a = Pbind(\note, Pseq([7, 4, 0], 2), \dur, Pseq([1, 0.5, 1.5], inf));
 b = Pbind(\note, Pseq([5, 10, 12], 2), \dur, 1);
 Pseq([a, b ]).play;
)

And Pseqs can be passed to Ppars and Ptpars and vice versa.

Summary

  • Pairing an array with a symbol in a Pbind makes it play chords
  • \note determines pitch as a scale degree in an equal tempered scale and can be use with any synthdef.
  • \degree is the degree in the scale
  • \stepsPerOctave set the number of pitches in the scale used by \note or \degree
  • \midinote is the midinote number - in Floats
  • Pairing \rest with any of pitch-related symbols will cause a rest
  • We can start two or more Pbinds at the same time with Ppar
  • We can start two or more Pbinds with a pre-set delay between them using Ptpar
  • We can start two Pbinds sequentially by putting them in a Pseq
  • We can put Pseqs in Ppars and vice versa.

Problems

  1. Write a short A B A (refrain verse refrain) piece with a higher pitched line and a lower pitched line, with their own rythms. You can use the default synth for Pbind or one (or more) of your own.

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