Текстовый файл из учебного комплекта, не свидетельство установленного runtime. Вернуться к учебнику · Исходный файл
1 import type { AppConfig } from "../config/appConfig"; 2 3 export type VadInstance = { track: MediaStreamTrack; stop: () => void }; 4 5 export function createEnergyVad( 6 sourceTrack: MediaStreamTrack, 7 config: AppConfig, 8 getThresholdDb: () => number, 9 onThresholdText?: (value: number) => void, 10 onSpeechActivityChange?: (active: boolean) => void, 11 ): VadInstance { 12 const ctx = new AudioContext(); 13 const src = ctx.createMediaStreamSource(new MediaStream([sourceTrack])); 14 const analyser = ctx.createAnalyser(); 15 analyser.fftSize = config.audio.vad.fftSize; 16 17 const gainNode = ctx.createGain(); 18 gainNode.gain.value = 1.0; 19 20 src.connect(analyser); 21 analyser.connect(gainNode); 22 23 const dest = ctx.createMediaStreamDestination(); 24 gainNode.connect(dest); 25 26 const data = new Float32Array(analyser.fftSize); 27 let stopped = false; 28 let lastActive: boolean | null = null; 29 30 const tick = () => { 31 if (stopped) return; 32 33 analyser.getFloatTimeDomainData(data); 34 let sum = 0; 35 for (let i = 0; i < data.length; i += 1) sum += data[i] * data[i]; 36 37 const rms = Math.sqrt(sum / data.length); 38 const db = 20 * Math.log10(rms + 1e-12); 39 40 const gate = getThresholdDb(); 41 onThresholdText?.(Math.round(gate)); 42 43 const active = db > gate; 44 if (lastActive === null) { 45 lastActive = active; 46 if (active) { 47 onSpeechActivityChange?.(true); 48 } 49 } else if (lastActive !== active) { 50 lastActive = active; 51 onSpeechActivityChange?.(active); 52 } 53 54 const target = active ? 1.0 : 0.0; 55 const T = active ? config.audio.vad.attackSeconds : config.audio.vad.releaseSeconds; 56 57 if (gainNode.gain.value !== target) { 58 gainNode.gain.setTargetAtTime(target, ctx.currentTime, T); 59 } 60 61 requestAnimationFrame(tick); 62 }; 63 64 requestAnimationFrame(tick); 65 66 const outputTrack = dest.stream.getAudioTracks()[0]; 67 68 return { 69 track: outputTrack, 70 stop: () => { 71 stopped = true; 72 src.disconnect(); 73 analyser.disconnect(); 74 gainNode.disconnect(); 75 void ctx.close(); 76 }, 77 }; 78 }