Friday, October 17, 2008

Archaeopteryx: Multiple Time Signatures (Serial & Parallel)

I added an experimental/demonstration branch to Archaeopteryx on GitHub.

Check the diff to see how it's done. Basically, I hacked in a new global, and then wrote code in the db_drum_definition.rb file - which is re-evaluated every four measures or so - to reset that global. By resetting $beats from 16 to 9, I'm saying where before we had sixteen beats per measure, we now have nine. In practice this shifts the time signature from 4/4 to 3/3, although to be precise in music theory terms you'd say we're shifting from 16/16 to 9/9. Which is why nobody is ever precise in music theory terms about anything.

Likewise, I created a 9-column probability matrix to use in place of the normal 16-column matrix you use when you're cycling over 16 beats. For historical interest, this trick comes out of step sequencers and analog drum machines. The trick's at least as old as Chicago house and probably older still.

At the risk of sounding like an utter douche, this validates a point Chad Fowler makes in his excellent book My Job Went To India, which is that learning an unusual domain is valuable for the same reason that learning an unusual language is. I have to admit, I'm feeling guilty, because I might be bursting a bubble here. The person who posts cool links on Twitter isn't actually the first person to ask me if this is possible. It's just the first time I've bothered to demonstrate how. This "challenge" is beyond trivial.

Changing time signatures in Archaeopteryx probably seems like a big deal if you're a Rubyist who's never worked with step sequencers to make dance music. Time signatures are a fundamental of music; changing them seems like a serious mind-bender. However, what's fundamental to the territory is not necessarily fundamental to the map as well. If you have worked with step sequencers before, and if you grok that Archaeopteryx is essentially just a probabilistic step sequencer, then the ability to shift time signatures at will goes without saying. It's actually why I used step sequencers as my model in the first place.

Notice how it doesn't say 16. It says 12 and then 4.

If you actually play the music this code produces, you'll notice that it sounds like ass. This is because I just wanted to whip up a quick proof of concept. To exploit this trick, what you want to do is switch not from 16 to 9, but from 16 to 24 - which is essentially 16/16, but in triplets. And you don't want to switch in sequence - you want to switch in parallel. Many dance music producers don't know this trick, but the ones who do use time signature changes with step sequencers "in the wild" not to alternate between two time signatures, one after the other, but to play rhythms in both simultaneously. For example, put the main drums in 4/4 and the high-hats in 6/8. This allows you to introduce variation into otherwise predictable rhythms.

Building that into Archaeopteryx took more work. Here's another experimental/demo branch which can play multiple time signatures simultaneously. And here's the biggest change:

This code will only work with Arrays of generators. Previously Archaeopteryx assumed only one generator - a generator is anything which generates notes for Archaeopteryx to play - and even implemented its Mix class, representing multiple Rhythms, by squashing the notes so that the set of generators acted as one big generator. What this code does is store the start time, and then loop over each generator, calculating the beats and ticking forward the clock for that generator. Later it resynchronizes the clocks. This allows different generators to tick their own individual clocks forward at different increments - which is necessary, since a 16-note step sequencer at 170bpm is going to fit 16 notes into the same amount of time that a 24-note step sequencer at 170bpm is going to fit 24 notes.

That might make you do a double-take. If one note represents one beat, how can different numbers of notes at 170bpm possibly fit in the same amount of time? It may also make you do a double-take if you have done this with a real step sequencer and you remember that you had to adjust the tempos to pull it off. The answer is that notes don't correspond to beats any more. Just like in the representation of time signatures themselves, a note represents a note and a beat represents a beat. This means we're not working in 16/16 and 24/24 any more. We're in 4/4 and 6/8, with 16th-note resolution.

That's why the code above used to use (0..(@beats - 1)) and now uses (0..( - 1)). The number of beats became Rhythm-specific. This took some other code changes as well.

The code which launches all of this passes two arguments to the Clock, where normally Arx only passes one. The second arg: beats per measure. Likewise it defaults to 16 beats total, as normal, but also allows you to specify a different number.

That's because this Clock can have any number of beats_in_a_measure you prefer:

You might need to read this post more than once to figure out what the code is doing. It baffles me, and I wrote it. The simple branch which alternated time signatures took me about fifteen minutes. The branch which plays two time signatures simultaneously took at least an hour and a half. Could be three or four hours. Hard to tell because I also spent a lot of that time googling for pictures of the sexy bondage Batgirl.

However, what it comes down to is Archaeopteryx can now play multiple rhythms in multiple time signatures at the same time, or one after the other. If you were feeling plain old stupid crazy, you could even have it do both.