Polyfilling the ConstantSourceNode

The ConstantSource­Node is the newest member of the family of AudioScheduledSource­Nodes. It is meant to output a signal with a constant value. At first glance its older siblings called AudioBuffer­Source­Node and Oscillator­Node seem to be much more powerful but that is actually not the case. A ConstantSource­Node is just an innocent looking low level building block which only shows its true power when taking a closer look.

The AudioScheduled­Source­Node interface

In the world of RxJS one would probably describe an AudioScheduledSource­Node as a cold observable. That means it doesn't do anything until it is explictly asked to do so. In case of an AudioScheduledSource­Node this can be done by calling its start() method. Once it has been started it continues playing until stop() gets called or (in case of an AudioBuffer­Source­Node) there is no more audio data left to be played. Another similarity to cold observables is that an AudioScheduledSource­Node can't be restarted once it is stopped. It is designed for one-time use only.

This is what the interface looks like, when written in TypeScript:

interface AudioScheduledSource­Node extends AudioNode {

    onended: EventHandler;

    start (when?: number): void;
    stop (when?: number): void;

}

The ConstantSource­Node interface

In addition to the common methods shared by all AudioScheduledSource­Nodes a ConstantSource­Node has an AudioParam called offset which regulates its value. This turns the ConstantSource­Node into the missing piece which finally allows AudioParams to be connected to other AudioParams. Of course a ConstantSource­Node is an AudioNode and not an AudioParam, but you can think of it as a programmable AudioParam which can be connected to other AudioNodes and AudioParams. This is the scenario where it shows its full potential. MDN has a very detailed article about the ConstantSource­Node which also features an example use case.

The declaration of the interface in TypeScript looks like this:

interface ConstantSourceNode extends AudioScheduledSourceNode {

    readonly offset: AudioParam;

}

Browser support

Unfortunately the ConstantSource­Node is not supported in Edge and Safari. And at least in Safari the term Web Audio API is rather uncommon to be found in the release notes. Therefore chances are not high that there will be a native implementation in all major browsers any time soon. This is why a polyfill is currently required.

Polyfill design

Luckily a ConstantSource­Node (CSN) is mainly syntactic sugar. It can be modelled by using an AudioBuffer­Source­Node (ABSN) and a GainNode as the following diagram shows:

CSNoffsetABSNGainNodegain

In order for that to work the AudioBuffer­Source­Node needs to be constructed with a single channel AudioBuffer with all samples set to 1. The AudioBuffer­Source­Node also needs to be looped. With all that in place all that needs to be done is connecting the AudioBuffer­Source­Node to the GainNode and setting up a proxy to make them appear as a single node to the outside world.

Sadly this is not the whole story. There are some subtle bugs which make the implementation a bit more complicated. But the good news is that you don't have to roll your own polyfill, instead you can use my package called standardized-audio-context which comes with a polyfill for the ConstantSource­Node. It also contains a polyfill for the AudioWorklet which relies on ConstantSource­Nodes internally. But this is a whole topic on its own.

Ironically an AudioBuffer­Source­Node in Safari still has a built-in gain AudioParam which could be used to simplify the polyfill even more. However that gain AudioParam is not present in Edge as it was removed from the spec a while ago.