self.onmessage = function(e) { const { channelData, sampleRate } = e.data; const targetSampleRate = 16000; try { // 1. Progress: Downsampling self.postMessage({ type: 'progress', status: 'downsampling', progress: 20 }); const downsampled = downsample(channelData, sampleRate, targetSampleRate); // 2. Progress: Encoding self.postMessage({ type: 'progress', status: 'encoding', progress: 50 }); const wavBuffer = encodeWAV(downsampled, targetSampleRate); // 3. Progress: Completed self.postMessage({ type: 'progress', status: 'completed', progress: 100 }); self.postMessage({ type: 'done', buffer: wavBuffer }, [wavBuffer]); } catch (error) { self.postMessage({ type: 'error', message: error.message }); } }; // Simple average-based downsampler for speech recognition function downsample(buffer, inputSampleRate, outputSampleRate) { if (inputSampleRate === outputSampleRate) { return buffer; } const sampleRateRatio = inputSampleRate / outputSampleRate; const newLength = Math.round(buffer.length / sampleRateRatio); const result = new Float32Array(newLength); let offsetResult = 0; let offsetBuffer = 0; while (offsetResult < result.length) { const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio); let accum = 0; let count = 0; for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) { accum += buffer[i]; count++; } result[offsetResult] = count > 0 ? accum / count : 0; offsetResult++; offsetBuffer = nextOffsetBuffer; } return result; } // Write standard 16-bit PCM WAV file function encodeWAV(samples, sampleRate) { const buffer = new ArrayBuffer(44 + samples.length * 2); const view = new DataView(buffer); /* RIFF identifier */ writeString(view, 0, 'RIFF'); /* file length */ view.setUint32(4, 36 + samples.length * 2, true); /* RIFF type */ writeString(view, 8, 'WAVE'); /* format chunk identifier */ writeString(view, 12, 'fmt '); /* format chunk length */ view.setUint32(16, 16, true); /* sample format (raw PCM) */ view.setUint16(20, 1, true); /* channel count */ view.setUint16(22, 1, true); // Mono /* sample rate */ view.setUint32(24, sampleRate, true); /* byte rate (sample rate * block align) */ view.setUint32(28, sampleRate * 2, true); /* block align (channel count * bytes per sample) */ view.setUint16(32, 2, true); /* bits per sample */ view.setUint16(34, 16, true); /* data chunk identifier */ writeString(view, 36, 'data'); /* data chunk length */ view.setUint32(40, samples.length * 2, true); // Write samples const volume = 1.0; let offset = 44; for (let i = 0; i < samples.length; i++, offset += 2) { const s = Math.max(-1, Math.min(1, samples[i])); view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } return buffer; } function writeString(view, offset, string) { for (let i = 0; i < string.length; i++) { view.setUint8(offset + i, string.charCodeAt(i)); } }