scotw-002: Visualization and Inter-pattern Communication

Inter-pattern Communication

Last week’s track made extensive use of patterns to control the pitch, duration, timing, and timbre of musical events. Many of these patterns involve probabilities, i.e. they are examples of stochastic processes. Practically speaking, this allows the creation of musical personalities. The notes emitted by a given pattern cannot be determined in advance, but the resulting musical line will always have a particular character. This corresponds well with our real-life experience of improvising musicians. If I go to a Keith Jarrett concert, I can be fairly certain it won’t sound like Cecil Taylor.

Musicians display their character not only in how they play, however, but also in how they react to other players, i.e. how they listen. If we want to implement something like this in SuperCollider, we need to find a way for patterns to listen to each other. James Harkins covers several methods for this in his (previously-mentioned) tutorial, so I will just cover what I feel to be the simplest one.

The basic issue is that we need a place to store completed events. We can do this by calling the collect method on a pattern as shown below:

1
2
3
Pbind(
    \freq, Prand([220,330,440], inf),
    \dur, Pwrand([1,0.5], [0.25, 0.75], inf)).collect({|ev| ~event = ev}).play

So what’s going on here? First we have a simple Pbind in which both the frequency and durations are randomly chosen from a list. For the duration, we use Pwrand (weighted random) which takes as an additional argument a list of weights. In this case, we want to have more short notes than long notes. We then call the collect method on this Pbind. Collect takes as its argument a function that will be evaluated for each event. In this case, we simply assign the event to the global variable ~event. We can now reference ~event from other patterns:

1
2
3
Pbind(
    \freq, Pif( Pfunc({(~event[freq] == 440)}, 880, \rest ),
    \dur, 2)

Here we use the Pif pattern, which first evaluates the expression in its first argument; if this is true, it returns the second argument, if false, the third. We check if the frequency of the other pattern is 440. If it is, we play one octave above, if not, we simply rest. (Note that the conditional needs to wrapped in a Pfunc, otherwise it will only be evaluated once when the pattern is defined).

This is a fairly simple example, but it should be easy to imagine more complicated applications. In this week’s track, this method is used to make patterns that play more when the other patterns are silent and vice versa, just like real musicians!

Visualization

So, sound is pretty great, but it’s even better when coupled with visuals. SuperCollider provides extensive drawing functionality, and it would be nice if we could use the same patterns that are creating the sound to drive the visuals. There are several ways to do this, and I’m sure I’ve chosen the worst one, but I’ll go ahead and outline it here:

  1. For each parameter of the visualization, make a control bus - In this case, I am visualizing the amplitudes of six distinct pitches (three from each pattern), so I need six control buses.
  2. Make a synth with both audio rate (ar) and control rate (kr) outputs - The audio output is what will be heard (duh), and the control output will drive the visuals. I chose to use the amplitude envelope as the control output, but you can generate any signal that can be derived from the synth inputs.
  3. Make sure everything goes to the right place - We have six control buses and six pitches and we need to make sure that the right pitches go to the right bus. This is probably the ugliest part of this code. The pitches are stored in an array and so are the control buses. Within the pattern, the pitch of the current event is checked (using Pkey) and then the appropriate control bus is sent to the Synth as a parameter.
  4. Draw it!- This is the fun part. We use userView, which is fairly well documented in the included help. In our drawFunc, we use the getSynchronous method to get the current value of the control bus. (Don’t use normal get, that won’t work).

Simple, isn’t it? As always, code is below.