UNPKG

wavewave

Version:

Record and stream WAV audio data in the browser across all platforms

114 lines (108 loc) 3.3 kB
/** * Raw wav audio file contents * @typedef {Object} WavPackerAudioType * @property {Blob} blob * @property {string} url * @property {number} channelCount * @property {number} sampleRate * @property {number} duration */ /** * Utility class for assembling PCM16 "audio/wav" data * @class */ export class WavPacker { /** * Converts Float32Array of amplitude data to ArrayBuffer in Int16Array format * @param {Float32Array} float32Array * @returns {ArrayBuffer} */ static floatTo16BitPCM(float32Array) { const buffer = new ArrayBuffer(float32Array.length * 2) const view = new DataView(buffer) let offset = 0 for (let i = 0; i < float32Array.length; i++, offset += 2) { let s = Math.max(-1, Math.min(1, float32Array[i])) view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true) } return buffer } /** * Concatenates two ArrayBuffers * @param {ArrayBuffer} leftBuffer * @param {ArrayBuffer} rightBuffer * @returns {ArrayBuffer} */ static mergeBuffers(leftBuffer, rightBuffer) { const tmpArray = new Uint8Array( leftBuffer.byteLength + rightBuffer.byteLength, ) tmpArray.set(new Uint8Array(leftBuffer), 0) tmpArray.set(new Uint8Array(rightBuffer), leftBuffer.byteLength) return tmpArray.buffer } /** * Packs data into an Int16 format * @private * @param {number} size 0 = 1x Int16, 1 = 2x Int16 * @param {number} arg value to pack * @returns */ _packData(size, arg) { return [ new Uint8Array([arg, arg >> 8]), new Uint8Array([arg, arg >> 8, arg >> 16, arg >> 24]), ][size] } /** * Packs audio into "audio/wav" Blob * @param {number} sampleRate * @param {{bitsPerSample: number, channels: Array<Float32Array>, data: Int16Array}} audio * @returns {WavPackerAudioType} */ pack(sampleRate, audio) { if (!audio?.bitsPerSample) { throw new Error(`Missing "bitsPerSample"`) } else if (!audio?.channels) { throw new Error(`Missing "channels"`) } else if (!audio?.data) { throw new Error(`Missing "data"`) } const { bitsPerSample, channels, data } = audio const output = [ // Header "RIFF", this._packData( 1, 4 + (8 + 24) /* chunk 1 length */ + (8 + 8) /* chunk 2 length */, ), // Length "WAVE", // chunk 1 "fmt ", // Sub-chunk identifier this._packData(1, 16), // Chunk length this._packData(0, 1), // Audio format (1 is linear quantization) this._packData(0, channels.length), this._packData(1, sampleRate), this._packData(1, (sampleRate * channels.length * bitsPerSample) / 8), // Byte rate this._packData(0, (channels.length * bitsPerSample) / 8), this._packData(0, bitsPerSample), // chunk 2 "data", // Sub-chunk identifier this._packData( 1, (channels[0].length * channels.length * bitsPerSample) / 8, ), // Chunk length data, ] const blob = new Blob(output, { type: "audio/mpeg" }) const url = URL.createObjectURL(blob) return { blob, url, channelCount: channels.length, sampleRate, duration: data.byteLength / (channels.length * sampleRate * 2), } } } globalThis.WavPacker = WavPacker