Skip to main content

Composing with Accelerating Rhythms

Thanks to all who checked out my album Haywire Frontier. Yesterday, I gave a remote talk for the NOTAM SuperCollider meetup on the project. The talk wasn’t recorded, but I decided to rework it into prose. This is partially for the benefit of people that missed the event, but mostly because I’m too lazy to research and write a new post wholly from scratch this month.

It’s not necessary to listen to the album to understand this post, but of course I would appreciate it.

Acceleration from notes to an entire piece

One of the earliest decisions I had to make while planning out Haywire Frontier was how to approach rhythm. I’m a huge fan of breakcore and old school ragga jungle (Venetian Snares’ work convinced me to dedicate my life to electronic music), and partially as a result of that, unpitched percussion and complex rhythms are central to a lot of my output.

However, I resolved pretty early on that I didn’t want the rhythmic material of the project to fall into the grids and time signatures of dance music. My reasons for this are nebulous and difficult to articulate, but I think a big part is that I wanted to challenge myself. When I make beat-based music, which I do frequently, I tend to think relative to established genres like drum-‘n’-bass or techno or house, and I mimic the tropes of what I want to imitate. Removing those guardrails, while still trying to make music conducive to active listening, puts me out of my comfort zone. I like to put myself in creative situations where I feel a little awkward or uncomfortable, because if there’s anything I personally fear in my creative output, it’s complacency. [1]

So beats are out. An alternative, which I have used a lot in the past, is a type of randomized rhythm I call the “i.i.d. rhythm,” or “Pwhite-into-\dur rhythm:”

SuperCollider code:

// NB: Full aggregated code from example, plus SynthDefs, are at the end of the post.
Routine({
    loop {
        s.bind { Synth(\kick) };
        rrand(0.03, 0.6).wait;
    };
}).play;

In these rhythms, the inter-onset intervals (IOIs), or time between successive hits, are chosen with a single random distribution. In statistics terms, the IOIs are i.i.d., or independently and identically distributed. The distribution is uniform in this example, but you can use log-uniform, or any distribution over the positive real numbers.

Every SuperCollider user has written one of these rhythms at some point. They’re perfectly serviceable for some applications. However, for rhythmic material that drives an entire percussion section, I have to admit that I find these tiresome and uninspiring. In one word, what these rhythms lack is phrasing.

If you were to grab a non-musician, give them a snare drum, and ask them to hit it “randomly,” their result would be nothing like this. They might produce a cluster of rapid hits, then silence, then a nearly steady rhythm, and modulate between all those approaches. That’s to say nothing of a free jazz drummer who’s spent years training to produce complex, compelling rhythms that may not fall on a grid. It’s well known to psychologists that humans are very bad at producing data that passes randomness tests; I view it as Geiger-counter-type rhythms failing to pass humanity tests.

What is there between the extremes of i.i.d. rhythms and grid-based rhythms? There’s a lot, and at some point I arrived at accelerating rhythms, which ended up being a major strand clearly audible throughout the album.

Routine({
    loop {
        (0.9 ** (0..10)).do { |duration|
            s.bind { Synth(\kick) };
            duration.wait;
        };
    };
}).play;

This is a sequence of 10 durations that follow a decaying geometric series, which then restarts periodically. (This is not a Risset rhythm; the acceleration suddenly restarts, and there’s no auditory illusion here.) This kind of rhythm begins to tap into the notions of motion and phrasing, and is immediately far more inspiring for me than total randomness. I don’t remember exactly how I arrived at it, but Conlon Nancarrow’s Canon X may have planted the seed. Speaking of influences, I’m not claiming that any ideas I present here are novel; this is just my personal journey with rhythm.

As a minor point of housekeeping, I find the expression 0.9 * (0..10) to be a bit unmusical, as I can’t compute the total duration of a phrase in my head. To fix this, I normalize the sum of those durations to 1 and then explicitly multiply it by the number of seconds a single accelerating phrase lasts:

Routine({
    loop {
        ((0.9 ** (0..10)).normalizeSum * 3.5).do { |duration|
            s.bind { Synth(\kick) };
            duration.wait;
        };
    };
}).play;

This is minor, but for me code is the “score” of my music, and little cosmetic choices like this have a butterfly effect on how I compose.

The next step, which was a key moment in the inception of the album, was to recurse this process. Now the phrase durations themselves can accelerate according to a geometric series:

Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick) };
            duration.wait;
        };
    };
}).play;

