PageSourceSearch

https://ailikeme.ai/deepgram-worklet.js

js ailikeme.ai collected 2026-10-05 15:27:09 UTC 2,748 bytes, 76 lines download raw bytes

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.