Pattern Guide 04: Words to Phrases:
Filter:
Tutorials/A-Practical-Guide | Streams-Patterns-Events > A-Practical-Guide

Pattern Guide 04: Words to Phrases

Nesting patterns, arranging music in terms of phrases

From words to phrases

A single pattern by itself is not so exciting. But patterns can be used together to get more complex results.

Patterns within list patterns

We saw list patterns ( Pseq, Prand, etc.) that returned numbers from a preset list, either in the order given or rearranged randomly. The list may also include other patterns. When a list pattern encounters another pattern in its list, the inner pattern is embedded into the stream. That is, the inner pattern takes over until it runs out of values to return. Then, control returns to the outer list pattern. This is like calling a function in the middle of another function.

There is no preset limit to the number of levels of embedding.

If a single pattern is like a word, a list pattern that uses other patterns could be more like a sentence or phrase. You can alternate between different behaviors, either in a predictable order as in the example below, or randomly by using one of the random-order list patterns.

// Scale segments, in the sequence: up, up, down (repeat)
(
TempoClock.default.tempo = 1;
p = Pbind(
    \degree, Pseq([
        Pseries({ rrand(0, 7) }, 1, { rrand(4, 8) }),    // up (step = 1)
        Pseries({ rrand(0, 7) }, 1, { rrand(4, 8) }),    // up (step = 1)
        Pseries({ rrand(7, 14) }, -1, { rrand(4, 8) })    // down (step = -1)
    ], inf),
    \dur, 0.125
).play;
)

p.stop;

But it gets even more fun -- list patterns don't care whether they're enclosing value patterns (as in the previous example) or event patterns. That means you can write a set of Pbind-style patterns, each one representing a phrase, and string them together. This next example is longer, but that's only because of a larger number of phrase patterns. The structure is very simple, though: Pxrand([Pbind(), Pmono(), Pmono()...], inf) . Some of the phrases are written with Pmono to slide between notes.

(
SynthDef(\bass, { |out, freq = 440, gate = 1, amp = 0.5, slideTime = 0.17, ffreq = 1100, width = 0.15,
        detune = 1.005, preamp = 4|
    var    sig,
        env = Env.adsr(0.01, 0.3, 0.4, 0.1);
    freq = Lag.kr(freq, slideTime);
    sig = Mix(VarSaw.ar([freq, freq * detune], 0, width, preamp)).distort * amp
        * EnvGen.kr(env, gate, doneAction: Done.freeSelf);
    sig = LPF.ar(sig, ffreq);
    Out.ar(out, sig ! 2)
}).add;
)

(
TempoClock.default.tempo = 132/60;
p = Pxrand([
    Pbind(            // repeated notes
        \instrument, \bass,
        \midinote, 36,
        \dur, Pseq([0.75, 0.25, 0.25, 0.25, 0.5], 1),
        \legato, Pseq([0.9, 0.3, 0.3, 0.3, 0.3], 1),
        \amp, 0.5, \detune, 1.005
    ),
    Pmono(\bass,        // octave jump
        \midinote, Pseq([36, 48, 36], 1),
        \dur, Pseq([0.25, 0.25, 0.5], 1),
        \amp, 0.5, \detune, 1.005
    ),
    Pmono(\bass,        // tritone jump
        \midinote, Pseq([36, 42, 41, 33], 1),
        \dur, Pseq([0.25, 0.25, 0.25, 0.75], 1),
        \amp, 0.5, \detune, 1.005
    ),
    Pmono(\bass,        // diminished triad
        \midinote, Pseq([36, 39, 36, 42], 1),
        \dur, Pseq([0.25, 0.5, 0.25, 0.5], 1),
        \amp, 0.5, \detune, 1.005
    )
], inf).play(quant: 1);
)

p.stop;

Shortcut notation : Just like you can concatenate arrays with ++, you can also concatenate patterns the same way. Writing pattern1 ++ pattern2 is the same as writing Pseq([pattern1, pattern2], 1) .

Some ways to string together patterns

Sequentially
Each sub-pattern follows the next in the same order every time. Use Pseq or Pser.
Randomized order
Sub-patterns in completely random order ( Prand ), random order with no repeats ( Pxrand ), or random order according to a set of probabilities ( Pwrand ). Pshuf creates one random ordering and uses it repeatedly.
Direct array indexing
Patterns can be chosen in arbitrary order by index. This gives you more control than Pwrand. Both Pindex and Pswitch can be used for this.
// scale degree segments, every fifth choice is odd-numbered only (descending)
(
var    n = 10,
    scaleSegments = Array.fill(n, { |i|
        if(i.odd) {
            Pseries(11, -1, rrand(5, 10))
        } {
            Pseries(rrand(-4, 4), 1, i+2)
        }
    });

TempoClock.default.tempo = 1;
p = Pbind(
    \degree, Pswitch(scaleSegments, Pseq([Pwhite(0, n-1, 4), Pwhite(0, n-1, 1).select(_.odd)], inf)),
    \dur, 0.125
).play;
)

p.stop;
Finite state machine (Pfsm, Pdfsm)
A finite state machine is a way of associating an item with its possible successors. It is closer to a "grammar" than purely random selection. Pfsm defines a finite state machine as a set of possible "entry points," followed by a list of the possible "states" of the machine and, for each state, a list of the possible states that may follow the current state. States may be single values or patterns, meaning that phrases can be linked to other phrases that "make sense" in succession (and unwanted transitions can be prevented).

