
Metronomes in JavaScript - baylearn
https://meowni.ca/posts/metronomes
======
rock_artist
As an audio developer, making a timer metronome is the wrong approach. Still,
many metronome apps with this design flaw exists and... they perform bad.
(that's why prescheduled audio events works best...)

Timers in general will jitter.

Audio processing usually involves a callback to process an audio block (array
of audio samples... floats).

To simplify things, this audio block runs on realtime thread and copies the
block to desired output. you're guaranteed it'll be continuous between each
call. (you can cause overflow/stutter if you hold it for too long).

With setTimer approaches each callback WILL have offset. But if you need to
synchronize things or your metronome needs to run with other audio, it WILL be
jerky. That's why a proper metronome would also be 'sample accurate'.

WebAudio / AudioContext was made exactly to solve that!

similar to common audio processing standard, you end up getting a callback and
usually need to use WebAsm/C++ under the hood to keep the golden DSP rule -
never allocate on block processing callback.

btw, on Firefox 69 (macOS) only the pre-scheduled audio made a sound :)

~~~
andoma
Very true. Another upside of deriving all timing from actual outputted
samples: If your application have a sequencer which does "bouncing" (i.e.
generating a recorded file, or similar, from the project) you can just run the
sequencer as fast as possible in a loop which just writes audio to a file
without any concerns about real time, etc.

~~~
rock_artist
Good point. It really depends what’s the use case but as your suggested...
time is relative. :)

------
danShumway
This is a really great writeup -- it's concise and to the point, the demos are
good.

It's not just audio; timing in general is kind of terrible in Javascript.
Spectre/Meltdown made it worse, because now some browsers introduce arbitrary
jitter for security purposes. The advice I've always seen is to try and avoid
ever doing your own timing in Javascript -- use `requestAnimationFrame`
instead of `setTimeout`, use web audio scheduling instead of `setTimeout`.
Basically, avoid `setTimeout` for most high-precision things if possible.

I don't know what the status is with shared array buffers and WASM. At one
point I was very excited about the potential of WASM to get around some of the
problems above, but last I checked, this was being partially held back by the
same security concerns.

~~~
tabtab
The desktop has spoiled everybody. Everybody wants web apps to act like and
have the control of desktop apps. So far, it's not possible without top IT
rocket scientists, and the next browser release will probably break the rocket
anyhow.

------
dkthehuman
If anyone actually needs a simple metronome that tracks time precisely using
the Web Audio API, I built one recently:
[https://dkthehuman.com/metronome](https://dkthehuman.com/metronome)

\- It's fast (the #1 metronome app on the iOS App Store taking 7+ seconds to
start up on my iPhone was the reason I built this)

\- It has keyboard shortcuts on desktop (use arrow keys to adjust BPM and
space bar to toggle playing)

\- It's a progressive web app so you can add it to your home screen and it'll
behave like an app

~~~
delta1
improvement suggestion: allow the user to input the desired tempo in a textbox
instead of +/\- controls

~~~
asdfman123
Also, I need subdivisions in a metronome app.

------
whiddershins
Great article.

I want to weigh in about this whole perceivable jitter thing.

I think it’s important when making music related programming decisions to
recognize there’s a whole area of perception in between actually conscious
“hey that metronome is off!” and actually being imperceptible. In that area
the feel and impact of music can be altered while no one can pinpoint why.

For safety’s sake I think sub-millisecond timing in controllers and things
like metronomes needs to be the standard.

The ideal should be audio rate accuracy, when it can be reasonably achieved.

It’s really bad to fall in the trap of thinking that just because no one can
point out a problem, it isn’t having an effect. Especially with audio where
people have trouble explaining what they are hearing.

~~~
jwandborg
Keep in mind that the testing methodology may have already accounted for your
concerns. I would guesstimate that mechanical vinyl and tape players have rate
variations on the order of milliseconds.

~~~
whiddershins
Good point, but jitter is within a phrase, speeding something up and down
smoothly would be entirely different than discontinuous random timing
variation.

I am not sure exactly what the tests and methodologies are that different
people refer to, but I do know that when I have brought up this concern to
instrument and software developers who are operating at the cutting edge of
this stuff and really _should_ know the answer, they never bother to debate it
until you get to the difference between sub-millisecond and audio-rate.

It is axiomatic that anything that has less resolution than audio rate can be
perceived, under the correct circumstances.

For example, if you had two metronomes which each played the same wide-
frequency burst and had independent jitters on their start time, the combined
sound would likely shift in timbre due to the phase relationship of the summed
waves.

If you had those two outputs going to a stereo output, one on the left
channel, and one on the right, the resulting effect should be that the "click"
will randomly pan around the soundfield in the listener's perception.

So, I guess it also depends on not only your use case but how much of a hassle
it is to get the right resolution. I would be really sad if I subtly messed up
some musician's sense of time for years in the future because they were
practicing diligently with some jittery metronome I made.

------
sriku
The age old solution to this problem continues to work just fine - have
jitter-tolerant low frequency scheduler callbacks that schedule events using
an accurate clock (sample time) a short way (100ms or so) into the future.
That way you get both accurate audio with timely response. Been doing this
kind of thing for 20+ years now .. web or noweb.

I use an encapsulated "steller" library to do this with JS on the web. Steller
can sync graphics with audio too.

Really wish the joint "get time stamp" function that gives current
DOMHiresTimestamp and the synchronized AudioContext currentTime were available
on all browsers.

