UNPKG

assemblyai

Version:

The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.

76 lines (67 loc) 2.84 kB
import { VadDetector, VadDetectorResult, } from "../../types/streaming/dual-channel"; export type EnergyVadParams = { /** Threshold = noiseFloor * thresholdRatio. Default 3.0 (~ +9.5 dB above noise). */ thresholdRatio?: number; /** EMA smoothing for the noise-floor estimate when frame is non-speech. Default 0.05. */ noiseFloorAlpha?: number; /** Hangover in frames: stay "active" this many frames after the last speech frame. Default 10 (\~200 ms at 20 ms frames). */ hangoverFrames?: number; /** Initial noise floor estimate. Default 1e-4. Adaptive after the first non-speech frame. */ initialNoiseFloor?: number; }; /** * Energy-based VAD with adaptive noise-floor tracking and hangover. Pure JS, * no dependencies. Suitable for the "which physical channel is speaking" task * because the channels are already physically separated at capture — the harder * problem (speech vs. non-speech in the wild) is one a customer can swap in a * DNN VAD for via the `createVad` parameter. * * Tuning notes: * - thresholdRatio below 2 will treat anything above noise as speech (too sensitive). * - thresholdRatio above 6 will miss quiet utterance onsets/offsets. * - noiseFloorAlpha above 0.1 makes the floor track quickly (good for non-stationary * background) but risks slowly adapting *up* to a sustained low voice. */ export class EnergyVad implements VadDetector { private readonly thresholdRatio: number; private readonly noiseFloorAlpha: number; private readonly hangoverFrames: number; private readonly initialNoiseFloor: number; private noiseFloor: number; private hangoverRemaining = 0; constructor(params: EnergyVadParams = {}) { this.thresholdRatio = params.thresholdRatio ?? 3.0; this.noiseFloorAlpha = params.noiseFloorAlpha ?? 0.05; this.hangoverFrames = params.hangoverFrames ?? 10; this.initialNoiseFloor = params.initialNoiseFloor ?? 1e-4; this.noiseFloor = this.initialNoiseFloor; } process(frame: Float32Array): VadDetectorResult { let sumSq = 0; for (let i = 0; i < frame.length; i++) { sumSq += frame[i] * frame[i]; } const rms = frame.length > 0 ? Math.sqrt(sumSq / frame.length) : 0; const threshold = this.noiseFloor * this.thresholdRatio; let active = rms > threshold; if (active) { this.hangoverRemaining = this.hangoverFrames; } else if (this.hangoverRemaining > 0) { this.hangoverRemaining--; active = true; // While in hangover, do not update noise floor — RMS may still reflect tail energy. } else { this.noiseFloor = this.noiseFloor * (1 - this.noiseFloorAlpha) + rms * this.noiseFloorAlpha; } return { active, energy: rms }; } reset(): void { this.noiseFloor = this.initialNoiseFloor; this.hangoverRemaining = 0; } }