fix: remove xo, migration to eslint

This commit is contained in:
JellyBrick
2023-08-29 17:22:38 +09:00
parent 31a7588cee
commit c722896a73
142 changed files with 17210 additions and 18409 deletions

View File

@ -16,345 +16,346 @@
*/
(function (root) {
"use strict";
'use strict';
// internal utility: check if value is a valid volume level and throw if not
let validateVolumeLevel = (value) => {
// number between 0 and 1?
if (!Number.isNaN(value) && value >= 0 && value <= 1) {
// yup, that's fine
return;
} else {
// abort and throw an exception
throw new TypeError("Number between 0 and 1 expected as volume!");
}
};
// Internal utility: check if value is a valid volume level and throw if not
const validateVolumeLevel = (value) => {
// Number between 0 and 1?
if (!Number.isNaN(value) && value >= 0 && value <= 1) {
// Yup, that's fine
// main class
class VolumeFader {
/**
* VolumeFader Constructor
*
* @param media {HTMLMediaElement} - audio or video element to be controlled
* @param options {Object} - an object with optional settings
* @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid
*
* options:
* .logger: {Function} logging `function(stuff, …)` for execution information (default: no logging)
* .fadeScaling: {Mixed} either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic)
* .initialVolume: {Number} media volume 0…1 to apply during setup (volume not touched by default)
* .fadeDuration: {Number} time in milliseconds to complete a fade (default: 1000 ms)
*/
constructor(media, options) {
// passed media element of correct type?
if (media instanceof HTMLMediaElement) {
// save reference to media element
this.media = media;
} else {
// abort and throw an exception
throw new TypeError("Media element expected!");
}
} else {
// Abort and throw an exception
throw new TypeError('Number between 0 and 1 expected as volume!');
}
};
// make sure options is an object
options = options || {};
// Main class
class VolumeFader {
/**
* VolumeFader Constructor
*
* @param media {HTMLMediaElement} - audio or video element to be controlled
* @param options {Object} - an object with optional settings
* @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid
*
* options:
* .logger: {Function} logging `function(stuff, …)` for execution information (default: no logging)
* .fadeScaling: {Mixed} either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic)
* .initialVolume: {Number} media volume 0…1 to apply during setup (volume not touched by default)
* .fadeDuration: {Number} time in milliseconds to complete a fade (default: 1000 ms)
*/
constructor(media, options) {
// Passed media element of correct type?
if (media instanceof HTMLMediaElement) {
// Save reference to media element
this.media = media;
} else {
// Abort and throw an exception
throw new TypeError('Media element expected!');
}
// log function passed?
if (typeof options.logger == "function") {
// set log function to the one specified
this.logger = options.logger;
} else {
// set log function explicitly to false
this.logger = false;
}
// Make sure options is an object
options = options || {};
// linear volume fading?
if (options.fadeScaling == "linear") {
// pass levels unchanged
this.scale = {
internalToVolume: (level) => level,
volumeToInternal: (level) => level,
};
// Log function passed?
if (typeof options.logger === 'function') {
// Set log function to the one specified
this.logger = options.logger;
} else {
// Set log function explicitly to false
this.logger = false;
}
// log setting
this.logger && this.logger("Using linear fading.");
}
// no linear, but logarithmic fading…
else {
let dynamicRange;
// Linear volume fading?
if (options.fadeScaling == 'linear') {
// Pass levels unchanged
this.scale = {
internalToVolume: (level) => level,
volumeToInternal: (level) => level,
};
// default dynamic range?
if (
options.fadeScaling === undefined ||
options.fadeScaling == "logarithmic"
) {
// set default of 60 dB
dynamicRange = 3;
}
// custom dynamic range?
else if (
!Number.isNaN(options.fadeScaling) &&
options.fadeScaling > 0
) {
// turn amplitude dB into a multiple of 10 power dB
dynamicRange = options.fadeScaling / 2 / 10;
}
// unsupported value
else {
// abort and throw exception
throw new TypeError(
"Expected 'linear', 'logarithmic' or a positive number as fade scaling preference!"
);
}
// Log setting
this.logger && this.logger('Using linear fading.');
}
// No linear, but logarithmic fading…
else {
let dynamicRange;
// use exponential/logarithmic scaler for expansion/compression
this.scale = {
internalToVolume: (level) =>
this.exponentialScaler(level, dynamicRange),
volumeToInternal: (level) =>
this.logarithmicScaler(level, dynamicRange),
};
// Default dynamic range?
if (
options.fadeScaling === undefined
|| options.fadeScaling == 'logarithmic'
) {
// Set default of 60 dB
dynamicRange = 3;
}
// Custom dynamic range?
else if (
!Number.isNaN(options.fadeScaling)
&& options.fadeScaling > 0
) {
// Turn amplitude dB into a multiple of 10 power dB
dynamicRange = options.fadeScaling / 2 / 10;
}
// Unsupported value
else {
// Abort and throw exception
throw new TypeError(
"Expected 'linear', 'logarithmic' or a positive number as fade scaling preference!",
);
}
// log setting if not default
options.fadeScaling &&
this.logger &&
this.logger(
"Using logarithmic fading with " +
String(10 * dynamicRange) +
" dB dynamic range."
);
}
// Use exponential/logarithmic scaler for expansion/compression
this.scale = {
internalToVolume: (level) =>
this.exponentialScaler(level, dynamicRange),
volumeToInternal: (level) =>
this.logarithmicScaler(level, dynamicRange),
};
// set initial volume?
if (options.initialVolume !== undefined) {
// validate volume level and throw if invalid
validateVolumeLevel(options.initialVolume);
// Log setting if not default
options.fadeScaling
&& this.logger
&& this.logger(
'Using logarithmic fading with '
+ String(10 * dynamicRange)
+ ' dB dynamic range.',
);
}
// set initial volume
this.media.volume = options.initialVolume;
// Set initial volume?
if (options.initialVolume !== undefined) {
// Validate volume level and throw if invalid
validateVolumeLevel(options.initialVolume);
// log setting
this.logger &&
this.logger(
"Set initial volume to " + String(this.media.volume) + "."
);
}
// Set initial volume
this.media.volume = options.initialVolume;
// fade duration given?
if (options.fadeDuration !== undefined) {
// try to set given fade duration (will log if successful and throw if not)
this.setFadeDuration(options.fadeDuration);
} else {
// set default fade duration (1000 ms)
this.fadeDuration = 1000;
}
// Log setting
this.logger
&& this.logger(
'Set initial volume to ' + String(this.media.volume) + '.',
);
}
// indicate that fader is not active yet
this.active = false;
// Fade duration given?
if (options.fadeDuration === undefined) {
// Set default fade duration (1000 ms)
this.fadeDuration = 1000;
} else {
// Try to set given fade duration (will log if successful and throw if not)
this.setFadeDuration(options.fadeDuration);
}
// initialization done
this.logger && this.logger("Initialized for", this.media);
}
// Indicate that fader is not active yet
this.active = false;
/**
* Re(start) the update cycle.
* (this.active must be truthy for volume updates to take effect)
*
* @return {Object} VolumeFader instance for chaining
*/
start() {
// set fader to be active
this.active = true;
// Initialization done
this.logger && this.logger('Initialized for', this.media);
}
// start by running the update method
this.updateVolume();
/**
* Re(start) the update cycle.
* (this.active must be truthy for volume updates to take effect)
*
* @return {Object} VolumeFader instance for chaining
*/
start() {
// Set fader to be active
this.active = true;
// return instance for chaining
return this;
}
// Start by running the update method
this.updateVolume();
/**
* Stop the update cycle.
* (interrupting any fade)
*
* @return {Object} VolumeFader instance for chaining
*/
stop() {
// set fader to be inactive
this.active = false;
// Return instance for chaining
return this;
}
// return instance for chaining
return this;
}
/**
* Stop the update cycle.
* (interrupting any fade)
*
* @return {Object} VolumeFader instance for chaining
*/
stop() {
// Set fader to be inactive
this.active = false;
/**
* Set fade duration.
* (used for future calls to fadeTo)
*
* @param {Number} fadeDuration - fading length in milliseconds
* @throws {TypeError} if fadeDuration is not a number greater than zero
* @return {Object} VolumeFader instance for chaining
*/
setFadeDuration(fadeDuration) {
// if duration is a valid number > 0…
if (!Number.isNaN(fadeDuration) && fadeDuration > 0) {
// set fade duration
this.fadeDuration = fadeDuration;
// Return instance for chaining
return this;
}
// log setting
this.logger &&
this.logger("Set fade duration to " + String(fadeDuration) + " ms.");
} else {
// abort and throw an exception
throw new TypeError("Positive number expected as fade duration!");
}
/**
* Set fade duration.
* (used for future calls to fadeTo)
*
* @param {Number} fadeDuration - fading length in milliseconds
* @throws {TypeError} if fadeDuration is not a number greater than zero
* @return {Object} VolumeFader instance for chaining
*/
setFadeDuration(fadeDuration) {
// If duration is a valid number > 0…
if (!Number.isNaN(fadeDuration) && fadeDuration > 0) {
// Set fade duration
this.fadeDuration = fadeDuration;
// return instance for chaining
return this;
}
// Log setting
this.logger
&& this.logger('Set fade duration to ' + String(fadeDuration) + ' ms.');
} else {
// Abort and throw an exception
throw new TypeError('Positive number expected as fade duration!');
}
/**
* Define a new fade and start fading.
*
* @param {Number} targetVolume - level to fade to in the range 0…1
* @param {Function} callback - (optional) function to be called when fade is complete
* @throws {TypeError} if targetVolume is not in the range 0…1
* @return {Object} VolumeFader instance for chaining
*/
fadeTo(targetVolume, callback) {
// validate volume and throw if invalid
validateVolumeLevel(targetVolume);
// Return instance for chaining
return this;
}
// define new fade
this.fade = {
// volume start and end point on internal fading scale
volume: {
start: this.scale.volumeToInternal(this.media.volume),
end: this.scale.volumeToInternal(targetVolume),
},
// time start and end point
time: {
start: Date.now(),
end: Date.now() + this.fadeDuration,
},
// optional callback function
callback: callback,
};
/**
* Define a new fade and start fading.
*
* @param {Number} targetVolume - level to fade to in the range 0…1
* @param {Function} callback - (optional) function to be called when fade is complete
* @throws {TypeError} if targetVolume is not in the range 0…1
* @return {Object} VolumeFader instance for chaining
*/
fadeTo(targetVolume, callback) {
// Validate volume and throw if invalid
validateVolumeLevel(targetVolume);
// start fading
this.start();
// Define new fade
this.fade = {
// Volume start and end point on internal fading scale
volume: {
start: this.scale.volumeToInternal(this.media.volume),
end: this.scale.volumeToInternal(targetVolume),
},
// Time start and end point
time: {
start: Date.now(),
end: Date.now() + this.fadeDuration,
},
// Optional callback function
callback,
};
// log new fade
this.logger && this.logger("New fade started:", this.fade);
// Start fading
this.start();
// return instance for chaining
return this;
}
// Log new fade
this.logger && this.logger('New fade started:', this.fade);
// convenience shorthand methods for common fades
fadeIn(callback) {
this.fadeTo(1, callback);
}
fadeOut(callback) {
this.fadeTo(0, callback);
}
// Return instance for chaining
return this;
}
/**
* Internal: Update media volume.
* (calls itself through requestAnimationFrame)
*
* @param {Number} targetVolume - linear level to fade to (0…1)
* @param {Function} callback - (optional) function to be called when fade is complete
*/
updateVolume() {
// fader active and fade available to process?
if (this.active && this.fade) {
// get current time
let now = Date.now();
// Convenience shorthand methods for common fades
fadeIn(callback) {
this.fadeTo(1, callback);
}
// time left for fading?
if (now < this.fade.time.end) {
// compute current fade progress
let progress =
(now - this.fade.time.start) /
(this.fade.time.end - this.fade.time.start);
fadeOut(callback) {
this.fadeTo(0, callback);
}
// compute current level on internal scale
let level =
progress * (this.fade.volume.end - this.fade.volume.start) +
this.fade.volume.start;
/**
* Internal: Update media volume.
* (calls itself through requestAnimationFrame)
*
* @param {Number} targetVolume - linear level to fade to (0…1)
* @param {Function} callback - (optional) function to be called when fade is complete
*/
updateVolume() {
// Fader active and fade available to process?
if (this.active && this.fade) {
// Get current time
const now = Date.now();
// map fade level to volume level and apply it to media element
this.media.volume = this.scale.internalToVolume(level);
// Time left for fading?
if (now < this.fade.time.end) {
// Compute current fade progress
const progress
= (now - this.fade.time.start)
/ (this.fade.time.end - this.fade.time.start);
// schedule next update
root.requestAnimationFrame(this.updateVolume.bind(this));
} else {
// log end of fade
this.logger &&
this.logger(
"Fade to " + String(this.fade.volume.end) + " complete."
);
// Compute current level on internal scale
const level
= progress * (this.fade.volume.end - this.fade.volume.start)
+ this.fade.volume.start;
// time is up, jump to target volume
this.media.volume = this.scale.internalToVolume(this.fade.volume.end);
// Map fade level to volume level and apply it to media element
this.media.volume = this.scale.internalToVolume(level);
// set fader to be inactive
this.active = false;
// Schedule next update
root.requestAnimationFrame(this.updateVolume.bind(this));
} else {
// Log end of fade
this.logger
&& this.logger(
'Fade to ' + String(this.fade.volume.end) + ' complete.',
);
// done, call back (if callable)
typeof this.fade.callback == "function" && this.fade.callback();
// Time is up, jump to target volume
this.media.volume = this.scale.internalToVolume(this.fade.volume.end);
// clear fade
this.fade = undefined;
}
}
}
// Set fader to be inactive
this.active = false;
/**
* Internal: Exponential scaler with dynamic range limit.
*
* @param {Number} input - logarithmic input level to be expanded (float, 0…1)
* @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - expanded level (float, 0…1)
*/
exponentialScaler(input, dynamicRange) {
// special case: make zero (or any falsy input) return zero
if (input == 0) {
// since the dynamic range is limited,
// allow a zero to produce a plain zero instead of a small faction
// (audio would not be recognized as silent otherwise)
return 0;
} else {
// scale 0…1 to minus something × 10 dB
input = (input - 1) * dynamicRange;
// Done, call back (if callable)
typeof this.fade.callback === 'function' && this.fade.callback();
// compute power of 10
return Math.pow(10, input);
}
}
// Clear fade
this.fade = undefined;
}
}
}
/**
* Internal: Logarithmic scaler with dynamic range limit.
*
* @param {Number} input - exponential input level to be compressed (float, 0…1)
* @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - compressed level (float, 0…1)
*/
logarithmicScaler(input, dynamicRange) {
// special case: make zero (or any falsy input) return zero
if (input == 0) {
// logarithm of zero would be -∞, which would map to zero anyway
return 0;
} else {
// compute base-10 logarithm
input = Math.log10(input);
/**
* Internal: Exponential scaler with dynamic range limit.
*
* @param {Number} input - logarithmic input level to be expanded (float, 0…1)
* @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - expanded level (float, 0…1)
*/
exponentialScaler(input, dynamicRange) {
// Special case: make zero (or any falsy input) return zero
if (input == 0) {
// Since the dynamic range is limited,
// allow a zero to produce a plain zero instead of a small faction
// (audio would not be recognized as silent otherwise)
return 0;
}
// scale minus something × 10 dB to 0…1 (clipping at 0)
return Math.max(1 + input / dynamicRange, 0);
}
}
}
// Scale 0…1 to minus something × 10 dB
input = (input - 1) * dynamicRange;
// export class to root scope
root.VolumeFader = VolumeFader;
// Compute power of 10
return 10 ** input;
}
/**
* Internal: Logarithmic scaler with dynamic range limit.
*
* @param {Number} input - exponential input level to be compressed (float, 0…1)
* @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0…∞)
* @return {Number} - compressed level (float, 0…1)
*/
logarithmicScaler(input, dynamicRange) {
// Special case: make zero (or any falsy input) return zero
if (input == 0) {
// Logarithm of zero would be -∞, which would map to zero anyway
return 0;
}
// Compute base-10 logarithm
input = Math.log10(input);
// Scale minus something × 10 dB to 0…1 (clipping at 0)
return Math.max(1 + input / dynamicRange, 0);
}
}
// Export class to root scope
root.VolumeFader = VolumeFader;
})(window);