/web-audio-js

Pure JS implementation of the Web Audio API

Primary LanguageJavaScript

@descript/web-audio-js

Node CI NPM Version License

Pure JS implementation of the Web Audio API

Fork of mohayonao/web-audio-engine with following changes:

  • Use TypeScript and fix some types (#5)
    • Remove BaseAudioContext.suspend() (#6)
  • Add new RawDataAudioContext (#5)
  • Add support for DynamicsCompressorNode (#1)
  • Bug fixes
    • Fixes for BiquadFilterNode and DelayNode (#8)
    • Fix WAVE file decoding (#5)
    • Fix AudioNode.disconnect(x) not disconnecting (#7)

Installation

npm install --save web-audio-engine

API

web-audio-engine provides some AudioContext class for each use-case: audio playback, rendering and simulation.

Class: StreamAudioContext

StreamAudioContext writes raw PCM audio data to a writable node stream. It can be used to playback audio in realtime.

new StreamAudioContext(opts?: object)

Creates new StreamAudioContext instance.

  • opts.sampleRate: number audio sample rate (in Hz) - default: 44100
  • opts.numberOfChannels: number audio channels (e.g. 2: stereo) - default: 2
  • opts.blockSize: number samples each rendering quantum - default: 128
  • opts.bitDepth: number bits per sample - default: 16
  • opts.float: boolean use floating-point values - default: false
context.pipe(destination: stream.Writable): stream.Writable

:constructionworker: _TODO: WRITE DESCRIPTION

import { StreamAudioContext as AudioContext } from 'web-audio-engine';
const context = new AudioContext();

// Set the output for audio streaming
context.pipe(process.stdout);

// If you want to playback sound directly in this process, you can use 'node-speaker'.
// const Speaker = require("speaker");
// context.pipe(new Speaker());

// Start to render audio
context.resume();

// composeWith(context);

Class: RenderingAudioContext

RenderingAudioContext records audio data with stepwise processing. It is used to export to a wav file or test a web audio application.

new RenderingAudioContext(opts?: object)

Creates new RenderingAudioContext instance.

  • opts.sampleRate: number audio sample rate (in Hz) - default: 44100
  • opts.numberOfChannels: number audio channels (e.g. 2: stereo) - default: 2
  • opts.blockSize: number samples each rendering quantum - default: 128
context.processTo(time: number|string)

Executes rendering process until the provided time.

  • time: e.g. 10 (10 seconds), "01:30.500" (convert to 90.5 seconds)
context.exportAsAudioData(): AudioData

Exports the rendered data as AudioData format.

context.encodeAudioData(audioData: AudioData, opts?:object): Promise< ArrayBuffer >

Encode audio data to the binary format.

  • audioData: AudioData
  • opts.bitDepth: number bits per sample - default: 16
  • opts.float: boolean use floating-point values - default: false
import fs from 'fs';
import { RenderingAudioContext as AudioContext } from 'web-audio-engine';
const context = new AudioContext();

// composeWith(context);

context.processTo('00:01:30.000');
// context.currentTime -> 90.00054421768708

context.processTo('00:02:00.000');
// context.currentTime -> 120.00072562358277

const audioData = context.exportAsAudioData();

context.encodeAudioData(audioData).then((arrayBuffer) => {
  fs.writeFile('output.wav', new Buffer(arrayBuffer));
});

Class: RawDataAudioContext

RawDataAudioContext allows you to synchronously step through an AudioContext. This is useful for streaming output at and controlling the rate

new RawDataAudioContext(opts?: object)

Creates new RenderingAudioContext instance.

  • opts.sampleRate: number audio sample rate (in Hz) - default: 44100
  • opts.numberOfChannels: number audio channels (e.g. 2: stereo) - default: 2
  • opts.blockSize: number samples each rendering quantum - default: 128
context.process(channelData: Float32Array[], offset: number = 0)

Renders the next blockSize samples of audio into channelData.

import { RawDataAudioContext } from 'web-audio-engine';
const context = new RawDataAudioContext();
const { blockSize } = context;
const channelData = [
  new Float32Array(blockSize),
  new Float32Array(blockSize),
];

for (let i = 0; i < 100_000; i += blockSize)
  context.process(channelData);
  // Do something with channeLData
}

Class: WebAudioContext

:constructionworker: _TODO: WRITE DESCRIPTION

new WebAudioContext(opts?: object)

Creates new WebAudioContext instance.

  • opts.context?: AudioContext the native Web Audio API AudioContext instance
  • opts.destination?: AudioNode - default: opts.context.destination
  • opts.numberOfChannels: number audio channels (e.g. 2: stereo) - default: 2
  • opts.blockSize: number samples each rendering quantum - default: 128
<script src="/path/to/web-audio-engine.js"></script>
<script>
  var context = new WebAudioEngine.WebAudioContext({
    context: new AudioContext(),
  });

  // composeWith(context);

  context.resume();
</script>

Class: OfflineAudioContext

This context is compatible with the natvie Web Audio API OfflineAudioContext.

import { OfflineAudioContext } from 'web-audio-engine';
const context = new OfflineAudioContext(2, 44100 * 10, 44100);

// composeWith(context);

context.startRendering().then((audioBuffer) => {
  console.log(audioBuffer);
});

Interface: AudioData

interface AudioData {
  numberOfChannels?: number;
  length?: number;
  sampleRate: number;
  channelData: Float32Array[];
}

decoder

The default decoder of web-audio-engine supports "wav" format only. If you need to support other audio format, you are necessary to prepare a decoder yourself.

decoder.get(type: string): function

Returns the function for decoding currently set.

decoder.set(type: string, decodeFn: function)

Sets the function for decoding.

  • decodeFn: (audioData: ArrayBuffer, opts?: object) => Promise< AudioData > The decoding to use.
decoder.decode(audioData: ArrayBuffer, opts?: object): Promise< AudioData >

Executes decoding.

  • audioData: ArrayBuffer
mp3 decoder example
import wae from 'web-audio-engine';
import mp3decoder from '/path/to/mp3decoder';
import fs from 'fs';
wae.decoder.set('mp3', mp3decoder);
const AudioContext = wae.RenderingAudioContext;
const context = new AudioContext();
const audioData = fs.readFileSync('amen.mp3');

context.decodeAudioData(audioData).then((audioBuffer) => {
  console.log(audioBuffer);
});

encoder

The default encoder of web-audio-engine supports "wav" format only. If you need to support other audio format, you are necessary to prepare an encoder yourself.

encoder.get(type: string): function

Returns the function for encoding currently set.

encoder.set(type: string, encodeFn: function)

Sets the function for encoding.

  • encodeFn: (audioData: AudioData, opts?: object) => Promise< ArrayBuffer > The encoding to use.
encoder.encode(audioData: AudioData, opts?: object): Promise< ArrayBuffer >

Executes encoding.

  • audioData: AudioData
  • opts.type: string audio format type - default: "wav"
mp3 encoder example
import wae from 'web-audio-engine';
import mp3encoder from '/path/to/mp3encoder';
import fs from 'fs';
wae.encoder.set('mp3', mp3encoder);
const AudioContext = wae.RenderingAudioContext;
const context = new AudioContext();
const audioData = context.exportAsAudioData();

context.encodeAudioData(audioData, { type: 'mp3' }).then((arrayBuffer) => {
  fs.writeFile('output.mp3', new Buffer(arrayBuffer));
});

Implemented API

  • AnalyserNode
  • AudioBuffer
  • AudioBufferSourceNode
  • AudioContext
  • AudioDestinationNode
  • AudioNode
  • AudioParam
  • BiquadFilterNode (audio rate parameter is not supported)
  • ChannelMergerNode
  • ChannelSplitterNode
  • DelayNode (noisy..)
  • DynamicsCompressorNode
  • GainNode
  • IIRFIlterNode
  • OscillatorNode (use wave-table synthesis, not use periodic wave)
  • PeriodicWave
  • ScriptProcessorNode
  • StereoPannerNode
  • WaveShaperNode
  • The other not implemented nodes will pass its input to its output without modification.
  • See: Comparison Chart of implemented nodes

Example

import Speaker from 'speaker';
import { StreamAudioContext as AudioContext } from 'web-audio-engine';
const context = new AudioContext();

const osc = context.createOscillator();
const amp = context.createGain();

osc.type = 'square';
osc.frequency.setValueAtTime(987.7666, 0);
osc.frequency.setValueAtTime(1318.5102, 0.075);
osc.start(0);
osc.stop(2);
osc.connect(amp);
osc.onended = () => {
  context.close().then(() => {
    process.exit(0);
  });
};

amp.gain.setValueAtTime(0.25, 0);
amp.gain.setValueAtTime(0.25, 0.075);
amp.gain.linearRampToValueAtTime(0, 2);
amp.connect(context.destination);

context.pipe(new Speaker());
context.resume();

Online Demo

The online demo is here. In this site, you can compare web-audio-engine and the native Web Audio API.

Offline Demo

$ git clone git@github.com:mohayonao/web-audio-engine.git
$ cd web-audio-engine
$ npm install && npm run build
$ cd demo
$ npm install
$ node demo --help

Simplest play demo with node-speaker.

$ node demo sines

Rendering and export to the wav file.

$ node demo -o out.wav sines

Online Benchmark

Currently, this benchmark doesn't work in Chrome or Safari, please use Firefox.

Offline Benchmark

$ git clone git@github.com:mohayonao/web-audio-engine.git
$ cd web-audio-engine
$ npm install && npm run build
$ cd benchmark
$ npm install
$ node .

License

MIT