Spaces:
Sleeping
Sleeping
| /** | |
| * AudioWorklet Processor for exact chunk size control | |
| * Accumulates audio samples and sends exactly 1600 samples (100ms at 16kHz) per chunk | |
| * | |
| * IMPORTANT: This processor resamples from browser's native sample rate (usually 48kHz) | |
| * down to 16kHz required by the backend. | |
| */ | |
| class AudioChunkProcessor extends AudioWorkletProcessor { | |
| constructor() { | |
| super(); | |
| this.buffer = []; | |
| // Target: 100ms at 16kHz = 1600 samples (matches VAD chunk_duration_ms) | |
| // Server accumulates 6x 100ms chunks into 600ms chunks for ASR | |
| this.targetSamples = 1600; | |
| this.targetSampleRate = 16000; | |
| // Resampling state | |
| this.resampleBuffer = []; | |
| this.inputSampleRate = sampleRate; // AudioContext's native sample rate (e.g., 48000) | |
| this.resampleRatio = this.inputSampleRate / this.targetSampleRate; // e.g., 48000/16000 = 3 | |
| } | |
| process(inputs, outputs, parameters) { | |
| const input = inputs[0]; | |
| if (!input || !input[0]) return true; | |
| const inputData = input[0]; // Float32Array from Web Audio API (at native sample rate, e.g., 48kHz) | |
| // Resample from native rate (48kHz) to target rate (16kHz) | |
| // Using simple linear interpolation decimation | |
| for (let i = 0; i < inputData.length; i++) { | |
| this.resampleBuffer.push(inputData[i]); | |
| } | |
| // Decimate: take every Nth sample (e.g., every 3rd sample for 48kHz->16kHz) | |
| while (this.resampleBuffer.length >= this.resampleRatio) { | |
| // Simple decimation: take first sample, discard rest | |
| // For better quality, could use averaging or proper low-pass filter | |
| const sample = this.resampleBuffer[0]; | |
| this.buffer.push(sample); | |
| // Remove processed samples | |
| this.resampleBuffer.splice(0, Math.floor(this.resampleRatio)); | |
| } | |
| // Send chunks when we have enough samples | |
| while (this.buffer.length >= this.targetSamples) { | |
| const chunk = this.buffer.splice(0, this.targetSamples); | |
| // Convert Float32Array to Int16Array (PCM 16-bit) | |
| const int16Data = new Int16Array(chunk.length); | |
| for (let i = 0; i < chunk.length; i++) { | |
| const s = Math.max(-1, Math.min(1, chunk[i])); | |
| int16Data[i] = s < 0 ? s * 0x8000 : s * 0x7FFF; | |
| } | |
| // Send to main thread (transfer ownership for zero-copy) | |
| this.port.postMessage({ | |
| type: 'audio-chunk', | |
| data: int16Data.buffer, | |
| samples: this.targetSamples, | |
| timestamp: currentTime | |
| }, [int16Data.buffer]); | |
| } | |
| return true; // Keep processor alive | |
| } | |
| } | |
| registerProcessor('audio-chunk-processor', AudioChunkProcessor); | |