auditok.js
Audio activity detection and segmentation for JavaScript — the auditok Python package, ported to run in Node and in the browser. Zero dependencies.
auditok finds where the sound is. It splits audio into events — regions
of acoustic activity shaped by duration and silence rules — rather than
emitting raw per-frame speech/no-speech booleans:
- Event semantics, not frame booleans: minimum and maximum event duration, tolerated silence inside an event, leading/trailing silence handling for natural onsets and fadeouts.
- Offline file segmentation and online streaming with the same API: events are yielded as soon as their end is decided, while the input is still being read, decoded or recorded.
- Generic acoustic activity, not speech-only — music, claps, beeps, speech alike. For speech-specific detection, plug any frame-level VAD into the tokenizer as a custom validator and get proper event shaping on top of its decisions.
- A few KB of plain JS — no ONNX runtime, no model downloads, no native modules.
The detection engine, energy scale and parameters are those of Python auditok: thresholds are in dB relative to 16-bit full scale, and the test suite replays fixtures generated by the Python implementation to keep the two in lockstep — same tokens, same event boundaries, same estimated thresholds.
Try it in your browser — drop an audio file or record your microphone, tune the threshold, listen to the detected events. Everything runs client-side.
Install
npm install auditok
Node ≥ 18. In Node, WAV files are parsed natively; every other format
(mp3, ogg, flac, aac, …) and microphone capture go through
ffmpeg, which must be on the PATH. In the browser,
decoding and microphone input use the Web Audio API — nothing to install.
Quick start (Node)
import { load, split } from "auditok";
const audio = await load("audio.mp3"); // WAV parsed natively, rest via ffmpeg
for await (const event of split(audio)) {
console.log(`event: ${event.start.toFixed(3)}s -> ${event.end.toFixed(3)}s`);
}
Split a large compressed file while it is being decoded:
import { decodeFileStream, split } from "auditok";
const stream = decodeFileStream("podcast.mp3", { sampleRate: 16000 });
for await (const event of split(stream.chunks, { sampleRate: stream.sampleRate })) {
console.log(event.start, event.end);
}
Detect events from the microphone (captured through ffmpeg):
import { microphone, split } from "auditok";
const mic = microphone({ sampleRate: 16000 });
for await (const event of split(mic.chunks, { sampleRate: mic.sampleRate })) {
console.log(`heard something: ${event.start.toFixed(2)}s, ${event.duration.toFixed(2)}s`);
}
Save events or processed audio as WAV:
import { load, fixPauses, save } from "auditok";
const cleaned = await fixPauses(await load("interview.wav"), 0.4);
await save("cleaned.wav", cleaned);
Quick start (browser)
Same core API; load uses the browser's own decoder and microphone uses
getUserMedia + AudioWorklet:
import { load, microphone, split } from "auditok";
// from an <input type="file"> or drag-and-drop
const audio = await load(fileInput.files[0]);
for await (const event of split(audio)) {
highlight(event.start, event.end);
}
// live from the microphone (call from a user gesture)
const mic = await microphone();
for await (const event of split(mic.chunks, { sampleRate: mic.sampleRate })) {
console.log("speech-like activity", event.start, event.end);
}
// later: mic.stop()
A Web Audio AudioBuffer can be passed to split directly.
API
split(input, options) → AsyncGenerator<AudioRegion>
input is one of: Float32Array (mono samples), Float32Array[] (planar
channels), an AudioRegion, { channelData, sampleRate }, an
AudioBuffer, or a sync/async iterable of interleaved Float32Array
chunks (the streaming path). Bare samples and chunk streams need
options.sampleRate.
| Option | Default | Meaning |
|---|---|---|
minDur |
0.2 |
Minimum event duration in seconds |
maxDur |
5 |
Maximum event duration (null = unlimited); longer events are truncated |
maxSilence |
0.3 |
Maximum continuous silence within an event — controls when an event ends |
maxLeadingSilence |
0 |
Silence kept before each event (natural attack) |
maxTrailingSilence |
null |
Trailing silence kept at the end of each event: null keeps all (up to maxSilence), 0 drops it, larger values extend collection past the boundary (natural fadeout) |
strictMinDur |
false |
Reject events shorter than minDur even right after a truncated event |
analysisWindow |
0.05 |
Analysis window duration in seconds |
energyThreshold |
50 |
Detection threshold in dB (relative to int16 full scale), used when no validator is given |
validator |
— | "otsu", "percentile", "pXX" or a function — see below |
useChannel |
"any" |
Channel used for detection on multichannel audio: "any" (max over channels), "mix", or a channel index |
splitAll(input, options) returns the events as an array.
Automatic thresholding and custom validators
If validator is set it is the whole story and energyThreshold is
overlooked:
"otsu"splits the energy histogram in two classes (balanced, parameter-free);"percentile"(alias"p10") or"pXX"reads the noise floor at the XXth percentile of window energies and adds a 6 dB margin (recall-oriented).
For in-memory input the threshold is estimated from the whole input before
detection starts. For chunk-stream input (e.g., a live microphone) it is
calibrated on the first calibrationDur seconds (default 3), clamped to
minEnergyThreshold (default 40 dB); the calibration audio is replayed, so
no data is lost. A digitally silent calibration window (muted microphone)
falls back to the floor and detection keeps running.
A function validator receives each analysis window as planar per-channel samples and decides its validity — this is how you put proper event shaping on top of any frame-level VAD (an energy rule of your own, a WASM model, Silero frame decisions, …):
for await (const event of split(audio, {
validator: ([samples]) => myFrameVad(samples), // true = active frame
maxSilence: 0.1,
})) { ... }
To detect speech specifically rather than any acoustic activity, the
auditok-webrtcvad
add-on (in this repository under webrtcvad/) provides the WebRTC voice
activity detector as such a validator — libfvad compiled to a ~30 KB
WASM binary, the counterpart of Python auditok's validator="webrtc:N":
import { createWebrtcValidator } from "auditok-webrtcvad";
const validator = await createWebrtcValidator({ sampleRate: 16000, mode: 1 });
for await (const event of split(audio, { validator, maxSilence: 0.1 })) { ... }
trim(input, options) → Promise<AudioRegion>
Audio from the start of the first detection to the end of the last — leading and trailing silence removed. Streams are read once and recorded while being consumed.
fixPauses(input, silenceDuration, options) → Promise<AudioRegion>
All detected events (never truncated) joined with exactly
silenceDuration seconds of silence between them.
AudioRegion
What split yields: channelData (planar Float32Array per channel),
sampleRate, start, end, duration, channels, sampleCount, and
toWav() for a WAV-encoded Uint8Array.
Lower level
StreamTokenizer— the generic event-shaping state machine, usable over any frame type;calculateEnergy,windowEnergy,computeFrameEnergies,estimateEnergyThreshold— the energy/threshold toolbox;decodeWav,encodeWav— dependency-free WAV codec (Node and browser);- Node:
load,save,decodeFileStream,microphone; - browser:
load,microphone.
import ... from "auditok/core" gives the environment-free core only.
Demo
The playground lives in docs/ and is served by GitHub Pages straight from
the main branch — plain ES modules, no bundler. It imports the built
library from docs/lib; refresh that copy after changing the source with
npm run demo:lib. To try it locally, serve the folder with any static
server, e.g. npm run demo:serve.
Relationship to Python auditok
Same algorithm, same parameters (camelCased), same dB scale — a threshold
that works with one implementation works with the other, and the
Python documentation is a valid
reference for the detection semantics. Parity is enforced by fixtures
generated with the Python package (tools/generate_fixtures.py): the JS
test suite replays ~300 recorded cases through the tokenizer, split and
the threshold estimators.
The WebRTC VAD validator (validator="webrtc:N" in Python) is available
as the separate opt-in
auditok-webrtcvad
package, so the core keeps its size claim honest; its frame decisions are
parity-tested bit for bit against Python's webrtcvad. Not (yet) ported:
live threshold calibration for streams and the command-line tool.
License
MIT — Amine Sehili