There is obviously perceptible motion both at the scale of a phrase and at the scale of an entire section. Also note that the 15.0 allows me to enter the duration in seconds of this entire section. While mathematically and computationally simple, having the section duration explicitly encoded as a number has a big psychological impact on how I work. Instead of thinking of this rhythm as an endless generative process that gets cut off at some point, this makes me think of plotting out the next 15 seconds of music and then subdividing it. When I make algorithmic music based on infinitely unfolding processes, I often have trouble with formal structure, and thinking in terms of directions and goals helped a lot with ossifying a piece with a beginning, middle, and end.

You can imagine where this is going, but some quick digressions first. The first is some minor sound design seasoning. I find the drum to be a little too mechanical-sounding, so I like to pass the duration into the synth and have its timbre react accordingly:

Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick, [duration: duration]) };
            duration.wait;
        };
    };
}).play;

I’ve written about this previously as the Duration Trick. I haven’t shown the actual kick patch, but it is quieter and softer for shorter durations, which mimicks how a musician tends to play notes with softer velocities for rapid passages.

Another, more dramatic change is that we can start interrupting this recursive rhythm with… well, anything, really. In this case I’ve added a 50% chance of a clanky snare drum at the end of each phrase (I love clanky, SOPHIE-style snare drums):

Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick, [duration: duration]) };
            duration.wait;
        };
        if(0.5.coin) {
            s.bind { Synth(\snare) };
            0.28.wait;
        };
    };
}).play;

And the next step is, you guessed it, three levels of acceleration.

Routine({
    ((0.8 ** (0..8)).normalizeSum * 50.0).do { |sectionDuration|
        ((0.8 ** (0..8)).normalizeSum * sectionDuration).do { |phraseDuration|
            ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
            if(0.5.coin) {
                s.bind { Synth(\snare) };
                0.28.wait;
            };
        };
    };
}).play;

Soon after I started experimenting with this approach to recursively accelerating rhythms, I realized there was a connection to both mathematics and a long-lost childhood hobby of mine. These rhythms are a loose sonification of the ordinal number system, which are a hierarchy of “infinities” developed by Georg Cantor.

Ordinal numbers are rather special to me because (life story incoming) as a kid, I was really fascinated by the study of large numbers, so much that in 2008 I created a wiki about them called “Googology Wiki.” I was 11 at the time. The ordinal number system is closely linked to certain systems for producing extremely fast-growing functions, and as such the site has many articles on various landmarks in the ordinal number system. I grew disinterested in the wiki over the years, but over time it ended up getting a lot of traction. Although I can’t take credit for the majority of its content, it’s easily the most famous thing I’ve created, having amassed tens of thousands of articles and even some citations in academic papers. Fair warning if you visit the wiki — it was made on then-Wikia, now Fandom, and it is now sadly plastered with video ads for the MCU.

I’m not the first person to connect ordinals to rhythms, in fact. In a blog post titled “Ordinal music,” Adam P. Goucher noticed that there’s a vaguely ordinal-like structure in the buildups in PSY’s “Gangnam Style.” (Stay tuned for my analysis of chaotic dynamical systems in “Call Me Maybe.”) He produced an audio track that sonifies four levels of recursive acceleration.

One of the tracks on my album is like that, comprising a pure, skeletal sonification of recursive acceleration, although only three levels. It’s meant as an interlude and a nod to the process music of composers like Tom Johnson. For the rest of the album, ordinal rhythms gave me inspiration for structuring form, but I frequently interrupted and altered them to keep the pieces exciting. For these pieces I didn’t use a geometric series for the third level. Instead, I wrapped up the section as a function, and manually typed in the section lengths:

var playSectionA;

playSectionA = { |sectionDuration|
    ((0.8 ** (0..8)).normalizeSum * sectionDuration).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick, [duration: duration]) };
            duration.wait;
        };
        if(0.5.coin) {
            s.bind { Synth(\snare) };
            0.28.wait;
        };
    };
};

Routine({
    playSectionA.(20.0);
    playSectionA.(7.0);
    playSectionA.(5.3);
}).play;

Structuring the piece this way lets me view the ordinal-rhythm sections as a sort of “refrain” or “chorus” that accelerates with every iteration, and I can interweave them with other sections:

Routine({
    playSectionA.(20.0);
    playSectionB.();
    playSectionA.(7.0);
    playSectionC.();
    playSectionA.(5.3);
    playOutro.();
}).play;

I’ve experimented in the past with algorithmic approaches to form, but I always end up with dialing in high-level formal structure manually. However, on a conceptual level, I find it really nice how form has emerged very naturally from the low-level materials.

Other variations

