use-audio-recorder
Version:
react hooks use audio recorder: PCM
116 lines (100 loc) • 3.62 kB
text/typescript
interface IOptions {
bitDepth: 8 | 16 | 32
}
export function audioBufferToWav (buffer: AudioBuffer, options?: IOptions): ArrayBuffer {
const numChannels = buffer.numberOfChannels
const sampleRate = buffer.sampleRate
/*format 8bit and 16bi format = 1 , 32bit format = 3 */
const format = options?.bitDepth === 32 ? 3 : 1
const bitDepth = options?.bitDepth || 16
let result: Float32Array
if (numChannels === 2) {
/*2 channels*/
result = interleave(buffer.getChannelData(0), buffer.getChannelData(1))
} else {
/*1 channel*/
result = buffer.getChannelData(0)
}
return encodeWAV(result, format, sampleRate, numChannels, bitDepth)
}
function encodeWAV (samples: Float32Array, format: number, sampleRate: number, numChannels: number, bitDepth: 8 | 16 | 32): ArrayBuffer {
const bytesPerSample = bitDepth / 8
const blockAlign = numChannels * bytesPerSample
/* length = pcm body and 44 wav header*/
let buffer = new ArrayBuffer(44 + samples.length * bytesPerSample)
let view = new DataView(buffer)
/* RIFF identifier */
writeString(view, 0, 'RIFF')
/* RIFF chunk length */
view.setUint32(4, 36 + samples.length * bytesPerSample, true)
/* RIFF type */
writeString(view, 8, 'WAVE')
/* format chunk identifier */
writeString(view, 12, 'fmt ')
/* format chunk length */
view.setUint32(16, 16, true)
/* sample format (raw) */
view.setUint16(20, format, true)
/* channel count */
view.setUint16(22, numChannels, true)
/* sample rate */
view.setUint32(24, sampleRate, true)
/* byte rate (sample rate * block align) */
view.setUint32(28, sampleRate * blockAlign, true)
/* block align (channel count * bytes per sample) */
view.setUint16(32, blockAlign, true)
/* bits per sample */
view.setUint16(34, bitDepth, true)
/* data chunk identifier */
writeString(view, 36, 'data')
/* data chunk length */
view.setUint32(40, samples.length * bytesPerSample, true)
switch (bitDepth) {
case 8:
floatTo8BitPCM(view, 44, samples)
break
case 16:
floatTo16BitPCM(view, 44, samples)
break
case 32:
writeFloat32(view, 44, samples)
}
return buffer
}
/* interleave merge left and right audio */
function interleave (inputL: Float32Array, inputR: Float32Array): Float32Array {
const length = inputL.length + inputR.length
let result = new Float32Array(length)
let index = 0
let inputIndex = 0
while (index < length) {
result[index++] = inputL[inputIndex]
result[index++] = inputR[inputIndex]
inputIndex++
}
return result
}
function writeFloat32 (output: DataView, offset: number, input: Float32Array): void {
for (let i = 0; i < input.length; i++, offset += 4) {
output.setFloat32(offset, input[i], true)
}
}
function floatTo16BitPCM (output: DataView, offset: number, input: Float32Array): void {
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]))
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
}
}
function floatTo8BitPCM (output: DataView, offset: number, input: Float32Array): void {
for (let i = 0; i < input.length; i++, offset++) {
let s = Math.max(-1, Math.min(1, input[i]))
let val = s < 0 ? s * 128 : s * 127
val = +val + 128
output.setInt8(offset, val)
}
}
function writeString (view: DataView, offset: number, string: string): void {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i))
}
}