What it measures
Audio: we render a short audio graph through OfflineAudioContext and hash the rendered samples; engine-bound.
Because the audio DSP math is implemented independently in V8/Blink, SpiderMonkey/Gecko, and JavaScriptCore/WebKit, the floating-point accumulation produces distinct results across browser engines even when run on identical hardware. The signal is therefore engine-bound and contributes to fingerprint but not to hardwareFingerprint.
How it's collected
The collector renders the same audio graph across several standard oscillator waveform types. For each waveform we apply per-call noise mitigation across multiple render passes and select a stable per-waveform value. The resulting per-waveform values are concatenated and hashed with xxHash64 to produce a 16-character hex string.
The per-pass and per-waveform parameters, the noise-mitigation pass count, the rounding precision, and the tie-breaker behaviour are deliberately withheld from the public docs. The hash output is the stable interface; the internals evolve between releases.
Value shape
The `value` field is a typed object. The `waveforms` sub-field exposes per-waveform stable values; `divergence` carries per-waveform divergence diagnostics that feed the consistency layer.
Waveform indices are stable: `waveforms[0]` corresponds to a triangle oscillator, `waveforms[1]` to a sine, `waveforms[2]` to a square, and `waveforms[3]` to a sawtooth. Callers that store the array can rely on that order across releases.
// Public-facing value shape
interface AudioValue {
waveforms: number[]; // index-stable: [triangle, sine, square, sawtooth]
divergence: {
perWaveformValues: number[][]; // raw per-pass samples per waveform (diagnostics)
perWaveformStddev: number[]; // per-waveform noise amplitude (diagnostics)
};
}
// Reading the result
const { waveforms, divergence } = result.signals.audio.value as AudioValue;
console.log(waveforms[0]); // triangle-oscillator stable value
console.log(divergence.perWaveformStddev);Treat the field shape and the waveform-index ordering as the stable interface. The internal collection recipe (sample-rate, buffer length, compressor configuration, noise-mitigation parameters) is not part of the public contract.
Confidence rules
| Confidence | Trigger |
|---|---|
| normal | Every waveform produced a stable per-waveform value |
| degraded | At least one waveform failed the per-call stability check; a 'randomized' sentinel is attached |
| absent | OfflineAudioContext is unavailable, the collector threw, or the global / per-component timeout fired |
Sentinel codes
The optional `sentinel` field on the signal result provides finer-grained diagnostics without changing which value gets hashed. Four values are possible across all signals: `unsupported` (the underlying API is absent), `threw` (the collector raised an exception), `timeout` (killed by the global or per-component timeout), and `randomized` (the collector ran and produced a hash, but per-call stability checks detected noise).
When `sentinel` is `unsupported`, `threw`, or `timeout`, the hash is the canonical absent placeholder. When `sentinel` is `randomized`, the hash carries real data and confidence is `degraded`.
Why engine-bound
The OfflineAudioContext rendering pipeline is implemented independently in each JS engine's audio backend. Differences in the math library implementations, floating-point rounding order, and sample accumulation order produce distinct sums even for identical hardware and sample parameters.
Because these differences are engine-level, not hardware-level, the signal contributes to fingerprint but not to hardwareFingerprint.
Things worth knowing
- The per-call noise-mitigation strategy, pass counts, rounding precision, and tie-breaker logic are intentionally not published. Treat the hash as the stable interface.
- The collector yields to the main thread periodically during rendering to avoid blocking the UI.
- Typical total collection time is 2–4 seconds; worst case is bounded by the per-pass timeout times the total pass count.
- `value.waveforms` holds the per-waveform stable values; `value` is null when confidence is `absent`.
- `value.divergence.perWaveformValues` and `value.divergence.perWaveformStddev` expose the raw per-pass samples and their population stddev for downstream analysis.
- The two-stage Source pattern (dispatch/resolve, 1.22.0) is used for this collector; all rendering stays inside resolve to avoid parallelising the OfflineAudioContext calls beyond browser concurrency caps.
Last reviewed 2026-06-04

