Awesome
standardized-audio-context
A cross-browser wrapper for the Web Audio API which aims to closely follow the standard.
This package provides a subset (although it's almost complete) of the Web Audio API which works in a reliable and consistent way in every supported browser. In contrast to other popular polyfills standardized-audio-context
does not patch or modify anything on the global scope. In other words, it does not cause any side effects. It can therefore be used safely inside of libraries. It's what's known as a ponyfill.
One of the goals of standardized-audio-context
is to only implement missing functionality and to
avoid rewriting built-in features whenever possible. Please take a look at the paragraph about the
browser support below for more information.
There are some things which cannot be faked in a way that makes them as performant as they could be when implemented natively. The most prominent amongst those things is the AudioWorklet
. Please have a look at the list of all supported methods below for more detailed information.
Usage
The standardized-audio-context
is available on
npm and can be installed as usual.
npm install standardized-audio-context
You can then import the AudioContext
and OfflineAudioContext
like this:
import { AudioContext, OfflineAudioContext } from 'standardized-audio-context';
It is also possible to load standardized-audio-context
with a service like jspm. The import statement from above would then need to be changed to point to a URL.
import { AudioContext, OfflineAudioContext } from 'https://jspm.dev/standardized-audio-context';
Once the AudioContext
and/or OfflineAudioContext
are imported they can be used in the same way as their native counterparts. The following snippet will for example produce a nice and clean (as well as annoying) sine wave.
import { AudioContext } from 'standardized-audio-context';
const audioContext = new AudioContext();
const oscillatorNode = audioContext.createOscillator();
oscillatorNode.connect(audioContext.destination);
oscillatorNode.start();
An alternative approach would be to use the AudioNode constructors (the OscillatorNode constructor in this case) instead of the factory methods.
import { AudioContext, OscillatorNode } from 'standardized-audio-context';
const audioContext = new AudioContext();
const oscillatorNode = new OscillatorNode(audioContext);
oscillatorNode.connect(audioContext.destination);
oscillatorNode.start();
API
AudioContext
This is an almost complete implementation of the AudioContext
interface. It only misses the createScriptProcessor()
method which is deprecated anyway.
The AudioContext implements the following TypeScript interface.
interface IAudioContext extends EventTarget {
readonly audioWorklet?: IAudioWorklet;
readonly baseLatency: number;
readonly currentTime: number;
readonly destination: IAudioDestinationNode<IAudioContext>;
readonly listener: IAudioListener;
onstatechange: null | TEventHandler<IAudioContext>;
readonly sampleRate: number;
readonly state: TAudioContextState;
close(): Promise<void>;
createAnalyser(): IAnalyserNode<IAudioContext>;
createBiquadFilter(): IBiquadFilterNode<IAudioContext>;
createBuffer(numberOfChannels: number, length: number, sampleRate: number): IAudioBuffer;
createBufferSource(): IAudioBufferSourceNode<IAudioContext>;
createChannelMerger(numberOfInputs?: number): IAudioNode<IAudioContext>;
createChannelSplitter(numberOfOutputs?: number): IAudioNode<IAudioContext>;
createConstantSource(): IConstantSourceNode<IAudioContext>;
createConvolver(): IConvolverNode<IAudioContext>;
createDelay(maxDelayTime?: number): IDelayNode<IAudioContext>;
createDynamicsCompressor(): IDynamicsCompressorNode<IAudioContext>;
createGain(): IGainNode<IAudioContext>;
createIIRFilter(feedforward: number[], feedback: number[]): IIIRFilterNode<IAudioContext>;
createMediaElementSource(mediaElement: HTMLMediaElement): IMediaElementAudioSourceNode<IAudioContext>;
createMediaStreamDestination(): IMediaElementAudioDestinationNode<IAudioContext>;
createMediaStreamSource(mediaStream: MediaStream): IMediaStreamAudioSourceNode<IAudioContext>;
createMediaStreamTrackSource(mediaStreamTrack: MediaStreamTrack): IMediaStreamTrackAudioSourceNode<IAudioContext>;
createOscillator(): IOscillatorNode<IAudioContext>;
createPanner(): IPannerNode<IAudioContext>;
createPeriodicWave(real: number[], imag: number[], constraints?: Partial<IPeriodicWaveConstraints>): IPeriodicWave;
createStereoPanner(): IStereoPannerNode<IAudioContext>;
createWaveShaper(): IWaveShaperNode<IAudioContext>;
decodeAudioData(
audioData: ArrayBuffer,
successCallback?: TDecodeSuccessCallback,
errorCallback?: TDecodeErrorCallback
): Promise<IAudioBuffer>;
resume(): Promise<void>;
suspend(): Promise<void>;
}
The properties and methods are described in greater detail below.
OfflineAudioContext
This is an almost complete implementation of the OfflineAudioContext
interface. It only misses the createScriptProcessor()
method which is deprecated anyway.
It implements the following TypeScript interface.
interface IOfflineAudioContext extends EventTarget {
readonly audioWorklet?: IAudioWorklet;
readonly baseLatency: number;
readonly currentTime: number;
readonly destination: IAudioDestinationNode<IOfflineAudioContext>;
readonly length: number;
readonly listener: IAudioListener;
onstatechange: null | TEventHandler<IOfflineAudioContext>;
readonly sampleRate: number;
readonly state: TAudioContextState;
createAnalyser(): IAnalyserNode<IOfflineAudioContext>;
createBiquadFilter(): IBiquadFilterNode<IOfflineAudioContext>;
createBuffer(numberOfChannels: number, length: number, sampleRate: number): IAudioBuffer;
createBufferSource(): IAudioBufferSourceNode<IOfflineAudioContext>;
createChannelMerger(numberOfInputs?: number): IAudioNode<IOfflineAudioContext>;
createChannelSplitter(numberOfOutputs?: number): IAudioNode<IOfflineAudioContext>;
createConstantSource(): IConstantSourceNode<IOfflineAudioContext>;
createConvolver(): IConvolverNode<IOfflineAudioContext>;
createDelay(maxDelayTime?: number): IDelayNode<IOfflineAudioContext>;
createDynamicsCompressor(): IDynamicsCompressorNode<IOfflineAudioContext>;
createGain(): IGainNode<IOfflineAudioContext>;
createIIRFilter(feedforward: number[], feedback: number[]): IIIRFilterNode<IOfflineAudioContext>;
createOscillator(): IOscillatorNode<IOfflineAudioContext>;
createPanner(): IPannerNode<IOfflineAudioContext>;
createPeriodicWave(real: number[], imag: number[], constraints?: Partial<IPeriodicWaveConstraints>): IPeriodicWave;
createStereoPanner(): IStereoPannerNode<IOfflineAudioContext>;
createWaveShaper(): IWaveShaperNode<IOfflineAudioContext>;
decodeAudioData(
audioData: ArrayBuffer,
successCallback?: TDecodeSuccessCallback,
errorCallback?: TDecodeErrorCallback
): Promise<IAudioBuffer>;
startRendering(): Promise<IAudioBuffer>;
}
The properties and methods are described in greater detail below.
audioWorklet
The audioworklet
property is only available in a SecureContext
.
listener
⚠️ <!-- Bug #117 --> Firefox and Safari up to version 14.0.1 do not support modifying the listener via AudioParams. Therefore calling any of the scheduling functions on the AudioParams of a listener of an OfflineAudioContext throws a NotSupportedError
.
createAnalyser() / AnalyserNode
This is an implementation of the createAnalyser()
factory method. The AnalyserNode
constructor may be used as an alternative.
createBiquadFilter() / BiquadFilterNode
This is an implementation of the createBiquadFilter()
factory method. The BiquadFilterNode
constructor may be used as an alternative.
createBuffer() / AudioBuffer
This is an implementation of the createBuffer()
factory method. The AudioBuffer
constructor may be used as an alternative.
createBufferSource() / AudioBufferSourceNode
This is an implementation of the createBufferSource()
factory method. The AudioBufferSourceNode
constructor may be used as an alternative.
createChannelMerger() / ChannelMergerNode
This is an implementation of the createChannelMerger()
factory method. The ChannelMergerNode
constructor may be used as an alternative.
createChannelSplitter() / ChannelSplitterNode
This is an implementation of the createChannelSplitter()
factory method. The ChannelSplitterNode
constructor may be used as an alternative.
createConstantSource() / ConstantSourceNode
This is an implementation of the createConstantSource()
factory method. The ConstantSourceNode
constructor may be used as an alternative.
createConvolver() / ConvolverNode
This is an implementation of the createConvolver()
factory method. The ConvolverNode
constructor may be used as an alternative.
createDelay() / DelayNode
This is an implementation of the createDelay()
factory method. The DelayNode
constructor may be used as an alternative.
⚠️ <!-- Bug #163 --> The delayTime AudioParam can't be set to very small values in any browser except Firefox.
createDynamicsCompressor() / DynamicsCompressorNode
This is an implementation of the createDynamicsCompressor()
factory method. The DynamicsCompressorNode
constructor may be used as an alternative.
createGain() / GainNode
This is an implementation of the createGain()
factory method. The GainNode
constructor may be used as an alternative.
createIIRFilter() / IIRFilterNode
This is an implementation of the createIIRFilter()
factory method. The IIRFilterNode
constructor may be used as an alternative.
createMediaElementSource() / MediaElementAudioSourceNode
This is an implementation of the createMediaElementSource()
factory method. The MediaElementAudioSourceNode
constructor may be used as an alternative.
It does only work with an AudioContext but not with an OfflineAudioContext.
createMediaStreamDestination() / MediaStreamAudioDestinationNode
This is an implementation of the createMediaStreamDestination()
factory method. The MediaStreamAudioDestinationNode
constructor may be used as an alternative.
It does only work with an AudioContext but not with an OfflineAudioContext.
createMediaStreamSource() / MediaStreamAudioSourceNode
This is an implementation of the createMediaStreamSource()
factory method. The MediaStreamAudioSourceNode
constructor may be used as an alternative.
It does only work with an AudioContext but not with an OfflineAudioContext.
createMediaStreamTrackSource() / MediaStreamTrackAudioSourceNode
This is an implementation of the createMediaStreamTrackSource()
factory method. The MediaStreamTrackAudioSourceNode
constructor may be used as an alternative.
It does only work with an AudioContext but not with an OfflineAudioContext.
createOscillator() / OscillatorNode
This is an implementation of the createOscillator()
factory method. The OscillatorNode
constructor may be used as an alternative.
createPanner() / PannerNode
This is an implementation of the createPanner()
factory method. The PannerNode
constructor may be used as an alternative.
createPeriodicWave() / PeriodicWave
This is an implementation of the createPeriodicWave()
factory method. The PeriodicWave
constructor may be used as an alternative.
createStereoPanner() / StereoPannerNode
This is an implementation of the createStereoPanner()
factory method. The StereoPannerNode
constructor may be used as an alternative.
The channelCountMode can only be 'explicit'
unless Safari comes up with a native implementation.
createWaveShaper() / WaveShaperNode
This is an implementation of the createWaveShaper()
factory method. The WaveShaperNode
constructor may be used as an alternative.
decodeAudioData()
This is an implementation of the
decodeAudioData()
method. There is also a standalone method with a similar interface described below.
decodeAudioData()
This is a standalone wrapper which can be used in a similar way as the instance method with the same name. The most notable difference is that it expects an (Offline)AudioContext created with this library as the first parameter. But it can also handle a native (Offline)AudioContext. Another difference is that it only returns a promise. It will not call any callbacks.
import { decodeAudioData } from 'standardized-audio-context';
const nativeAudioContext = new AudioContext();
const response = await fetch('/a-super-cool-audio-file');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await decodeAudioData(nativeAudioContext, arrayBuffer);
isAnyAudioContext()
This is a utility function which determines if the given value is an AudioContext or not. It does not differentiate between an AudioContext created by standardized-audio-context
or a native one. But it will return false for an OfflineAudioContext.
import { AudioContext, isAnyAudioContext } from 'standardized-audio-context';
// This will create an AudioContext from standardized-audio-context.
const audioContext = new AudioContext();
isAnyAudioContext(audioContext); // true
// This will create a native AudioContext.
const nativeAudioContext = new window.AudioContext();
isAnyAudioContext(nativeAudioContext); // true
isAnyAudioNode()
This is a helper function which allows to identify an AudioNode without any custom instanceof or property checks. It returns true if the given value is an AudioNode or false if not. It doesn't matter if the given value is an AudioNode which has been created with standardized-audio-context
or not.
import { OfflineAudioContext, isAnyAudioNode } from 'standardized-audio-context';
// This will create a native AudioContext.
const nativeAudioContext = new AudioContext();
isAnyAudioNode(nativeAudioContext.createGain()); // true
// This will create an OfflineAudioContext from standardized-audio-context.
const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyAudioNode(offlineAudioContext.createGain()); // true
isAnyAudioParam()
This is a helper function similiar to isAnyAudioNode()
but for AudioParams. It returns true if the given value is an AudioParam or false in case it isn't. It doesn't matter if the given value is an AudioParam which has been created with standardized-audio-context
or not.
import { OfflineAudioContext, isAnyAudioParam } from 'standardized-audio-context';
// This will create a native AudioContext.
const nativeAudioContext = new AudioContext();
isAnyAudioParam(nativeAudioContext.createGain().gain); // true
// This will create an OfflineAudioContext from standardized-audio-context.
const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyAudioParam(offlineAudioContext.createGain().gain); // true
isAnyOfflineAudioContext()
This is a utility function which determines if the given value is an OfflineAudioContext or not. It does not differentiate between an OfflineAudioContext created by standardized-audio-context
or a native one.
import { OfflineAudioContext, isAnyOfflineAudioContext } from 'standardized-audio-context';
// This will create an OfflineAudioContext from standardized-audio-context.
const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyOfflineAudioContext(offlineAudioContext); // true
// This will create a native OfflineAudioContext.
const nativeOfflineAudioContext = new window.OfflineAudioContext(1, 10, 44100);
isAnyOfflineAudioContext(nativeOfflineAudioContext); // true
isSupported()
standardized-audio-context
is also exporting a promise which can be accessed by calling isSupported()
. This promise resolves to a boolean which indicates if the functionality is supported within the currently used browser. This is not part of the specification.
import { isSupported } from 'standardized-audio-context';
isSupported().then((isSupported) => {
if (isSupported) {
// yeah everything should work
} else {
// oh no this browser seems to be outdated
}
});
Browser Support
The goal of this package is to provide a consistent API. But at the same time this package should not grow indefinitely until everything works in IE 6. Whenever a feature is implemented in every supported browser which required a polyfill before to work correctly that polyfill gets removed from this package. And hopefully at some point in the future this package boils down to a file which only re-exports built-in objects.
But until then great care is taken to avoid any unnecessary bloat. This means whenever a workaround
for a certain browser is added to this library it will be accompanied by a test which checks that
this particular workaround is still needed. I call those tests expectation tests because they test
if a browser behaves as expected. An expectation test is designed to fail when the browser
eventually ships a fix. Once that happens the workaround and the backing expectation test get
removed. The expectation test however gets recycled and will now be used as part of the browser
check performed when calling isSupported()
.
The list of currently supported browsers includes Chrome v105+, Firefox v113+ and Safari v16.4+. Please note that the tests only run in the current and upcoming version of each browser.
Supporting a browser only means that it is supported on the feature level. It is absolutely possible that a transpiler like Babel is necessary to use this package in every supported browser without encountering any syntax errors.
This package doesn't work with Node.js.
TypeScript
This package is written in TypeScript which means it can be used seamlessly in any TypeScript project. But that is entirely optional.
In contrast to the Web Audio API types that TypeScript provides out of the box the types exported
by standardized-audio-context
do actually match the concrete implementation. TypeScript
generates its types from the Web IDL definition of the Web Audio
API which does not always match the actually available implementations.
Tests
All implemented methods are covered by a large number of tests which run in all the browsers mentioned above. Many thanks to BrowserStack and Sauce Labs for allowing this module to be tested with their services.
Security contact information
To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.