115 lines
4.2 KiB
JavaScript
115 lines
4.2 KiB
JavaScript
// RNNoise Web Audio Worklet Processor
|
|
// Uses the RNNoise WASM binary to perform real-time noise suppression
|
|
|
|
class RNNoiseProcessor extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
this.alive = true;
|
|
this.wasmInstance = null;
|
|
this.rnnoise = null;
|
|
this.initialized = false;
|
|
this.inputBuffer = new Float32Array(480); // RNNoise expects 480 samples (10ms at 48kHz)
|
|
this.outputBuffer = new Float32Array(480);
|
|
this.bufferPtr = 0;
|
|
this.heapInputPtr = null;
|
|
this.heapOutputPtr = null;
|
|
this.statePtr = null;
|
|
|
|
this.vadThreshold = 0.85; // Default strict threshold
|
|
this.enabled = true;
|
|
|
|
this.port.onmessage = (event) => {
|
|
if (event.data.type === 'INIT') {
|
|
this.initWasm(event.data.wasmBinary);
|
|
} else if (event.data.type === 'SET_ENABLED') {
|
|
this.enabled = event.data.enabled;
|
|
} else if (event.data.type === 'SET_THRESHOLD') {
|
|
this.vadThreshold = event.data.value;
|
|
}
|
|
};
|
|
}
|
|
|
|
async initWasm(wasmBinary) {
|
|
try {
|
|
const wasmModule = await WebAssembly.instantiate(wasmBinary, {
|
|
env: {
|
|
memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),
|
|
abort: () => { console.error("WASM Abort"); }
|
|
}
|
|
});
|
|
this.wasmInstance = wasmModule.instance;
|
|
this.rnnoise = this.wasmInstance.exports;
|
|
|
|
// Allocate memory on the WASM heap
|
|
// rnnoise_create returns a pointer to the state
|
|
this.statePtr = this.rnnoise.rnnoise_create(0);
|
|
|
|
// Buffer size is 480 floats (4 bytes each)
|
|
this.heapInputPtr = this.rnnoise.malloc(480 * 4);
|
|
this.heapOutputPtr = this.rnnoise.malloc(480 * 4);
|
|
|
|
this.initialized = true;
|
|
console.log("RNNoise Processor Initialized with WASM");
|
|
} catch (e) {
|
|
console.error("Failed to initialize RNNoise WASM:", e);
|
|
}
|
|
}
|
|
|
|
process(inputs, outputs, parameters) {
|
|
const input = inputs[0];
|
|
const output = outputs[0];
|
|
|
|
if (!input || !input[0] || !this.initialized || !this.enabled) {
|
|
// Pas d'entrée ou pas initialisé, on bypass
|
|
if (input && input[0] && output && output[0]) {
|
|
output[0].set(input[0]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const inputChannel = input[0];
|
|
const outputChannel = output[0];
|
|
|
|
// Fill our internal buffer until we have 480 samples
|
|
for (let i = 0; i < inputChannel.length; i++) {
|
|
this.inputBuffer[this.bufferPtr] = inputChannel[i] * 32768.0; // RNNoise expects 16-bit PCM range
|
|
this.bufferPtr++;
|
|
|
|
if (this.bufferPtr >= 480) {
|
|
// Process 480 samples
|
|
this.processRNNoise();
|
|
this.bufferPtr = 0;
|
|
}
|
|
|
|
// Output from our buffer (with latency of 480 samples)
|
|
// We use a simple circular buffer approach here for the output too
|
|
outputChannel[i] = (this.outputBuffer[this.bufferPtr] / 32768.0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
processRNNoise() {
|
|
// Copy input to WASM heap
|
|
const heapInput = new Float32Array(this.rnnoise.memory.buffer, this.heapInputPtr, 480);
|
|
heapInput.set(this.inputBuffer);
|
|
|
|
// Process audio: rnnoise_process_frame(state, output, input)
|
|
// Returns the probability of speech (0.0 to 1.0)
|
|
const vadProbability = this.rnnoise.rnnoise_process_frame(this.statePtr, this.heapOutputPtr, this.heapInputPtr);
|
|
|
|
// Copy output from WASM heap
|
|
const heapOutput = new Float32Array(this.rnnoise.memory.buffer, this.heapOutputPtr, 480);
|
|
|
|
// Aggressive Voice Gate based on RNNoise VAD
|
|
if (vadProbability < this.vadThreshold) {
|
|
// Not voice -> Mute completely
|
|
this.outputBuffer.fill(0);
|
|
} else {
|
|
// Voice detected -> Copy denoised output
|
|
this.outputBuffer.set(heapOutput);
|
|
}
|
|
}
|
|
}
|
|
|
|
registerProcessor('rnnoise-processor', RNNoiseProcessor); |