UNPKG

use-audio-recorder

Version:

react hooks use audio recorder: PCM

116 lines (100 loc) 3.62 kB
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)) } }