/** Adaptive onset detector; it never drops silence after ASR has started. */
export class AdaptiveSpeechGate {
    noiseRms = 0.001;
    voicedSamples = 0;
    hangoverSamples = 0;
    observe(samples, sampleRate) {
        const rms = rmsOf(samples);
        const threshold = Math.max(0.0015, this.noiseRms * Math.pow(10, 10 / 20));
        const voiced = rms >= threshold;
        if (voiced) {
            this.voicedSamples += samples.length;
            this.hangoverSamples = Math.round(sampleRate * 0.3);
        }
        else {
            this.noiseRms = this.noiseRms * 0.95 + rms * 0.05;
            this.voicedSamples = 0;
            this.hangoverSamples = Math.max(0, this.hangoverSamples - samples.length);
        }
        // Before a stream starts require a real 100 ms onset. Once the engine is
        // active it continues to submit silence so Sherpa can detect endpoints;
        // hangover protects only this detector's internal state.
        return this.voicedSamples >= sampleRate / 10;
    }
    reset() { this.noiseRms = 0.001; this.voicedSamples = 0; this.hangoverSamples = 0; }
}
function rmsOf(samples) {
    let sum = 0;
    for (const value of samples) {
        const normalized = value / 0x7fff;
        sum += normalized * normalized;
    }
    return Math.sqrt(sum / Math.max(1, samples.length));
}
