/** * Web Worker for heavy audio computations * Offloads waveform generation, analysis, and normalization to background thread */ export interface WorkerMessage { id: string; type: 'generatePeaks' | 'generateMinMaxPeaks' | 'normalizePeaks' | 'analyzeAudio' | 'findPeak'; payload: any; } export interface WorkerResponse { id: string; type: string; result?: any; error?: string; } // Message handler self.onmessage = (event: MessageEvent) => { const { id, type, payload } = event.data; try { let result: any; switch (type) { case 'generatePeaks': result = generatePeaks( payload.channelData, payload.width ); break; case 'generateMinMaxPeaks': result = generateMinMaxPeaks( payload.channelData, payload.width ); break; case 'normalizePeaks': result = normalizePeaks( payload.peaks, payload.targetMax ); break; case 'analyzeAudio': result = analyzeAudio(payload.channelData); break; case 'findPeak': result = findPeak(payload.channelData); break; default: throw new Error(`Unknown worker message type: ${type}`); } const response: WorkerResponse = { id, type, result }; self.postMessage(response); } catch (error) { const response: WorkerResponse = { id, type, error: error instanceof Error ? error.message : String(error), }; self.postMessage(response); } }; /** * Generate waveform peaks from channel data */ function generatePeaks(channelData: Float32Array, width: number): Float32Array { const peaks = new Float32Array(width); const samplesPerPeak = Math.floor(channelData.length / width); for (let i = 0; i < width; i++) { const start = i * samplesPerPeak; const end = Math.min(start + samplesPerPeak, channelData.length); let max = 0; for (let j = start; j < end; j++) { const abs = Math.abs(channelData[j]); if (abs > max) { max = abs; } } peaks[i] = max; } return peaks; } /** * Generate min/max peaks for more detailed waveform visualization */ function generateMinMaxPeaks( channelData: Float32Array, width: number ): { min: Float32Array; max: Float32Array } { const min = new Float32Array(width); const max = new Float32Array(width); const samplesPerPeak = Math.floor(channelData.length / width); for (let i = 0; i < width; i++) { const start = i * samplesPerPeak; const end = Math.min(start + samplesPerPeak, channelData.length); let minVal = 1; let maxVal = -1; for (let j = start; j < end; j++) { const val = channelData[j]; if (val < minVal) minVal = val; if (val > maxVal) maxVal = val; } min[i] = minVal; max[i] = maxVal; } return { min, max }; } /** * Normalize peaks to a given range */ function normalizePeaks(peaks: Float32Array, targetMax: number = 1): Float32Array { const normalized = new Float32Array(peaks.length); let max = 0; // Find max value for (let i = 0; i < peaks.length; i++) { if (peaks[i] > max) { max = peaks[i]; } } // Normalize const scale = max > 0 ? targetMax / max : 1; for (let i = 0; i < peaks.length; i++) { normalized[i] = peaks[i] * scale; } return normalized; } /** * Analyze audio data for statistics */ function analyzeAudio(channelData: Float32Array): { peak: number; rms: number; crestFactor: number; dynamicRange: number; } { let peak = 0; let sumSquares = 0; let min = 1; let max = -1; for (let i = 0; i < channelData.length; i++) { const val = channelData[i]; const abs = Math.abs(val); if (abs > peak) peak = abs; if (val < min) min = val; if (val > max) max = val; sumSquares += val * val; } const rms = Math.sqrt(sumSquares / channelData.length); const crestFactor = rms > 0 ? peak / rms : 0; const dynamicRange = max - min; return { peak, rms, crestFactor, dynamicRange, }; } /** * Find peak value in channel data */ function findPeak(channelData: Float32Array): number { let peak = 0; for (let i = 0; i < channelData.length; i++) { const abs = Math.abs(channelData[i]); if (abs > peak) peak = abs; } return peak; }