Branching off from my investigations into rhythm, there are a few other variants and related concepts that made their way into the album. My general philosophy for finding these rhythms is that they need to 1. sound good and 2. be elementary to represent in code. Concise, elegant code is a constraint I take seriously in creative coding; I try to avoid engineering complex software to compose music.

Mixed acceleration and deceleration

You can of course reverse the entire rhythm to produce recursive deceleration. More exciting to me is the idea of reversing only some levels in the hierarchy. Here’s what happens when the phrases and the entire piece are accelerating, but the section is decelerating:

Routine({
    ((0.8 ** (0..8)).normalizeSum * 50.0).do { |sectionDuration|
        ((0.8 ** (0..8)).normalizeSum.reverse * sectionDuration).do { |phraseDuration|
            ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
        };
    };
}).play;

There are eight different possibilities with three levels of acceleration/deceleration, and they all have different “vibes” that are hard to predict on paper. The possibilities multiply if the phrases and sections are themselves heterogeneous; for example one can pick randomly between acceleration and deceleration for each phrase or section.

Sorted random rhythms

Another way to generate accelerating and decelerating rhythms is to generate a bunch of durations with a random distribution and then sort them. It’s not radically different in sound from a deterministic approach, but it sounds a little less predictable and I find the controls easier to tweak.

Routine({
    (({ exprand(0.01, 0.5) } ! 20).sort.normalizeSum * 3.5).do { |duration|
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;

Recursive subdivision

Here, the notion of acceleration and deceleration is generalized with an arbitrary “row” of durations that are recursively subdivided in a self-similar or fractal manner:

Routine({
    var row;
    row = [0.2, 1, 1.4, 0.8, 1].normalizeSum;
    (row * 20.0).do { |sectionDuration|
        (row * sectionDuration).do { |phraseDuration|
            (row * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
        };
    };
}).play;

This is an old concept, and not far off from the tradition of total serialism. (As someone who often uses mathematical concepts, my philosophy sometimes overlaps with 20th-century modernist music.) John Cage structured his prepared piano studies this way, and John Coolidge Adams’ piano piece Phrygian Gates has “marks” placed with a scheme like this. I didn’t use this much on my album, except for the final track.

Sinusoidal rhythms

This is just a steady beat with tempo fluctuating according to a sine wave. This is one of the more prosaic ideas here as far as mathematics goes, but it still sounds really great to my ears.

Routine({
    30.do { |i|
        var duration;
        duration = cos(i * 2pi / 20).linlin(-1, 1, 0.05, 0.2);
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;

I can imagine some variants of this — you can substitute the cosine function for any mathematical function you want. Maybe a sum of sinusoids to produce “additive synthesis rhythms?” What other audio signal processing algorithms can generate compelling tempo signals?

Gridless discrete rhythms

These are actually a type of i.i.d. rhythm, but instead of using a continuous distribution a discrete distribution is used. The durations are randomly selected from a finite set of IOIs, which are not related to each other by an audible grid.

Routine({
    30.do { |i|
        var duration;
        duration = [0.02, 0.1, 0.432].choose;
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;

If an IOI repeats, the listener may perceive a brief grid; the randomness starts to form recognizable patterns.

My track “You Poor Thing” elaborates on this concept using a first-order Markov chain of discrete IOIs that also determines the type of drum hit (kick, snare, etc). The B-section that occurs at the midway point uses gridless discrete rhythms, but manually sequenced.

Conclusion

As an album with shamelessly maximalist aesthetics, there are many, many strands in Haywire Frontier. This is only one, but it was the most important for me developmentally. Working out the rhythmic material was a breakthrough for figuring out how I wanted the project to sound, and turned out to be a huge boon for getting past the classic problem of the blank page.

I should mention that these approaches all technically fall under the umbrella of xenrhythm, a variant of xenharmony, and a term so obscure that it makes basically no appearance outside a Facebook group and Fandom (ugh) wiki with 15 pages. One blog post that’s been sitting in my drafts is a survey of xenrhythms, structured much like my survey of non-standard oscillators.

Footnotes

Appendix: full source code

Here’s the full SuperCollider code I used to generate the examples. I don’t share SynthDefs publicly that much, but I’m dropping this as a thanks to all those who supported the album.

(
SynthDef(\kick, {
    var snd, duration, velocity;
    duration = \duration.kr(1.0);
    velocity = duration.linlin(1, 0, 1, 0);
    snd = SinOsc.ar(
        60
        * (1 + (8 * Env.perc(0, 0.001).ar * velocity))
        * (1 + (8 * Env.perc(0, 0.03).ar * velocity))
        * (1 + (0.5 * Env.perc(0, 0.3).ar * velocity))
        * ([1, -1] * 0.1).midiratio
    );
    snd = snd * (1 + (Env.perc(0, 0.03).ar * velocity));
    snd = snd + (BPF.ar(Hasher.ar(Sweep.ar), 8321, 0.3) * Env.perc(0.001, 0.003).ar * 1.dbamp * velocity);
    snd = snd.tanh;
    snd = snd + (BPF.ar(Hasher.ar(Sweep.ar), 3321, 0.3) * Env.perc(0.03, 0.05).ar * -10.dbamp * velocity);
    snd = snd * velocity.sqrt;
    snd = snd + GVerb.ar(snd.sum * -30.dbamp, 30, 1);
    snd = snd * Env.perc(0.001, duration.min(0.6)).ar(Done.freeSelf);
    snd = snd * -3.dbamp;
    Out.ar(\out.kr(0), snd);
}).add;

SynthDef(\snare, {
    var snd;
    snd = SinOsc.ar(
        260
        * (1 + (3 * Env.perc(0.001, 0.04, curve: -6).ar))
        * [1, 4.3, 8.4]
    );
    snd = snd * [0, -8, -12].dbamp;
    snd = snd * Env.perc(0.001, [0.3, 0.1, 0.03]).ar;
    snd = snd.sum;
    snd = snd + (BPF.ar(WhiteNoise.ar, 2310, 0.25) * Env.perc(0.03, 0.3).ar * 12.dbamp);
    snd = snd + (BPF.ar(WhiteNoise.ar, 7310, 0.3) * Env.perc(0.003, 0.04).ar * 8.dbamp);
    snd = snd.tanh;
    snd = snd + PitchShift.ar(snd, 0.06, 2.4);
    snd = snd + PitchShift.ar(snd * -5.dbamp, 0.08, 1.3);
    snd = snd * Env.linen(0.001, 0.23, 0.01).ar(Done.freeSelf);
    snd = snd * -7.dbamp;
    snd = snd ! 2;
    Out.ar(\out.kr(0), snd);
}).add;
)

(
Routine({
    20.do {
        s.bind { Synth(\kick) };
        rrand(0.03, 0.6).wait;
    };
}).play;
)

(
Routine({
    3.do {
        (0.75 ** (0..10)).do { |duration|
            s.bind { Synth(\kick) };
            duration.wait;
        };
    };
}).play;
)

(
Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick) };
            duration.wait;
        };
    };
}).play;
)

(
Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick, [duration: duration]) };
            duration.wait;
        };
    };
}).play;
)


