Changed all classes to using private fields and properties.

This commit is contained in:
Harrison Deng 2022-04-17 23:12:53 -05:00
parent b5a4a7dcb4
commit 2cb4ad2652
7 changed files with 234 additions and 229 deletions

View File

@ -1,5 +1,4 @@
import { parseColor, rgbaToHexRgba } from "../support/colors.js";
import VisualizerUpdateManager from "../visualization/VisualizerUpdateManager.js";
import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
@ -14,7 +13,7 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
* @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: VisualizerUpdateManager.visualizerRangedUpdateListener}} The ranged listener that was added.
* @returns {{bin: number, listener: VisualizerUpdateManager.visualizerBinUpdateListener}|{lower: number, upper: number, listener: visualizerRangedUpdateListener}} The ranged listener that was added.
*/
export function mapRgba({ element, color, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
const rgbaStr = "rgba";

View File

@ -5,6 +5,12 @@ import Visualizer from "../visualization/Visualizer.js";
*
*/
export default class PlayListSong {
#displayName;
#author;
#url;
#audio;
#visualizer;
#ready = false;
/**
* Constructs a song for a {@link SongPlaylist}.
@ -14,20 +20,13 @@ export default class PlayListSong {
* @param {string} author the author of the song.
*/
constructor(url, name, author) {
this._displayName = name;
this._author = author;
this._url = url;
this._audio = null;
this._visualizer = null;
/**
* Whether or not this song is ready to be played.
*/
this.ready = false;
this.#displayName = name;
this.#author = author;
this.#url = url;
}
/**
* @callback AudioEventCallback
* @typedef {function} AudioEventCallback
* @param {HTMLAudioElement} audio
*/
@ -36,45 +35,45 @@ export default class PlayListSong {
* @param {AudioEventCallback} [onReady] called when the song is ready, including right away if the song is already ready.
* @returns {HTMLAudioElement} The audio element that represents this song.
*/
getAudio(onReady) {
if (this._audio) {
fetchAudio(onReady) {
if (this.#audio) {
if (this.ready) {
if (onReady) onReady(this._audio);
if (onReady) onReady(this.#audio);
}
return this._audio;
return this.#audio;
}
this._audio = new Audio(this._url);
this.#audio = new Audio(this._url);
this._audio.addEventListener("canplaythrough", () => {
this.#audio.addEventListener("canplaythrough", () => {
this.ready = true;
if (onReady && this.isAudioInstantiated()) {
onReady(this._audio);
onReady(this.#audio);
}
});
return this._audio;
return this.#audio;
}
/**
*
* @returns {string} representing the name of the song to be displayed.
* The name of the song to be displayed.
*/
getDisplayName() {
get displayName() {
return this._displayName;
}
/**
*
* @returns {string} representing the author of the song.
* The author of the song.
*/
getAuthor() {
get author() {
return this._getAuthor;
}
/**
*
* @returns {string} representing the url at which the file for this song can be found.
* The url at which the file for this song can be found.
*/
getUrl() {
get url() {
return this._url;
}
@ -83,7 +82,7 @@ export default class PlayListSong {
* @returns {boolean} true if and only if there is audio data that is either already loaded or is being loaded.
*/
isAudioInstantiated() {
return this._audio ? true : false;
return this.#audio ? true : false;
}
/**
@ -92,7 +91,7 @@ export default class PlayListSong {
* This function uses the {@link PlaylistSong#getAudio} function and specifically, the {@link AudioEventCallback}.
*/
play() {
this.getAudio((audio) => {
this.fetchAudio((audio) => {
audio.play();
});
}
@ -102,48 +101,48 @@ export default class PlayListSong {
*/
pause() {
if (!this.isAudioInstantiated()) return;
this.getAudio((audio) => {
this.fetchAudio((audio) => {
audio.pause();
});
}
/**
*
* @returns {number} the volume on a scale of 0 to 1.
* The volume on a scale of 0 to 1.
*/
getVolume() {
return this.getAudio().volume;
get volume() {
return this.fetchAudio().volume;
}
/**
*
* @param {number} volume a normalized volume on a scale of 0 to 1.
* The normalized volume on a scale of 0 to 1.
*/
setVolume(volume) {
this.getAudio().volume = volume;
set volume(volume) {
this.fetchAudio().volume = volume;
}
/**
*
* @returns {number} the number of seconds into the song.
* The number of seconds into the song.
*/
getCurrentTime() {
return this.getAudio().currentTime;
get currentTime() {
return this.fetchAudio().currentTime;
}
/**
*
* @param {number} currentTime the time position in the song to jump to in seconds.
* The time position in the song to jump to in seconds.
*/
setCurrentTime(currentTime) {
this.getAudio().currentTime = currentTime;
set currentTime(currentTime) {
this.fetchAudio().currentTime = currentTime;
}
/**
*
* @returns {number} the duration of the song.
* The duration of the song.
*/
getDuration() {
return this.getAudio().duration;
get duration() {
return this.fetchAudio().duration;
}
/**
@ -154,9 +153,9 @@ export default class PlayListSong {
*/
unloadAudio() {
if (!this.isAudioInstantiated()) return;
this._audio.pause();
this.#audio.pause();
this.unloadVisualizer();
this._audio = null;
this.#audio = null;
this.ready = false;
}
@ -167,7 +166,7 @@ export default class PlayListSong {
*/
getVisualizer(fftSize = 1024) {
if (this._visualizer && this._visualizer.getFftSize() === fftSize) return this._visualizer;
this._visualizer = new Visualizer(this.getAudio(), fftSize);
this._visualizer = new Visualizer(this.fetchAudio(), fftSize);
return this._visualizer;
}

View File

@ -4,45 +4,43 @@ import "../styles/songplayer.css";
* A player that keeps track of global properties for playback as well as traversing a playlist. Additionally keeps track of song audio data and attempts to reduce memory usage when possible.
*/
export default class SongPlayer {
#playlist;
#current = 0;
#volume = 1;
#playing = true;
#playlistChangeListeners = [];
#currentSongChangeListeners = [];
#volumeChangeListeners = [];
#playingChangeListeners = [];
#allAudioEventListeners = {};
/**
*
* @param {SongPlaylist} playlist the playlist of songs that this player is in charge of.
*/
constructor(playlist) {
this._playlist = playlist;
this._current = 0;
this._volume = 1;
this._playing = true;
this._playlistChangeListeners = [];
this._currentSongChangeListeners = [];
this._volumeChangeListeners = [];
this._playingChangeListener = [];
this._allAudioEventListeners = {};
this.#playlist = playlist;
}
/**
*
* @param {SongPlaylist} playlist the new playlist of songs that this player is in charge of.
* The new playlist of songs that this player is in charge of.
*/
setPlaylist(playlist) {
this._playlist.unloadAllAudio();
const old = this._playlist;
this._playlist = playlist;
this._current = 0;
this._playlistChangeListeners.forEach(playlistChangeListener => {
set playlist(playlist) {
this.#playlist.unloadAllAudio();
const old = this.#playlist;
this.#playlist = playlist;
this.#current = 0;
this.#playlistChangeListeners.forEach(playlistChangeListener => {
playlistChangeListener(old, this.getPlaylist());
});
}
/**
*
* @returns {SongPlaylist} the current playlist of songs that this player is in charge of.
* The current playlist of songs that this player is in charge of.
*/
getPlaylist() {
return this._playlist;
get playlist() {
return this.#playlist;
}
/**
@ -68,27 +66,27 @@ export default class SongPlayer {
* @returns {boolean} true if and only if successful jumping to the given index.
*/
changeCurrentSongIndex(index) {
if (index >= this._playlist.total()) return false;
if (index >= this.#playlist.total()) return false;
if (index <= 0) return false;
Object.keys(this._allAudioEventListeners).forEach(key => {
const listeners = this._allAudioEventListeners[key];
Object.keys(this.#allAudioEventListeners).forEach(key => {
const listeners = this.#allAudioEventListeners[key];
listeners.forEach(listener => {
this.getCurrentSong().getAudio().removeEventListener(key, listener);
});
});
this.getCurrentSong().unloadAudio();
const old = this.getCurrentSong();
this._current = index;
this._currentSongChangeListeners.forEach(currentChangeListener => {
this.#current = index;
this.#currentSongChangeListeners.forEach(currentChangeListener => {
currentChangeListener(old, this.getCurrentSong());
});
Object.keys(this._allAudioEventListeners).forEach(key => {
const listeners = this._allAudioEventListeners[key];
Object.keys(this.#allAudioEventListeners).forEach(key => {
const listeners = this.#allAudioEventListeners[key];
listeners.forEach(listener => {
this.getCurrentSong().getAudio().addEventListener(key, listener);
});
});
if (this._playing) {
if (this.#playing) {
this.playCurrent();
}
return true;
@ -96,30 +94,30 @@ export default class SongPlayer {
/**
*
* @returns {number} the current song's index in the playlist.
* The current song's index in the playlist.
*/
getCurrentSongIndex() {
return this._current;
get currentSongIndex() {
return this.#current;
}
playCurrent() {
this.getCurrentSong().getAudio((audio) => {
audio.volume = this._volume;
audio.volume = this.#volume;
audio.play();
});
this._playing = true;
this.#playing = true;
}
pauseCurrent() {
this.getCurrentSong().getAudio().pause();
this._playing = true;
this.#playing = true;
}
/**
* Toggles whether or not the current song is playing.
*/
togglePlay() {
if (this._playing) {
if (this.#playing) {
this.pauseCurrent();
} else {
this.playCurrent();
@ -127,24 +125,21 @@ export default class SongPlayer {
}
/**
*
* @param {number} volume a number between 0 to 1 inclusive representing the volume of the player.
* @returns {boolean} true if and only if the volume was successfully set.
* A number between 0 to 1 inclusive representing the volume of the player. Values out of these bounds will be rounded to the nearest bound.
*/
setVolume(volume) {
if (volume > 1) return false;
if (volume < 0) return false;
this._volume = volume;
this.getCurrentSong().getAudio().volume = this._volume;
return true;
set volume(volume) {
if (volume > 1) volume = 1;
if (volume < 0) volume = 0;
this.#volume = volume;
this.getCurrentSong().getAudio().volume = this.#volume;
}
/**
*
* @returns {number} the current volume of the player represented by a number between 0 and 1 inclusive.
* The current volume of the player represented by a number between 0 and 1 inclusive.
*/
getVolume() {
return this._volume;
get volume() {
return this.#volume;
}
/**
@ -163,17 +158,17 @@ export default class SongPlayer {
/**
*
* @returns {number} how many seconds into the audio track has been played in seconds.
* How many seconds into the audio track has been played in seconds.
*/
getCurrentPosition() {
get currentPosition() {
return this.getCurrentSong().getAudio().currentTime;
}
/**
*
* @returns {number} the total length of the song, or NaN if this information has not loaded yet.
* The total length of the song, or NaN if this information has not loaded yet.
*/
getCurrentDuration() {
get currentDuration() {
return this.getCurrentSong().getAudio().duration;
}
@ -188,7 +183,7 @@ export default class SongPlayer {
const playButton = document.createElement("button");
playButton.classList.add("player");
playButton.classList.add("play-btn");
if (!this._playing) playButton.classList.add("paused");
if (!this.#playing) playButton.classList.add("paused");
playButton.addEventListener("click", () => {
this.togglePlay();
@ -224,8 +219,8 @@ export default class SongPlayer {
return previousButton;
}
getCurrentSong() {
return this._playlist.songAtIndex(this._current);
get currentSong() {
return this.#playlist.songAtIndex(this.#current);
}
/**
@ -240,8 +235,8 @@ export default class SongPlayer {
* @returns {boolean} true if and only if successfully added the listener.
*/
addCurrentSongChangeListener(listener) {
if (this._currentSongChangeListeners.includes(listener)) return false;
this._currentChangeListener.push(listener);
if (this.#currentSongChangeListeners.includes(listener)) return false;
this.#currentSongChangeListeners.push(listener);
return true;
}
@ -251,10 +246,10 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the song change listener given was successfully removed.
*/
removeCurrentSongChangeListener(listener) {
const removeIndex = this._currentSongChangeListeners.indexOf(listener);
const removeIndex = this.#currentSongChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this._currentSongChangeListeners.splice(removeIndex, 1);
this.#currentSongChangeListeners.splice(removeIndex, 1);
return true;
}
@ -264,8 +259,8 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the given listener was successfully registered.
*/
addPlaylistChangeListener(listener) {
if (this._playlistChangeListeners.includes(listener)) return false;
this._playlistChangeListeners.push(listener);
if (this.#playlistChangeListeners.includes(listener)) return false;
this.#playlistChangeListeners.push(listener);
return true;
}
@ -276,7 +271,7 @@ export default class SongPlayer {
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
*/
removePlaylistChangeListener(listener) {
const removeIndex = this._playlistChangeListeners.indexOf(listener);
const removeIndex = this.#playlistChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this.playlistChangeListener.splice(removeIndex, 1);
@ -289,8 +284,8 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the listener was successfully added.
*/
addVolumeChangeListener(listener) {
if (this._volumeChangeListeners.includes(listener)) return false;
this._volumeChangeListeners.push(listener);
if (this.#volumeChangeListeners.includes(listener)) return false;
this.#volumeChangeListeners.push(listener);
return true;
}
@ -300,10 +295,10 @@ export default class SongPlayer {
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
*/
removeVolumeChangeListener(listener) {
const removeIndex = this._volumeChangeListeners.indexOf(listener);
const removeIndex = this.#volumeChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this._volumeChangeListeners.splice(removeIndex, 1);
this.#volumeChangeListeners.splice(removeIndex, 1);
return true;
}
@ -314,8 +309,8 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the listener was successfully added.
*/
addPlayChangeListener(listener) {
if (this._playingChangeListener.includes(listener)) return false;
this._playingChangeListener.push(listener);
if (this.#playingChangeListeners.includes(listener)) return false;
this.#playingChangeListeners.push(listener);
return true;
}
@ -325,10 +320,10 @@ export default class SongPlayer {
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
*/
removePlayChangeListener(listener) {
const removeIndex = this._playingChangeListener.indexOf(listener);
const removeIndex = this.#playingChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this._playingChangeListener.splice(removeIndex, 1);
this.#playingChangeListeners.splice(removeIndex, 1);
return true;
}
@ -339,10 +334,10 @@ export default class SongPlayer {
* @returns {boolean} true if and only if successfully registered event listener.
*/
addEventListenerToCurrentAudio(type, eventListener) {
let typeListeners = this._allAudioEventListeners[type];
let typeListeners = this.#allAudioEventListeners[type];
if (!typeListeners) {
typeListeners = [];
this._allAudioEventListeners[type] = typeListeners;
this.#allAudioEventListeners[type] = typeListeners;
}
if (typeListeners.includes(eventListener)) return false;
typeListeners.push(eventListener);
@ -357,7 +352,7 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the event listener was successfully added.
*/
removeEventListenerFromCurrentAudio(type, eventListener) {
let typeListeners = this._allAudioEventListeners[type];
let typeListeners = this.#allAudioEventListeners[type];
if (!typeListeners) return false;
const removeIndex = typeListeners.indexOf(eventListener);
if (removeIndex < 0) return false;

View File

@ -4,6 +4,8 @@ import PlaylistSong from "./PlaylistSong.js";
* A playlist that holds a multitude of songs in the form of {@see PlaylistSong}.
*/
export default class SongPlaylist {
#list = [];
#name;
/**
* Instantiates a playlist for songs.
@ -11,17 +13,15 @@ export default class SongPlaylist {
* @param {string} name The name of the playlist.
*/
constructor(name) {
this._list = [];
this._name = name;
this._current = 0;
this.#name = name;
}
/**
*
* @returns {string} the name of the string.
* The name of the playlist.
*/
getName() {
return this._name;
get name() {
return this.#name;
}
/**
@ -30,7 +30,7 @@ export default class SongPlaylist {
* @returns {PlaylistSong} the song at the given index.
*/
songAtIndex(index) {
if (index >= this._list.length) {
if (index >= this.#list.length) {
return null;
}
return this.list[index];
@ -44,7 +44,7 @@ export default class SongPlaylist {
* @param {string} author the author(s) of the song.
*/
add(url, name, author) {
this._list.push(new PlaylistSong(url, name, author, this));
this.#list.push(new PlaylistSong(url, name, author, this));
}
/**
@ -54,10 +54,10 @@ export default class SongPlaylist {
* @returns {PlaylistSong} the song that was removed, or null if the index of was invalid.
*/
remove(index) {
if (index >= this._list.length) {
if (index >= this.#list.length) {
return null;
}
let removed = this._list.splice(index, 1)[0];
let removed = this.#list.splice(index, 1)[0];
if (removed.length > 0) {
return removed[0];
}
@ -70,7 +70,7 @@ export default class SongPlaylist {
* @returns {number} the index of the song found, or -1 if it was not found.
*/
findSongIndex(name) {
return this._list.findIndex((item) => item.getDisplayName() == name);
return this.#list.findIndex((item) => item.getDisplayName() == name);
}
/**
@ -78,14 +78,14 @@ export default class SongPlaylist {
* @returns {number} total number of songs in this playlist.
*/
total() {
return this._list.length;
return this.#list.length;
}
/**
* Unloads the audio data of all songs in this playlist.
*/
unloadAllAudio() {
this._list.forEach(playlistSong => {
this.#list.forEach(playlistSong => {
playlistSong.unloadAudio();
});
}

View File

@ -9,6 +9,8 @@ import SongPlayer from "./SongPlayer.js";
* Automatically loads the songs.
*/
export default class VisualizedSongPlayer extends SongPlayer {
#fftSize = 1024;
#visualizerUpdateManager;
/**
* Instantiates a song player with visualization features.
@ -18,18 +20,16 @@ export default class VisualizedSongPlayer extends SongPlayer {
*/
constructor(playlist, fftSize = 1024) {
super(playlist);
this._fftSize = fftSize;
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this.#fftSize = fftSize;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
}
/**
* Sets the the playlist.
*
* @param {SongPlaylist} playlist the new playlist of songs that this player is in charge of.
*/
setPlaylist(playlist) {
super.setPlaylist(playlist);
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
set playlist(playlist) {
super.playlist = playlist;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
}
/**
@ -37,10 +37,10 @@ export default class VisualizedSongPlayer extends SongPlayer {
* @returns {boolean} true if and only if successful in changing to the next song.
*/
next() {
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.next()) return false;
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true;
}
@ -50,11 +50,11 @@ export default class VisualizedSongPlayer extends SongPlayer {
* @returns {boolean} true if and only if successful in switching to the previous song in the playlist.
*/
previous() {
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.previous()) return false;
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true;
}
@ -66,24 +66,22 @@ export default class VisualizedSongPlayer extends SongPlayer {
changeCurrent(index) {
const updateListeners = this.VisualizerUpdateManager.getBinnedListeners();
if (!super.changeCurrent(index)) return false;
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this._visualizerUpdateManager.setBinnedListeners = updateListeners();
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
this.#visualizerUpdateManager.setBinnedListeners = updateListeners();
return true;
}
/**
*
* @returns {VisualizerUpdateManager} the current visualizer update manager.
* The current visualizer update manager.
*/
getCurrentVisualizerUpdateManager() {
return this._visualizerUpdateManager();
get currentVisualizerUpdateManager() {
return this.#visualizerUpdateManager();
}
/**
*
* @returns {Visualizer} the current song's visualizer.
* The current song's visualizer.
*/
getCurrentSongVisualizer() {
get currentSongVisualizer() {
return this.getCurrentSong().getVisualizer();
}
}

View File

@ -2,6 +2,14 @@
* Provides a simplified access point to the frequency bins in the form of a visualization update listener.
*/
export default class Visualizer {
#stream;
#analyzing = false;
#updateListeners = [];
#audioCtx;
#source;
#analyzer;
#buffer;
#lastUpdate;
/**
* @callback visualizerUpdateListener
@ -15,22 +23,22 @@ export default class Visualizer {
* @param {number} [fftSize = 1024] the size of the fft window.
*/
constructor(mediaSource, fftSize = 1024) {
this._stream = mediaSource;
this._analyzing = false;
this._updateListeners = [];
this._audioCtx = new window.AudioContext();
this.#stream = mediaSource;
this.#analyzing = false;
this.#updateListeners = [];
this.#audioCtx = new window.AudioContext();
if (mediaSource instanceof HTMLMediaElement) {
this._source = this._audioCtx.createMediaElementSource(this._stream);
this.#source = this.#audioCtx.createMediaElementSource(this.#stream);
} else {
this._source = this._audioCtx.createMediaStreamSource(this._stream);
this.#source = this.#audioCtx.createMediaStreamSource(this.#stream);
}
this._analyzer = this._audioCtx.createAnalyser();
this._analyzer.fftSize = fftSize;
this._buffer = new Uint8Array(this._analyzer.frequencyBinCount);
this._source.connect(this._analyzer);
this._analyzer.connect(this._audioCtx.destination);
this.#analyzer = this.#audioCtx.createAnalyser();
this.#analyzer.fftSize = fftSize;
this.#buffer = new Uint8Array(this.#analyzer.frequencyBinCount);
this.#source.connect(this.#analyzer);
this.#analyzer.connect(this.#audioCtx.destination);
this.lastUpdate = null;
this.#lastUpdate = null;
}
@ -38,22 +46,22 @@ export default class Visualizer {
* Begins analyzing and sending out update pings.
*/
analyze() {
if (this._analyzing) {
if (this.#analyzing) {
return;
}
this._analyzing = true;
this.#analyzing = true;
let self = this; // since calling from requestAnimationFrame means "this" is no longer set to produced object.
const update = (timestamp) => {
if (!self._analyzing) return;
if (!self.#analyzing) return;
if (!self.lastUpdate) {
self.lastUpdate = timestamp;
if (!self.#lastUpdate) {
self.#lastUpdate = timestamp;
}
let delta = timestamp - self.lastUpdate;
self._analyzer.getByteFrequencyData(self._buffer);
let delta = timestamp - self.#lastUpdate;
self.#analyzer.getByteFrequencyData(self.#buffer);
self._updateListeners.forEach(listener => {
listener(delta, self._buffer);
self.#updateListeners.forEach(listener => {
listener(delta, self.#buffer);
});
requestAnimationFrame(update);
};
@ -64,7 +72,7 @@ export default class Visualizer {
* Stops the analysis. Listeners will stop receiving bins.
*/
stop() {
this._analyzing = false;
this.#analyzing = false;
}
/**
@ -73,8 +81,8 @@ export default class Visualizer {
* @returns {boolean} true if and only if the listener was successfully added.
*/
addUpdateListener(listener) {
if (this._updateListeners.includes(listener));
this._updateListeners.push(listener);
if (this.#updateListeners.includes(listener));
this.#updateListeners.push(listener);
return true;
}
@ -84,27 +92,26 @@ export default class Visualizer {
* @returns {boolean} true if and only if the removal of the listener was a success.
*/
removeUpdateListener(listener) {
const removeIndex = this._updateListeners.indexOf(listener);
const removeIndex = this.#updateListeners.indexOf(listener);
if (removeIndex < 0) return false;
this._updateListeners.splice(removeIndex, 1);
this.#updateListeners.splice(removeIndex, 1);
return true;
}
/**
*
* @returns {number} the number of bins based on the size of the FFT window.
* The number of bins based on the size of the FFT window.
*/
getNumberOfBins() {
return this._buffer.length;
get numberOfBins() {
return this.#buffer.length;
}
/**
*
* @returns {number} the fft window size.
* The fft window size.
*/
getFftSize() {
return this._analyzer.fftSize;
get fftSize() {
return this.#analyzer.fftSize;
}
}

View File

@ -5,24 +5,31 @@
* In the rare event that a bin has not changed, then it will not receive an update call.
*/
export default class VisualizerUpdateManager {
#binnedListeners = [];
#rangedListeners = [];
#lastBins;
#visualizer;
#visualizerListener;
/**
*
* @param {Visualizer} visualizer the visualizer this manager obtains data from.
*/
constructor(visualizer) {
this._binnedListeners = [];
this._rangedListeners = [];
this.#binnedListeners = [];
this.#rangedListeners = [];
for (let i = 0; i < visualizer.getNumberOfBins(); i++) {
this._binnedListeners.push([]);
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];
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 => {
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.
@ -34,22 +41,22 @@ export default class VisualizerUpdateManager {
rangedInd--;
}
}
this._lastBins[binInd] = bins[binInd];
this.#lastBins[binInd] = bins[binInd];
}
}
};
visualizer.addUpdateListener(this._visualizerListener);
visualizer.addUpdateListener(this.#visualizerListener);
}
/**
* @callback VisualizerUpdateManager.visualizerBinUpdateListener
* @typedef {function} 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 VisualizerUpdateManager.visualizerRangedUpdateListener
* @typedef {function} visualizerRangedUpdateListener
* @param {number} timeDelta elapsed time since last update.
* @param {number} bins the bins of the range.
*/
@ -60,12 +67,12 @@ export default class VisualizerUpdateManager {
*
* @param {object} freqBinListener the listener for a specific frequency bin.
* @param {number} freqBinListener.freqBin the frequency bin this update listener should listen to.
* @param {VisualizerUpdateManager.visualizerBinUpdateListener} freqBinListener.listener the listener itself that will be called upon the bin updating.
* @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);
if (this.#binnedListeners[freqBin].includes(listener)) return false;
this.#binnedListeners[freqBin].push(listener);
return true;
}
@ -75,7 +82,7 @@ export default class VisualizerUpdateManager {
* @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 {VisualizerUpdateManager.visualizerRangedUpdateListener} rangedUpdateListener.listener The listener to register to the range.
* @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 }) {
@ -84,9 +91,9 @@ export default class VisualizerUpdateManager {
upper: upper,
listener: listener
};
if (this._rangedListeners.includes(rangedListener)) return false;
this._rangedListeners.push(rangedListener);
this._rangedListeners.sort((a, b) => a.lower - b.lower);
if (this.#rangedListeners.includes(rangedListener)) return false;
this.#rangedListeners.push(rangedListener);
this.#rangedListeners.sort((a, b) => a.lower - b.lower);
return true;
}
@ -94,13 +101,13 @@ export default class VisualizerUpdateManager {
*
* @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 {VisualizerUpdateManager.visualizerBinUpdateListener} binFreqListener.listener the listener that is to be removed.
* @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);
const removeIndex = this.#binnedListeners[freqBin].indexOf(listener);
if (removeIndex < 0) return false;
this._binnedListeners[freqBin].splice(removeIndex, 1);
this.#binnedListeners[freqBin].splice(removeIndex, 1);
return true;
}
@ -110,7 +117,7 @@ export default class VisualizerUpdateManager {
* @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 {VisualizerUpdateManager.visualizerRangedUpdateListener} rangedListener.listener The update listener to remove from the bins.
* @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 }) {
@ -119,36 +126,36 @@ export default class VisualizerUpdateManager {
upper: upper,
listener: listener
};
const removeIndex = this._rangedListeners.indexOf(rangedListener);
const removeIndex = this.#rangedListeners.indexOf(rangedListener);
if (removeIndex < 0) return false;
this._binnedListeners.splice(removeIndex, 1);
this.#binnedListeners.splice(removeIndex, 1);
return true;
}
/**
* @param {object} listeners The listeners that this visualizer update manager should be set to use.
* @param {VisualizerUpdateManager.visualizerBinUpdateListener[][]} listeners.binned The bin listeners.
* @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 {VisualizerUpdateManager.visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges.
* @param {visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges.
* @returns {boolean} true if and only if successfully loaded the new listeners.
*/
setBinnedListeners(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);
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 VisualizerUpdateManager#setBinnedListeners} to see the structure of the returned object.
* @returns {object} All the listeners, both for binned, and ranged listeners. See {@link VisualizerUpdateManager#loadListeners} to see the structure of the returned object.
*/
getAllListeners() {
retrieveListeners() {
return {
binned: this._binnedListeners,
ranged: this._rangedListeners
binned: this.#binnedListeners,
ranged: this.#rangedListeners
};
}
@ -156,7 +163,7 @@ export default class VisualizerUpdateManager {
* Clears this manager of all listeners.
*/
clearBinnedListeners() {
this._binnedListeners.forEach(bin => {
this.#binnedListeners.forEach(bin => {
bin.length = 0;
});
}
@ -165,6 +172,6 @@ export default class VisualizerUpdateManager {
* Unbinds this update manager from the initial visualizer. Effectively meaning this manager will no longer be used.
*/
unbindVisualizer() {
this._visualizer.removeUpdateListener(this._visualizerListener);
this.#visualizer.removeUpdateListener(this.#visualizerListener);
}
}