|
|
|
export class AudioUtils { |
|
static async createPCM16Data(audioBuffer: AudioBuffer): Promise<ArrayBuffer> { |
|
const numChannels = 1; |
|
const sampleRate = 16000; |
|
const format = 1; |
|
const bitDepth = 16; |
|
|
|
|
|
let samples = audioBuffer.getChannelData(0); |
|
if (audioBuffer.sampleRate !== sampleRate) { |
|
samples = await this.resampleAudio(samples, audioBuffer.sampleRate, sampleRate); |
|
} |
|
|
|
const dataLength = samples.length * (bitDepth / 8); |
|
const headerLength = 44; |
|
const totalLength = headerLength + dataLength; |
|
|
|
const buffer = new ArrayBuffer(totalLength); |
|
const view = new DataView(buffer); |
|
|
|
|
|
this.writeString(view, 0, 'RIFF'); |
|
view.setUint32(4, totalLength - 8, true); |
|
this.writeString(view, 8, 'WAVE'); |
|
this.writeString(view, 12, 'fmt '); |
|
view.setUint32(16, 16, true); |
|
view.setUint16(20, format, true); |
|
view.setUint16(22, numChannels, true); |
|
view.setUint32(24, sampleRate, true); |
|
view.setUint32(28, sampleRate * numChannels * (bitDepth / 8), true); |
|
view.setUint16(32, numChannels * (bitDepth / 8), true); |
|
view.setUint16(34, bitDepth, true); |
|
this.writeString(view, 36, 'data'); |
|
view.setUint32(40, dataLength, true); |
|
|
|
|
|
this.floatTo16BitPCM(view, 44, samples); |
|
|
|
return buffer; |
|
} |
|
|
|
static writeString(view: DataView, offset: number, string: string): void { |
|
for (let i = 0; i < string.length; i++) { |
|
view.setUint8(offset + i, string.charCodeAt(i)); |
|
} |
|
} |
|
|
|
static floatTo16BitPCM(view: DataView, offset: number, input: Float32Array): void { |
|
for (let i = 0; i < input.length; i++, offset += 2) { |
|
const s = Math.max(-1, Math.min(1, input[i])); |
|
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); |
|
} |
|
} |
|
|
|
static async resampleAudio( |
|
audioData: Float32Array, |
|
originalSampleRate: number, |
|
targetSampleRate: number |
|
): Promise<Float32Array> { |
|
const originalLength = audioData.length; |
|
const ratio = targetSampleRate / originalSampleRate; |
|
const newLength = Math.round(originalLength * ratio); |
|
const result = new Float32Array(newLength); |
|
|
|
for (let i = 0; i < newLength; i++) { |
|
const position = i / ratio; |
|
const index = Math.floor(position); |
|
const fraction = position - index; |
|
|
|
if (index + 1 < originalLength) { |
|
result[i] = audioData[index] * (1 - fraction) + audioData[index + 1] * fraction; |
|
} else { |
|
result[i] = audioData[index]; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
} |
|
|
|
export default AudioUtils; |
|
|