Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Utility functions for [_AudioBuffers_](https://github.com/audiojs/audio-buffer)
* [util.normalize(buf, dst?, start?, end?)](#utilnormalizebuffer-target-start0-end-0)
* [util.removeStatic(buf, dst?, start?, end?)](#utilremovestaticbuffer-target-start0-end-0)
* [util.trim(buf, lvl)](#utiltrimbuffer-threshold0)
* [util.mix(a, b, amt?, off?)](#utilmixbuffera-bufferb-ratiovala-valb-i-channelval-offset0)
* [util.mix(a, b, amt?, off?, longest?)](#utilmixbuffera-bufferb-ratiovala-valb-i-channelval-offset0)
* [util.size(buf)](#utilsizebuffer)
* [util.data(buf, dst?)](#utildatabuffer-data)

Expand Down Expand Up @@ -194,8 +194,8 @@ a.getChannelData(1) // [-.1, .1]

Create buffer with trimmed zeros from the start and/or end, by the threshold amplitude.

### `util.mix(bufferA, bufferB, ratio|(valA, valB, i, channel)=>val?, offset=0)`
Mix second buffer into the first one. Pass optional weight value or mixing function.
### `util.mix(bufferA, bufferB, ratio|(valA, valB, i, channel)=>val?, offset=0, longest=false)`
Mix second buffer into the first one. Pass optional weight value or mixing function. If longest parameter is passed, function will keep mixing until the longest of either `(bufferA)` or `(offset + bufferB)` has finished. For example, if `bufferA.length = 10`, `offset = 5` and `bufferB.length = 7`, then the total length will be `12` since `(offset + bufferB.length)` overruns `bufferA.length` by `2`.

### `util.size(buffer)`
Return buffer size, in bytes. Use [pretty-bytes](https://npmjs.org/package/pretty-bytes) package to format bytes to a string, if needed.
Expand Down
47 changes: 43 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -562,28 +562,67 @@ function trimInternal(buffer, level, trimLeft, trimRight) {
* is reduced amount of calculations and flexibility.
* If required, the cloning can be done before mixing, which will be the same.
*/
function mix (bufferA, bufferB, ratio, offset) {
function mix (bufferA, bufferB, ratio, offset, longest) {
validate(bufferA);
validate(bufferB);


if (ratio == null) ratio = 0.5;
var fn = ratio instanceof Function ? ratio : function (a, b) {
return a * (1 - ratio) + b * ratio;
};

if (offset == null) offset = 0;
else if (offset < 0) offset += bufferA.length;

if (longest) {
return mixLongest(bufferA, bufferB, fn, offset);
} else {
for (var channel = 0; channel < bufferA.numberOfChannels; channel++) {
var aData = bufferA.getChannelData(channel);
var bData = bufferB.getChannelData(channel);

for (var i = offset, j = 0; i < bufferA.length && j < bufferB.length; i++, j++) {
aData[i] = fn.call(bufferA, aData[i], bData[j], j, channel);
}
}

return bufferA;
}
}

/**
* Mix as far as possible. This means to make the resulting buffer as large as
* the max of (offset + bufferB) or bufferA. So if offset + bufferB finishes
* after bufferA, keep mixing in bufferB after bufferA has finished.
*/
function mixLongest(bufferA, bufferB, fn, offset) {
const newLen = Math.max(offset + bufferB.length, bufferA.length);
var newBuf = create(newLen, bufferA.numberOfChannels, bufferA.sampleRate);

// Copy bufferA so we can start mixing at the offset later
copy(bufferA, newBuf);

for (var channel = 0; channel < bufferA.numberOfChannels; channel++) {
var newData = newBuf.getChannelData(channel);
var aData = bufferA.getChannelData(channel);
var bData = bufferB.getChannelData(channel);

for (var i = offset, j = 0; i < bufferA.length && j < bufferB.length; i++, j++) {
aData[i] = fn.call(bufferA, aData[i], bData[j], j, channel);
for (var i = 0; i < newLen; i++) {
var a = aData[i+offset] === undefined ? 0 : aData[i+offset];
var b = bData[i] === undefined ? 0 : bData[i];

if(a === 0) {
newData[i+offset] = b;
} else if (b === 0) {
newData[i+offset] = a;
} else {
newData[i+offset] = fn.call(bufferA, a, b, i, channel);
}
}
}

return bufferA;
return newBuf;
}


Expand Down
24 changes: 24 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,30 @@ test('mix', function (t) {
t.throws(function () {
util.mix([1,2,3], [4,5,6], 0.1);
});

// Offset mix
// Starting with normal mix to see normal result
var a = AudioBuffer(2, [1,1,1,1]);
var b = AudioBuffer(2, [0,0,0,0]);
util.mix(a, b, null);
t.deepEqual(a.getChannelData(0), [0.5,0.5])
t.deepEqual(a.getChannelData(1), [0.5,0.5])

// Mix in at offset one, i.e. keep a's data until index 2
var a = AudioBuffer(2, [1,1,1,1]);
var b = AudioBuffer(2, [0,0,0,0]);
util.mix(a, b, null,1);
t.deepEqual(a.getChannelData(0), [1,0.5])
t.deepEqual(a.getChannelData(1), [1,0.5])

// Mix with longest setting. Will keep b's data after
// a finishes with respect to offset
var a = AudioBuffer(2, [1,1,1,1]);
var b = AudioBuffer(2, [0,0,0,0,1,1,1,1]);
var newBuf = util.mix(a, b, null, 1, true);
t.deepEqual(newBuf.getChannelData(0), [1,1,0,0,0])
t.deepEqual(newBuf.getChannelData(1), [1,1,1,1,1])

t.end()
});

Expand Down