RASMUS's picture
Upload webapp/src/audio.ts with huggingface_hub
816687b verified
import { APP_CONFIG, getFinnishUrl } from './config'
function interleaveMonoPcm16(samples: Float32Array): Int16Array {
const pcm = new Int16Array(samples.length)
for (let index = 0; index < samples.length; index += 1) {
const value = Math.max(-1, Math.min(1, samples[index]))
pcm[index] = value < 0 ? value * 0x8000 : value * 0x7fff
}
return pcm
}
export function createWavBlob(samples: Float32Array, sampleRate: number): Blob {
const pcm = interleaveMonoPcm16(samples)
const headerSize = 44
const buffer = new ArrayBuffer(headerSize + pcm.byteLength)
const view = new DataView(buffer)
const writeString = (offset: number, value: string) => {
for (let index = 0; index < value.length; index += 1) {
view.setUint8(offset + index, value.charCodeAt(index))
}
}
writeString(0, 'RIFF')
view.setUint32(4, 36 + pcm.byteLength, true)
writeString(8, 'WAVE')
writeString(12, 'fmt ')
view.setUint32(16, 16, true)
view.setUint16(20, 1, true)
view.setUint16(22, 1, true)
view.setUint32(24, sampleRate, true)
view.setUint32(28, sampleRate * 2, true)
view.setUint16(32, 2, true)
view.setUint16(34, 16, true)
writeString(36, 'data')
view.setUint32(40, pcm.byteLength, true)
new Int16Array(buffer, headerSize).set(pcm)
return new Blob([buffer], { type: 'audio/wav' })
}
async function decodeAudioBuffer(data: ArrayBuffer): Promise<AudioBuffer> {
const context = new AudioContext()
try {
return await context.decodeAudioData(data.slice(0))
} finally {
await context.close()
}
}
async function resampleMono(audioBuffer: AudioBuffer, sampleRate: number): Promise<Float32Array> {
const offline = new OfflineAudioContext(
1,
Math.ceil(audioBuffer.duration * sampleRate),
sampleRate,
)
const source = offline.createBufferSource()
const mono = offline.createBuffer(1, audioBuffer.length, audioBuffer.sampleRate)
const left = audioBuffer.getChannelData(0)
if (audioBuffer.numberOfChannels === 1) {
mono.copyToChannel(left, 0)
} else {
const mixed = mono.getChannelData(0)
for (let index = 0; index < mixed.length; index += 1) {
let sum = 0
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel += 1) {
sum += audioBuffer.getChannelData(channel)[index]
}
mixed[index] = sum / audioBuffer.numberOfChannels
}
}
source.buffer = mono
source.connect(offline.destination)
source.start()
const rendered = await offline.startRendering()
return rendered.getChannelData(0).slice()
}
export async function loadReferenceAudio(): Promise<Float32Array> {
const response = await fetch(getFinnishUrl(APP_CONFIG.paths.referenceAudio))
if (!response.ok) {
throw new Error(`Failed to load reference audio: ${response.status}`)
}
const decoded = await decodeAudioBuffer(await response.arrayBuffer())
return resampleMono(decoded, APP_CONFIG.sampleRate)
}