Algorithmic Manipulation

One of the nice things about live coding is that you can schedule events to happen, or repeat, in the future. This allows you to continue coding while repeated functions are called and adding variety to your music. This section is an in-depth look at how the Player every method is implemented and how you can combine multiple instances of it to create complex music from simple patterns.

Basics

First let’s look at a simple example which reverses a sequence every 8 beats:

p1 >> pluck([0, 1, 2, 3, 4, 5, 6, 7]).every(8, "reverse")

The first argument is the number of beats between each call of a method and the second argument is the name of the method itself as a string. The reason for using the string name of the method instead of a function is so that Python can check if the method is valid using the getattr function and raise an error if it isn’t. Running the code print(getattr(p1, "reverse")) will give you something similar to <bound method="" player.reverse="" of="" >. What then happens, in essence, is that the scheduling clock runs getattr(p1, "reverse").__call__() every 8 beats.

You can use a list of durations to schedule method calls at irregular intervals like so:

p1 >> pluck([0, 1, 2, 3, 4, 5, 6, 7]).every([6, 2], "reverse")

The code above will call the reverse method after 6 beats, then 2 beats after that, then again 6 beats after that call, repeating this until stopped. You can also use a Pattern or PatternGenerator object such as PRand to call methods at times not pre-determined:

p1 >> pluck([0, 1, 2, 3, 4, 5, 6, 7]).every(PRand([2, 4, 8]), "reverse")

If you try and specify multiple calls of the same method you’ll find that only the last one updated gets scheduled. If you want to use more than one repeated call to the same method you can use the ident keyword and give it a name or number to differentiate it:

# Call "reverse" every 8 beats *and* every 5 beats
d1 >> pluck([0, 1, 2, 3, 4, 5, 6, 7]).every(8, "reverse").every(5, "reverse", ident=1)

Player methods that can be used with every effectively are reverse, rotate, shuffle, jump, and stutter.

The cycle keyword

Sometimes it might be useful to schedule a method for the same point in an N-beat cycle e.g. stutter the sound on the 6th beat of every 8 beat cycle. You can do this by simply specifying the length of the cycle as a keyword argument:

d1 >> play("x-o-").every(6, "stutter", cycle=8)

Rather than calling stutter every 6 beats, it is called every 8 beats (the cycle size) but offset by 6 beats.

The stutter method in depth

One of the most useful methods that can be called using every is the stutter method. This plays the last event sent to SuperCollider multiple times across a specified duration. You can also specify attributes/effects to attach to the events such as pan or shape using keyword arguments.

You can specify the number of times an event is stuttered simply by supplying an integer to the every call following the name of the method as a string. The default for this is 2 which means you will hear 1 extra event – 2 minus the event already being played. Using a value of 4 will play 3 extra events (you get the idea). By default the events will be stuttered across the duration of the event you are stuttering but you can also stutter the events across a given timeframe by supplying a dur keyword:

# Play the event 4 times every 6 beats across 1/2 a beat
d1 >> play("x-o-", dur=1/2).every(6, "stutter", 4)

# Play the event 4 times every 6 beats across 3 beats
d1 >> play("x-o-", dur=1/2).every(6, "stutter", 4, dur=3)

# You can also specify the number of events to stutter using the 'n' keyword
d1 >> play("x-o-", dur=1/2).every(6, "stutter", dur=3, n=4)

Just as you supply keyword arguments to control the sound of your synths, you can do the same with stutter to control the sound being played. These can be a list or pattern of values which are given to each event stuttered in turn i.e. not played all at once:

# Stutter 8 times with increasing playback speed
d1 >> play("x-o-").every(4, "stutter", 8, rate=[1,2,3,4,5,6,7,8])

# Stutter 4 times with alternating panning and higher rate
d1 >> play("x-o-").every(4, "stutter", 4, dur=3, pan=[-1, 1], rate=2)

# You can still use tuples / PGroups to add simultaneous effects
d1 >> play("x-o-").every(4, "stutter", 4, dur=1, pan=(-1,1), rate=(4, 1/2))

Note that when using a list of values, only the first n values will be used (where n is the number of times being stuttered).

Using Pattern methods

On top of reverse, rotate, shuffle, jump, and stutter, you can also schedule any method that belongs to the Pattern class to be called on any attribute of a player. The behaviour is slightly different to when scheduling player methods in that instead of being called every n beats, it is called and then un-called so-to-speak. It’s probably best to demonstrate with an example:

# Calls the "trim" method on the degree attribute
d1 >> play("x-o-").every(4, "trim", 3)

The “x-o-” pattern is trimmed to just “x-o” after 4 beats then reverts back to “x-o-” again after the next 4 beats. By default the method is called on degree attribute (which is pitch for most synths and the string of characters for the play synth) – you can specify a different attribute by prefixing the method name with the name of the attribute then a “.” like so:

# Trim the octave pattern to 3 every 4 beats
p1 >> pluck([0,1,2,3], oct=[4,5,6,7]).every(4, "oct.trim", 3)

Arguments that would be supplied to the pattern method are given following the name of the method. For example the pattern method offadd, which layers a pattern with itself but with a value added and delayed by a duration, takes 2 arguments; the value to add and the delay time (default is 0.5). Here are some examples on how to use it with every:

# Play a note 2 steps higher delayed 1/2 a beat
p1 >> pasha([0, 4], dur=[3/4, 3/4, 1/2]).every(3, "offadd", 2)

# Play a note 4 steps higher delayed 3/4 of a beat
p1 >> pasha([0,1,3,4], dur=1/2).every(5, "offadd", 4, 3/4)

You can use any method of the Pattern class, which you can see by running help(Pattern) or looking at the in depth descriptions.