Spaces:
Running
Running
File size: 2,308 Bytes
c0ddd13 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | const TARGET_SAMPLE_RATE = 16000;
const CHUNK_SAMPLES = 3200; // 100ms at 16kHz
export class AudioRecorder {
private context: AudioContext | null = null;
private stream: MediaStream | null = null;
private workletNode: AudioWorkletNode | null = null;
private accumulator: Float32Array = new Float32Array(0);
micLevel = 0;
async start(onChunk: (pcm: ArrayBuffer) => void): Promise<void> {
this.stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
echoCancellation: true,
noiseSuppression: true,
},
});
this.context = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE });
await this.context.resume();
await this.context.audioWorklet.addModule(
new URL("./RecorderWorkletProcessor.js", import.meta.url).href
);
const source = this.context.createMediaStreamSource(this.stream);
this.workletNode = new AudioWorkletNode(this.context, "recorder-processor");
this.workletNode.port.onmessage = (e: MessageEvent<Float32Array>) => {
const input = e.data;
const combined = new Float32Array(this.accumulator.length + input.length);
combined.set(this.accumulator);
combined.set(input, this.accumulator.length);
this.accumulator = combined;
while (this.accumulator.length >= CHUNK_SAMPLES) {
const chunk = this.accumulator.slice(0, CHUNK_SAMPLES);
this.accumulator = this.accumulator.slice(CHUNK_SAMPLES);
// RMS for barge-in detection
let sumSq = 0;
for (let i = 0; i < chunk.length; i++) sumSq += chunk[i] * chunk[i];
this.micLevel = Math.sqrt(sumSq / chunk.length) * 32767;
// Convert float32 → int16 PCM
const int16 = new Int16Array(chunk.length);
for (let i = 0; i < chunk.length; i++) {
const s = Math.max(-1, Math.min(1, chunk[i]));
int16[i] = s < 0 ? s * 32768 : s * 32767;
}
onChunk(int16.buffer);
}
};
source.connect(this.workletNode);
}
stop(): void {
this.workletNode?.disconnect();
this.workletNode = null;
this.stream?.getTracks().forEach((t) => t.stop());
this.stream = null;
this.context?.close();
this.context = null;
this.accumulator = new Float32Array(0);
this.micLevel = 0;
}
}
|