| const TARGET_SAMPLE_RATE = 16000; |
|
|
| function writeString(view: DataView, offset: number, text: string) { |
| for (let i = 0; i < text.length; i++) { |
| view.setUint8(offset + i, text.charCodeAt(i)); |
| } |
| } |
|
|
| export function encodeWav(samples: Float32Array, sampleRate: number): Blob { |
| const numChannels = 1; |
| const bitsPerSample = 16; |
| const blockAlign = (numChannels * bitsPerSample) / 8; |
| const byteRate = sampleRate * blockAlign; |
| const dataSize = samples.length * blockAlign; |
| const buffer = new ArrayBuffer(44 + dataSize); |
| const view = new DataView(buffer); |
|
|
| writeString(view, 0, "RIFF"); |
| view.setUint32(4, 36 + dataSize, true); |
| writeString(view, 8, "WAVE"); |
| writeString(view, 12, "fmt "); |
| view.setUint32(16, 16, true); |
| view.setUint16(20, 1, true); |
| view.setUint16(22, numChannels, true); |
| view.setUint32(24, sampleRate, true); |
| view.setUint32(28, byteRate, true); |
| view.setUint16(32, blockAlign, true); |
| view.setUint16(34, bitsPerSample, true); |
| writeString(view, 36, "data"); |
| view.setUint32(40, dataSize, true); |
|
|
| let offset = 44; |
| for (let i = 0; i < samples.length; i++) { |
| const s = Math.max(-1, Math.min(1, samples[i])); |
| view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); |
| offset += 2; |
| } |
|
|
| return new Blob([buffer], { type: "audio/wav" }); |
| } |
|
|
| export async function resampleTo16kMono(audioBuffer: AudioBuffer): Promise<Float32Array> { |
| if (audioBuffer.sampleRate === TARGET_SAMPLE_RATE && audioBuffer.numberOfChannels === 1) { |
| return audioBuffer.getChannelData(0).slice(); |
| } |
|
|
| const duration = audioBuffer.duration; |
| const offline = new OfflineAudioContext( |
| 1, |
| Math.ceil(duration * TARGET_SAMPLE_RATE), |
| TARGET_SAMPLE_RATE, |
| ); |
| const source = offline.createBufferSource(); |
| source.buffer = audioBuffer; |
| source.connect(offline.destination); |
| source.start(0); |
| const rendered = await offline.startRendering(); |
| return rendered.getChannelData(0).slice(); |
| } |
|
|
| export { TARGET_SAMPLE_RATE }; |
|
|