Files
bruitiste/src/domain/audio/WavExporter.ts
2025-10-04 14:52:20 +02:00

82 lines
2.2 KiB
TypeScript

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)
}