82 lines
2.2 KiB
TypeScript
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)
|
|
} |