# waveform-data.js: zoomable audio waveforms for the browser

> waveform-data.js is the JavaScript half of the BBC waveform toolchain, and it does one job: turn precomputed waveform data, or audio decoded in the browser, into the point data a zoomable waveform display needs. It ships as UMD, ES module and CommonJS builds and can run the analysis in a Web Worker.

**bbc/waveform-data.js** — Audio Waveform Data Manipulation API – resample, offset and segment waveform data in JavaScript.

- Repository: https://github.com/bbc/waveform-data.js
- Stars: 1,091 · Forks: 107
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/bbc-waveform-data-js

## Where the library sits in the BBC waveform toolchain

waveform-data.js is one part of a family of projects the BBC R&D built for browser based audio waveform visualisation, and the family explains the design better than the library alone can. audiowaveform is a C++ program that generates waveform data files from MP3 or WAV audio. audio_waveform-ruby is a Ruby gem that reads and writes those same data files. waveform-data.js is the JavaScript library that gives an application access to precomputed waveform data files, and it can also generate waveform data in the browser using the Web Audio API. peaks.js is the user interface component for interacting with a waveform. The pieces are used inside the BBC in the World Service Radio Archive and in browser based editing and sharing tools for content editors, which is the context to keep in mind, because a tool built for editors working on broadcast material has different priorities from a tool built for a music player.

## Installing the package and choosing a build

The package installs like any other npm dependency, for Node and for browser applications alike:

```bash
npm install --save waveform-data
```

The distribution is deliberately generous, because the library is consumed by projects built in very different ways. The UMD build, available as dist/waveform-data.js and its minified counterpart, is the one to drop into a page with a script tag, and it is also what a RequireJS setup expects. An ES module build at dist/waveform-data.esm.js is meant for bundlers such as Webpack and Rollup, and a CommonJS build at dist/waveform-data.cjs.js is the entry point for Node, with the package.json main field pointing at it and the browser field pointing at the ES module build. TypeScript users get waveform-data.d.ts, and the package also ships the src/ directory, so the internals are available to anyone who needs to read or patch them.

```javascript
import WaveformData from 'waveform-data';
```

That single import is the whole module surface in an ES module project.

## Loading precomputed data in the binary format

The normal production path is to compute the waveform on a machine that can afford it and ship the result. audiowaveform produces the data files, and the README gives the invocation for the binary format:

```shell
audiowaveform -i track.mp3 -o track.dat -b 8 -z 256
```

The -b flag sets the resolution in bits per sample and -z sets the zoom level, so this command yields a file where each value carries eight bits of amplitude and the peaks are grouped at that zoom. The file is copied to the web server and the browser asks for it with the Fetch API, turning the response into an array buffer and handing that to the factory:

```javascript
fetch('https://example.com/waveforms/track.dat')
  .then(response => response.arrayBuffer())
  .then(buffer => WaveformData.create(buffer))
  .then(waveform => {
    console.log(`Waveform has ${waveform.channels} channels`);
    console.log(`Waveform has length ${waveform.length} points`);
  });
```

The resulting object carries the channel count and the number of points, which is what a renderer needs to map a position on a timeline to an amplitude.

## The JSON format and why it exists

The same tool can emit JSON instead of binary, with the same input and output pattern:

```shell
audiowaveform -i track.mp3 -o track.json -b 8 -z 256
```

The loading code changes only in the first step, where the response is parsed as JSON rather than converted to an array buffer, and everything after it is identical, with the result passed to WaveformData.create. The choice between the two is a trade of size against inspectability. The binary file is the compact representation and is the one to ship for a large catalogue, while the JSON form is readable and easy to diff, transform or generate in a test fixture, which is why the repository's demo directory carries both a .dat and a .json version of the same two channel sample. The Ruby gem in the same family can read and write these files, so a pipeline that already runs in Ruby does not need a second tool.

## Generating the waveform in the browser instead

When there is no server, the library can do the work itself from the audio itself. The entry point is WaveformData.createFromAudio, which takes an options object holding an audio context, either an array buffer of encoded audio or an audio buffer of decoded samples, and a scale value that sets the resolution of the result. The README explains the two steps that follow. Encoded audio is decoded with the Web Audio API decodeAudioData method, and that has to happen on the user interface thread, so it is a blocking operation. The processing of the decoded samples is then moved off that thread into a Web Worker where the browser supports it, and the worker can be turned off by setting disable_worker to true in the options when a caller needs the result on the main thread. If the caller already holds decoded samples from an earlier decodeAudioData call, it can pass that audio buffer directly and skip the decoding step entirely.

