waveform-data.js is a JavaScript library for creating zoomable, browsable and segmentable representations of audio waveforms.
waveform-data.js is part of a BBC R&D Browser-based audio waveform visualisation software family:
- audiowaveform: C++ program that generates waveform data files from MP3 or WAV format audio.
- audio_waveform-ruby: A Ruby gem that can read and write waveform data files.
- waveform-data.js: JavaScript library that provides access to precomputed waveform data files, or can generate waveform data using the Web Audio API.
- peaks.js: JavaScript UI component for interacting with waveforms.
We use these projects daily in applications such as BBC World Service Radio Archive and browser editing and sharing tools for BBC content editors.
You can use npm
to install waveform-data
, both for Node.js or your frontend needs:
npm install --save waveform-data
Simply add waveform-data.js
in a script
tag in your HTML page.
Additional and detailed examples are showcased below and in the documentation pages.
<!DOCTYPE html>
<html>
<body>
<script src="/path/to/waveform-data.js"></script>
<script>
var waveform = new WaveformData(...);
</script>
</body>
</html>
dist/waveform-data.js
is delivered as UMD module so it can be used as:
- Vanilla JavaScript: available as
window.WaveformData
- RequireJS module: available as
define(['WaveformData'], function(WaveformData) { ... })
- CommonJS module: available as
const WaveformData = require('waveform-data');
const xhr = new XMLHttpRequest();
// .dat file generated by audiowaveform program
xhr.responseType = 'arraybuffer';
xhr.open("GET", 'https://example.com/waveforms/track.dat');
xhr.addEventListener('load', (progressEvent) => {
const waveform = WaveformData.create(progressEvent.target);
console.log(waveform.duration);
});
xhr.send();
fetch is the next generation of data fetching for the web.
fetch('http://example.com/waveforms/track.dat')
.then(response => response.arrayBuffer())
.then(buffer => {
const waveform = WaveformData.create(buffer);
console.log(waveform.duration);
});
Web Audio is an HTML5 API which can help fetch the waveform from the file directly. It is less optimised and more CPU intensive than consuming a waveform data file.
const webAudioBuilder = require('waveform-data/webaudio');
const audioContext = new AudioContext();
xhr.open('GET', 'https://example.com/audio/track.ogg');
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', (progressEvent) => {
webAudioBuilder(audioContext, progressEvent.target.response, (err, waveform) => {
if (err) {
console.error(err);
return;
}
console.log(waveform.duration);
});
});
xhr.send();
fetch('https://example.com/audio/track.ogg')
.then(response => response.arrayBuffer())
.then(buffer => {
webAudioBuilder(audioContext, buffer, (err, waveform) => {
if (err) {
console.error(err);
return;
}
console.log(waveform.duration);
});
});
const webAudioBuilder = require('waveform-data/webaudio');
const audioContext = new AudioContext();
audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
webAudioBuilder.fromAudioBuffer(audioBuffer, {}, (err, waveform) => {
console.log('waveform', waveform);
});
})
const waveform = WaveformData.create(raw_data);
const interpolateHeight = (total_height) => {
const amplitude = 256;
return (size) => total_height - ((size + 128) * total_height) / amplitude;
};
const y = interpolateHeight(canvas.height);
const ctx = canvas.getContext('2d');
ctx.beginPath();
// from 0 to 100
waveform.min.forEach((val, x) => {
ctx.lineTo(x + 0.5, y(val) + 0.5);
});
// then looping back from 100 to 0
waveform.max.reverse().forEach((val, x) => {
ctx.lineTo((waveform.offset_length - x) + 0.5, y(val) + 0.5);
});
ctx.closePath();
ctx.stroke();
ctx.fill();
const waveform = WaveformData.create(raw_data);
const layout = d3.select(this).select('svg');
const x = d3.scale.linear();
const y = d3.scale.linear();
const offsetX = 100;
x.domain([0, waveform.adapter.length]).rangeRound([0, 1024]);
y.domain([d3.min(waveform.min), d3.max(waveform.max)]).rangeRound([offsetX, -offsetX]);
var area = d3.svg.area()
.x((d, i) => x(i))
.y0((d, i) => y(waveform.min[i]))
.y1((d, i) => y(d));
graph.select('path')
.datum(waveform.max)
.attr('transform', () => `translate(0, ${offsetX})`)
.attr('d', area);
You can use the library to both consume the data on the frontend and emitting them from a Node.js HTTP server, for example.
// app.js
const WaveformData = require('waveform-data');
const express = require('express');
const fs = require('fs');
const app = express();
// ...
app.get('/waveforms/:id.json', (req, res) => {
res.set('Content-Type', 'application/json');
fs.createReadStream(`path/to/${req.params.id}.json`)
.pipe(res);
});
You could even self-consume the data from another application:
#!/usr/bin/env node
// Save as: app/bin/cli-resampler.js
const WaveformData = require('waveform-data');
const request = require('superagent');
const args = require('yargs').argv;
request.get(`https://api.example.com/waveforms/${args.waveformid}.json`)
.then(response => {
const resampled_waveform = WaveformData.create(response.body).resample(2000);
process.stdout.write(JSON.stringify({
min: resampled_waveform.min,
max: resampled_waveform.max
}));
});
Usage: ./app/bin/cli-resampler.js --waveformid=1337
The file format used and consumed by WaveformData
is documented here as part of the audiowaveform project.
This section describes the WaveformData
API.
This is the main object you use to interact with the waveform data. It helps you to:
- access the whole dataset
- iterate easily on an offset (visible subset of data, for example)
- generate one or several resampled views, e.g., to display the waveform at different zoom levels
- convert positions (in pixels, in seconds, in the offset)
- create and manage segments of waveform data, e.g., to represent different music tracks, or speakers, etc.
WaveformData
API Documentation
Each segment of data is independent and can overlap other existing ones. Segments allow you to keep track of portions of sound you would be interested to highlight.
WaveformDataSegment
API Documentation
This interface provides a backend abstraction for a WaveformData
instance.
You should not manipulate this data directly.
WaveformDataArrayBufferAdapter
API Documentation
WaveformDataObjectAdapter
API Documentation
Any browser supporting ECMAScript 5 will be enough to use the library -
think Array.forEach
:
- IE9+, Firefox Stable, Chrome Stable, Safari 6+ are fully supported;
- IE10+ is required for the TypedArray Adapter;
- Firefox 23+ and Webkit/Blink browsers are required for the experimental Web Audio Builder.
To develop the code, install Node.js and npm. After obtaining the waveform-data.js source code, run npm install
to install Node.js package dependencies.
This program contains code adapted from Audacity, used with permission.
See COPYING for details.
Every contribution is welcomed, either it's code, idea or a merci!
Guidelines are provided and every commit is tested against unit tests using Karma runner and the Chai assertion library.
Copyright 2018 British Broadcasting Corporation