An open-source Javascript metronome, take one

May 1, 2013 | |Source Code

I've recently been playing around with Sound Slice, an ambitious new project from Django co-founder Adrian Holovaty that creates interactive guitar tabs. The site has huge potential for moving guitars beyond the domain of ASCII files and 14 variations on the same song.

SoundSlice pegs each transcription to a specific recording of the song which is embedded as a video alongside the tabs. Each chord or tablature is pegged to a specific timestamp in the video.

My favorite feature is the ability to tap out measures on your laptop's keyboard as the user listens to the song, marking out blank chords that he or she can then fill in. At the moment, however, there's no margin of error for users who cannot tap out each measure with precisely the same duration. I'm interested in how a computer could be trained to correct for human error to create regular measures. (After all, tabs are always slightly idealized versions of exactly what's happening in a recording.)

To play around with this task, I needed a metronome that plays in the browser. So I made one using Raphael, my favorite Javascript wrapper for SVG graphics. The math and philosophy behind tempo error correction will come in a future post, but for now, here's a demo and the source for the metronome.

You initialize the metronome with a few parameters to set the size and angle of the animation. You can also attach custom functions to the tick event and the event that fires on the final tick. In the above example, I attach two functions that write updates to the screen.

function tick(t) {
    $("<div />").html(t%2 === 1 ? "Tick":"Tock").appendTo(".status");
    $("#count").html(t);    
}

function done() {
    $("<div />").html("Done!").appendTo(".status");
    $("#startstop").html("start");
}

var m = metronome({
    len: 200,
    angle: 20,
    paper: "metronome_container",
    tick: tick,
    complete: done,
    path: ""
});

These are not actual Javascript events, though they probably should be.

The metronome has two functions, .start() and .stop(). The first takes two arguments, a tempo (expressed as beats per minute, like your piano teacher taught you) and a number of ticks:

m.start(120, 50);

You can interrupt the execution with:

m.stop()

At fast tempos, the weight occasionally gets disconnected from the metronome's arm. I addressed this issue with Raphael's .animateWith() function on Stack Overflow, but I'm not convinced the accepted answer is complete.