From aa8c43044a2a0d0ece9e2a25b5b583e4114f48af Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Sat, 23 Apr 2022 01:11:01 -0500 Subject: [PATCH] Added bin selection function. --- src/mappings/coloring.js | 36 +++++++++++++++++----------- src/mappings/dimensions.js | 44 ++++++++++++++++++++--------------- src/mappings/numeric.js | 20 ++++++++-------- src/support/bins.js | 23 ++++++++++++++++++ tutorials/VisMusicPlayer.html | 1 + 5 files changed, 81 insertions(+), 43 deletions(-) create mode 100644 src/support/bins.js diff --git a/src/mappings/coloring.js b/src/mappings/coloring.js index 9e773fd..f8f8871 100644 --- a/src/mappings/coloring.js +++ b/src/mappings/coloring.js @@ -1,6 +1,6 @@ import { hslaToCssHsla, hslaToRgba, parseColorToHsla, parseColorToRgba, rgbaToCssRgba, rgbaToHexRgba, rgbaToHsla } from "../support/colors.js"; import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; -import { numerical } from "./numeric.js"; +import { numericalBins } from "./numeric.js"; /**@module */ @@ -13,14 +13,15 @@ import { numerical } from "./numeric.js"; * @param {string} conf.select 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 {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector to use to calculate a number to use for the mapping. * @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 {Function} [conf.interpolator=undefined] The interpolation function to use. * @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 backgroundColorRgba({ element, select, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { +export function backgroundColorRgba({ element, select, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { const rgbaStr = "rgba"; const defaultColor = [0, 0, 0, 255]; select = rgbaStr.indexOf(select); @@ -50,11 +51,12 @@ export function backgroundColorRgba({ element, select, lowerBin, visUpdateRouter upperBin: upperBin, getter: getter, setter: setter, - interpolator: interpolator, + binSelector: binSelector, visUpdateRouter: visUpdateRouter, + interpolator: interpolator, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } /** @@ -65,14 +67,15 @@ export function backgroundColorRgba({ element, select, lowerBin, visUpdateRouter * @param {number} conf.select Where h for hue, s, for saturation, l for lightness, 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 {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector to use to calculate a number to use for the mapping. * @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 {Function} [conf.interpolator=undefined] The interpolation function to use. * @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 360 if conf.color is "h", 1 if conf.color is "s" or "l", and 255 if conf.color is "a"). * @returns {{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added. */ -export function backgroundColorHsla({ element, select, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = undefined }) { +export function backgroundColorHsla({ element, select, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false, lowerVal = 0, upperVal = undefined }) { const rgbaStr = "hsla"; const defaultColor = [0, 0.5, 0.5, 255]; select = rgbaStr.indexOf(select); @@ -110,11 +113,12 @@ export function backgroundColorHsla({ element, select, lowerBin, visUpdateRouter upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } /** @@ -125,14 +129,15 @@ export function backgroundColorHsla({ element, select, lowerBin, visUpdateRouter * @param {string} conf.select 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 {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector to use to calculate a number to use for the mapping. * @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 {Function} [conf.interpolator=undefined] The interpolation function to use. * @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, select, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { +export function fontColorRgba({ element, select, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false, lowerVal = 0, upperVal = 255 }) { const rgbaStr = "rgba"; const defaultColor = [0, 0, 0, 255]; select = rgbaStr.indexOf(select); @@ -162,11 +167,12 @@ export function fontColorRgba({ element, select, lowerBin, visUpdateRouter, inte upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } /** @@ -177,14 +183,15 @@ export function fontColorRgba({ element, select, lowerBin, visUpdateRouter, inte * @param {number} conf.select Where h for hue, s, for saturation, l for lightness, 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 {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector to use to calculate a number to use for the mapping. * @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 {Function} [conf.interpolator=undefined] The interpolation function to use. * @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 360 if conf.color is "h", 1 if conf.color is "s" or "l", and 255 if conf.color is "a"). * @returns {{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added. */ -export function fontColorHsla({ element, select, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = undefined }) { +export function fontColorHsla({ element, select, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false, lowerVal = 0, upperVal = undefined }) { const rgbaStr = "hsla"; const defaultColor = [0, 0.5, 0.5, 255]; select = rgbaStr.indexOf(select); @@ -224,9 +231,10 @@ export function fontColorHsla({ element, select, lowerBin, visUpdateRouter, inte upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } \ No newline at end of file diff --git a/src/mappings/dimensions.js b/src/mappings/dimensions.js index 18320d6..323979a 100644 --- a/src/mappings/dimensions.js +++ b/src/mappings/dimensions.js @@ -1,5 +1,5 @@ import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; -import { numerical } from "./numeric.js"; +import { numericalBins } from "./numeric.js"; /**@module */ @@ -13,12 +13,13 @@ import { numerical } from "./numeric.js"; * @param {string} conf.unit The unit the upper and lower bounds are measured in. * @param {number} conf.lowerBin The lower boundary of bins to be mapped (or the single bin to be mapped if the upper bin is undefined). * @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager to be mapped to. - * @param {module:support/easings~interpolator} conf.interpolator The interpolation function to be used to transition from one value to the next. + * @param {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector function to use to get a number for the mapping. * @param {number} [conf.upperBin] The upper bin or undefined, which results in mapping to a single bin. + * @param {module:support/easings~interpolator} [conf.interpolator=undefined] The interpolation function to be used to transition from one value to the next. * @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 width({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function width({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false }) { const getter = () => parseInt(element.style.width) || 0; const setter = (value) => element.style.width = value + unit; const conf = { @@ -28,11 +29,12 @@ export function width({ element, growLower, growUpper, unit, lowerBin, visUpdate upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } /** @@ -45,12 +47,13 @@ export function width({ element, growLower, growUpper, unit, lowerBin, visUpdate * @param {string} conf.unit The unit the upper and lower bounds are measured in. * @param {number} conf.lowerBin The lower boundary of bins to be mapped (or the single bin to be mapped if the upper bin is undefined). * @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager to be mapped to. - * @param {module:support/easings~interpolator} conf.interpolator The interpolation function to be used to transition from one value to the next. + * @param {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector function to use to get a number for the mapping. * @param {number} [conf.upperBin] The upper bin or undefined, which results in mapping to a single bin. + * @param {module:support/easings~interpolator} [conf.interpolator=undefined] The interpolation function to be used to transition from one value to the next. * @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 height({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function height({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false }) { const getter = () => parseInt(element.style.height) || 0; const setter = (value) => element.style.height = value + unit; const conf = { @@ -60,28 +63,30 @@ export function height({ element, growLower, growUpper, unit, lowerBin, visUpdat upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } /** * - * @param {object} fontSizeMapConfiguration The configuration of the font size mapping. - * @param {HTMLElement} fontSizeMapConfiguration.element The element whose font sizes will be mapped to the amplitude. - * @param {number} fontSizeMapConfiguration.growLower The lower limit of the font size. - * @param {number} fontSizeMapConfiguration.growUpper The upper limit of the font size. - * @param {string} fontSizeMapConfiguration.unit The unit the upper and lower bounds are measured in. - * @param {number} fontSizeMapConfiguration.lowerBin The lower boundary of bins to be mapped (or the single bin to be mapped if the upper bin is undefined). - * @param {VisUpdateRouter} fontSizeMapConfiguration.visUpdateRouter the visualizer update manager to be mapped to. - * @param {module:support/easings~interpolator} fontSizeMapConfiguration.interpolator The interpolation function to be used to transition from one value to the next. - * @param {number} [fontSizeMapConfiguration.upperBin=undefined] The upper bin, or undefined, which results in mapping to a single bin. - * @param {boolean} [fontSizeMapConfiguration.reversed=false] If true, then high amplitudes are mapped to lower values and vice versa. + * @param {object} conf The configuration of the font size mapping. + * @param {HTMLElement} conf.element The element whose font sizes will be mapped to the amplitude. + * @param {number} conf.growLower The lower limit of the font size. + * @param {number} conf.growUpper The upper limit of the font size. + * @param {string} conf.unit The unit the upper and lower bounds are measured in. + * @param {number} conf.lowerBin The lower boundary of bins to be mapped (or the single bin to be mapped if the upper bin is undefined). + * @param {VisUpdateRouter} conf.visUpdateRouter the visualizer update manager to be mapped to. + * @param {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector function to use to get a number for the mapping. + * @param {number} [conf.upperBin=undefined] The upper bin, or undefined, which results in mapping to a single bin. + * @param {module:support/easings~interpolator} [conf.interpolator=undefined] The interpolation function to be used to transition from one value to the next. + * @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 fontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) { +export function fontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false }) { const getter = () => parseInt(element.style.fontSize) || 0; const setter = (value) => element.style.fontSize = value + unit; const conf = { @@ -91,9 +96,10 @@ export function fontSize({ element, growLower, growUpper, unit, lowerBin, visUpd upperBin: upperBin, getter: getter, setter: setter, + binSelector: binSelector, interpolator: interpolator, visUpdateRouter: visUpdateRouter, reversed: reversed }; - return numerical(conf); + return numericalBins(conf); } diff --git a/src/mappings/numeric.js b/src/mappings/numeric.js index 844cfd3..a74a78a 100644 --- a/src/mappings/numeric.js +++ b/src/mappings/numeric.js @@ -1,3 +1,5 @@ +import { createUniAvgSel } from "../support/bins.js"; +import { createEaseLinear } from "../support/easings.js"; import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; /**@module */ @@ -22,27 +24,25 @@ 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=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. * @param {VisUpdateRouter} conf.visUpdateRouter the visualizer update manager this mapping corresponds with. + * @param {module:support/bins~binSelector} [conf.binSelector=undefined] The bin selector function to use to get a number for the mapping. + * @param {number} [conf.upperBin=undefined] The upper bin of the range of bins this value is to be mapped to. + * @param {module:support/easings~interpolator} [conf.interpolator=undefined] The interpolation function to use. * @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 numerical({ minVal, maxVal, lowerBin, upperBin = undefined, getter, setter, interpolator, visUpdateRouter, reversed = false }) { +export function numericalBins({ minVal, maxVal, lowerBin, getter, setter, visUpdateRouter, binSelector = undefined, upperBin = undefined, interpolator = undefined, reversed = false }) { + if (!binSelector) binSelector = createUniAvgSel(); + if (!interpolator) interpolator = createEaseLinear(2); const rangedListener = { lower: lowerBin, upper: upperBin ? upperBin : lowerBin, listener: (timeDelta, bins) => { - const normalBins = [...bins]; // TODO: Future: add weighting / distribution system? - let normAvg = 0; - for (let i = 0; i < normalBins.length; i++) { - normalBins[i] = normalBins[i] / 255.0; - normAvg += normalBins[i]; - } - normAvg /= normalBins.length; + let normAvg = binSelector(bins); + normAvg /= 255; const range = maxVal - minVal; let interpolated = interpolator((getter() - minVal) / range, normAvg, Math.min(timeDelta, 1)); diff --git a/src/support/bins.js b/src/support/bins.js new file mode 100644 index 0000000..3fe28d3 --- /dev/null +++ b/src/support/bins.js @@ -0,0 +1,23 @@ +/**@module */ + +/** + * @callback binSelector + * @param {number[]} bins Array of frequency bins. + * @returns {number} A number from 0 to 255 representing the bin value to be used. + */ + +/** + * Creates a {@link module:support/bins~binSelector} that simply uniformly averages all bins and returns said average. + * + * @returns {module:support/bins~binSelector} that simply uniformly averages all bins and returns said average. + */ +export function createUniAvgSel() { + return (bins) => { + let normAvg = 0; + for (let i = 0; i < bins.length; i++) { + normAvg += bins[i]; + } + normAvg /= bins.length; + return normAvg; + }; +} \ No newline at end of file diff --git a/tutorials/VisMusicPlayer.html b/tutorials/VisMusicPlayer.html index 75eb211..74ee59f 100644 --- a/tutorials/VisMusicPlayer.html +++ b/tutorials/VisMusicPlayer.html @@ -182,6 +182,7 @@ playlist.add("./assets/audio/XXI.mp3", "XXI", "QR"); playlist.add("./assets/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen"); + playlist.add("./assets/audio/pathetique.mp3", "Pathetique", "Cryvera (Beethoven Remix)"); const playlistDisp = playlist.generatePlaylistElement(); document.getElementById("playlist-display").appendChild(playlistDisp);