wavesurfer.js
Version:
Interactive navigable audio visualization using Web Audio and Canvas
128 lines (119 loc) • 4.25 kB
JavaScript
/**
* Caches the decoded peaks data to improve rendering speed for large audio
*
* Is used if the option parameter `partialRender` is set to `true`
*/
export default class PeakCache {
/**
* Instantiate cache
*/
constructor() {
this.clearPeakCache();
}
/**
* Empty the cache
*/
clearPeakCache() {
/**
* Flat array with entries that are always in pairs to mark the
* beginning and end of each subrange. This is a convenience so we can
* iterate over the pairs for easy set difference operations.
* @private
*/
this.peakCacheRanges = [];
/**
* Length of the entire cachable region, used for resetting the cache
* when this changes (zoom events, for instance).
* @private
*/
this.peakCacheLength = -1;
}
/**
* Add a range of peaks to the cache
*
* @param {number} length The length of the range
* @param {number} start The x offset of the start of the range
* @param {number} end The x offset of the end of the range
* @return {Number.<Array[]>} Array with arrays of numbers
*/
addRangeToPeakCache(length, start, end) {
if (length != this.peakCacheLength) {
this.clearPeakCache();
this.peakCacheLength = length;
}
// Return ranges that weren't in the cache before the call.
let uncachedRanges = [];
let i = 0;
// Skip ranges before the current start.
while (
i < this.peakCacheRanges.length &&
this.peakCacheRanges[i] < start
) {
i++;
}
// If |i| is even, |start| falls after an existing range. Otherwise,
// |start| falls between an existing range, and the uncached region
// starts when we encounter the next node in |peakCacheRanges| or
// |end|, whichever comes first.
if (i % 2 == 0) {
uncachedRanges.push(start);
}
while (
i < this.peakCacheRanges.length &&
this.peakCacheRanges[i] <= end
) {
uncachedRanges.push(this.peakCacheRanges[i]);
i++;
}
// If |i| is even, |end| is after all existing ranges.
if (i % 2 == 0) {
uncachedRanges.push(end);
}
// Filter out the 0-length ranges.
uncachedRanges = uncachedRanges.filter((item, pos, arr) => {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
});
// Merge the two ranges together, uncachedRanges will either contain
// wholly new points, or duplicates of points in peakCacheRanges. If
// duplicates are detected, remove both and extend the range.
this.peakCacheRanges = this.peakCacheRanges.concat(uncachedRanges);
this.peakCacheRanges = this.peakCacheRanges
.sort((a, b) => a - b)
.filter((item, pos, arr) => {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
});
// Push the uncached ranges into an array of arrays for ease of
// iteration in the functions that call this.
const uncachedRangePairs = [];
for (i = 0; i < uncachedRanges.length; i += 2) {
uncachedRangePairs.push([uncachedRanges[i], uncachedRanges[i + 1]]);
}
return uncachedRangePairs;
}
/**
* For testing
*
* @return {Number.<Array[]>} Array with arrays of numbers
*/
getCacheRanges() {
const peakCacheRangePairs = [];
let i;
for (i = 0; i < this.peakCacheRanges.length; i += 2) {
peakCacheRangePairs.push([
this.peakCacheRanges[i],
this.peakCacheRanges[i + 1]
]);
}
return peakCacheRangePairs;
}
}