182 lines
7.8 KiB
JavaScript
182 lines
7.8 KiB
JavaScript
import Visualizer from "./Visualizer.js";
|
|
|
|
/**@module */
|
|
|
|
/**
|
|
* @callback visualizerBinUpdateListener
|
|
* @param {number} timeDelta elapsed time since last update.
|
|
* @param {number} amplitude The amplitude of the associated bin.
|
|
* @param {number} ampDelta change in amplitude of the frequency bin.
|
|
*/
|
|
|
|
/**
|
|
* @callback visualizerRangedUpdateListener
|
|
* @param {number} timeDelta elapsed time since last update.
|
|
* @param {number} bins the bins of the range.
|
|
*/
|
|
|
|
|
|
/**
|
|
* A visualizer update manager offers an extra layer of abstraction on top of the {@link Visualizer}'s update listener.
|
|
*
|
|
* Specifically, the update manager handles updates directly from the {@link Visualizer} and checks for changes in the individual bins. These changes are then broadcasted to the individual bin listeners.
|
|
* In the rare event that a bin has not changed, then it will not receive an update call.
|
|
*/
|
|
export default class VisUpdateRouter {
|
|
#binnedListeners = [];
|
|
#rangedListeners = [];
|
|
#lastBins;
|
|
#visualizer;
|
|
#visualizerListener;
|
|
|
|
|
|
/**
|
|
*
|
|
* @param {Visualizer} visualizer the visualizer this manager obtains data from.
|
|
*/
|
|
constructor(visualizer) {
|
|
this.#binnedListeners = [];
|
|
this.#rangedListeners = [];
|
|
for (let i = 0; i < visualizer.getNumberOfBins(); i++) {
|
|
this.#binnedListeners.push([]);
|
|
}
|
|
this.#lastBins = new Uint8Array(this.#binnedListeners.length);
|
|
this.#visualizer = visualizer;
|
|
this.#visualizerListener = (delta, bins) => {
|
|
const sortedCopyOfRangedListeners = [... this.#rangedListeners]; // We assume this is sorted properly. A priority queue could be better.
|
|
for (let binInd = 0; binInd < this.#lastBins.length; binInd++) {
|
|
const lastBin = this.#lastBins[binInd];
|
|
if (lastBin !== bins[binInd]) {
|
|
this.#binnedListeners[binInd].forEach(listener => {
|
|
listener(delta, bins[binInd], bins[binInd] - lastBin);
|
|
});
|
|
for (let rangedInd = 0; rangedInd < sortedCopyOfRangedListeners.length; rangedInd++) { // Could switch to a while loop.
|
|
const { lower, upper, listener } = sortedCopyOfRangedListeners[rangedInd];
|
|
if (lower > binInd) break; // Don't need to check the rest if the current lowest minimum is greater than the current bin index.
|
|
if (binInd <= upper) {
|
|
listener(delta, bins.slice(lower, upper));
|
|
sortedCopyOfRangedListeners.shift();
|
|
rangedInd--;
|
|
}
|
|
}
|
|
this.#lastBins[binInd] = bins[binInd];
|
|
}
|
|
}
|
|
};
|
|
visualizer.addUpdateListener(this.#visualizerListener);
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds a listener to a specific frequency bin.
|
|
*
|
|
* @param {object} freqBinListener the listener for a specific frequency bin.
|
|
* @param {number} freqBinListener.freqBin the frequency bin this update listener should listen to.
|
|
* @param {visualizerBinUpdateListener} freqBinListener.listener the listener itself that will be called upon the bin updating.
|
|
* @returns {boolean} true if and only if the updater was added successfully.
|
|
*/
|
|
AddVisualizerBinUpdateListener({ freqBin, listener }) {
|
|
if (this.#binnedListeners[freqBin].includes(listener)) return false;
|
|
this.#binnedListeners[freqBin].push(listener);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Similar to {@link VisUpdateRouter#AddVisualizerBinUpdateListener}, this method adds a listener for to a range of bins.
|
|
*
|
|
* @param {object} rangedUpdateListener The ranged update listener to add.
|
|
* @param {number} rangedUpdateListener.lower The lower bound of the bins to listen to (inclusive).
|
|
* @param {number} rangedUpdateListener.upper The upper bound of the bins to listen to (inclusive).
|
|
* @param {visualizerRangedUpdateListener} rangedUpdateListener.listener The listener to register to the range.
|
|
* @returns {boolean} True if and only if the ranged listener was added successfully.
|
|
*/
|
|
addVisualizerRangedUpdateListener({ lower, upper, listener }) {
|
|
const rangedListener = {
|
|
lower: lower,
|
|
upper: upper,
|
|
listener: listener
|
|
};
|
|
if (this.#rangedListeners.includes(rangedListener)) return false;
|
|
this.#rangedListeners.push(rangedListener);
|
|
this.#rangedListeners.sort((a, b) => a.lower - b.lower);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} binFreqListener The bin frequency listener to remove.
|
|
* @param {number} binFreqListener.freqBin the frequency bin the update listener to be removed from is in.
|
|
* @param {visualizerBinUpdateListener} binFreqListener.listener the listener that is to be removed.
|
|
* @returns {boolean} true if and only if the listener was successfully removed.
|
|
*/
|
|
removeVisualizerBinUpdateListener({ freqBin, listener }) {
|
|
const removeIndex = this.#binnedListeners[freqBin].indexOf(listener);
|
|
if (removeIndex < 0) return false;
|
|
this.#binnedListeners[freqBin].splice(removeIndex, 1);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Similar to {@link removeVisualizerBinUpdateListener}, this method removes the given listener from a range of bins.
|
|
*
|
|
* @param {object} rangedListener The ranged listener to remove.
|
|
* @param {number} rangedListener.lower The lower bound of bins to remove the listener from (inclusive).
|
|
* @param {number} rangedListener.upper The upper bound of bin to remove the listener from (inclusive.)
|
|
* @param {visualizerRangedUpdateListener} rangedListener.listener The update listener to remove from the bins.
|
|
* @returns {boolean} True if and only if the given listener was removed.
|
|
*/
|
|
removeVisualizerRangedUpdateListener({ lower, upper, listener }) {
|
|
const rangedListener = {
|
|
lower: lower,
|
|
upper: upper,
|
|
listener: listener
|
|
};
|
|
const removeIndex = this.#rangedListeners.indexOf(rangedListener);
|
|
if (removeIndex < 0) return false;
|
|
this.#binnedListeners.splice(removeIndex, 1);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {object} listeners The listeners that this visualizer update manager should be set to use.
|
|
* @param {visualizerBinUpdateListener[][]} listeners.binned The bin listeners.
|
|
* @param {object[]} listeners.ranged The array of ranged listeners.
|
|
* @param {number} listeners.ranged[].lower The lower bound of the listener.
|
|
* @param {number} listeners.ranged[].upper The upper bound of the listener.
|
|
* @param {visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges.
|
|
* @returns {boolean} true if and only if successfully loaded the new listeners.
|
|
*/
|
|
loadListeners(listeners) {
|
|
if (listeners.binned.length !== this.#binnedListeners.length) return false;
|
|
this.#binnedListeners = listeners.binned;
|
|
this.#rangedListeners = listeners.ranged.sort((a, b) => a.lower - b.lower);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {object} All the listeners, both for binned, and ranged listeners. See {@link VisUpdateRouter#loadListeners} to see the structure of the returned object.
|
|
*/
|
|
retrieveListeners() {
|
|
return {
|
|
binned: this.#binnedListeners,
|
|
ranged: this.#rangedListeners
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clears this manager of all listeners.
|
|
*/
|
|
clearBinnedListeners() {
|
|
this.#binnedListeners.forEach(bin => {
|
|
bin.length = 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Unbinds this update manager from the initial visualizer. Effectively meaning this manager will no longer be used.
|
|
*/
|
|
unbindVisualizer() {
|
|
this.#visualizer.removeUpdateListener(this.#visualizerListener);
|
|
}
|
|
} |