74 lines
1.9 KiB
JavaScript
74 lines
1.9 KiB
JavaScript
/**
|
|
* AudioWorklet processor for VoicePaste.
|
|
*
|
|
* Captures raw Float32 PCM from the microphone, accumulates samples into
|
|
* ~200ms frames, and posts them to the main thread for resampling + WS send.
|
|
*
|
|
* Communication:
|
|
* Main → Processor: { command: "start" | "stop" }
|
|
* Processor → Main: { type: "audio", samples: Float32Array, sampleRate: number }
|
|
*/
|
|
class AudioProcessor extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
this.recording = false;
|
|
this.buffer = [];
|
|
this.bufferLen = 0;
|
|
// ~200ms worth of samples at current sample rate
|
|
// sampleRate is a global in AudioWorkletGlobalScope
|
|
this.frameSize = Math.floor(sampleRate * 0.2);
|
|
|
|
this.port.onmessage = (e) => {
|
|
if (e.data.command === "start") {
|
|
this.recording = true;
|
|
this.buffer = [];
|
|
this.bufferLen = 0;
|
|
} else if (e.data.command === "stop") {
|
|
// Flush remaining samples
|
|
if (this.bufferLen > 0) {
|
|
this._flush();
|
|
}
|
|
this.recording = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
process(inputs) {
|
|
if (!this.recording) return true;
|
|
|
|
const input = inputs[0];
|
|
if (!input || !input[0]) return true;
|
|
|
|
// Mono channel 0
|
|
const channelData = input[0];
|
|
this.buffer.push(new Float32Array(channelData));
|
|
this.bufferLen += channelData.length;
|
|
|
|
if (this.bufferLen >= this.frameSize) {
|
|
this._flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_flush() {
|
|
// Merge buffer chunks into a single Float32Array
|
|
const merged = new Float32Array(this.bufferLen);
|
|
let offset = 0;
|
|
for (const chunk of this.buffer) {
|
|
merged.set(chunk, offset);
|
|
offset += chunk.length;
|
|
}
|
|
|
|
this.port.postMessage(
|
|
{ type: "audio", samples: merged, sampleRate: sampleRate },
|
|
[merged.buffer] // Transfer ownership for zero-copy
|
|
);
|
|
|
|
this.buffer = [];
|
|
this.bufferLen = 0;
|
|
}
|
|
}
|
|
|
|
registerProcessor("audio-processor", AudioProcessor);
|