// AudioWorkletProcessors for the real-time voice call mode. // Kept deliberately dumb: capture does downsampling + chunking + RMS only, // playback does ring-buffer PCM output only. All turn-taking / VAD / WS // logic lives in voice-call.js on the main thread. class MicCaptureProcessor extends AudioWorkletProcessor { constructor() { super(); this.targetRate = 16000; this.srcRate = sampleRate; // AudioWorkletGlobalScope — native context rate this.ratio = this.srcRate / this.targetRate; this.acc = 0; // fractional resample accumulator this.chunk = []; // pending int16 samples for the current outgoing chunk this.chunkSamples = Math.round(this.targetRate * 0.1); // ~100ms per chunk } process(inputs) { const input = inputs[0]; if (!input || !input[0]) return true; const ch = input[0]; for (let i = 0; i < ch.length; i++) { this.acc += 1; if (this.acc >= this.ratio) { this.acc -= this.ratio; const s = Math.max(-1, Math.min(1, ch[i])); this.chunk.push(s < 0 ? s * 0x8000 : s * 0x7fff); if (this.chunk.length >= this.chunkSamples) { const int16 = new Int16Array(this.chunk); let sumSq = 0; for (let j = 0; j < int16.length; j++) { const v = int16[j] / 32768; sumSq += v * v; } const rms = Math.sqrt(sumSq / int16.length); this.port.postMessage({ type: 'audio', pcm: int16, rms }, [int16.buffer]); this.chunk = []; } } } return true; } } registerProcessor('mic-capture-processor', MicCaptureProcessor); class PcmPlayerProcessor extends AudioWorkletProcessor { constructor() { super(); this.queue = []; // array of Float32Array this.qi = 0; // read offset within queue[0] this.port.onmessage = (e) => { const msg = e.data; if (msg.type === 'push') { const int16 = msg.pcm; const f32 = new Float32Array(int16.length); for (let i = 0; i < int16.length; i++) f32[i] = int16[i] / 32768; this.queue.push(f32); } else if (msg.type === 'clear') { this.queue = []; this.qi = 0; } }; } process(_inputs, outputs) { const out = outputs[0][0]; if (!out) return true; let oi = 0; while (oi < out.length) { if (this.queue.length === 0) { out[oi++] = 0; continue; } const cur = this.queue[0]; out[oi++] = cur[this.qi++]; if (this.qi >= cur.length) { this.queue.shift(); this.qi = 0; } } return true; } } registerProcessor('pcm-player-processor', PcmPlayerProcessor);