npm.io
0.1.0 • Published yesterday

auditok

Licence
MIT
Version
0.1.0
Deps
0
Size
90 kB
Vulns
0
Weekly
0

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

Keywords