Steller -
[https://github.com/srikumarks/steller](https://github.com/srikumarks/steller)

Taka Keeper -
[http://talakeeper.org/talas.html](http://talakeeper.org/talas.html) (targeted
at South Indian classical music forms, so not all may get the terms)

Edit: Fixed autocorrect errors

------
braindongle
Nicely done. I wonder: what is the use case that prompted this investigation?
40-50 milliseconds is correct in terms of latency that musicians notice. If
you're aiming at any realtime musical collaboration over the internet, the Big
Problem is network latency. Unpredictable and just too long. And, if you're
not working over the internet, why use a browser?

Edit: [https://hello-magenta.glitch.me](https://hello-magenta.glitch.me) seems
to be the use case. Cool and inevitable that this would be worked-on, but
after spending years with both algorithmically-supported composition and all-
human composition, I'm skeptical. There is a special sauce that machines will
never grok. Or, I'm wrong.

~~~
onion2k
_40-50 milliseconds is correct in terms of latency that musicians notice._

In a 60fps app that's as much as 5 frames out of sync. Users would notice
that.

------
saagarjha
I wrote a bad metronome using setInterval at some point
([http://server.saagarjha.com/metronome](http://server.saagarjha.com/metronome))
and had somewhat different issues: if you moved the page to the background, my
browser would throttle it and coalesce the events so it wouldn’t actually
“tick” at the right rate anymore. And for whatever reason, in WebKit setting
60 bpm just doesn’t make any noise at all…

------
kmnt
hard to imagine an environment with worse input response and timing behavior
than web browsers. Buffering audio output would be easiest way to compensate
for callback jitter, but shortening delay between user input and a note
playing seems unresolvable.

------
javajosh
Cool article, but there's one additional experiment worth trying: use
recursive `requestAnimationFrame()` calls. On most browsers this will give you
a relatively steady 60fps/17ms callback rate.

[https://developer.mozilla.org/en-
US/docs/Web/API/window/requ...](https://developer.mozilla.org/en-
US/docs/Web/API/window/requestAnimationFrame)

------
gatherhunterer
This is intended for game loops but it is a nice timer that targets iterations
per second (FPS) using requestAnimationFrame. This is my preferred solution
when I cannot just read the system clock on a recursive setTimeout.

[http://pixijs.download/next/docs/PIXI.Ticker.html](http://pixijs.download/next/docs/PIXI.Ticker.html)

------
menssen
I don't know the answer to this, but I've always been genuinely curious: how
accurate is the Date object?

setInterval's minimum interval is ~15ms [1], so shouldn't something like...

setInterval(() => (new Date()).valueOf() % DESIRED_TICK_INTERVAL < 15 &&
tickTheMetronome(), 15)

...get you within 15ms accuracy, and it can probably be reduced in Chrome et
al?

[1] [http://www.adequatelygood.com/Minimum-Timer-Intervals-in-
Jav...](http://www.adequatelygood.com/Minimum-Timer-Intervals-in-
JavaScript.html)

~~~
jcelerier
and to say that 20 years ago people were laughing at MS windows and
considering it as a completely non-viable platform for pro audio for its 10ms-
accurate timer...

------
bezieio
This is similar to a problem I ran into when building Bezie. I needed to keep
track of the current tempo to send out MIDI clock events. I ended up using a
web worker since `setInteral` was unreliable in backgrounded tabs. Here's the
worker:
[https://github.com/jperler/bezie/blob/master/app/workers/tic...](https://github.com/jperler/bezie/blob/master/app/workers/tick.js)

------
sheerun
If you want something really accurate and resistant to any problems with CPU
and drift in tickers, then you need to schedule ticks by yourself by reading
e.g. performance.now(), like so:
[https://gist.github.com/sheerun/00e358e7bdbcd2088573e4b7b921...](https://gist.github.com/sheerun/00e358e7bdbcd2088573e4b7b921ce67)

Above example will indefinitely beat with 60bpm (10ms accuracy)

~~~
jwandborg
That's nodejs code. I'm not sure you can compare console.log in nodejs to
playing sound in a browser.

------
cyberferret
This brings back so many vivid memories of me trying to write a hybrid mobile
app a few years ago which had a metronome feature. I wrestled with a pure
Javascript metronome clicker for so long, and eventually built that component
using WebAudio which was much more accurate and reliable.

I did start writing a blog post about it but never finished it. This article
is a much better and more in depth analysis of the problem anyhow.

------
ZoomZoomZoom
Don't know about JS, but speaking of metronomes in general, for Android
there's a few of them on F-Droid, and last time I checked, the _only one_ that
worked consistently is the one that's integrated in an old app Practice Hub. I
don't get it, my phone has a clock that seems to work ok, how hard can it be
to click in time?

------
hughes
I enjoyed reading this. Playing with a problem is such a great way to
understand it. The charts are hard to read though - I wish they showed
something like absolute error, or at least omitted the initial zero point so
the y-axis could be centered around the target.

------
onemoresoop
>and if you can, move any expensive work that you have to do from the main
thread to a Worker.

And if you can, move away from the browser to native.

And if you can use a hardware midi clock or just a standalone metronome.

------
delvinj
Very interesting analysis.

I used to use google metronome but the constant hiccups were annoying. I
wonder if they are using Web Audio scheduling or setInterval().

