Fixed linear easing function.

This commit is contained in:
Harrison Deng 2022-04-18 15:11:42 -05:00
parent ad6c4b15e4
commit 5313d20fff
14 changed files with 4403 additions and 104 deletions

View File

@ -32,6 +32,9 @@
"import/extensions": [
"error",
"ignorePackages"
],
"no-unused-vars": [
"warn"
]
},
"plugins": [

View File

@ -3,6 +3,7 @@
"audioshowkit",
"audioshowkitlib",
"ckeditor",
"Ctrls",
"easings",
"linebreak",
"musicplayer",

4218
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,12 @@
"test:console": "mocha tests/**",
"watch:docs": "nodemon --watch dist/ --watch tutorials/ --watch src/ --exec 'npm run docs' -e js,mjs,json,htm,html,xml,markdown,md,xhtml --ignore ./tutorials/assets/js/audioshowkit.js",
"watch:lib": "webpack --watch --config webpack.dev.cjs",
"serve:docs": "cd ./docs && live-server",
"build": "npm run build:dev",
"test": "npm run test:console",
"watch": "npm run watch:lib",
"docs": "jsdoc -c jsdoc.config.json && cp ./dist/audioshowkit.js ./tutorials/assets/js/audioshowkit.js && syncdir ./tutorials/assets/ ./docs/assets/"
"docs": "jsdoc -c jsdoc.config.json && cp ./dist/audioshowkit.js ./tutorials/assets/js/audioshowkit.js && syncdir ./tutorials/assets/ ./docs/assets/",
"serve": "npm run serve:docs"
},
"author": "",
"license": "ISC",
@ -32,6 +34,7 @@
"eslint-plugin-jsdoc": "^39.2.2",
"eslint-webpack-plugin": "^3.1.1",
"jsdoc": "^3.6.10",
"live-server": "^1.2.1",
"mocha": "^9.2.2",
"mocha-junit-reporter": "^2.0.2",
"nodemon": "^2.0.15",

View File

