38443-vm/assets/js/rnnoise-processor.js
2026-03-30 07:18:59 +00:00

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);