What it measures
The audio signal renders a deterministic oscillator-through-compressor chain using OfflineAudioContext and sums the floating-point sample values from the output buffer. This is done four times — once each for triangle, sine, square, and sawtooth waveforms — and the four sums are concatenated and hashed.
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 with identical parameters.
How it's collected
For each of the four waveforms, the collector runs 5 independent render passes (NOISE_RUNS = 5). Each pass constructs a fresh OfflineAudioContext with 1 channel, a BUFFER_LENGTH of 5000 samples, and a SAMPLE_RATE of 44100 Hz. An oscillator (frequency 1000 Hz, type set to the current waveform) is connected to a DynamicsCompressor (threshold -50, knee 40, ratio 12, attack 0, release 0.25) and then to the context destination. After startRendering completes (timeout: 2000 ms), the channel data samples at indices 4500 through 4999 are summed.
Each of the 5 per-waveform sums is rounded to 8 decimal places (ROUND_PRECISION = 8). The mode of the 5 rounded values is selected; a mode is considered clear when at least 3 of the 5 runs agree. If all four waveforms yield a clear mode, confidence is normal. The four mode values are joined with pipe delimiters and hashed with xxHash64 to produce a 16-character hex string.
For each waveform in [triangle, sine, square, sawtooth]:
Run 5 passes, each:
AudioCtx(1 ch, 5000 samples, 44100 Hz)
oscillator(type=waveform, freq=1000) -> compressor -> destination
sum channelData[4500..4999]
round to 8 decimal places
mode = most frequent of 5 rounded sums (clear if count >= 3)
concatenated = triangle|sine|square|sawtooth
hash = xxHash64(concatenated) -> 16-char hexAudio collection algorithm summary
Confidence rules
| Confidence | Trigger |
|---|---|
| normal | All 4 waveforms returned a clear mode (count >= 3 out of 5 runs) |
| degraded | At least one waveform had no clear mode (all 5 values differ) |
| absent | OfflineAudioContext unavailable, or top-level catch fires |
Why engine-bound
The OfflineAudioContext rendering pipeline is implemented independently in each JS engine's audio backend. Differences in the sin/compressor math libraries, floating-point rounding order, and sample accumulation order produce distinct sums even for identical hardware and sample parameters.
The triangle-waveform sum alone is the classic AudioContext fingerprint; adding sine, square, and sawtooth extends discrimination across engines that happen to match on one waveform. Because these differences are engine-level, not hardware-level, the signal contributes to fingerprint but not to hardwareFingerprint.
Things worth knowing
- NOISE_RUNS was reduced from 7 to 5 passes for performance; the clear-mode threshold (maxCount >= 3) was adjusted accordingly.
- The collector yields to the main thread after every 3rd run within a waveform, and between each waveform (except after the last), to avoid blocking the UI.
- Worst-case timing is 2000 ms x 5 x 4 = 40 seconds (all passes hitting the timeout). Typical total time is 2-4 seconds.
- The value field exposed to callers is an array of the four per-waveform sums: [triangleSum, sineSum, squareSum, sawtoothSum]. It is null when confidence is absent.

