export type BitDepth = 8 | 16 | 24 export interface ExportOptions { sampleRate: number bitDepth?: BitDepth } function encodeWAV(samples: Float32Array, sampleRate: number, bitDepth: BitDepth): Blob { const numChannels = 1 const bytesPerSample = bitDepth / 8 const blockAlign = numChannels * bytesPerSample const byteRate = sampleRate * blockAlign const dataSize = samples.length * bytesPerSample const bufferSize = 44 + dataSize const buffer = new ArrayBuffer(bufferSize) const view = new DataView(buffer) const writeString = (offset: number, string: string) => { for (let i = 0; i < string.length; i++) { view.setUint8(offset + i, string.charCodeAt(i)) } } writeString(0, 'RIFF') view.setUint32(4, 36 + dataSize, true) writeString(8, 'WAVE') writeString(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, bitDepth, true) writeString(36, 'data') view.setUint32(40, dataSize, true) const maxValue = Math.pow(2, bitDepth - 1) - 1 let offset = 44 for (let i = 0; i < samples.length; i++) { const sample = Math.max(-1, Math.min(1, samples[i])) const intSample = Math.round(sample * maxValue) if (bitDepth === 8) { view.setUint8(offset, intSample + 128) offset += 1 } else if (bitDepth === 16) { view.setInt16(offset, intSample, true) offset += 2 } else if (bitDepth === 24) { const bytes = [ intSample & 0xff, (intSample >> 8) & 0xff, (intSample >> 16) & 0xff ] view.setUint8(offset, bytes[0]) view.setUint8(offset + 1, bytes[1]) view.setUint8(offset + 2, bytes[2]) offset += 3 } } return new Blob([buffer], { type: 'audio/wav' }) } export function exportToWav( samples: Float32Array, options: ExportOptions ): Blob { const bitDepth = options.bitDepth || 8 return encodeWAV(samples, options.sampleRate, bitDepth) } export function createDownloadUrl(blob: Blob): string { return URL.createObjectURL(blob) } export function revokeDownloadUrl(url: string): void { URL.revokeObjectURL(url) }