murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
115 lines (114 loc) • 4.84 kB
JavaScript
// Removed wasm-loader import - using direct implementation
import { WASMLogger } from './logger';
import { ErrorFactory, ErrorType } from './error-handler';
let modulePromise = null;
// Direct WASM loading implementation
export async function loadRNNoiseModule({ fallbackImplementation, retryCount = 2 } = {}) {
if (!modulePromise) {
modulePromise = loadWASMOptimized(retryCount);
}
try {
return await modulePromise;
}
catch (error) {
WASMLogger.error('Primary WASM load failed', error);
if (fallbackImplementation) {
WASMLogger.warn('Attempting fallback implementation');
return fallbackImplementation();
}
throw ErrorFactory.wrapError(error, ErrorType.WASM_MODULE, 'Failed to load RNNoise module');
}
}
// Modified to support retry mechanism
async function loadWASMOptimized(retriesLeft = 2) {
try {
// Import only the async function to avoid bundling the 1.9MB sync version
const { createRNNWasmModule } = await import('@jitsi/rnnoise-wasm');
const module = await createRNNWasmModule({
locateFile: (filename) => {
if (filename.endsWith('.wasm')) {
const wasmPath = getOptimizedWASMPath(filename);
WASMLogger.debug('Loading WASM from path', { wasmPath });
return wasmPath;
}
return filename;
},
instantiateWasm: async (imports, successCallback) => {
try {
const wasmPath = getOptimizedWASMPath('rnnoise.wasm');
const response = await fetch(wasmPath);
if (!response.ok) {
throw ErrorFactory.wasmModuleLoadFailed(new Error(`HTTP ${response.status}`), { url: wasmPath, status: response.status });
}
if ('instantiateStreaming' in WebAssembly) {
const result = await WebAssembly.instantiateStreaming(response, imports);
successCallback(result.instance, result.module);
}
else {
const buffer = await response.arrayBuffer();
const result = await WebAssembly.instantiate(buffer, imports);
successCallback(result.instance, result.module);
}
return {}; // Return empty object to satisfy TypeScript
}
catch (error) {
if (retriesLeft > 0) {
console.warn(`[RNNoise] WASM load failed, retrying... (${retriesLeft} attempts left)`);
return loadWASMOptimized(retriesLeft - 1);
}
console.error('[RNNoise Loader] WASM instantiation failed:', error);
throw error;
}
}
});
return module;
}
catch (error) {
console.error('[RNNoise] Complete WASM module load failed:', error);
throw error;
}
}
// Centralized WASM path resolution - SINGLE SOURCE OF TRUTH
function getOptimizedWASMPath(filename) {
if (typeof window === 'undefined') {
return filename;
}
// ONE location for ALL environments - no duplication
return `/wasm/${filename}`;
}
// Lazy loader for RNNoise module
export const lazyLoadRNNoise = () => loadRNNoiseModule();
// Preload WASM for better performance
// Performance tracking for WASM loading
const WASM_SIZE_THRESHOLD_KB = 200; // Adjust based on actual file size
const WASM_LOAD_TIMEOUT_MS = 5000;
export async function preloadRNNoiseWASM(options = {}) {
const { force = false, timeout = WASM_LOAD_TIMEOUT_MS } = options;
// Only preload if file is larger than threshold or force is true
const shouldPreload = force || (await isWASMSizeLarge());
if (shouldPreload) {
const preloadStart = performance.now();
try {
// Use Promise.race to prevent indefinite loading
await Promise.race([
loadRNNoiseModule(),
new Promise((_, reject) => setTimeout(() => reject(new Error('WASM load timeout')), timeout))
]);
const loadTime = performance.now() - preloadStart;
WASMLogger.info('WASM module preloaded', { loadTime: `${loadTime.toFixed(2)}ms` });
}
catch (error) {
console.warn('[RNNoise] Preload failed:', error);
}
}
}
async function isWASMSizeLarge() {
try {
const response = await fetch('/wasm/rnnoise.wasm', { method: 'HEAD' });
const size = Number(response.headers.get('Content-Length') || 0) / 1024;
return size > WASM_SIZE_THRESHOLD_KB;
}
catch {
return false;
}
}