If this sounds a bit like a Markov chain, that's because the Pfsm implementation is a special case of a Markov chain where there is an equal probability of choosing the next state from the valid successors. In a Markov chain, the probabilities are weighted according to analysis of a real-world data stream.

The Pfsm help file includes very good examples of organizing single values and pattern phrases. Also see Pattern Guide Cookbook 06: Phrase Network for an application of Pfsm to generate a corny jazz solo.

The name Pdfsm stands for "deterministic finite state machine," where there is no random selection.

- Third-party extension alert : A good Markov chain implementation for SuperCollider exists in the MathLib quark.

Library of named sub-patterns

One very effective way to manage phrases is to make a library, or more precisely Dictionary, of sub-patterns, and then call them up one at a time. Psym is the pattern to do this. The advantage here is that you can store the phrases in a separate place, while the pattern that you actually play is much simpler and describes the musical intent at a much higher level.

// Uses the bass SynthDef above
(
~phrases = (
    repeated: Pbind(
        \instrument, \bass,
        \midinote, 36,
        \dur, Pseq([0.75, 0.25, 0.25, 0.25, 0.5], 1),
        \legato, Pseq([0.9, 0.3, 0.3, 0.3, 0.3], 1),
        \amp, 0.5, \detune, 1.005
    ),
    octave: Pmono(\bass,
        \midinote, Pseq([36, 48, 36], 1),
        \dur, Pseq([0.25, 0.25, 0.5], 1),
        \amp, 0.5, \detune, 1.005
    ),
    tritone: Pmono(\bass,
        \midinote, Pseq([36, 42, 41, 33], 1),
        \dur, Pseq([0.25, 0.25, 0.25, 0.75], 1),
        \amp, 0.5, \detune, 1.005
    ),
    dim: Pmono(\bass,
        \midinote, Pseq([36, 39, 36, 42], 1),
        \dur, Pseq([0.25, 0.5, 0.25, 0.5], 1),
        \amp, 0.5, \detune, 1.005
    )
);

TempoClock.default.tempo = 128/60;

// the higher level control pattern is really simple now
p = Psym(Pxrand(#[repeated, octave, tritone, dim], inf), ~phrases).play;
)

p.stop;

A complicated pattern with lots of embedding can be hard to read because it's more work to separate note-level details from the larger structure. The pattern choosing the phrases -- Pxrand(#[repeated, octave, tritone, dim], inf) -- is self-explanatory, however, and Psym fills in the details transparently.

NOTE: Because of some special handling needed for event patterns, there are two versions of Psym. Psym handles event patterns, while Pnsym is for value patterns. Think of it this way: Pbind can be contained within Psym, but it contains Pnsym.
( Psym ( Pbind ( Pnsym ) ) )
Good:
  • Psym(**, (pattern1: Pbind(**))
  • Pbind(\someValue, Pnsym(**, (pattern1: Pwhite(**)))

Bad:
  • Pbind(\someValue, Psym(**, (pattern1: Pwhite(**)))

Switching between patterns for individual values

In the examples above, if a list pattern encounters another pattern in its input values, the subpattern is embedded in its entirety before the list pattern is allowed to continue. Sometimes you might want to get just one value out of the subpattern, and then choose a different subpattern on the next event. Pswitch, Psym and Pnsym have cousins that do exactly this: Pswitch1, Psym1 and Pnsym1.

// random pitches in two distinct ranges; use a coin toss to decide which for this event
// 70% low, 30% high
(
TempoClock.default.tempo = 1;
p = Pbind(
    \degree, Pswitch1([Pwhite(7, 14, inf), Pwhite(-7, 0, inf)], Pfunc { 0.7.coin.binaryValue }),
    \dur, 0.25
).play;
)

p.stop;

Compare to the following:

(
p = Pbind(
    \degree, Pswitch([Pwhite(7, 14, inf), Pwhite(-7, 0, inf)], Pfunc { 0.7.coin.binaryValue }),
    \dur, 0.25
).play;
)

p.stop;

With Pswitch, one of the items is chosen from the list and keeps playing until it's finished. But the length of both Pwhite patterns is infinite, so whichever one is chosen first retains control. Pswitch1 does the coin toss on every event and embeds just one item.

Psym1 and Pnsym1 behave similarly, choosing the name to look up the pattern for each event.

Related: Conditional patterns

Pif supports this kind of structure: If the next value from a Boolean pattern is true, return the next item from pattern A, otherwise take it from pattern B. Another way to write the Pswitch1 example is to use a Boolean test directly on Pwhite, instead of writing a Pfunc for the coin toss. This might be clearer to read. However, this works only when there are two alternatives. Pswitch1 and Psym1 allow any number of choices.

(
TempoClock.default.tempo = 1;
p = Pbind(
        // translation: if(0.7.coin) { rrand(-7, 0) } { rrand(7, 14 }
    \degree, Pif(Pwhite(0.0, 1.0, inf) < 0.7, Pwhite(-7, 0, inf), Pwhite(7, 14, inf)),
    \dur, 0.25
).play;
)

p.stop;

We will see in Pattern Guide 06e: Language Control that Pif can be used on values that were previously calculated in the Pbind. It adds considerably to the intelligence Pbind can manage, when its value streams are aware of other values in the event.

Previous: Pattern Guide 03: What Is Pbind

Next: Pattern Guide 05: Math on Patterns