## Where the in-browser path stops paying

The README itself states the limit, and it is worth repeating before anyone builds a catalogue on it. Generating waveform data in the browser is generally less efficient than preprocessing the audio server side with audiowaveform. The reasons are visible in the API. Decoding is a blocking operation on the user interface thread, so a long track stalls the interface while it happens, and the worker only protects the second phase. The scale parameter also caps how much detail the client side result can carry compared with a file computed with a chosen zoom level. A browser side approach is reasonable for a single track, for an editor preview, or for a case where the audio never reaches a server anyway, and it becomes the wrong choice for a library of thousands of items, where the same decode and analyse work would be repeated for every visitor who opens a page.

## The build, the test matrix and the demo

The repository is set up for a library that has to work in every environment it claims to support, and the package.json scripts show the matrix. Linting runs eslint over the configuration files, the sources and the test files, and it is wired as a prebuild step, so a build that passes lint runs rollup -c to produce the bundles, and a postbuild step copies the built files into the demo directory. Tests run the build first as well, then split into a Node half and a browser half. The Node half runs mocha twice, once against the CommonJS test entry point and once against the ES module one, which is how a single library is checked against both module systems. The browser half runs karma with karma.conf.cjs, and a watch variant is available for the same runner. A start script serves the demo folder on port 8080, and the demo directory holds a two channel sample in both data formats along with the source audio, so the whole thing can be exercised locally.

## Licence, releases and the move away from GitHub

Two administrative points deserve attention. The first is the licence, which GitHub cannot categorise, with the terms in a LICENSE file at the repository root and a licence field in the package manifest that points to the same thing, so anyone embedding the library in a product should read that file rather than assume a standard grant. The second is where the work now happens. A note at the top of the README states that ongoing development of the project has moved to a Codeberg repository, which changes what a new user should watch and what they should expect from the GitHub copy. The published history is stable and slow: v4.5.2 on 2025-06-07, v4.5.1 on 2025-02-14 and v4.5.0 on 2024-01-18, with the last push to the GitHub repository on 2026-10-12. The npm package version and the release tags agree at 4.5.2, so a consumer pinning the published version is not missing anything newer in the registry.

## Conclusion

waveform-data.js fits a team that already runs the audiowaveform C++ tool to produce waveform files, or a browser application that needs a waveform for a piece of audio without a server side pipeline, and it fits anyone who wants a zoomable waveform without writing the maths. It does not fit a high volume catalogue pipeline, because the README says the browser route is generally less efficient than preprocessing the audio server side, and decoding blocks the user interface thread. Before adopting it, read the LICENSE file, because the licence is one GitHub cannot categorise, and check the Codeberg repository the README points to, since ongoing development has moved there while the GitHub copy remains published on npm.

## FAQ

### How do I install waveform-data.js?

Run npm install --save waveform-data. The package ships a UMD build for a script tag, an ES module build for bundlers, a CommonJS build for Node and a TypeScript declaration file.

### How do I load precomputed waveform data in the browser?

Fetch the data file, convert the response with response.arrayBuffer() for the binary format or response.json() for the JSON format, and pass the result to WaveformData.create.

### Can waveform-data.js generate a waveform without a server?

Yes. WaveformData.createFromAudio takes an audio context, either encoded audio as an array buffer or decoded samples as an audio buffer, and a scale value. Processing runs in a Web Worker by default and the worker can be disabled with the disable_worker option.

### Which program generates the waveform data files?

audiowaveform, the C++ tool in the same BBC family, with flags such as -b 8 for the bit resolution and -z 256 for the zoom level. A Ruby gem in the family can also read and write those files.

### Why does the README recommend server side preprocessing?

Because decoding in the browser is less efficient, and decodeAudioData has to run on the user interface thread, where it blocks, so the client side route does not scale to a large catalogue.

### Where is waveform-data.js maintained now?

The README notes that ongoing development has moved to a Codeberg repository. The GitHub releases are v4.5.2 from 2025-06-07, v4.5.1 from 2025-02-14 and v4.5.0 from 2024-01-18, with the last push to GitHub on 2026-10-12.

## Sources

- [bbc/waveform-data.js on GitHub](https://github.com/bbc/waveform-data.js)
- [Issues](https://github.com/bbc/waveform-data.js/issues)
- [README](https://github.com/bbc/waveform-data.js/blob/master/README.md)
- [Releases](https://github.com/bbc/waveform-data.js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bbc-waveform-data-js
