Pattern#
Pattern is an abstract class that is the base for the pattern library. The classes of the pattern library form a rich and concise score language for music. Patterns are the stateless blueprint of streams.
Calling asStream
on a Pattern transforms it into a Stream.
As already mentioned, all simple objects respond to this interface, by returning themselves.
Consequently, most objects are Pattern that define a Stream representing an infinite sequence of that object.
Pattern and Streams
Similar to classes and objects, a Pattern is the stateless blueprint for a stateful Stream.
The difference between (a) Pattern and (its) Stream becomes clear if we think in musical terms. A composition is a specific Pattern and a performance is a Stream of that pattern. Playing a piano can be seen as a stream of specific Events. We press some keys, with some velocity, for some duration, then we might wait for some amount of time and press the next keys. The incredible power of patterns lies in their ability build more complex patterns by combining different patterns, very similar to the decorator pattern used in software engineering. Similar to unit generators, they are very flexible.
Let us look at a non-trivial example, where we combine multiple Prand with a Pseq multiplied by 10
.
Prand expects an array of values and picks one of the elements at random whenever next
is called.
Pseq, on the other hand, also expects an array but picks each element in consecutive order.
Most patterns can be configured such that the stream repeats
a certain number of times.
We can also configure them to repeat an infinite inf
number of times.
p = Pseq(list: [Prand((5..7)), Prand((1..4))], repeats: 2) * 10;
q = p.asStream();
q.next; // random number between 50 and 70
q.next; // random number between 10 and 40
q.next; // random number between 50 and 70
q.next; // random number between 10 and 40
q.next; // nil
By using the parameter repeats: 2
, we configure Pseq to go over list
twice.
By multiplying the pattern by 10
a new pattern is created.
The resulting stream is the same but each number is multiplied by 10
.
Calling q.next
will evaluate next
until the resulting object streams itself.
In the example above, next
is called three times,
first on a stream generated by Pseq, then on a stream generated by Prand and then on a number.
Calling next
on a Stream defined by a pattern will lead to a recursive evaluation.
next
is called as long as the return value is another non-trivial stream.
Let us look at another example.
Here we use a Task to trigger the \beep
synth continuously.
We will discuss tasks, routines, and more in section Scheduling.
We generate three melodies notated by midi notes, randomly (but weighted) chosen and transformed into frequencies.
A note is played, and the task waits for 0.25
seconds before it continues playing.
The pattern consists of a Pwrand (similar to Prand but weighted) initialized with three sequence patterns.
Each sequence represents a fixed melody.
Combining fixed sequences with a random choice generates an overall spontaneous melody that is not entirely chaotic.
(
SynthDef(\beep, {
var env = Env.sine(dur: 0.1, level: \amp.kr(0.5)).ar(doneAction: Done.freeSelf);
var sig = SinOsc.ar(\freq.kr(200 * [1.0, 1.01]));
sig = sig * env;
Out.ar(0, sig);
}).add;
)
(
var mel1, mel2, mel3;
mel1 = Pseq([40, 45, 55]);
mel2 = Pseq([77, 55, 67, 61]);
mel3 = Pseq([65, 43, 71]);
p = Pwrand([mel1, mel2, mel3], [3, 5, 2].normalizeSum, inf).midicps;
q = p.asStream;
t = Task({
loop{
Synth(\beep, [\freq, q.next]);
0.25.wait
}
});
t.play;
)
Why are patterns so useful?
Well, they can be combined, and all regular math functions can manipulate them if they return numbers.
In our first example, we multiplied the Pseq pattern by 10
(calling asStream
returns a BinaryOpStream).
Patterns are compelling for building complex Streams.
Therefore, they offer us a very comfortable way of building melodies without dealing with threads directly; more on that in section Event Player.
The pattern library abstracts away not only thread creation, synchronization, joining, and termination, but also the clean-up task—one of the most challenging problems in computer programming.
Let us look at the following pattern-free example.
Instead of using a pattern, we create an infinite loop.
Within the loop, we play a random midi note and sleep for 0.2
seconds.
// playing sound without using any pattern or stream
// instead we create a new thread.
(
{
inf.do({
var midinote = 50 + 20.rand;
(\midinote: midinote).play;
0.2.wait;
})
}.fork;
)
This example seems to work just fine, and it looks pretty similar to our second example. When our piece becomes more complicated and we use and manipulate multiple parameters of our synth, the code becomes hard to read and interact with. For example, we might want to play notes with different durations, i.e., schedule musical events at non-equidistant times. We will see how we can replace threading by modeling a composition by a discrete musical event simulation.
As mentioned, like functions, patterns support many mathematical operations including composition.
(
a = Pfunc { arg x; x * 2 };
b = Pfunc { arg x; if(x > 5) { 0 } { 1 } };
c = Pseq((0..6));
z = b <> a <> c;
q = z.asStream;
Array.fill(8, {q.next}); // gives us [ 1, 1, 1, 0, 0, 0, 0, nil ]
)