| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export function encodeWavBase64(buffer: AudioBuffer, targetRate = 16000): string { |
| const mono = downmixMono(buffer); |
| const resampled = resampleLinear(mono, buffer.sampleRate, targetRate); |
| const wav = pcm16Wav(resampled, targetRate); |
| return arrayBufferToBase64(wav); |
| } |
|
|
| function downmixMono(buffer: AudioBuffer): Float32Array { |
| const channels = buffer.numberOfChannels; |
| if (channels === 1) return buffer.getChannelData(0); |
| const len = buffer.length; |
| const out = new Float32Array(len); |
| for (let c = 0; c < channels; c++) { |
| const data = buffer.getChannelData(c); |
| for (let i = 0; i < len; i++) out[i] += data[i] / channels; |
| } |
| return out; |
| } |
|
|
| function resampleLinear(data: Float32Array, from: number, to: number): Float32Array { |
| if (from === to) return data; |
| const ratio = from / to; |
| const outLen = Math.max(1, Math.floor(data.length / ratio)); |
| const out = new Float32Array(outLen); |
| for (let i = 0; i < outLen; i++) { |
| const idx = i * ratio; |
| const i0 = Math.floor(idx); |
| const i1 = Math.min(i0 + 1, data.length - 1); |
| const frac = idx - i0; |
| out[i] = data[i0] * (1 - frac) + data[i1] * frac; |
| } |
| return out; |
| } |
|
|
| function pcm16Wav(samples: Float32Array, rate: number): ArrayBuffer { |
| const buffer = new ArrayBuffer(44 + samples.length * 2); |
| const view = new DataView(buffer); |
| const writeStr = (off: number, s: string) => { |
| for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i)); |
| }; |
| writeStr(0, 'RIFF'); |
| view.setUint32(4, 36 + samples.length * 2, true); |
| writeStr(8, 'WAVE'); |
| writeStr(12, 'fmt '); |
| view.setUint32(16, 16, true); |
| view.setUint16(20, 1, true); |
| view.setUint16(22, 1, true); |
| view.setUint32(24, rate, true); |
| view.setUint32(28, rate * 2, true); |
| view.setUint16(32, 2, true); |
| view.setUint16(34, 16, true); |
| writeStr(36, 'data'); |
| view.setUint32(40, samples.length * 2, true); |
| let off = 44; |
| for (let i = 0; i < samples.length; i++) { |
| const s = Math.max(-1, Math.min(1, samples[i])); |
| view.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7fff, true); |
| off += 2; |
| } |
| return buffer; |
| } |
|
|
| function arrayBufferToBase64(buf: ArrayBuffer): string { |
| let binary = ''; |
| const bytes = new Uint8Array(buf); |
| const chunk = 0x8000; |
| for (let i = 0; i < bytes.length; i += chunk) { |
| binary += String.fromCharCode(...bytes.subarray(i, i + chunk)); |
| } |
| return btoa(binary); |
| } |
|
|