Reputation: 14957
I'm trying to make a synthesizer/sequencer with the WebAudio API. Mostly trying to make something that can playback a song made up of notes and events like MIDI to control the multiple channels of the synth. But I still need to tackle the timing aspect.
I've read tutorials on the WebAudio API, but I don't quite understand how timing/scheduling/clock works. At first I made a polyphonic synthesizer that played notes for keypresses, and this did not need any consideration for timing. I then made the oscillator playback an an array of notes at specific time intervals using setInterval
. However I have noticed in the past when switching tabs in Google Chrome, the playback rate is significantly slowed down, I assume to be less resource intensive - but other synth libraries didn't have this issue. Also I can only assume that using setInterval
isn't great for smooth (is buffered the right word?) playback, especially when considering ADSR envelopes oscillators.
From what I am reading, the WebAudio API has an ever-increasing currentTime
timer variable. But if notes are stored in an array and I want to cycle it at a set rate, how would the WebAudio timer be manipulated to iterate an array over a set rate? My setInterval solution (volume warning) doesn't take into account the WebAudio timer at all, and just calls voice()
with setInterval
to play a note, overwriting the last voice played.
Are there better or efficient ways to playback/loop a sequence of notes other than setInterval
?
Just an update to clarify: When I wrote this question, I was using setInterval
to play a sequence of notes at fixed intervals (e.g. unscheduled--constantly starting/stopping oscillators) and was wondering about more reliable ways to play notes. I found that a better solution would be to use setValueAtTime
on osc.frequency
as well as a GainNode
. For a simple prototype synth, this works well, but a more advanced synth could use the timer on osc.start
and osc.stop
. The problem is looping the song. In order to loop, I need to constantly schedule more notes so that by the end of the song, it starts over. I can use setInterval
to schedule the entire song loop, but it still has to keep time with the song playing, and setInterval
(and even Web Workers' onmessage
calls) could be slowed down when the tab is inactive for example. setInterval's clock isn't even reliable, so I simply check every 5ms if (currentTime >= songTime)
and schedule if condition is met. That method may not be too efficient. What I have so far.
Upvotes: 3
Views: 1374
Reputation: 13908
I wrote this article a few years ago: https://www.html5rocks.com/en/tutorials/audio/scheduling/. It gets into the differences between the web audio timers and setInterval; in short, you can't "manipulate time" in the web audio clock, it's driven by a hardware clock in your sound card. You can schedule events to occur in its time frame, though, using setInterval as a very jittery/ slower cycle reminder to schedule new events.
As for your switching-tabs issue, this is a 1Hz rate limiter that most modern browsers have to avoid power cost from people using setInterval as a rendering timer (e.g. setInterval with an interval of 16ms to try to schedule every frame of video). You can avoid it in two ways - a hack using Web Workers, which I use in my metronome demo (https://github.com/cwilso/metronome), or you can hook in to when the window loses focus (onblur event firing on the window object) to start scheduling more than 1s of events at a time. The only problem is when the window gets focus again (onfocus event fires on the window object), you'll have up to a second's worth of events already scheduled in Web Audio - you couldn't switch back into the window and immediately hit "stop", e.g.
setInterval by itself isn't a great solution for musical timing of repetitive rhythms, since it will jitter (i.e. be delayed) when there is substantial other stuff going on in the main thread (including garbage collection). You'll start to hear the jitter, especially on less-capable CPUs.
Upvotes: 5