UNPKG

@soundcut/decode-audio-data-fast

Version:

Decode audio data asynchronously, in the browser using Base​Audio​Context​.decode​Audio​Data(), as fast as possible.

158 lines (132 loc) 4.46 kB
import parser from 'mp3-parser'; const CHUNK_MAX_SIZE = 1000 * 1000; const DEFAULT_CONCURRENCY = 4; const CONCURRENCY = ((typeof navigator !== 'undefined' && navigator.hardwareConcurrency) || 1) > 2 ? navigator.hardwareConcurrency : DEFAULT_CONCURRENCY; /** * Creates a new ArrayBuffer out of two Uint8Arrays * * @private * @param {Uint8Array} baseUint8Array first Uint8Array. * @param {Uint8Array} buffer second Uint8Array. * @return {ArrayBuffer} The new ArrayBuffer */ function makeChunk(array1, array2) { const tmp = new Uint8Array(array1.byteLength + array2.byteLength); tmp.set(array1, 0); tmp.set(array2, array1.byteLength); return tmp.buffer; } function makeSaveChunk(chunkArrayBuffers, tagsUInt8Array, sourceUInt8Array) { return function saveChunk(chunk) { chunkArrayBuffers.push( makeChunk( tagsUInt8Array, sourceUInt8Array.subarray( chunk.frames[0]._section.offset, chunk.frames[chunk.frames.length - 1]._section.offset + chunk.frames[chunk.frames.length - 1]._section.byteLength ) ) ); }; } function emptyChunk(chunk) { chunk.byteLength = 0; chunk.frames.length = 0; } function addChunkFrame(chunk, frame) { chunk.byteLength = chunk.byteLength + frame._section.byteLength; chunk.frames.push(frame); } const asyncWorker = (source, items, fn, output) => async () => { let next; while ((next = items.pop())) { output[source.get(next)] = await fn(next); } }; function getArrayBuffer(file) { return new Promise((resolve) => { let fileReader = new FileReader(); fileReader.onloadend = () => { resolve(fileReader.result); }; fileReader.readAsArrayBuffer(file); }); } // Use a promise wrapper on top of event based syntax // for browsers (Safari) which do not support promise-based syntax. function decodeArrayBuffer(audioCtx, arrayBuffer) { return new Promise(audioCtx.decodeAudioData.bind(audioCtx, arrayBuffer)); } async function getFileAudioBuffer(file, audioCtx, options = {}) { /* Copyright (c) 2019, Timothée 'Tim' Pillard, @ziir @tpillard - ISC */ const { native = false, concurrency = CONCURRENCY } = options; const arrayBuffer = await getArrayBuffer(file); if (native) { return decodeArrayBuffer(audioCtx, arrayBuffer); } const safari = !!window.webkitAudioContext; if (safari) { return getFileAudioBuffer(file, audioCtx, { native: true }); } const view = new DataView(arrayBuffer); const tags = parser.readTags(view); const firstFrame = tags.pop(); const uInt8Array = new Uint8Array(arrayBuffer); const tagsUInt8Array = uInt8Array.subarray(0, firstFrame._section.offset); const chunkArrayBuffers = []; const saveChunk = makeSaveChunk( chunkArrayBuffers, tagsUInt8Array, uInt8Array ); let chunk = { byteLength: 0, frames: [] }; let next = firstFrame._section.offset + firstFrame._section.byteLength; while (next) { const frame = parser.readFrame(view, next); next = frame && frame._section.nextFrameIndex; if (frame) { const chunkEnd = chunk && chunk.byteLength + frame._section.byteLength >= CHUNK_MAX_SIZE; if (chunkEnd) { saveChunk(chunk); emptyChunk(chunk); } addChunkFrame(chunk, frame); } if (chunk && (!frame || !next)) { saveChunk(chunk); } } const workers = []; const source = new Map(chunkArrayBuffers.map((chunk, idx) => [chunk, idx])); const audioBuffers = new Array(chunkArrayBuffers.length); const decode = decodeArrayBuffer.bind(null, audioCtx); for (let i = 0; i < Math.min(concurrency, source.size); i++) { workers.push( asyncWorker(source, chunkArrayBuffers, decode, audioBuffers)() ); } await Promise.all(workers); const { numberOfChannels, sampleRate } = audioBuffers[0]; let length = audioBuffers.reduce((acc, current) => acc + current.length, 0); const audioBuffer = audioCtx.createBuffer( numberOfChannels, length, sampleRate ); for (let j = 0; j < numberOfChannels; j++) { const channelData = audioBuffer.getChannelData(j); let offset = 0; for (let i = 0; i < audioBuffers.length; i++) { channelData.set(audioBuffers[i].getChannelData(j), offset); offset += audioBuffers[i].length; } } return audioBuffer; } export { getFileAudioBuffer }; //# sourceMappingURL=decode-audio-data-fast.esm.js.map