@ -11,14 +11,14 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
* @param {number} conf.growUpper The upper limit of the width.
* @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.visUpdateManager The visualizer update manager to be mapped to.
* @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager to be mapped to.
* @param {interpolator} conf.interpolator The interpolation function to be used to transition from one value to the next.
* @param {number} [conf.upperBin] The upper bin or undefined, which results in mapping to a single bin.
* @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).
*/
function mapWidth({ element, growLower, growUpper, unit, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
const getter = () => element.style.width;
function mapWidth({ 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,
@ -27,7 +27,7 @@ function mapWidth({ element, growLower, growUpper, unit, lowerBin, visUpdateMana
getter: getter,
setter: setter,
interpolator: interpolator,
visUpdateManager: visUpdateManager,
visUpdateRouter: visUpdateRouter,
reversed: reversed
};
@ -49,13 +49,13 @@ function mapWidth({ element, growLower, growUpper, unit, lowerBin, visUpdateMana
* @param {number} conf.growUpper The upper limit of the height.
* @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.visUpdateManager The visualizer update manager to be mapped to.
* @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager to be mapped to.
* @param {interpolator} conf.interpolator The interpolation function to be used to transition from one value to the next.
* @param {number} [conf.upperBin] The upper bin or undefined, which results in mapping to a single bin.
* @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).
*/
function mapHeight({ element, growLower, growUpper, unit, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
function mapHeight({ element, growLower, growUpper, unit, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) {
const getter = () => element.style.height;
const setter = (value) => element.style.height = value + unit;
const conf = {
@ -65,7 +65,7 @@ function mapHeight({ element, growLower, growUpper, unit, lowerBin, visUpdateMan
getter: getter,
setter: setter,
interpolator: interpolator,
visUpdateManager: visUpdateManager,
visUpdateRouter: visUpdateRouter,
reversed: reversed
};

View File

@ -24,30 +24,32 @@ import VisUpdateRouter from "../visualization/VisUpdateRouter.js";
* @param {numericalGetter} conf.getter The getter callback to be used to get the current number.
* @param {numericalSetter} conf.setter The setter callback to be used to set the new number.
* @param {interpolator} conf.interpolator The interpolation function to use.
* @param {VisUpdateRouter} conf.visUpdateManager the visualizer update manager this mapping corresponds with.
* @param {VisUpdateRouter} conf.visUpdateRouter the visualizer update manager this mapping corresponds with.
* @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.
*/
function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, upperBin, getter, setter, interpolator, visUpdateManager, reversed = false }) {
function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, upperBin, getter, setter, interpolator, visUpdateRouter, reversed = false }) {
console.log("mapping average numerical.");
const rangedListener = {
lower: lowerBin,
upper: upperBin,
listener: (timeDelta, bins) => {
const normalBins = [...bins];
// TODO: Future: add weighting / distribution system?
let average = 0;
let normAvg = 0;
for (let i = 0; i < normalBins.length; i++) {
normalBins[i] = normalBins[i] / 255.0;
average += normalBins[i];
normAvg += normalBins[i];
}
average /= normalBins.length;
normAvg /= normalBins.length;
const range = maxVal - minVal;
let interpolated = interpolator(getter(), average, timeDelta);
let interpolated = interpolator((getter() - minVal) / range, normAvg, Math.min(timeDelta, 1));
if (reversed) interpolated = 1 - interpolated;
setter(minVal + range * interpolated);
}
};
if (visUpdateManager.addVisualizerRangedUpdateListener(rangedListener)) {
if (visUpdateRouter.addVisualizerRangedUpdateListener(rangedListener)) {
return rangedListener;
}
return null; // Technically doesn't occur since the functions are anonymous?
@ -63,21 +65,22 @@ function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, upperBin,
* @param {numericalGetter} conf.getter The callback to be used to get the current number.
* @param {numericalSetter} conf.setter The callback to be used to set the new number.
* @param {interpolator} conf.interpolator The interpolation function to use.
* @param {VisUpdateRouter} conf.visUpdateManager The update manager to map to.
* @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.
*/
function mapBinNumerical({ minVal, maxVal, bin, getter, setter, interpolator, visUpdateManager, reversed = false }) {
function mapBinNumerical({ minVal, maxVal, bin, getter, setter, interpolator, visUpdateRouter, reversed = false }) {
console.log("mapping numerical...");
const listener = {
bin: bin,
listener: (timeDelta, amp) => {
const range = maxVal - minVal;
let interpolated = interpolator(getter(), amp, timeDelta);
let interpolated = interpolator(getter(), amp, Math.min(timeDelta, 1));
if (reversed) interpolated = 1 - interpolated;
setter(minVal + range * interpolated);
}
};
if (visUpdateManager.AddVisualizerBinUpdateListener(listener)) {
if (visUpdateRouter.AddVisualizerBinUpdateListener(listener)) {
return listener;
}
return null; // Technically doesn't occur since the functions are anonymous?

View File

@ -9,13 +9,13 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
* @param {HTMLElement} rgbaMapConfiguration.element The element whose text's red value should be mapped.
* @param {number} rgbaMapConfiguration.color Where r for red, g, for green, b for blue, and a for alpha.
* @param {number} rgbaMapConfiguration.lowerBin The lower bound of the bins to be mapped.
* @param {VisualizerUpdateManager} rgbaMapConfiguration.visUpdateManager The visualizer update manager associated with the audio playback you would like the mapping with.
* @param {VisualizerUpdateManager} rgbaMapConfiguration.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with.
* @param {interpolator} rgbaMapConfiguration.interpolator The interpolation function to use.
* @param {number} [rgbaMapConfiguration.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} [rgbaMapConfiguration.reversed=true] If true, then the quieter, the greater the red value.
* @returns {{bin: number, listener: VisualizerUpdateManager.visualizerBinUpdateListener}|{lower: number, upper: number, listener: visualizerRangedUpdateListener}} The ranged listener that was added.
*/
function mapRgba({ element, color, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
function mapRgba({ element, color, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false }) {
const rgbaStr = "rgba";
color = rgbaStr.indexOf(color);
if (color < 0) throw new Error("Invalid color parameter provided.");
@ -32,7 +32,7 @@ function mapRgba({ element, color, lowerBin, visUpdateManager, interpolator, upp
getter: getter,
setter: setter,
interpolator: interpolator,
visUpdateManager: visUpdateManager,
visUpdateRouter: visUpdateRouter,
reversed: reversed
};
if (upperBin) {
@ -52,13 +52,13 @@ function mapRgba({ element, color, lowerBin, visUpdateManager, interpolator, upp
* @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 {VisualizerUpdateManager} fontSizeMapConfiguration.visUpdateManager the visualizer update manager to be mapped to.
* @param {VisualizerUpdateManager} fontSizeMapConfiguration.visUpdateRouter the visualizer update manager to be mapped to.
* @param {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.
* @returns {{bin: number, listener: VisualizerUpdateManager.visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisualizerUpdateManager.visualizerRangedUpdateListener}} The listener that was added (ranged if an upper bound was provided, binned otherwise).
*/
function mapFontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
function mapFontSize({ 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 = {
@ -68,7 +68,7 @@ function mapFontSize({ element, growLower, growUpper, unit, lowerBin, visUpdateM
getter: getter,
setter: setter,
interpolator: interpolator,
visUpdateManager: visUpdateManager,
visUpdateRouter: visUpdateRouter,
reversed: reversed
};
if (upperBin) {

View File

@ -162,12 +162,15 @@ export default class Music {
}
/**
* Fetches a visualizer for this music. If there did not exist one, one is instantiated.
*
* If the provided fft window size is different from the current, a new one will also be instantiated.
*
* @param {number} [fftSize=1024] the size of the FFT window.
* @returns {Visualizer} returns the visualizer.
*/
getVisualizer(fftSize = 1024) {
if (this.#visualizer && this.#visualizer.getFftSize() === fftSize) return this.#visualizer;
fetchVisualizer(fftSize = 1024) {
if (this.#visualizer && this.#visualizer.fftSize === fftSize) return this.#visualizer;
this.#visualizer = new Visualizer(this.fetchAudio(), fftSize);
return this.#visualizer;
}

View File

@ -13,7 +13,8 @@ import MusicPlaylist from "./MusicPlaylist.js";
*/
export default class VisMusicPlayer extends MusicPlayer {
#fftSize = 1024;
#visualizerUpdateManager;
#visUpdateRouter;
#visUpdateRouterListeners = null;
/**
* Instantiates a music player with visualization features.
@ -23,75 +24,54 @@ export default class VisMusicPlayer extends MusicPlayer {
*/
constructor(playlist, fftSize = 1024) {
super(playlist);
playlist.addPositionChangeListener(this.#onMusicChange); // First time only: after setting the playlist, add the listener.
this.#fftSize = fftSize;
this.#updateVisUpdateManager();
this.#visUpdateRouter = new VisUpdateRouter(fftSize);
}
#updateVisUpdateManager() {
this.#visualizerUpdateManager = new VisUpdateRouter(this.getCurrentMusicVisualizer());
}
#onMusicChange = () => {
this.#visUpdateRouterListeners = this.#visUpdateRouter.retrieveListeners();
this.#visUpdateRouter?.unbindVisualizer();
};
/**
* Sets the the playlist.
*/
set playlist(playlist) {
if (!this.playlist) {
super.playlist = playlist;
return; // HACK: If it's the first time, then wait until super() is done.
}
this.playlist?.removePositionChangeListener(this.#onMusicChange);
this.playlist.addPositionChangeListener(this.#onMusicChange);
super.playlist = playlist;
}
/**
*
* @returns {boolean} true if and only if successful in changing to the next music.
*/
next() {
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.next()) return false;
this.#updateVisUpdateManager();
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true;
get playlist() {
return super.playlist;
}
/**
* Jumps to the previous music if possible.
*
* @returns {boolean} true if and only if successful in switching to the previous music in the playlist.
*/
previous() {
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.previous()) return false;
this.#updateVisUpdateManager();
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true;
playCurrent() {
console.log("attempted play from vis");
super.playCurrent();
const visualizer = this.playlist.currentMusic.fetchVisualizer();
this.#visUpdateRouter.visualizer = visualizer;
visualizer.analyze();
if (this.#visUpdateRouterListeners) this.#visUpdateRouter.loadListeners(this.#visUpdateRouterListeners);
}
/**
*
* @param {number} index the index of the music to change to.
* @returns {boolean} true if and only if successful in jumping to the given index.
*/
changeCurrent(index) {
const updateListeners = this.VisualizerUpdateManager.getBinnedListeners();
if (!super.changeCurrent(index)) return false;
this.#updateVisUpdateManager();
this.#visualizerUpdateManager.setBinnedListeners = updateListeners();
return true;
}
/**
* @returns {VisUpdateRouter} The current visualizer update manager.
*/
get currentVisualizerUpdateManager() {
return this.#visualizerUpdateManager();
get visUpdateRouter() {
return this.#visUpdateRouter;
}
/**
* @returns {Visualizer} The current music's visualizer.
*/
get currentMusicVisualizer() {
return this.getCurrentMusic().getVisualizer();
return this.playlist.currentMusic.fetchVisualizer(this.#fftSize);
}
}

View File

@ -16,11 +16,13 @@
*/
function createEaseLinear(rate) {
return (current, dest, frameDelta) => {
let direction = 1;
if (dest < current) {
direction = -1;
const res = current - (rate * frameDelta);
return res < dest ? dest : res;
} else {
const res = current + (rate * frameDelta);
return res > dest ? dest : res;
}
return current + (Math.min(rate * frameDelta, dest) * direction);
};
}

View File

@ -1,5 +1,3 @@
import Visualizer from "./Visualizer.js";
/**@module */
/**
@ -27,42 +25,52 @@ export default class VisUpdateRouter {
#rangedListeners = [];
#lastBins;
#visualizer;
#visualizerListener;
/**
*
* @param {Visualizer} visualizer the visualizer this manager obtains data from.
* @param {number} fftSize the fft window size of the visualizer this manager obtains data from.
*/
constructor(visualizer) {
constructor(fftSize) {
this.#binnedListeners = [];
this.#rangedListeners = [];
for (let i = 0; i < visualizer.getNumberOfBins(); i++) {
const bins = fftSize / 2; // TODO: Clean this up.
for (let i = 0; i < bins; 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--;
}
}
#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];
}
this.#lastBins[binInd] = bins[binInd];
}
};
}
};
/**
* Sets the current visualizer.
*/
set visualizer(visualizer) {
console.log("vis update router visualizer set.");
if (this.#visualizer) this.unbindVisualizer();
this.#visualizer = visualizer;
visualizer.addUpdateListener(this.#visualizerListener);
}
@ -91,6 +99,7 @@ export default class VisUpdateRouter {
* @returns {boolean} True if and only if the ranged listener was added successfully.
*/
addVisualizerRangedUpdateListener({ lower, upper, listener }) {
console.log("range update listener added.");
const rangedListener = {
lower: lower,
upper: upper,
@ -174,9 +183,10 @@ export default class VisUpdateRouter {
}
/**
* Unbinds this update manager from the initial visualizer. Effectively meaning this manager will no longer be used.
* Unbinds this update manager from the initial visualizer.
*/
unbindVisualizer() {
this.#visualizer.removeUpdateListener(this.#visualizerListener);
this.#visualizer = null;
}
}

View File

@ -83,6 +83,7 @@ export default class Visualizer {
* @returns {boolean} true if and only if the listener was successfully added.
*/
addUpdateListener(listener) {
console.log("visualizer received listener");
if (this.#updateListeners.includes(listener));
this.#updateListeners.push(listener);
return true;

View File

@ -63,8 +63,8 @@
const ask = window.audioshowkit;
const playlist = new ask.player.MusicPlaylist("Awesome Music");
playlist.add("https://res.sys.reslate.xyz/dl/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen");
playlist.add("https://res.sys.reslate.xyz/dl/audio/XXI.mp3", "XXI", "QR");
playlist.add("/assets/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen");
playlist.add("assets/audio/XXI.mp3", "XXI", "QR");
const player = new ask.player.MusicPlayer(playlist);
const playElem = player.generatePlayElement();

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./assets/css/prism.css" />
<script src="./assets/js/prism.js" defer></script>
<script src="./assets/js/audioshowkit.js"></script>
</head>
<body>
<div>
<h1>Visualizing Music</h1>
<p>This is the fun part. We can use a {@link module:player/VisualizedMusicPlayer} and a {@link module:player/MusicPlaylist} to create a music player that is like {@link module:player/MusicPlayer} but with the ability to automatically fetch the current {@link module:visualization/Visualizer}. On top of that, it then routes that visualizer data to {@link module:visualization/VisualizerUpdateManager} which can be to make much more refined mappings.</p>
This library comes with a variety of mapping tools:
<ul>
<li>Want to map ranges of frequency bins to a plethora of element style properties? Take a look at {@link module:mapping/mappings}!</li>
<li>Check out {@link module:patterns/canvas} for built in canvas patterns.</li>
<li>We even do font size and color with the {@link module:mapping/text} module!</li>
</ul>
</div>
<div>
<h2>Instantiation</h2>
<p>Exactly like when instantiating a normal music player, you will need a playlist. Other than that, it's simple.</p>
<pre><code class="language-js">
const ask = window.audioshowkit; // Get a reference to the audioshowkit stuff.
const playlist = previousPlaylist; // We are assuming you have a playlist ready.
const player = new ask.player.VisualizedMusicPlayer(playlist) // Creates a new music player with the playlist.
</code></pre>
</div>
<div>
<h2>Playback</h2>
<p>Since the usage of playback is the same as a normal {@link module:player/MusicPlayer}, see [the MusicPlayer tutorial]{@tutorial MusicPlayer} for more information.</p>
<div id="playback-ctrls">
</div>
</div>
<div>
<h2>Visualization</h2>
<p>The actual visualization can be performed in a variety of ways. We can use canvases, or even better, actual HTML elements!</p>
<div class="result">
<div id="width-map-demo" style="height: 2rem; background-color: black;"></div>
</div>
</div>
<script>
const ask = window.audioshowkit;
const playlist = new ask.player.MusicPlaylist("Awesome Music");
playlist.add("/assets/audio/XXI.mp3", "XXI", "QR");
playlist.add("/assets/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen");
const player = new ask.player.VisMusicPlayer(playlist);
const playbackCtrlsElem = document.getElementById("playback-ctrls");
const prevElem = player.generatePreviousElement();
const playElem = player.generatePlayElement();
const nextElem = player.generateNextElement();
playbackCtrlsElem.appendChild(prevElem);
playbackCtrlsElem.appendChild(playElem);
playbackCtrlsElem.appendChild(nextElem);
const widthElem = document.getElementById("width-map-demo");
console.log("attempting to map");
ask.mappings.dimensions.mapWidth({
element: widthElem,
growLower: 2,
growUpper: 5,
unit: "rem",
lowerBin: 0,
upperBin: 128,
visUpdateRouter: player.visUpdateRouter,
interpolator: ask.support.easings.createEaseLinear(1)
});
</script>
</body>
</html>