Signal

Audio fingerprint

Measures the numeric output of the browser's audio rendering pipeline when a deterministic oscillator-plus-compressor signal is rendered offline across four waveform types.

Tier 1 engine src/signals/audio.ts

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.

text
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 hex

Audio collection algorithm summary

Confidence rules

ConfidenceTrigger
normalAll 4 waveforms returned a clear mode (count >= 3 out of 5 runs)
degradedAt least one waveform had no clear mode (all 5 values differ)
absentOfflineAudioContext 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.