Spaces:
Running
Running
| 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)); | |
| } | |
| } | |