Init
This commit is contained in:
78
src/spectral-synthesis/audio/export.ts
Normal file
78
src/spectral-synthesis/audio/export.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Create WAV buffer from audio data
|
||||
*/
|
||||
export function createWAVBuffer(audioData: Float32Array, sampleRate: number): ArrayBuffer {
|
||||
const length = audioData.length
|
||||
const buffer = new ArrayBuffer(44 + length * 2)
|
||||
const view = new DataView(buffer)
|
||||
|
||||
// WAV header
|
||||
writeString(view, 0, 'RIFF')
|
||||
view.setUint32(4, 36 + length * 2, true) // file length - 8
|
||||
writeString(view, 8, 'WAVE')
|
||||
writeString(view, 12, 'fmt ')
|
||||
view.setUint32(16, 16, true) // format chunk length
|
||||
view.setUint16(20, 1, true) // PCM format
|
||||
view.setUint16(22, 1, true) // mono
|
||||
view.setUint32(24, sampleRate, true)
|
||||
view.setUint32(28, sampleRate * 2, true) // byte rate
|
||||
view.setUint16(32, 2, true) // block align
|
||||
view.setUint16(34, 16, true) // bits per sample
|
||||
writeString(view, 36, 'data')
|
||||
view.setUint32(40, length * 2, true) // data chunk length
|
||||
|
||||
// Convert float samples to 16-bit PCM
|
||||
let offset = 44
|
||||
for (let i = 0; i < length; i++) {
|
||||
const sample = Math.max(-1, Math.min(1, audioData[i]))
|
||||
view.setInt16(offset, sample * 0x7FFF, true)
|
||||
offset += 2
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
function writeString(view: DataView, offset: number, string: string) {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download audio as WAV file
|
||||
*/
|
||||
export function downloadWAV(audioData: Float32Array, sampleRate: number, filename: string) {
|
||||
const buffer = createWAVBuffer(audioData, sampleRate)
|
||||
const blob = new Blob([buffer], { type: 'audio/wav' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
a.click()
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Play audio in browser
|
||||
*/
|
||||
export async function playAudio(audioData: Float32Array, sampleRate: number): Promise<void> {
|
||||
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
|
||||
|
||||
if (audioContext.sampleRate !== sampleRate) {
|
||||
console.warn(`Audio context sample rate (${audioContext.sampleRate}) differs from data sample rate (${sampleRate})`)
|
||||
}
|
||||
|
||||
const buffer = audioContext.createBuffer(1, audioData.length, sampleRate)
|
||||
buffer.copyToChannel(audioData, 0)
|
||||
|
||||
const source = audioContext.createBufferSource()
|
||||
source.buffer = buffer
|
||||
source.connect(audioContext.destination)
|
||||
source.start()
|
||||
|
||||
return new Promise(resolve => {
|
||||
source.onended = () => resolve()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user