murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
134 lines (133 loc) • 4.82 kB
JavaScript
// RNNoise Web Worker - Handles audio processing in separate thread
// 2025 best practices: Zero-copy transfers, robust error handling, proper WASM loading
import { RNNoiseEngine } from '../engines/rnnoise-engine';
class RNNoiseWorker {
constructor() {
this.engine = null;
this.isInitialized = false;
this.isInitializing = false;
console.log('[RNNoiseWorker] Worker started');
self.addEventListener('message', this.handleMessage.bind(this));
}
async handleMessage(event) {
const { type, id, data } = event.data;
try {
switch (type) {
case 'init':
await this.initialize();
break;
case 'process':
if (id && data) {
await this.processAudio(id, data.buffer, data.sampleRate);
}
break;
case 'destroy':
await this.destroy();
break;
default:
this.postError('unknown-message', `Unknown message type: ${type}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.postError(id || 'unknown', errorMessage);
}
}
async initialize() {
if (this.isInitialized || this.isInitializing) {
this.postMessage({
type: 'initialized',
success: this.isInitialized,
error: this.isInitializing ? 'Already initializing' : undefined
});
return;
}
this.isInitializing = true;
try {
console.log('[RNNoiseWorker] Initializing RNNoise engine...');
this.engine = new RNNoiseEngine({
wasmPath: '/wasm/rnnoise.wasm',
scriptPath: '' // Not needed for direct WASM usage
});
await this.engine.initialize();
this.isInitialized = true;
this.isInitializing = false;
console.log('[RNNoiseWorker] Engine initialized successfully');
this.postMessage({
type: 'initialized',
success: true
});
}
catch (error) {
this.isInitializing = false;
const errorMessage = error instanceof Error ? error.message : 'Engine initialization failed';
console.error('[RNNoiseWorker] Initialization failed:', errorMessage);
this.postMessage({
type: 'initialized',
success: false,
error: errorMessage
});
}
}
async processAudio(id, buffer, sampleRate) {
if (!this.isInitialized || !this.engine) {
this.postError(id, 'Worker not initialized');
return;
}
try {
// Process the audio buffer (RNNoiseEngine.process is synchronous)
const processedBuffer = this.engine.process(buffer);
// Transfer the processed buffer back to main thread
this.postMessage({
type: 'processed',
id,
data: {
buffer: processedBuffer
}
}, [processedBuffer.buffer]); // Transfer ownership for zero-copy
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Processing failed';
console.error('[RNNoiseWorker] Processing failed:', errorMessage);
this.postError(id, errorMessage);
}
}
async destroy() {
console.log('[RNNoiseWorker] Destroying worker...');
try {
if (this.engine) {
this.engine.cleanup();
this.engine = null;
}
this.isInitialized = false;
this.isInitializing = false;
this.postMessage({
type: 'destroyed'
});
console.log('[RNNoiseWorker] Worker destroyed');
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Destroy failed';
console.error('[RNNoiseWorker] Destroy failed:', errorMessage);
this.postError('destroy', errorMessage);
}
}
postMessage(message, transfer) {
if (transfer && transfer.length > 0) {
// Use the Worker-specific postMessage with transfer list
self.postMessage(message, transfer);
}
else {
self.postMessage(message);
}
}
postError(id, error) {
this.postMessage({
type: 'error',
id,
error
});
}
}
// Initialize worker
new RNNoiseWorker();