(
Routine({
    ((0.8 ** (0..8)).normalizeSum * 15.0).do { |phraseDuration|
        ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
            s.bind { Synth(\kick, [duration: duration]) };
            duration.wait;
        };
        if(0.5.coin) {
            s.bind { Synth(\snare) };
            0.28.wait;
        };
    };
}).play;
)

(
Routine({
    ((0.8 ** (0..8)).normalizeSum * 50.0).do { |sectionDuration|
        ((0.8 ** (0..8)).normalizeSum * sectionDuration).do { |phraseDuration|
            ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
            if(0.5.coin) {
                s.bind { Synth(\snare) };
                0.28.wait;
            };
        };
    };
}).play;
)

(
Routine({
    ((0.8 ** (0..8)).normalizeSum * 50.0).do { |sectionDuration|
        ((0.8 ** (0..8)).normalizeSum.reverse * sectionDuration).do { |phraseDuration|
            ((0.75 ** (0..10)).normalizeSum * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
        };
    };
}).play;
)

(
Routine({
    (({ exprand(0.01, 0.5) } ! 20).sort.normalizeSum * 3.5).do { |duration|
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;
)

(
Routine({
    var row;
    row = [0.2, 1, 1.4, 0.8, 1].normalizeSum;
    (row * 20.0).do { |sectionDuration|
        (row * sectionDuration).do { |phraseDuration|
            (row * phraseDuration).do { |duration|
                s.bind { Synth(\kick, [duration: duration]) };
                duration.wait;
            };
        };
    };
}).play;
)

(
Routine({
    30.do { |i|
        var duration;
        duration = cos(i * 2pi / 20).linlin(-1, 1, 0.05, 0.2);
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;
)

(
Routine({
    30.do { |i|
        var duration;
        duration = [0.02, 0.1, 0.432].choose;
        s.bind { Synth(\kick, [duration: duration]) };
        duration.wait;
    };
}).play;
)