mattly.bbq~ RSS

mattly
cooking up some tasty bits from portland, or

elsewhere: tweet, code, photo

Archive

Oct
20th
Mon
permalink

scheduling

While I have yet to join in the Archaeopteryx zeitgeist, I have had a long interest in generative and permutative music sequencers. Mostly I have built these in Max/MSP, and even then several years ago.

I started building a sequencer that reflects my own philosophy of manipulating existing MIDI sequences instead of attempting to generate them, but ran into several problems. The first is that Max just isn’t that great of an environment for writing excessively complex logic, and the second was that all of a sudden I was poor and devoting all my time to starting a business. So that project fell by the wayside.

Recently I’ve started exploring the idea of rebuilding my old sequencer in Ruby, encouraged by the progress I’ve seen on Archaeopteryx and Ben Bleything’s midiator. From talking with both Ben and Giles however, one of the primary issues with Ruby has to do with scheduling, or, making sure that things happen at a certain time. Given ruby’s dynamic nature this is pretty understandable.

The recent release of Max/MSP 5 brought a lot of modernization to the 20+ year old environment, including a much improved timing engine, and I wanted to see if it would be possible to improve ruby’s scheduling by slaving it to Max. The most straightforward way of doing this is over UDP. Initial results looked good, so I wrote a quick little UDP Library and setup a max patch to talk to it.

Max’s scheduler can run from a given tempo (in this case, I used the default of 120bpm) and can run at “ticks” as fine as 480 pulses per quarter-note (ppq). I defaulted to running at 5 ticks (5.20833 milliseconds at 120bpm) or 1/384th note, a good interval grain for handling many odd timing lengths, down to 128th note triplets.

Then I ran some actual timing tests compared to Kernel#sleep. As the Digg headlines say, The Results May Surprise You

Stat    Perfect   Max->Ruby (UDP)   Ruby (sleep)
======================================================
count   5760      5763              5458
sum     30000     29998.6429214478  11526.6110897064
mean    5.20833   5.20809772941801  2.11598587245962
median  5.20833   5.5539608001709   2.00986862182617
max     5.20833   31.1489105224609  15.2361392974854
min     5.20833   0.528097152709961 0.0209808349609375
stddev  0.0       1.71355416285846  1.6851694046022

The important ones here are median and max. While the UDP-based one has the highest outlier at 31.1ms, it’s median is a lot closer to the goal, and it’s mean is MUCH closer to the ideal.

Thinking things might improve for Kernel#sleep if I loosened up the time grain a bit, I set it to 20 ticks, which is a 96th note, or 20.833ms at 120bpm. Unfortunately Kernel#sleep fared a lot worse:

Stat    Perfect   Max->Ruby (UDP)   Ruby (sleep)
======================================================
count   1440      1439              1427
sum     30000     29978.718996048   13650.7856845856
mean    20.8333   20.8330222349187  9.56607265913495
median  20.8333   20.3008651733398  9.51409339904785
max     20.8333   31.7790508270264  26.3772010803223
min     20.8333   10.0691318511963  0.0269412994384766
stddev  0.0       1.4683011270922   5.79134286901883

The other disadvantage to using Kernel#sleep is that it’s basically a bucket brigade: any errors in timing become cumulative, and unless the errors even themselves out it will get gradually more and more out of sync. Maybe it’s not a big deal, but if you’re dealing with outside forces (f.e. a real musician) this can become an issue.

I find these results promising, and am drawing up plans for a scheduling engine based on this. Max/MSP lets me compile standalone apps, that you can run without having any other software installed, and one of the benefits of using UDP is multicasting: you could have one time clock server running on a local network and slave several machines to it.