@omnimedia/omnitool
Version:
open source video processing tools
89 lines • 3.43 kB
JavaScript
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