Reference#
There are many different Pattern, I will only discuss some of them which I find most important. The official documentation of SuperCollider is not always super helpful but the tutorial Understanding Streams, Patterns and Events is an excellent source to get started.
Primary Pattern#
If you want to use the pattern library the following patterns are your bread and butter.
Lists#
Pseq is the most basic pattern that can be used to play through a list of values.
It is useful for deterministic number generatrion, e.g. playing a specific chord progression but also to combine non-deterministic patterns.
repeats
(which can be infinit inf
) spcifies how often the pattern goes through the list.
offset
indicate the starting index.
// [ 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2 ]
Pseq(list: [1,2,3,4], repeats: 3, offset: 2).asStream.all;
Prand chooses items from the list randomly (same as list.choose). You can reduce randomness if you but the same item into the list multiple times (or you use Pwrand instead).
// e.g. [ 4, 3, 4, 3, 3, 2, 1, 3, 2, 4 ]
Prand(list: [1,2,3,4], repeats: 10).asStream.all;
Pxrand chooses randomly, but never repeat the same item twice.
// e.g. [ 2, 1, 2, 4, 3, 1, 4, 1, 2, 4 ]
Pxrand(list: [1,2,3,4], repeats: 10).asStream.all;
Pshuf shuffles the list in random order, and use the same random order repeats
times.
This introduces randomness deterministically.
// e.g. [ 3, 1, 4, 2, 3, 1, 4, 2, 3, 1, 4, 2 ]
Pshuf(list: [1,2,3,4], repeats: 3).asStream.all;
Pwrand choose randomly by weighted probabilities (like list.wchoose(weights)).
Tipp: use normalizeSum
to guarantee that weights add up to 1.0.
// e.g. [ 1, 2, 1, 2, 2, 2, 1, 2, 2, 2 ]
Pwrand(list: [1,2,3,4], weights: [20, 30,1, 5].normalizeSum, repeats: 10).asStream.all;
Series#
Pseries generates numbers according to the arithmetic series (addition).
// [ 1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1 ]
Pseries(start: 1, step: -0.1, length: 10).asStream.all;
Pgeom generates numbers according to the geometric series (multiplication).
// [ 1, 0.5, 0.25, 0.125, 0.0625 ]
Pgeom(start: 1, grow: 0.5, length: 5).asStream.all;
Distributions#
Pwhite generates random numbers equal/uniformly distributed (white noise), like rrand(lo, hi)
.
// e.g. [ 0.55498147010803, 1.88440990448, 1.8215901851654, 1.8560950756073 ]
Pwhite(lo: 0, hi: 2.0, length: 4).asStream.all;
Pexprand generates random numbers exponentially distributed, like exprand(lo, hi)
.
Attention: Zero can not be in the interval!
// e.g. [ 0.35829930578573, 1.0719818402671, 1.0689460460456, 0.12406136454322 ]
Pexprand(lo: 0.1, hi: 2.0, length: 4).asStream.all;
Pbrown generates numbers according to the Brownian motion, arithmetic scale (addition).
The step
argument determines the maximum change per step.
Internal, the pattern uses xrand2
to determine the next step change, i.e., a uniform distribution fron -step
to +step
.
// e.g. [ 0.044661736488342, 0.12499458789825, 0.20763206481934, 0.30759530067444 ]
Pbrown(lo: 0, hi: 5, step: 0.1, length: 4).asStream.all;
Functions#
Pkey gets the value of another key
of a Pbind.
The key
has to be specified before it can be accesed.
In the following example we control the amplitude \amp
via the \midinote
we are playing such that higher notes are louder.
(
Pbind(
\midinote, Prand([65,66,68,70], inf),
\amp, Pkey(\midinote).linlin(65, 70, 0.3, 1.0),
\dur, 0.25
).trace.play;
)
// e.g. [ 3, 1, 3, 0, 0 ]
Pfunc(nextFunc: {4.rand}, resetFunc: {}).asStream.nextN(5)
Pfunc gets the stream values from a user-supplied function.
// e.g. [ 3, 1, 3, 0, 0 ]
Pfunc(nextFunc: {4.rand}, resetFunc: {}).asStream.nextN(5)
Pfuncn gets values from the function, but stop after repeats
items.
// e.g. [ 3, 3, 2, 1, 0, 0, 0 ]
Pfuncn(func: {4.rand}, repeats: 7).asStream.all;
Prout uses the function like a routine.
The function should return values using .yield
or .embedInStream
.
The folling code constructs a pattern that spits out the first 10 Fibonacci numbers.
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
(
Prout(routineFunc: {
var fib0 = 0, fib1 = 1, tmp = 0;
10.do {
fib0.yield;
tmp = fib0;
fib0 = fib1;
fib1 = tmp + fib1;
}
}).asStream.all;
)
Secondary Pattern#
Lists#
Pser similar to Pseq it plays through a list but the number of resulting times is defined by the second argument repeats
.
// [ 1, 2, 3 ]
Pser(list: [1,2,3,4], repeats: 3, offset: 0).asStream.all;
Pslide plays overlapping segments from the list.
// [ 2, 3, 4, 5, 4, 5, 6, 1, 6, 1, 2, 3 ]
(
Pslide(
list: [1,2,3,4,5,6],
repeats: 3,
len: 4,
step: 2,
start: 1,
wrapAtEnd: true).asStream.all;
)
Pwalk realizes a random walk over the list. The direction and the steps of the walk can be controled by patterns.
// [ 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 1, 2, 3, 4 ]
(
Pwalk(
list: [1,2,3,4,5],
stepPattern: Pseq([1,1,-1,1,1,-1], inf),
directionPattern: 1,
startPos: 0).asStream.nextN(15);
)
Place interlaces any arrays found in the main list. Basically this pattern returns elements in the list. But if an element is an array itself, it embeds the first element when it comes by first time, the second element when it comes by the second time and so on. The \(n\)-th when it comes by the \(n\)-th time. This can be very useful when we build a melodies.
// [ 1, 2, 5, 1, 3, 6 ]
Place(list: [1,[2,3,4],[5,6]], repeats: 2, offset: 0).asStream.all;
Ppatlace similar to Place but for patterns. Compare the following differences:
// [ 1, 2, 5, 6, 1, 3, 5, 6 ]
Place(list: [1,[2,3,4],Pseq([5,6])], repeats: 2, offset: 0).asStream.all;
// [ 1, [ 2, 3, 4 ], 5, 1, [ 2, 3, 4 ], 6 ]
Ppatlace(list: [1,[2,3,4],Pseq([5,6])], repeats: 2, offset: 0).asStream.all;
// [ 1, 2, 5, 1, 3, 6 ]
Ppatlace(list: [1,Pseq([2,3,4]),Pseq([5,6])], repeats: 2, offset: 0).asStream.all;
Ptuple collects the list items into an array as the return value.
The list
argument has to be an array of pattern.
// [ [ 1, 10, 4 ], [ 2, 11, 5 ], [ 1, 10, 4 ], [ 2, 11, 5 ] ]
(
Ptuple(list:
[
Pseq([1,2,3]),
Pseq([10,11,12]),
Pseq([4,5,nil])
],
repeats: 2).asStream.all;
)
Distributions#
Pattern Manipulation#
If a pattern returns numbers you can do all the math you can do with numbers. In addition, patterns can be composed similar to functions. This makes only sense for Pbind or functional pattern.
// [ 1.0, 4.0, 9.0, 1.0, 4.0, 9.0 ]
(Pfuncn(func: {|x| x**2}, repeats: 7) <> Pseq([1,2,3], 2)).asStream.all
// ( 'dur': 0.2, 'amp': 1.0, 'freq': 440 ) repeatedly
(Pbind(\dur, 0.2, \amp, 1.0) <> Pbind(\freq, 440, \amp, 0.5)).trace.play
Filter Pattern#
Filter pattern are manipulate existing pattern via the decorator approach. This means they not necessarily reduce the number of values a pattern generates.
Pn repeatedly embed a pattern.
This pattern is especially hand in the live coding environment when you want to adjust a pattern by decorate it by another pattern.
If key
is non-nil
, it sets the value of that key to true whenever it restarts the pattern.
This can be used to advance patterns enclosed by Pgate.
// [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ] equivalent to Pseq([1,2,3], 3)
Pn(pattern: Pseq([1,2,3]), repeats: 3, key: nil).asStream.all;
Pdup repeat input stream values.
Very similar to Pn
but instead of repeating the pattern it repeats each value of the pattern.
// [ 1, 2, 2, 3, 3, 3, 1, 1, 1, 1 ]
Pdup(n: Pseq([1,2,3,4]), pattern: Pseq([1,2,3], inf)).asStream.all;
Pgate listens to the key
and if it is set to true
the gate returns the next value in the source pattern.
Otherwise it will return the same value.
Suppose we want to advance a pattern when some other pattern spits out a specific value.
Pgate
can be used in that case.
In the following we change the \root
by 10 cent whenever the \rootStep
spits out true
.
(
Pbind(
\scale, Scale.minorPentatonic,
\degree, Pseq([1, 3, 5, 1], 20),
\rootStep, Pseq([false, false, true], inf),
\root, Pgate(Pseries(0, 0.1, inf), inf, \rootStep),
\dur, 0.25
).trace.play;
)
Pcollect is the equivalent to collect
.
This pattern is rarely needed since we can treat patterns like numbers.
// [ 2, 4, 6, 8 ] equivalent to Pseq([1,2,3,4]) * 2
Pcollect(func: {|x| x*2}, pattern: Pseq([1,2,3,4])).asStream.all;
Pselect is the equivalent to select
.
This pattern can be used to literally filter another pattern.
// [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ]
Pselect(func: {|x| x % 2 == 0}, pattern: Pseq((0..20))).asStream.all;
Preject similar to Pselect but selects the exact opposite items. This pattern can be used to literally filter another pattern.
// [ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ]
Preject(func: {|x| x % 2 == 0}, pattern: Pseq((0..20))).asStream.all;
Pdrop drops the first count
elements from a pattern.
// [ 4, 5 ]
Pdrop(count: 3, pattern: Pseq([1,2,3,4,5])).asStream.all;
Pfin drops all elements from a stream after the first count
elements.
It terminates the stream early.
// [ 1, 2, 3 ]
Pfin(count: 3, pattern: Pseq([1,2,3,4,5])).asStream.all;
Pfindur limits total duration of events embedded in a stream.
It terminates the stream early.
Attention: Note that pattern
has to be a pattern for an event stream.
In the following we terminate an infinite event stream early, i.e. after 7 beats.
(
Pfindur(
dur: 7,
pattern: Pbind(
\dur, Pseq([0.25, 0.5, 0.25, 1.0], inf),
\midinote, Pseq([65, 67, 69, [50, 62]], inf)
),
tolerance: 0.001
).play;
)
Psubdivide subdivides floating numbers spit out by pattern
into n
parts.
This is nice to change the rhythm of an event player without influencing its overall duration.
In the following we split the 0.5 duration note into two 0.25 notes.
(
Pbind(
\dur, Psubdivide(
n: Pseq([1, 2, 1, 1], inf),
pattern: Pseq([0.25, 0.5, 0.25, 1.0], inf)),
\midinote, Pseq([65, 67, 69, [50, 62]], inf)
).play;
)
With Pclutch one can control when another pattern
spits out its next or previous value.
If connected
spits out 1 or true
, the next value is spit out, otherwise the previous.
// [ 1, 1, 2, 2, 2, 2, 3, 4, 4, 4 ]
(
Pclutch(
pattern: Pseq([1, 2, 3, 4, 5], 3),
connected: Pseq([0, 0, 1, 0, 0, 0, 1, 1], inf)
).asStream.nextN(10);
)
To keep the range of numbers spit out by a pattern within a specific range defined by lo
and hi
, we can use Pwrap.
// [ 4, 5, 3, 4, 5, 3, 4, 5, 3, 4 ]
Pwrap(pattern: Pseq([1,2,3,4,5,6], inf), lo: 3, hi: 5).asStream.nextN(10)
To build chords from an arpegio one can use Pclump which packs the values generated by pattern
into arrays of length n
.
// [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
Pclump(n: 2, pattern: Pseq([1,2,3,4,5,6], inf)).asStream.nextN(3)
The reverse can be achieved by Pflatten.
// [ [ 1, 2, 3 ], [ 1, 3, 4 ], 1, 1, 1, [ 1, 2, 3 ] ]
Pflatten(n: 1, pattern: Pseq([[[[1,2,3], [1,3,4]]], [1,1,1]], inf)).asStream.nextN(6)