Host-Controlled Input Gain
Some recording setups require amplifying the microphone signal before the component can cross-correlate reliably. The component does not apply gain internally — the host builds a Web Audio gain chain and passes the processed stream via element.inputStream.
When to use
- Any scenario where the raw mic stream has insufficient level for a reliable cross-correlation result.
- Safari ≥ 16 with
echoCancellation: false: disabling echo cancellation on Safari can produce a very low input level. An empirical gain of 50× has been found effective.
Signal chain
getUserMedia stream
→ MediaStreamSource
→ ChannelSplitterNode (2 outputs)
→ output[0] ← left channel (or only channel for mono sources)
→ GainNode
→ MediaStreamDestinationNode (channelCount = 1)
→ dest.stream → element.inputStreamThe ChannelSplitterNode routes output index 0 — the left channel — to the gain stage. For mono inputs, output 0 carries the full signal. For stereo inputs (e.g. wired earpods on Safari, which force a stereo stream with signal only on the left channel), this isolates the useful channel before applying gain.
Measurement note
This pattern introduces an extra browser-managed Web Audio / MediaStream bridge. The component measures the latency of that pipeline, not a direct mic path. Results are internally consistent but should not be compared directly to measurements taken in direct-stream mode.
Example
const ac = new AudioContext({ latencyHint: 0 })
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false,
channelCount: 1
}
})
// Build the gain chain.
// Adjust gainValue as needed — 50 is a starting point for Safari >= 16
// with echoCancellation: false.
const gainValue = 1
const source = ac.createMediaStreamSource(stream)
const splitter = ac.createChannelSplitter(2)
const gainNode = ac.createGain()
gainNode.gain.value = gainValue
const dest = ac.createMediaStreamDestination()
dest.channelCount = 1
source.connect(splitter)
splitter.connect(gainNode, 0) // left channel only
gainNode.connect(dest)
const tester = document.querySelector('latency-test')
tester.audioContext = ac
tester.inputStream = dest.stream
tester.addEventListener('latency-result', e => {
console.log(`Latency: ${e.detail.latency.toFixed(2)} ms — ratio: ${e.detail.ratio.toFixed(2)} dB`)
})
tester.start()Adjusting gain at runtime
The gainNode is created once at session setup and can be updated between test runs without disconnecting or re-acquiring the mic:
gainNode.gain.value = 50 // update before or between test runsAudioWorklet mode
The same pattern works with recording-mode="audioworklet". The dest.stream is passed as inputStream and the component creates a MediaStreamSource from it internally. This introduces a second stream round-trip, so the pipeline differs from a direct AudioWorklet path — the accepted trade-off of host-controlled gain.