UNPKG

@omnimedia/omnitool

Version:

open source video processing tools

89 lines 3.43 kB
export class AudioMix { #chunkFrames; #clamp; constructor(options = {}) { this.#chunkFrames = options.chunkFrames ?? 1024; this.#clamp = options.clamp ?? true; } async *mix(samples) { const chunkFrames = this.#chunkFrames; let sampleRate = null; let channels = null; const active = []; let nextFrame = 0; let maxEnd = 0; for await (const input of samples) { if (channels === null) { channels = input.planes.length; sampleRate = input.sampleRate; } else { if (input.planes.length !== channels) throw new Error(`Channel count changed`); if (input.sampleRate !== sampleRate) throw new Error(`Sample rate changed`); } const inputStart = Math.round(input.timestamp * sampleRate); const frames = input.planes[0]?.length ?? 0; while (nextFrame + chunkFrames <= inputStart) { yield this.#processChunk(active, nextFrame, channels, sampleRate); nextFrame += chunkFrames; } active.push({ startFrame: inputStart, endFrame: inputStart + frames, data: input.planes }); maxEnd = Math.max(maxEnd, inputStart + frames); } if (channels !== null && sampleRate !== null) { while (nextFrame < maxEnd) { yield this.#processChunk(active, nextFrame, channels, sampleRate); nextFrame += chunkFrames; } } } #processChunk(active, currentStartFrame, channels, sampleRate) { const chunkFrames = this.#chunkFrames; const outputBuffer = new Float32Array(channels * chunkFrames); const chunkEnd = currentStartFrame + chunkFrames; for (let ch = 0; ch < channels; ch++) { const channelOffset = ch * chunkFrames; const outChannelView = outputBuffer.subarray(channelOffset, channelOffset + chunkFrames); for (const sample of active) { const data = sample.data[ch]; if (!data) continue; const start = Math.max(currentStartFrame, sample.startFrame); const end = Math.min(chunkEnd, sample.endFrame); if (start >= end) continue; const dstIdx = start - currentStartFrame; const srcIdx = start - sample.startFrame; const len = end - start; for (let i = 0; i < len; i++) { outChannelView[dstIdx + i] += data[srcIdx + i]; } } if (this.#clamp) { for (let i = 0; i < chunkFrames; i++) { const v = outChannelView[i]; outChannelView[i] = v < -1.0 ? -1.0 : (v > 1.0 ? 1.0 : v); } } } for (let i = active.length - 1; i >= 0; i--) { if (active[i].endFrame <= chunkEnd) { active.splice(i, 1); } } return { planar: outputBuffer, sampleRate, channels, frames: chunkFrames, startFrame: currentStartFrame }; } } //# sourceMappingURL=audio-mix.js.map