diff --git a/src/mappings/coloring.js b/src/mappings/coloring.js index bd42a0d..3bbec1b 100644 --- a/src/mappings/coloring.js +++ b/src/mappings/coloring.js @@ -1,12 +1,12 @@ import { parseColor, rgbaToHexRgba } from "../support/colors.js"; import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; -import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js"; +import { numerical } from "./numeric.js"; /**@module */ /** - * Maps the red component of the text color to a certain range of bins. + * Maps a color component of an element background color to a certain range of frequency bins. * * @param {object} conf The configuration of the mapping. * @param {HTMLElement} conf.element The element whose text's red value should be mapped. @@ -18,9 +18,53 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js"; * @param {boolean} [conf.reversed=true] If true, then the quieter, the greater the red value. * @param {number} [conf.lowerVal=0] The lower boundary of possible values for the color component (0 to upperVal inclusive). * @param {number} [conf.upperVal=0] The upper boundary of possible values for the color component (0 to 255 inclusive). - * @returns {{bin: number, listener: VisUpdateRouter~visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added. + * @returns {{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added. */ -export function mapFontRgba({ element, color, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { +export function backgroundColorRgba({ element, color, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { + const rgbaStr = "rgba"; + color = rgbaStr.indexOf(color); + if (color < 0) throw new Error("Invalid color parameter provided."); + const getter = () => { + if (!element.style.backgroundColor) element.style.backgroundColor = "rgba(0, 0, 0, 255)"; + return parseColor(element.style.backgroundColor)[color]; + }; + const setter = (value) => { + const changed = parseColor(element.style.backgroundColor); + changed[color] = value; + element.style.backgroundColor = rgbaToHexRgba(changed); + }; + upperVal = Math.min(Math.max(0, upperVal), 255); + lowerVal = Math.min(Math.max(0, lowerVal), upperVal); + const conf = { + minVal: lowerVal, + maxVal: upperVal, + lowerBin: lowerBin, + upperBin: upperBin, + getter: getter, + setter: setter, + interpolator: interpolator, + visUpdateRouter: visUpdateRouter, + reversed: reversed + }; + return numerical(conf); +} + +/** + * Maps a color component of the text color to a certain range of frequency bins. + * + * @param {object} conf The configuration of the mapping. + * @param {HTMLElement} conf.element The element whose text's red value should be mapped. + * @param {number} conf.color Where r for red, g, for green, b for blue, and a for alpha. + * @param {number} conf.lowerBin The lower bound of the bins to be mapped. + * @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with. + * @param {Function} conf.interpolator The interpolation function to use. + * @param {number} [conf.upperBin=undefined] The upper bound of the bins to be mapped. If left undefined, then only the bin defined by the min value is used. + * @param {boolean} [conf.reversed=true] If true, then the quieter, the greater the red value. + * @param {number} [conf.lowerVal=0] The lower boundary of possible values for the color component (0 to upperVal inclusive). + * @param {number} [conf.upperVal=0] The upper boundary of possible values for the color component (0 to 255 inclusive). + * @returns {{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added. + */ +export function fontColorRgba({ element, color, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { const rgbaStr = "rgba"; color = rgbaStr.indexOf(color); if (color < 0) throw new Error("Invalid color parameter provided."); @@ -38,18 +82,13 @@ export function mapFontRgba({ element, color, lowerBin, visUpdateRouter, interpo const conf = { minVal: lowerVal, maxVal: upperVal, - bin: lowerBin, + lowerBin: lowerBin, + upperBin: upperBin, getter: getter, setter: setter, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - if (upperBin) { - conf.bin = undefined; - conf.lowerBin = lowerBin; - conf.upperBin = upperBin; - return mapRangedAvgNumerical(conf); - } - return mapBinNumerical(conf); + return numerical(conf); } diff --git a/src/mappings/dimensions.js b/src/mappings/dimensions.js index 741b20d..a566a27 100644 --- a/src/mappings/dimensions.js +++ b/src/mappings/dimensions.js @@ -1,5 +1,5 @@ import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; -import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js"; +import { numerical } from "./numeric.js"; /**@module */ @@ -18,27 +18,21 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js"; * @param {boolean} [conf.reversed=false] If true, then high amplitudes are mapped to lower values and vice versa. * @returns {{bin: number, listener: VisUpdateRouter~visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisUpdateRouter.visualizerRangedUpdateListener}} The listener that was added (ranged if an upper bound was provided, binned otherwise). */ -export function mapWidth({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function width({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { const getter = () => parseInt(element.style.width) || 0; const setter = (value) => element.style.width = value + unit; const conf = { minVal: growLower, maxVal: growUpper, - bin: lowerBin, + lowerBin: lowerBin, + upperBin: upperBin, getter: getter, setter: setter, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - - if (upperBin) { - conf.bin = undefined; - conf.lowerBin = lowerBin; - conf.upperBin = upperBin; - return mapRangedAvgNumerical(conf); - } - return mapBinNumerical(conf); + return numerical(conf); } /** @@ -56,27 +50,21 @@ export function mapWidth({ element, growLower, growUpper, unit, lowerBin, visUpd * @param {boolean} [conf.reversed=false] If true, then high amplitudes are mapped to lower values and vice versa. * @returns {{bin: number, listener: VisUpdateRouter~visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisUpdateRouter.visualizerRangedUpdateListener}} The listener that was added (ranged if an upper bound was provided, binned otherwise). */ -export function mapHeight({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function height({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { const getter = () => parseInt(element.style.height) || 0; const setter = (value) => element.style.height = value + unit; const conf = { minVal: growLower, maxVal: growUpper, - bin: lowerBin, + lowerBin: lowerBin, + upperBin: upperBin, getter: getter, setter: setter, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - - if (upperBin) { - conf.bin = undefined; - conf.lowerBin = lowerBin; - conf.upperBin = upperBin; - return mapRangedAvgNumerical(conf); - } - return mapBinNumerical(conf); + return numerical(conf); } /** @@ -93,24 +81,19 @@ export function mapHeight({ element, growLower, growUpper, unit, lowerBin, visUp * @param {boolean} [fontSizeMapConfiguration.reversed=false] If true, then high amplitudes are mapped to lower values and vice versa. * @returns {{bin: number, listener: VisUpdateRouter~visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The listener that was added (ranged if an upper bound was provided, binned otherwise). */ -export function mapFontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function fontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { const getter = () => parseInt(element.style.fontSize); const setter = (value) => element.style.fontSize = value + unit; const conf = { minVal: growLower, maxVal: growUpper, - bin: lowerBin, + lowerBin: lowerBin, + upperBin: upperBin, getter: getter, setter: setter, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - if (upperBin) { - conf.bin = undefined; - conf.lowerBin = lowerBin; - conf.upperBin = upperBin; - return mapRangedAvgNumerical(conf); - } - return mapBinNumerical(conf); + return numerical(conf); } diff --git a/src/mappings/numeric.js b/src/mappings/numeric.js index 523ccaf..844cfd3 100644 --- a/src/mappings/numeric.js +++ b/src/mappings/numeric.js @@ -22,7 +22,7 @@ import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; * @param {number} conf.minVal The minimum value of the numerical value. * @param {number} conf.maxVal The maximum value of the numerical value. * @param {number} conf.lowerBin The lower bin of the range of bins this value is to be mapped to. - * @param {number} conf.upperBin The upper bin of the range of bins this value si to be mapped to. + * @param {number} [conf.upperBin=undefined] The upper bin of the range of bins this value si to be mapped to. * @param {module:mappings/numeric~numericalGetter} conf.getter The getter callback to be used to get the current number. * @param {module:mappings/numeric~numericalSetter} conf.setter The setter callback to be used to set the new number. * @param {module:support/easings~interpolator} conf.interpolator The interpolation function to use. @@ -30,10 +30,10 @@ import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; * @param {boolean} [conf.reversed = false] If true, then high amplitudes will be mapped to low values and vice versa. * @returns {{lower: number, upper: number, listener: VisUpdateRouter.visualizerRangedUpdateListener}} An object containing the lower and upper bounds for the range of a listener, which is also in the object. */ -export function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, upperBin, getter, setter, interpolator, visUpdateRouter, reversed = false }) { +export function numerical({ minVal, maxVal, lowerBin, upperBin = undefined, getter, setter, interpolator, visUpdateRouter, reversed = false }) { const rangedListener = { lower: lowerBin, - upper: upperBin, + upper: upperBin ? upperBin : lowerBin, listener: (timeDelta, bins) => { const normalBins = [...bins]; // TODO: Future: add weighting / distribution system? @@ -50,38 +50,8 @@ export function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, up setter(minVal + range * interpolated); } }; - if (visUpdateRouter.addVisualizerRangedUpdateListener(rangedListener)) { + if (visUpdateRouter.addUpdateListener(rangedListener)) { return rangedListener; } return null; // Technically doesn't occur since the functions are anonymous? -} - -/** - * Maps a single bin of frequency amplitudes to a numerical value defined by a getter and a setter. - * - * @param {object} conf The configuration for mapping a single bin to a numerical value. - * @param {number} conf.minVal The minimum value the mapping can produce. - * @param {number} conf.maxVal The maximum value the mapping can produce. - * @param {number} conf.bin The bin to map this number to. - * @param {module:mappings/numeric~numericalGetter} conf.getter The callback to be used to get the current number. - * @param {module:mappings/numeric~numericalSetter} conf.setter The callback to be used to set the new number. - * @param {module:support/easings~interpolator} conf.interpolator The interpolation function to use. - * @param {VisUpdateRouter} conf.visUpdateRouter The update manager to map to. - * @param {boolean} [conf.reversed] If true, then high amplitudes will be mapped to lower values and vice versa. - * @returns {{bin: number, listener: VisUpdateRouter~visualizerBinUpdateListener}} The bin listener that was added. - */ -export function mapBinNumerical({ minVal, maxVal, bin, getter, setter, interpolator, visUpdateRouter, reversed = false }) { - const listener = { - bin: bin, - listener: (timeDelta, amp) => { - const range = maxVal - minVal; - let interpolated = interpolator(getter(), amp, Math.min(timeDelta, 1)); - if (reversed) interpolated = 1 - interpolated; - setter(minVal + range * interpolated); - } - }; - if (visUpdateRouter.AddVisualizerBinUpdateListener(listener)) { - return listener; - } - return null; // Technically doesn't occur since the functions are anonymous? } \ No newline at end of file diff --git a/src/visualization/VisUpdateRouter.js b/src/visualization/VisUpdateRouter.js index fde15c8..0c996d7 100644 --- a/src/visualization/VisUpdateRouter.js +++ b/src/visualization/VisUpdateRouter.js @@ -11,8 +11,7 @@ import Visualizer from "./Visualizer.js"; * In the rare event that a bin has not changed, then it will not receive an update call. */ export default class VisUpdateRouter { - #binnedListeners = []; - #rangedListeners = []; + listeners = []; #lastBins; #visualizer; @@ -34,31 +33,23 @@ export default class VisUpdateRouter { * @param {Visualizer} visualizer the fft window size of the visualizer this manager obtains data from. */ constructor(visualizer) { - this.#binnedListeners = []; - this.#rangedListeners = []; - const bins = visualizer.numberOfBins; // TODO: Clean this up. - for (let i = 0; i < bins; i++) { - this.#binnedListeners.push([]); - } - this.#lastBins = new Uint8Array(this.#binnedListeners.length); + this.listeners = []; + this.#lastBins = new Uint8Array(visualizer.numberOfBins); this.#visualizer = visualizer; this.#visualizer.addUpdateListener(this.#visualizerListener); } #visualizerListener = (delta, bins) => { - const sortedCopyOfRangedListeners = [... this.#rangedListeners]; // We assume this is sorted properly. A priority queue could be better. + const copyOfRangedListeners = [... this.listeners]; // 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]; + for (let rangedInd = 0; rangedInd < copyOfRangedListeners.length; rangedInd++) { // Could switch to a while loop. + const { lower, upper, listener } = copyOfRangedListeners[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(); + copyOfRangedListeners.shift(); rangedInd--; } } @@ -67,21 +58,6 @@ export default class VisUpdateRouter { } }; - - /** - * 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 {VisUpdateRouter~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. * @@ -91,29 +67,15 @@ export default class VisUpdateRouter { * @param {VisUpdateRouter~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 }) { + addUpdateListener({ 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 {VisUpdateRouter~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); + if (this.listeners.includes(rangedListener)) return false; + this.listeners.push(rangedListener); + this.listeners.sort((a, b) => a.lower - b.lower); return true; } @@ -126,52 +88,36 @@ export default class VisUpdateRouter { * @param {VisUpdateRouter~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 }) { + removeUpdateListener({ lower, upper, listener }) { const rangedListener = { lower: lower, upper: upper, listener: listener }; - const removeIndex = this.#rangedListeners.indexOf(rangedListener); + const removeIndex = this.listeners.indexOf(rangedListener); if (removeIndex < 0) return false; - this.#binnedListeners.splice(removeIndex, 1); + this.listeners.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 {VisUpdateRouter~visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges. + * @param {object[]} listeners The listeners that this visualizer update manager should be set to use. + * @param {number} listeners[].lower The lower bound of the listener. + * @param {number} listeners[].upper The upper bound of the listener. + * @param {VisUpdateRouter~visualizerRangedUpdateListener} listeners[].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); + this.listeners = listeners.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. + * @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; - }); + return this.listeners; } /** diff --git a/tutorials/VisMusicPlayer.html b/tutorials/VisMusicPlayer.html index f58121f..e5a17ba 100644 --- a/tutorials/VisMusicPlayer.html +++ b/tutorials/VisMusicPlayer.html @@ -7,9 +7,9 @@

This is the fun part. We can use a {@link VisualizedMusicPlayer} and a {@link MusicPlaylist} to create a music player that is like {@link MusicPlayer} but with the ability to automatically fetch the current {@link Visualizer}. On top of that, it then routes that visualizer data to {@link VisualizerUpdateManager} which can be to make much more refined mappings.

This library comes with a variety of mapping tools:
@@ -41,7 +41,7 @@

To do this, we need to perform what's called a mapping between a range of frequency bins, or a single frequency bin, and the width property of the div element. We can then define a multitude of parameters to specify how the mapping will work. Following is the code that produced the example above with comment annotations.


             const widthElem = document.getElementById("width-map-demo"); // selecting an element.
-            ask.mappings.dimensions.mapWidth({ // the mapping function for width.
+            ask.mappings.dimensions.width({ // the mapping function for width.
                 element: widthElem, // the element to map.
                 growLower: 2, // the minimum width
                 growUpper: 8, // the maximum width
@@ -61,7 +61,7 @@
         

             const heightElem = document.getElementById("height-map-demo"); // Only big difference is the function being called.
-            ask.mappings.dimensions.mapHeight({
+            ask.mappings.dimensions.height({
                 element: heightElem,
                 growLower: 2, // height smallest can be 2 rem, tallest can be 8 rem.
                 growUpper: 8,
@@ -91,8 +91,8 @@
                 visUpdateRouter: player.visUpdateRouter,
                 interpolator: ask.support.easings.createEaseLinear(2.5)
             }
-            ask.mappings.dimensions.mapWidth(squareElemConf); // Apply them easily!
-            ask.mappings.dimensions.mapHeight(squareElemConf);
+            ask.mappings.dimensions.width(squareElemConf); // Apply them easily!
+            ask.mappings.dimensions.height(squareElemConf);
         
@@ -105,7 +105,7 @@

             const fontColorElem = document.getElementById("font-color-map-demo");
-            ask.mappings.coloring.mapFontRgba({ // Under mappings, the text module. We just want to map one of the RGBA color components...
+            ask.mappings.coloring.fontColorRgba({ // Under mappings, the text module. We just want to map one of the RGBA color components...
                 element: fontColorElem, // The element to map (same as above examples).
                 color: "r", // Choose the red component.
                 lowerBin: 128, // All other values are what we've seen above.
@@ -135,7 +135,7 @@
     playbackCtrlsElem.appendChild(nextElem);
 
     const widthElem = document.getElementById("width-map-demo");
-    ask.mappings.dimensions.mapWidth({
+    ask.mappings.dimensions.width({
         element: widthElem,
         growLower: 2,
         growUpper: 8,
@@ -147,7 +147,7 @@
     });
 
     const heightElem = document.getElementById("height-map-demo");
-    ask.mappings.dimensions.mapHeight({
+    ask.mappings.dimensions.height({
         element: heightElem,
         growLower: 2,
         growUpper: 8,
@@ -169,11 +169,11 @@
         visUpdateRouter: player.visUpdateRouter,
         interpolator: ask.support.easings.createEaseLinear(2.5)
     }
-    ask.mappings.dimensions.mapWidth(squareElemConf);
-    ask.mappings.dimensions.mapHeight(squareElemConf);
+    ask.mappings.dimensions.width(squareElemConf);
+    ask.mappings.dimensions.height(squareElemConf);
 
     const fontColorElem = document.getElementById("font-color-map-demo");
-    ask.mappings.coloring.mapFontRgba({
+    ask.mappings.coloring.fontColorRgba({
         element: fontColorElem,
         color: "r",
         lowerBin: 128,