TEST / audio-capture-worklet.js
EYEDOL's picture
Upload 10 files
0115b20 verified
Raw
History Blame Contribute Delete
2.57 kB
/**
* audio-capture-worklet.js
* ------------------------
* Runs in the AudioWorklet thread (dedicated audio thread, zero GC pauses).
*
* Responsibilities
* ----------------
* 1. Receive float32 PCM frames from the mic at AudioContext.sampleRate
* (typically 44100 or 48000 Hz).
* 2. Downsample to TARGET_SR = 16000 Hz using averaged decimation
* (basic anti-aliasing β€” good enough for speech).
* 3. Accumulate until CHUNK_SAMPLES (320 samples = 20 ms at 16 kHz) are
* ready, then convert to Int16 and post to the main thread.
*
* Messages posted to main thread
* --------------------------------
* { type: 'audio', pcm: ArrayBuffer } β€” 640 bytes = 320 int16 samples
*/
const TARGET_SR = 16_000;
const CHUNK_SAMPLES = 320; // 20 ms at 16 kHz β†’ 640 bytes as Int16
class AudioCaptureProcessor extends AudioWorkletProcessor {
constructor () {
super();
this._buf = []; // Downsampled float32 accumulator
this._active = false;
this.port.onmessage = ({ data }) => {
if (data.type === 'start') this._active = true;
if (data.type === 'stop') this._active = false;
};
}
process (inputs) {
if (!this._active) return true;
const channel = inputs[0]?.[0];
if (!channel || channel.length === 0) return true;
// ── Downsample: average samples within each output window ───────── //
const ratio = sampleRate / TARGET_SR; // e.g. 48000/16000 = 3.0
const outCount = Math.floor(channel.length / ratio);
for (let i = 0; i < outCount; i++) {
const srcStart = i * ratio;
const srcEnd = srcStart + ratio;
let sum = 0;
let n = 0;
for (let j = Math.floor(srcStart); j < Math.min(Math.ceil(srcEnd), channel.length); j++) {
sum += channel[j];
n++;
}
this._buf.push(n > 0 ? sum / n : 0);
}
// ── Emit CHUNK_SAMPLES at a time ─────────────────────────────────── //
while (this._buf.length >= CHUNK_SAMPLES) {
const slice = this._buf.splice(0, CHUNK_SAMPLES);
const int16 = new Int16Array(CHUNK_SAMPLES);
for (let i = 0; i < CHUNK_SAMPLES; i++) {
int16[i] = Math.max(-32_768, Math.min(32_767, Math.round(slice[i] * 32_767)));
}
this.port.postMessage({ type: 'audio', pcm: int16.buffer }, [int16.buffer]);
}
return true; // keep processor alive
}
}
registerProcessor('audio-capture-processor', AudioCaptureProcessor);