1/** 2 * AudioWorkletProcessor: downsamples mic audio to 16 kHz mono and converts 3 * Float32 â Int16 little-endian PCM. Posts ArrayBuffers back to the main 4 * thread, which forwards them straight into the Deepgram WebSocket. 5 * 6 * Why a worklet (not ScriptProcessor): 7 * - Runs on the audio rendering thread, no main-thread jank. 8 * - ScriptProcessorNode is deprecated. 9 * 10 * Downsampling note: 11 * - We use simple decimation with a small averaging filter (boxcar). This 12 * is plenty for speech: Deepgram tolerates minor aliasing and we already 13 * have AEC3/NS/AGC upstream from getUserMedia. 14 */ 15class DeepgramPcmWorklet extends AudioWorkletProcessor { 16 constructor(options) { 17 super(); 18 const opts = (options && options.processorOptions) || {}; 19 this.targetRate = opts.targetSampleRate || 16000; 20 this.inputRate = sampleRate; // global, set by AudioContext (usually 48000) 21 this.ratio = this.inputRate / this.targetRate; // e.g., 3.0 for 48kâ16k 22 this._acc = 0; // fractional sample accumulator within current input block 23 this._buf = []; // accumulated Int16 samples awaiting send 24 this._chunkSize = 1600; // ~100 ms at 16 kHz; small = low latency 25 } 26 27 process(inputs) { 28 const input = inputs[0]; 29 if (!input || input.length === 0) return true; 30 const ch0 = input[0]; 31 if (!ch0) return true; 32 33 // Average channels if multi-channel snuck through (we asked for mono). 34 const len = ch0.length; 35 let mono; 36 if (input.length === 1) { 37 mono = ch0; 38 } else { 39 mono = new Float32Array(len); 40 for (let c = 0; c < input.length; c++) { 41 const ch = input[c]; 42 for (let i = 0; i < len; i++) mono[i] += ch[i]; 43 } 44 for (let i = 0; i < len; i++) mono[i] /= input.length; 45 } 46 47 // Downsample with simple boxcar averaging. 48 while (this._acc < len) { 49 const next = this._acc + this.ratio; 50 const end = Math.min(len, Math.ceil(next)); 51 let sum = 0; 52 let n = 0; 53 for (let j = Math.floor(this._acc); j < end; j++) { 54 sum += mono[j]; 55 n++; 56 } 57 const sample = n > 0 ? sum / n : 0; 58 // Float32 [-1,1] â Int16 [-32768, 32767] 59 const s16 = Math.max(-1, Math.min(1, sample)); 60 this._buf.push(s16 < 0 ? s16 * 0x8000 : s16 * 0x7fff); 61 this._acc = next; 62 } 63 // Carry fractional remainder into next block. 64 this._acc -= len; 65 66 // Flush in fixed-size chunks for steady packet rate. 67 while (this._buf.length >= this._chunkSize) { 68 const chunk = this._buf.splice(0, this._chunkSize); 69 const out = new Int16Array(chunk); 70 this.port.postMessage(out.buffer, [out.buffer]); 71 } 72 return true; 73 } 74} 75 76registerProcessor("deepgram-pcm-worklet", DeepgramPcmWorklet);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.