From 4f438a5e03de61b519cf817cfa21e06e9c715cf7 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sat, 11 Sep 2021 13:26:41 -0300 Subject: [PATCH 01/16] Minor optimizations. --- src/audioMotion-analyzer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 120943d..f21072c 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -1823,10 +1823,10 @@ export default class AudioMotionAnalyzer { const callbacks = [ 'onCanvasDraw', 'onCanvasResize' ]; // compile valid properties; `start` is not an actual property and is handled after setting everything else - const validProps = Object.keys( defaults ).concat( callbacks, ['height', 'width'] ).filter( e => e != 'start' ); + const validProps = Object.keys( defaults ).filter( e => e != 'start' ).concat( callbacks, ['height', 'width'] ); if ( useDefaults || options === undefined ) - options = Object.assign( defaults, options ); // NOTE: defaults is modified! + options = { ...defaults, ...options }; // merge options with defaults for ( const prop of Object.keys( options ) ) { if ( callbacks.includes( prop ) && typeof options[ prop ] !== 'function' ) // check invalid callback From b7f1f7fec5247211c29e307ba5aed619e994526b Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sat, 11 Sep 2021 18:04:47 -0300 Subject: [PATCH 02/16] Add `alphaBars` property. --- src/audioMotion-analyzer.js | 32 +++++++++++++++++++------------- src/index.d.ts | 3 +++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index f21072c..6205f95 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -925,6 +925,7 @@ export default class AudioMotionAnalyzer { canvasX = this._scaleX.canvas, canvasR = this._scaleR.canvas, energy = this._energy, + isAlphaBars = this.alphaBars, isOctaveBands = this._isOctaveBands, isLedDisplay = this._isLedDisplay, isLumiBars = this._isLumiBars, @@ -958,12 +959,14 @@ export default class AudioMotionAnalyzer { // helper function - draw a polygon of width `w` and height `h` at (x,y) in radial mode const radialPoly = ( x, y, w, h ) => { + ctx.beginPath(); for ( const dir of ( mirrorMode ? [1,-1] : [1] ) ) { ctx.moveTo( ...radialXY( x, y, dir ) ); ctx.lineTo( ...radialXY( x, y + h, dir ) ); ctx.lineTo( ...radialXY( x + w, y + h, dir ) ); ctx.lineTo( ...radialXY( x + w, y, dir ) ); } + ctx.fill(); } // LED attributes and helper function for bar height calculation @@ -1071,12 +1074,13 @@ export default class AudioMotionAnalyzer { // helper function for FFT data interpolation const interpolate = ( bin, ratio ) => fftData[ bin ] + ( fftData[ bin + 1 ] - fftData[ bin ] ) * ratio; - // start drawing path + // start drawing path (for mode 10) ctx.beginPath(); - // draw bars / lines + // store line graph points to create mirror effect in radial mode + let points = []; - let points = []; // store line graph (mode 10) points to create mirror effect in radial mode + // draw bars / lines for ( let i = 0; i < nBars; i++ ) { @@ -1114,7 +1118,7 @@ export default class AudioMotionAnalyzer { continue; // set opacity for lumi bars before barHeight value is normalized - if ( isLumiBars ) + if ( isLumiBars || isAlphaBars ) ctx.globalAlpha = barHeight; // normalize barHeight @@ -1213,13 +1217,17 @@ export default class AudioMotionAnalyzer { } // Draw peak - if ( bar.peak[ channel ] > 0 && this.showPeaks && ! isLumiBars && posX >= initialX && posX < finalX ) { + const peak = bar.peak[ channel ]; + if ( peak > 0 && this.showPeaks && ! isLumiBars && posX >= initialX && posX < finalX ) { + if ( isAlphaBars ) + ctx.globalAlpha = peak; + if ( isLedDisplay ) - ctx.fillRect( posX, analyzerBottom - ledPosY( bar.peak[ channel ] ), width, ledHeight ); + ctx.fillRect( posX, analyzerBottom - ledPosY( peak ), width, ledHeight ); else if ( ! isRadial ) - ctx.fillRect( posX, analyzerBottom - bar.peak[ channel ] * maxBarHeight, adjWidth, 2 ); + ctx.fillRect( posX, analyzerBottom - peak * maxBarHeight, adjWidth, 2 ); else if ( mode != 10 ) // radial - no peaks for mode 10 - radialPoly( posX, bar.peak[ channel ] * maxBarHeight * ( ! channel || -1 ), adjWidth, -2 ); + radialPoly( posX, peak * maxBarHeight * ( ! channel || -1 ), adjWidth, -2 ); } } // for ( let i = 0; i < nBars; i++ ) @@ -1231,7 +1239,7 @@ export default class AudioMotionAnalyzer { // restore global alpha ctx.globalAlpha = 1; - // Fill/stroke drawing path for mode 10 and radial + // Fill/stroke drawing path for mode 10 if ( mode == 10 ) { if ( isRadial ) { if ( mirrorMode ) { @@ -1263,9 +1271,6 @@ export default class AudioMotionAnalyzer { ctx.globalAlpha = 1; } } - else if ( isRadial ) { - ctx.fill(); - } // Reflex effect if ( this._reflexRatio > 0 && ! isLumiBars ) { @@ -1816,7 +1821,8 @@ export default class AudioMotionAnalyzer { start : true, volume : 1, mirror : 0, - useCanvas : true + useCanvas : true, + alphaBars : false }; // callback functions properties diff --git a/src/index.d.ts b/src/index.d.ts index 90ddba7..3af9bfd 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -7,6 +7,7 @@ type OnCanvasResizeFunction = ( type CanvasResizeReason = "create" | "fschange" | "lores" | "resize" | "user"; export interface Options { + alphaBars?: boolean; barSpace?: number; bgAlpha?: number; fftSize?: number; @@ -86,6 +87,8 @@ export interface LedParameters { declare class AudioMotionAnalyzer { constructor(container?: HTMLElement, options?: ConstructorOptions); + public alphaBars: boolean; + get audioCtx(): AudioContext; get canvas(): HTMLCanvasElement; get canvasCtx(): CanvasRenderingContext2D; From 24282c15f2233af7ba3e76d5749296b6a5d6937f Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Tue, 28 Sep 2021 13:32:02 -0300 Subject: [PATCH 03/16] Regenerate the current gradient when it is registered (#21). --- src/audioMotion-analyzer.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 6205f95..6c72454 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -693,17 +693,15 @@ export default class AudioMotionAnalyzer { if ( options.colorStops === undefined || options.colorStops.length < 2 ) throw new AudioMotionError( 'ERR_GRADIENT_MISSING_COLOR', 'Gradient must define at least two colors' ); - this._gradients[ name ] = {}; - - if ( options.bgColor !== undefined ) - this._gradients[ name ].bgColor = options.bgColor; - else - this._gradients[ name ].bgColor = '#111'; - - if ( options.dir !== undefined ) - this._gradients[ name ].dir = options.dir; + this._gradients[ name ] = { + bgColor: options.bgColor || '#111', + dir: options.dir, + colorStops: options.colorStops + }; - this._gradients[ name ].colorStops = options.colorStops; + // if the registered gradient is the current one, regenerate it + if ( name == this._gradient ) + this._makeGrad(); } /** From 8f93d6e39650454ebc3069011f999444836d50d2 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Tue, 28 Sep 2021 15:09:30 -0300 Subject: [PATCH 04/16] Add `outlineBars` property. --- src/audioMotion-analyzer.js | 18 ++++++++++-------- src/index.d.ts | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 6c72454..54707f6 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -927,6 +927,7 @@ export default class AudioMotionAnalyzer { isOctaveBands = this._isOctaveBands, isLedDisplay = this._isLedDisplay, isLumiBars = this._isLumiBars, + isOutline = this.outlineBars && isOctaveBands && ! isLumiBars, isRadial = this._radial, isStereo = this._stereo, lineWidth = +this.lineWidth, // make sure the damn thing is a number! @@ -956,7 +957,7 @@ export default class AudioMotionAnalyzer { } // helper function - draw a polygon of width `w` and height `h` at (x,y) in radial mode - const radialPoly = ( x, y, w, h ) => { + const radialPoly = ( x, y, w, h, stroke ) => { ctx.beginPath(); for ( const dir of ( mirrorMode ? [1,-1] : [1] ) ) { ctx.moveTo( ...radialXY( x, y, dir ) ); @@ -964,7 +965,7 @@ export default class AudioMotionAnalyzer { ctx.lineTo( ...radialXY( x + w, y + h, dir ) ); ctx.lineTo( ...radialXY( x + w, y, dir ) ); } - ctx.fill(); + ctx[ stroke ? 'stroke' : 'fill' ](); } // LED attributes and helper function for bar height calculation @@ -1060,6 +1061,8 @@ export default class AudioMotionAnalyzer { ctx.setLineDash( [ ledHeight, ledSpaceV ] ); ctx.lineWidth = width; } + else // for outline effect ensure linewidth is greater than 0, but not greater than half the bar width + ctx.lineWidth = isOutline ? Math.min( lineWidth || 1, width / 2 ) : lineWidth; // set selected gradient for fill and stroke ctx.fillStyle = ctx.strokeStyle = this._canvasGradient; @@ -1208,9 +1211,9 @@ export default class AudioMotionAnalyzer { } else if ( posX >= initialX ) { if ( isRadial ) - radialPoly( posX, 0, adjWidth, barHeight ); + radialPoly( posX, 0, adjWidth, barHeight, isOutline ); else - ctx.fillRect( posX, isLumiBars ? channelTop : analyzerBottom, adjWidth, isLumiBars ? channelBottom : -barHeight ); + ctx[ `${ isOutline ? 'stroke' : 'fill' }Rect` ]( posX, isLumiBars ? channelTop : analyzerBottom, adjWidth, isLumiBars ? channelBottom : -barHeight ); } } @@ -1248,10 +1251,8 @@ export default class AudioMotionAnalyzer { ctx.closePath(); } - if ( lineWidth > 0 ) { - ctx.lineWidth = lineWidth; + if ( lineWidth > 0 ) ctx.stroke(); - } if ( this.fillAlpha > 0 ) { if ( isRadial ) { @@ -1820,7 +1821,8 @@ export default class AudioMotionAnalyzer { volume : 1, mirror : 0, useCanvas : true, - alphaBars : false + alphaBars : false, + outlineBars : false }; // callback functions properties diff --git a/src/index.d.ts b/src/index.d.ts index 3af9bfd..034ce2c 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -25,6 +25,7 @@ export interface Options { mode?: number; onCanvasDraw?: OnCanvasDrawFunction; onCanvasResize?: OnCanvasResizeFunction; + outlineBars?: boolean; overlay?: boolean; radial?: boolean; reflexAlpha?: number; @@ -155,6 +156,7 @@ declare class AudioMotionAnalyzer { get mode(): number; set mode(value: number); + public outlineBars: boolean; public overlay: boolean; get peakEnergy(): number; From ea651fa3e4ccc16c69c88c9696de913f52610e99 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Tue, 28 Sep 2021 17:11:16 -0300 Subject: [PATCH 05/16] Use fillAlpha with outlineBars; avoid borders at bottom of stroked bars. --- src/audioMotion-analyzer.js | 43 +++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 54707f6..30e3490 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -923,7 +923,8 @@ export default class AudioMotionAnalyzer { canvasX = this._scaleX.canvas, canvasR = this._scaleR.canvas, energy = this._energy, - isAlphaBars = this.alphaBars, + mode = this._mode, + isAlphaBars = this.alphaBars && mode != 10, isOctaveBands = this._isOctaveBands, isLedDisplay = this._isLedDisplay, isLumiBars = this._isLumiBars, @@ -932,7 +933,6 @@ export default class AudioMotionAnalyzer { isStereo = this._stereo, lineWidth = +this.lineWidth, // make sure the damn thing is a number! mirrorMode = this._mirror, - mode = this._mode, channelHeight = this._channelHeight, channelGap = this._channelGap, analyzerHeight = this._analyzerHeight, @@ -948,6 +948,15 @@ export default class AudioMotionAnalyzer { if ( energy.val > 0 ) this._spinAngle += this._spinSpeed * RPM; + const strokeIf = flag => { + if ( flag && lineWidth ) { + const alpha = ctx.globalAlpha; + ctx.globalAlpha = 1; + ctx.stroke(); + ctx.globalAlpha = alpha; + } + } + // helper function - convert planar X,Y coordinates to radial coordinates const radialXY = ( x, y, dir ) => { const height = radius + y, @@ -965,7 +974,9 @@ export default class AudioMotionAnalyzer { ctx.lineTo( ...radialXY( x + w, y + h, dir ) ); ctx.lineTo( ...radialXY( x + w, y, dir ) ); } - ctx[ stroke ? 'stroke' : 'fill' ](); + + strokeIf( stroke ); + ctx.fill(); } // LED attributes and helper function for bar height calculation @@ -1061,8 +1072,8 @@ export default class AudioMotionAnalyzer { ctx.setLineDash( [ ledHeight, ledSpaceV ] ); ctx.lineWidth = width; } - else // for outline effect ensure linewidth is greater than 0, but not greater than half the bar width - ctx.lineWidth = isOutline ? Math.min( lineWidth || 1, width / 2 ) : lineWidth; + else // for outline effect ensure linewidth is not greater than half the bar width + ctx.lineWidth = isOutline ? Math.min( lineWidth, width / 2 ) : lineWidth; // set selected gradient for fill and stroke ctx.fillStyle = ctx.strokeStyle = this._canvasGradient; @@ -1121,6 +1132,8 @@ export default class AudioMotionAnalyzer { // set opacity for lumi bars before barHeight value is normalized if ( isLumiBars || isAlphaBars ) ctx.globalAlpha = barHeight; + else if ( isOutline ) + ctx.globalAlpha = this.fillAlpha; // normalize barHeight if ( isLedDisplay ) { @@ -1212,16 +1225,28 @@ export default class AudioMotionAnalyzer { else if ( posX >= initialX ) { if ( isRadial ) radialPoly( posX, 0, adjWidth, barHeight, isOutline ); - else - ctx[ `${ isOutline ? 'stroke' : 'fill' }Rect` ]( posX, isLumiBars ? channelTop : analyzerBottom, adjWidth, isLumiBars ? channelBottom : -barHeight ); + else { + const x = posX, + y = isLumiBars ? channelTop : analyzerBottom, + w = adjWidth, + h = isLumiBars ? channelBottom : -barHeight; + + ctx.beginPath(); + ctx.moveTo( x, y ); + ctx.lineTo( x, y + h ); + ctx.lineTo( x + w, y + h ); + ctx.lineTo( x + w, y ); + + strokeIf( isOutline ); + ctx.fill(); + } } } // Draw peak const peak = bar.peak[ channel ]; if ( peak > 0 && this.showPeaks && ! isLumiBars && posX >= initialX && posX < finalX ) { - if ( isAlphaBars ) - ctx.globalAlpha = peak; + ctx.globalAlpha = isAlphaBars ? peak : 1; if ( isLedDisplay ) ctx.fillRect( posX, analyzerBottom - ledPosY( peak ), width, ledHeight ); From be2295d1c84a55dce21cd2f61a38f888f3ad68f2 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Wed, 29 Sep 2021 11:23:50 -0300 Subject: [PATCH 06/16] Improve peak opacity with alphaBars / outlineBars effects. --- src/audioMotion-analyzer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 30e3490..e130812 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -1246,8 +1246,13 @@ export default class AudioMotionAnalyzer { // Draw peak const peak = bar.peak[ channel ]; if ( peak > 0 && this.showPeaks && ! isLumiBars && posX >= initialX && posX < finalX ) { - ctx.globalAlpha = isAlphaBars ? peak : 1; + // choose the best opacity for the peaks + if ( isOutline && lineWidth > 0 ) + ctx.globalAlpha = 1; + else if ( isAlphaBars ) + ctx.globalAlpha = peak; + // render peak according to current mode / effect if ( isLedDisplay ) ctx.fillRect( posX, analyzerBottom - ledPosY( peak ), width, ledHeight ); else if ( ! isRadial ) From c0ee786241723a7d556dd083aeafc2633215ebc8 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Wed, 29 Sep 2021 11:58:13 -0300 Subject: [PATCH 07/16] Prevent fillAlpha of affecting the LEDs effect when outlineBars is on. --- src/audioMotion-analyzer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index e130812..91a74f1 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -928,7 +928,7 @@ export default class AudioMotionAnalyzer { isOctaveBands = this._isOctaveBands, isLedDisplay = this._isLedDisplay, isLumiBars = this._isLumiBars, - isOutline = this.outlineBars && isOctaveBands && ! isLumiBars, + isOutline = this.outlineBars && isOctaveBands && ! isLumiBars && ! isLedDisplay, isRadial = this._radial, isStereo = this._stereo, lineWidth = +this.lineWidth, // make sure the damn thing is a number! @@ -1129,7 +1129,7 @@ export default class AudioMotionAnalyzer { if ( ! useCanvas ) continue; - // set opacity for lumi bars before barHeight value is normalized + // set opacity for bar effects if ( isLumiBars || isAlphaBars ) ctx.globalAlpha = barHeight; else if ( isOutline ) From 2b44bd2ee40bb6d063d35f6f96f6d9447d507998 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Wed, 29 Sep 2021 21:15:59 -0300 Subject: [PATCH 08/16] Add getters/setters and auxiliary properties for the new bar effects. --- src/audioMotion-analyzer.js | 41 +++++++++++++++++++++++++++++++------ src/index.d.ts | 9 ++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 91a74f1..cc397ec 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -231,6 +231,17 @@ export default class AudioMotionAnalyzer { * ========================================================================== */ + + // alphaBars effect + + get alphaBars() { + return this._alphaBars; + } + set alphaBars( value ) { + this._alphaBars = !! value; + this._calcAux(); + } + // Bar spacing (for octave bands modes) get barSpace() { @@ -335,6 +346,16 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } + // Outlined bars + + get outlineBars() { + return this._outlineBars; + } + set outlineBars( value ) { + this._outlineBars = !! value; + this._calcAux(); + } + // Radial mode get radial() { @@ -509,6 +530,9 @@ export default class AudioMotionAnalyzer { get fps() { return this._fps; } + get isAlphaBars() { + return this._isAlphaBars; + } get isFullscreen() { return ( document.fullscreenElement || document.webkitFullscreenElement ) === this._fsEl; } @@ -524,6 +548,9 @@ export default class AudioMotionAnalyzer { get isOn() { return this._runId !== undefined; } + get isOutlineBars() { + return this._isOutline; + } get peakEnergy() { // DEPRECATED - to be removed in v4.0.0 return this.getEnergy('peak'); @@ -837,9 +864,11 @@ export default class AudioMotionAnalyzer { this._radius = Math.min( canvas.width, canvas.height ) * ( this._stereo ? .375 : .125 ) | 0; this._barSpacePx = Math.min( this._barWidth - 1, ( this._barSpace > 0 && this._barSpace < 1 ) ? this._barWidth * this._barSpace : this._barSpace ); - this._isOctaveBands = ( this._mode % 10 != 0 ); - this._isLedDisplay = ( this._showLeds && this._isOctaveBands && ! isRadial ); - this._isLumiBars = ( this._lumiBars && this._isOctaveBands && ! isRadial ); + this._isOctaveBands = this._mode % 10 != 0; + this._isLedDisplay = this._showLeds && this._isOctaveBands && ! isRadial; + this._isLumiBars = this._lumiBars && this._isOctaveBands && ! isRadial; + this._isAlphaBars = this._alphaBars && ! this._isLumiBars && this._mode != 10; + this._isOutline = this._outlineBars && this._isOctaveBands && ! this._isLumiBars && ! this._isLedDisplay; this._maximizeLeds = ! this._stereo || this._reflexRatio > 0 && ! this._isLumiBars; this._channelHeight = canvas.height - ( isDual && ! this._isLedDisplay ? .5 : 0 ) >> isDual; @@ -924,11 +953,11 @@ export default class AudioMotionAnalyzer { canvasR = this._scaleR.canvas, energy = this._energy, mode = this._mode, - isAlphaBars = this.alphaBars && mode != 10, - isOctaveBands = this._isOctaveBands, + isAlphaBars = this._isAlphaBars, isLedDisplay = this._isLedDisplay, isLumiBars = this._isLumiBars, - isOutline = this.outlineBars && isOctaveBands && ! isLumiBars && ! isLedDisplay, + isOctaveBands = this._isOctaveBands, + isOutline = this._isOutline, isRadial = this._radial, isStereo = this._stereo, lineWidth = +this.lineWidth, // make sure the damn thing is a number! diff --git a/src/index.d.ts b/src/index.d.ts index 034ce2c..e357373 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -88,7 +88,8 @@ export interface LedParameters { declare class AudioMotionAnalyzer { constructor(container?: HTMLElement, options?: ConstructorOptions); - public alphaBars: boolean; + get alphaBars(): boolean; + set alphaBars(value: boolean); get audioCtx(): AudioContext; get canvas(): HTMLCanvasElement; @@ -123,10 +124,12 @@ declare class AudioMotionAnalyzer { get width(): number; set width(w: number); + get isAlphaBars(): boolean; get isFullscreen(): boolean; get isLedDisplay(): boolean; get isLumiBars(): boolean; get isOctaveBands(): boolean; + get isOutlineBars(): boolean; get isOn(): boolean; @@ -156,7 +159,9 @@ declare class AudioMotionAnalyzer { get mode(): number; set mode(value: number); - public outlineBars: boolean; + get outlineBars(): boolean; + set outlineBars(value: boolean); + public overlay: boolean; get peakEnergy(): number; From 587d2abe6b3cc22851ab2ba20d20bad988765419 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Thu, 30 Sep 2021 17:42:49 -0300 Subject: [PATCH 09/16] Add `alphaBars`, `outlineBars` and read-only flags to the fluid demo. --- demo/fluid.html | 22 ++++++++++++++++------ demo/fluid.js | 8 ++++++-- demo/styles.css | 21 +++++++++++++++++++-- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/demo/fluid.html b/demo/fluid.html index 6cdb7bd..63748e3 100644 --- a/demo/fluid.html +++ b/demo/fluid.html @@ -20,8 +20,8 @@

| fl
-
+
@@ -139,7 +139,7 @@

| fl
- mode 10 only + mode 10 / outlineBars

-
+
- Switches + Switches & read-only flags + + -
+
+ isAlphaBars + isLedDisplay + isLumiBars + isOctaveBands + isOutlineBars + isOn
+
+
Custom LED parameters
- Features added via callback + Demo features added via callback diff --git a/demo/fluid.js b/demo/fluid.js index 3397267..365a73d 100644 --- a/demo/fluid.js +++ b/demo/fluid.js @@ -152,7 +152,7 @@ document.querySelectorAll('[data-prop]').forEach( el => { audioMotion[ el.dataset.func ](); else audioMotion[ el.dataset.prop ] = ! audioMotion[ el.dataset.prop ]; - el.classList.toggle( 'active', audioMotion[ el.dataset.prop ] ); + updateUI(); }); }); @@ -164,7 +164,10 @@ document.querySelectorAll('[data-feature]').forEach( el => { }); document.querySelectorAll('[data-setting]').forEach( el => { - el.addEventListener( 'change', () => audioMotion[ el.dataset.setting ] = el.value ); + el.addEventListener( 'change', () => { + audioMotion[ el.dataset.setting ] = el.value; + updateUI(); + }); }); document.querySelectorAll('[data-custom]').forEach( el => { @@ -304,6 +307,7 @@ function updateUI() { document.querySelectorAll('input[type="range"]').forEach( el => updateRangeElement( el ) ); document.querySelectorAll('button[data-prop]').forEach( el => el.classList.toggle( 'active', audioMotion[ el.dataset.prop ] ) ); document.querySelectorAll('button[data-feature]').forEach( el => el.classList.toggle( 'active', features[ el.dataset.feature ] ) ); + document.querySelectorAll('[data-flag]').forEach( el => el.classList.toggle( 'active', audioMotion[ el.dataset.flag ] ) ); } // Callback function used to add custom features for this demo diff --git a/demo/styles.css b/demo/styles.css index 7ee911e..8cbc748 100644 --- a/demo/styles.css +++ b/demo/styles.css @@ -193,11 +193,13 @@ ul { } .sticky { - background: #222e; - margin-bottom: -10px; + background: linear-gradient(to top, transparent 0%, #222 28px); + margin-bottom: -10px !important; padding-bottom: 30px; + pointer-events: none; position: sticky; top: 0; + z-index: 1; } /* background animation for overlay test on fluid demo */ @@ -214,6 +216,21 @@ ul { text-align: center; } +.flag { + background: #444; + border-radius: 999px; + display: inline-block; + font-size: .9em; + margin: .5em .25em; + opacity: .5; + padding: .25em 1em; +} +.flag.active { + background: #0f0; + color: #222; + opacity: 1; +} + .flex { align-items: center; display: flex; From 62328029860328e32e8306f11714d26362ee6e68 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Thu, 30 Sep 2021 18:12:46 -0300 Subject: [PATCH 10/16] Deprecate showLeds & isLedDisplay in favor of ledBars & isLedBars. --- src/audioMotion-analyzer.js | 32 ++++++++++++++++++++++++-------- src/index.d.ts | 14 ++++++++++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index cc397ec..9faaa36 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -278,6 +278,16 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } + // LEDs effect + + get ledBars() { + return this._showLeds; + } + set ledBars( value ) { + this._showLeds = !! value; + this._calcAux(); + } + // Canvas size get height() { @@ -439,14 +449,12 @@ export default class AudioMotionAnalyzer { this._analyzer[ i ].maxDecibels = value; } - // LEDs effect - + // DEPRECATED - use ledBars instead get showLeds() { - return this._showLeds; + return this.ledBars; } set showLeds( value ) { - this._showLeds = !! value; - this._calcAux(); + this.ledBars = value; } // Analyzer's smoothing time constant @@ -539,9 +547,13 @@ export default class AudioMotionAnalyzer { get isOctaveBands() { return this._isOctaveBands; } - get isLedDisplay() { + get isLedBars() { return this._isLedDisplay; } + get isLedDisplay() { + // DEPRECATED - use isLedBars instead + return this.isLedBars; + } get isLumiBars() { return this._isLumiBars; } @@ -1856,11 +1868,11 @@ export default class AudioMotionAnalyzer { minDecibels : -85, maxDecibels : -25, showBgColor : true, - showLeds : false, showScaleX : true, showScaleY : false, showPeaks : true, showFPS : false, + ledBars : false, lumiBars : false, loRes : false, reflexRatio : 0, @@ -1887,9 +1899,13 @@ export default class AudioMotionAnalyzer { // callback functions properties const callbacks = [ 'onCanvasDraw', 'onCanvasResize' ]; - // compile valid properties; `start` is not an actual property and is handled after setting everything else + // build an array of valid properties; `start` is not an actual property and is handled after setting everything else const validProps = Object.keys( defaults ).filter( e => e != 'start' ).concat( callbacks, ['height', 'width'] ); + // handle deprecated `showLeds` property + if ( options && options.showLeds !== undefined && options.ledBars === undefined ) + options.ledBars = options.showLeds; + if ( useDefaults || options === undefined ) options = { ...defaults, ...options }; // merge options with defaults diff --git a/src/index.d.ts b/src/index.d.ts index e357373..aedcbdc 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -14,6 +14,7 @@ export interface Options { fillAlpha?: number; gradient?: string; height?: number; + ledBars?: boolean; lineWidth?: number; loRes?: boolean; lumiBars?: boolean; @@ -34,7 +35,7 @@ export interface Options { reflexRatio?: number; showBgColor?: boolean; showFPS?: boolean; - showLeds?: boolean; + showLeds?: boolean; // DEPRECATED - use ledBars instead showPeaks?: boolean; showScaleX?: boolean; showScaleY?: boolean; @@ -103,7 +104,7 @@ declare class AudioMotionAnalyzer { get connectedSources(): AudioNode[]; get connectedTo(): AudioNode[]; - get energy(): number; + get energy(): number; // DEPRECATED - use getEnergy() instead get fftSize(): number; set fftSize(value: number); @@ -126,13 +127,17 @@ declare class AudioMotionAnalyzer { get isAlphaBars(): boolean; get isFullscreen(): boolean; - get isLedDisplay(): boolean; + get isLedBars(): boolean; + get isLedDisplay(): boolean; // DEPRECATED - use isLedBars instead get isLumiBars(): boolean; get isOctaveBands(): boolean; get isOutlineBars(): boolean; get isOn(): boolean; + get ledBars(): boolean; + set ledBars(value: boolean); + public lineWidth: number; get loRes(): boolean; @@ -164,7 +169,7 @@ declare class AudioMotionAnalyzer { public overlay: boolean; - get peakEnergy(): number; + get peakEnergy(): number; // DEPRECATED - use getEnergy('peak') instead get pixelRatio(): number; get radial(): boolean; @@ -180,6 +185,7 @@ declare class AudioMotionAnalyzer { public showBgColor: boolean; public showFPS: boolean; + // DEPRECATED - use ledBars instead get showLeds(): boolean; set showLeds(value: boolean); From 9d6ace02217f89aedcc1ad2348b1a125f0a4ee1c Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sat, 2 Oct 2021 21:20:33 -0300 Subject: [PATCH 11/16] Sort functions and defaults alphabetically (no actual code changes). --- src/audioMotion-analyzer.js | 849 +++++++++++++++++------------------- 1 file changed, 405 insertions(+), 444 deletions(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 9faaa36..79c03cf 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -232,8 +232,6 @@ export default class AudioMotionAnalyzer { */ - // alphaBars effect - get alphaBars() { return this._alphaBars; } @@ -242,8 +240,6 @@ export default class AudioMotionAnalyzer { this._calcAux(); } - // Bar spacing (for octave bands modes) - get barSpace() { return this._barSpace; } @@ -252,8 +248,6 @@ export default class AudioMotionAnalyzer { this._calcAux(); } - // FFT size - get fftSize() { return this._analyzer[0].fftSize; } @@ -265,8 +259,6 @@ export default class AudioMotionAnalyzer { this._calcBars(); } - // Gradient - get gradient() { return this._gradient; } @@ -278,7 +270,13 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } - // LEDs effect + get height() { + return this._height; + } + set height( h ) { + this._height = h; + this._setCanvas('user'); + } get ledBars() { return this._showLeds; @@ -288,24 +286,63 @@ export default class AudioMotionAnalyzer { this._calcAux(); } - // Canvas size + get loRes() { + return this._loRes; + } + set loRes( value ) { + this._loRes = !! value; + this._setCanvas('lores'); + } - get height() { - return this._height; + get lumiBars() { + return this._lumiBars; } - set height( h ) { - this._height = h; - this._setCanvas('user'); + set lumiBars( value ) { + this._lumiBars = !! value; + this._calcAux(); + this._calcLeds(); + this._makeGrad(); } - get width() { - return this._width; + + get maxDecibels() { + return this._analyzer[0].maxDecibels; } - set width( w ) { - this._width = w; - this._setCanvas('user'); + set maxDecibels( value ) { + for ( const i of [0,1] ) + this._analyzer[ i ].maxDecibels = value; + } + + get maxFreq() { + return this._maxFreq; + } + set maxFreq( value ) { + if ( value < 1 ) + throw new AudioMotionError( 'ERR_FREQUENCY_TOO_LOW', `Frequency values must be >= 1` ); + else { + this._maxFreq = value; + this._calcBars(); + } + } + + get minDecibels() { + return this._analyzer[0].minDecibels; + } + set minDecibels( value ) { + for ( const i of [0,1] ) + this._analyzer[ i ].minDecibels = value; } - // Mirror + get minFreq() { + return this._minFreq; + } + set minFreq( value ) { + if ( value < 1 ) + throw new AudioMotionError( 'ERR_FREQUENCY_TOO_LOW', `Frequency values must be >= 1` ); + else { + this._minFreq = value; + this._calcBars(); + } + } get mirror() { return this._mirror; @@ -317,8 +354,6 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } - // Visualization mode - get mode() { return this._mode; } @@ -334,30 +369,6 @@ export default class AudioMotionAnalyzer { throw new AudioMotionError( 'ERR_INVALID_MODE', `Invalid mode: ${value}` ); } - // Low-resolution mode - - get loRes() { - return this._loRes; - } - set loRes( value ) { - this._loRes = !! value; - this._setCanvas('lores'); - } - - // Luminance bars - - get lumiBars() { - return this._lumiBars; - } - set lumiBars( value ) { - this._lumiBars = !! value; - this._calcAux(); - this._calcLeds(); - this._makeGrad(); - } - - // Outlined bars - get outlineBars() { return this._outlineBars; } @@ -366,8 +377,6 @@ export default class AudioMotionAnalyzer { this._calcAux(); } - // Radial mode - get radial() { return this._radial; } @@ -378,20 +387,6 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } - // Radial spin speed - - get spinSpeed() { - return this._spinSpeed; - } - set spinSpeed( value ) { - value = +value || 0; - if ( this._spinSpeed === undefined || value == 0 ) - this._spinAngle = -HALF_PI; // initialize or reset the rotation angle - this._spinSpeed = value; - } - - // Reflex - get reflexRatio() { return this._reflexRatio; } @@ -407,48 +402,6 @@ export default class AudioMotionAnalyzer { } } - // Current frequency range - - get minFreq() { - return this._minFreq; - } - set minFreq( value ) { - if ( value < 1 ) - throw new AudioMotionError( 'ERR_FREQUENCY_TOO_LOW', `Frequency values must be >= 1` ); - else { - this._minFreq = value; - this._calcBars(); - } - } - get maxFreq() { - return this._maxFreq; - } - set maxFreq( value ) { - if ( value < 1 ) - throw new AudioMotionError( 'ERR_FREQUENCY_TOO_LOW', `Frequency values must be >= 1` ); - else { - this._maxFreq = value; - this._calcBars(); - } - } - - // Analyzer's sensitivity - - get minDecibels() { - return this._analyzer[0].minDecibels; - } - set minDecibels( value ) { - for ( const i of [0,1] ) - this._analyzer[ i ].minDecibels = value; - } - get maxDecibels() { - return this._analyzer[0].maxDecibels; - } - set maxDecibels( value ) { - for ( const i of [0,1] ) - this._analyzer[ i ].maxDecibels = value; - } - // DEPRECATED - use ledBars instead get showLeds() { return this.ledBars; @@ -457,8 +410,6 @@ export default class AudioMotionAnalyzer { this.ledBars = value; } - // Analyzer's smoothing time constant - get smoothing() { return this._analyzer[0].smoothingTimeConstant; } @@ -467,7 +418,15 @@ export default class AudioMotionAnalyzer { this._analyzer[ i ].smoothingTimeConstant = value; } - // Split gradient (in stereo mode) + get spinSpeed() { + return this._spinSpeed; + } + set spinSpeed( value ) { + value = +value || 0; + if ( this._spinSpeed === undefined || value == 0 ) + this._spinAngle = -HALF_PI; // initialize or reset the rotation angle + this._spinSpeed = value; + } get splitGradient() { return this._splitGradient; @@ -477,8 +436,6 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } - // Stereo - get stereo() { return this._stereo; } @@ -499,8 +456,6 @@ export default class AudioMotionAnalyzer { this._makeGrad(); } - // Volume - get volume() { return this._output.gain.value; } @@ -508,6 +463,14 @@ export default class AudioMotionAnalyzer { this._output.gain.value = value; } + get width() { + return this._width; + } + set width( w ) { + this._width = w; + this._setCanvas('user'); + } + // Read only properties get audioCtx() { @@ -525,18 +488,17 @@ export default class AudioMotionAnalyzer { get connectedTo() { return this._outNodes; } - get energy() { - // DEPRECATED - to be removed in v4.0.0 + get energy() { // DEPRECATED - use getEnergy() instead return this.getEnergy(); } - get fsWidth() { - return this._fsWidth; + get fps() { + return this._fps; } get fsHeight() { return this._fsHeight; } - get fps() { - return this._fps; + get fsWidth() { + return this._fsWidth; } get isAlphaBars() { return this._isAlphaBars; @@ -544,27 +506,25 @@ export default class AudioMotionAnalyzer { get isFullscreen() { return ( document.fullscreenElement || document.webkitFullscreenElement ) === this._fsEl; } - get isOctaveBands() { - return this._isOctaveBands; - } get isLedBars() { return this._isLedDisplay; } - get isLedDisplay() { - // DEPRECATED - use isLedBars instead + get isLedDisplay() { // DEPRECATED - use isLedBars instead return this.isLedBars; } get isLumiBars() { return this._isLumiBars; } + get isOctaveBands() { + return this._isOctaveBands; + } get isOn() { return this._runId !== undefined; } get isOutlineBars() { return this._isOutline; } - get peakEnergy() { - // DEPRECATED - to be removed in v4.0.0 + get peakEnergy() { // DEPRECATED - use getEnergy('peak') instead return this.getEnergy('peak'); } get pixelRatio() { @@ -605,6 +565,25 @@ export default class AudioMotionAnalyzer { return node; } + /** + * Connects the analyzer output to another audio node + * + * @param [{object}] an AudioNode; if undefined, the output is connected to the audio context destination (speakers) + */ + connectOutput( node = this.audioCtx.destination ) { + if ( this._outNodes.includes( node ) ) + return; + + this._output.connect( node ); + this._outNodes.push( node ); + + // when connecting the first node, also connect the analyzer nodes to the merger / output nodes + if ( this._outNodes.length == 1 ) { + for ( const i of [0,1] ) + this._analyzer[ i ].connect( ( ! this._stereo && ! i ? this._output : this._merger ), 0, i ); + } + } + /** * Disconnects audio sources from the analyzer * @@ -625,25 +604,6 @@ export default class AudioMotionAnalyzer { } } - /** - * Connects the analyzer output to another audio node - * - * @param [{object}] an AudioNode; if undefined, the output is connected to the audio context destination (speakers) - */ - connectOutput( node = this.audioCtx.destination ) { - if ( this._outNodes.includes( node ) ) - return; - - this._output.connect( node ); - this._outNodes.push( node ); - - // when connecting the first node, also connect the analyzer nodes to the merger / output nodes - if ( this._outNodes.length == 1 ) { - for ( const i of [0,1] ) - this._analyzer[ i ].connect( ( ! this._stereo && ! i ? this._output : this._merger ), 0, i ); - } - } - /** * Disconnects the analyzer output from other audio nodes * @@ -895,34 +855,213 @@ export default class AudioMotionAnalyzer { } /** - * Calculate attributes for the vintage LEDs effect, based on visualization mode and canvas resolution + * Precalculate the actual X-coordinate on screen for each analyzer bar */ - _calcLeds() { - if ( ! this._isOctaveBands || ! this._ready ) + _calcBars() { + /* + Since the frequency scale is logarithmic, each position in the X-axis actually represents a power of 10. + To improve performace, the position of each frequency is calculated in advance and stored in an array. + Canvas space usage is optimized to accommodate exactly the frequency range the user needs. + Positions need to be recalculated whenever the frequency range, FFT size or canvas size change. + + +-------------------------- canvas --------------------------+ + | | + |-------------------|-----|-------------|-------------------!-------------------|------|------------| + 1 10 | 100 1K 10K | 100K (Hz) + (10^0) (10^1) | (10^2) (10^3) (10^4) | (10^5) + |-------------|<--- logWidth ---->|--------------------------| + minFreq--> 20 (pixels) 22K <--maxFreq + (10^1.3) (10^4.34) + minLog + */ + + const bars = this._bars = []; // initialize object property + + if ( ! this._ready ) return; - // adjustment for high pixel-ratio values on low-resolution screens (Android TV) - const dPR = this._pixelRatio / ( window.devicePixelRatio > 1 && window.screen.height <= 540 ? 2 : 1 ); + // helper functions + const binToFreq = bin => bin * this.audioCtx.sampleRate / this.fftSize || 1; // returns 1 for bin 0 + const barsPush = ( posX, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi ) => bars.push( { posX, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi, peak: [0,0], hold: [0], value: [0] } ); - const params = [ [], - [ 128, 3, .45 ], // mode 1 - [ 128, 4, .225 ], // mode 2 - [ 96, 6, .225 ], // mode 3 - [ 80, 6, .225 ], // mode 4 - [ 80, 6, .125 ], // mode 5 - [ 64, 6, .125 ], // mode 6 - [ 48, 8, .125 ], // mode 7 - [ 24, 16, .125 ], // mode 8 - ]; + const analyzerWidth = this._analyzerWidth, + initialX = this._initialX, + maxFreq = this._maxFreq, + minFreq = this._minFreq; - // use custom LED parameters if set, or the default parameters for the current mode - const customParams = this._ledParams, - [ maxLeds, spaceVRatio, spaceHRatio ] = customParams || params[ this._mode ]; + let minLog, logWidth; - let ledCount, spaceV, - analyzerHeight = this._analyzerHeight; + if ( this._isOctaveBands ) { - if ( customParams ) { + // generate a 11-octave 24-tone equal tempered scale (16Hz to 33kHz) + + /* + A simple linear interpolation is used to obtain an approximate amplitude value for the desired frequency + from available FFT data, like so: + + h = hLo + ( hHi - hLo ) * ( f - fLo ) / ( fHi - fLo ) + \___________________________/ + | + ratio + where: + + f - desired frequency + h - amplitude of desired frequency + fLo - frequency represented by the lower FFT bin + fHi - frequency represented by the higher FFT bin + hLo - amplitude of fLo + hHi - amplitude of fHi + + ratio is calculated in advance here, to reduce computational complexity during real-time rendering in the _draw() function + */ + + let temperedScale = []; + + for ( let octave = 0; octave < 11; octave++ ) { + for ( let note = 0; note < 24; note++ ) { + + const freq = C0 * ROOT24 ** ( octave * 24 + note ), + bin = this._freqToBin( freq, 'floor' ), + binFreq = binToFreq( bin ), + nextFreq = binToFreq( bin + 1 ), + ratio = ( freq - binFreq ) / ( nextFreq - binFreq ); + + temperedScale.push( { freq, bin, ratio } ); + } + } + + // generate the frequency bands according to current analyzer settings + + const steps = [0,1,2,3,4,6,8,12,24][ this._mode ]; // number of notes grouped per band for each mode + + for ( let index = 0; index < temperedScale.length; index += steps ) { + let { freq: freqLo, bin: binLo, ratio: ratioLo } = temperedScale[ index ], // band start + { freq: freqHi, bin: binHi, ratio: ratioHi } = temperedScale[ index + steps - 1 ]; // band end + + const nBars = bars.length, + prevBar = bars[ nBars - 1 ]; + + // if the ending frequency is out of range, we're done here + if ( freqHi > maxFreq || binHi >= this.fftSize / 2 ) { + prevBar.binHi++; // add an extra bin to the last bar, to fully include the last valid band + prevBar.ratioHi = 0; // disable interpolation + prevBar.freqHi = binToFreq( prevBar.binHi ); // update ending frequency + break; + } + + // is the starting frequency in the selected range? + if ( freqLo >= minFreq ) { + if ( nBars > 0 ) { + const diff = binLo - prevBar.binHi; + + // check if we skipped any available FFT bins since the last bar + if ( diff > 1 ) { + // allocate half of the unused bins to the previous bar + prevBar.binHi = binLo - ( diff >> 1 ); + prevBar.ratioHi = 0; + prevBar.freqHi = binToFreq( prevBar.binHi ); // update ending frequency + + // if the previous bar doesn't share any bins with other bars, no need for interpolation + if ( nBars > 1 && prevBar.binHi > prevBar.binLo && prevBar.binLo > bars[ nBars - 2 ].binHi ) { + prevBar.ratioLo = 0; + prevBar.freqLo = binToFreq( prevBar.binLo ); // update starting frequency + } + + // start the current bar at the bin following the last allocated bin + binLo = prevBar.binHi + 1; + } + + // if the lower bin is not shared with the ending frequency nor the previous bar, no need to interpolate it + if ( binHi > binLo && binLo > prevBar.binHi ) { + ratioLo = 0; + freqLo = binToFreq( binLo ); + } + } + + barsPush( 0, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi ); + } + } + + this._barWidth = analyzerWidth / bars.length; + + bars.forEach( ( bar, index ) => bar.posX = initialX + index * this._barWidth ); + + minLog = Math.log10( bars[0].freqLo ); + logWidth = analyzerWidth / ( Math.log10( bars[ bars.length - 1 ].freqHi ) - minLog ); + } + else { + + // Discrete frequencies modes + + this._barWidth = 1; + + minLog = Math.log10( minFreq ); + logWidth = analyzerWidth / ( Math.log10( maxFreq ) - minLog ); + + const minIndex = this._freqToBin( minFreq, 'floor' ), + maxIndex = this._freqToBin( maxFreq ); + + let lastPos = -999; + + for ( let i = minIndex; i <= maxIndex; i++ ) { + const freq = binToFreq( i ), // frequency represented by this index + pos = initialX + Math.round( logWidth * ( Math.log10( freq ) - minLog ) ); // avoid fractionary pixel values + + // if it's on a different X-coordinate, create a new bar for this frequency + if ( pos > lastPos ) { + barsPush( pos, i, i, freq, freq, 0, 0 ); + lastPos = pos; + } // otherwise, add this frequency to the last bar's range + else if ( bars.length ) { + bars[ bars.length - 1 ].binHi = i; + bars[ bars.length - 1 ].freqHi = freq; + } + } + } + + // save these for scale generation + this._minLog = minLog; + this._logWidth = logWidth; + + // update internal variables + this._calcAux(); + + // generate the X-axis and radial scales + this._createScales(); + + // update LED properties + this._calcLeds(); + } + + /** + * Calculate attributes for the vintage LEDs effect, based on visualization mode and canvas resolution + */ + _calcLeds() { + if ( ! this._isOctaveBands || ! this._ready ) + return; + + // adjustment for high pixel-ratio values on low-resolution screens (Android TV) + const dPR = this._pixelRatio / ( window.devicePixelRatio > 1 && window.screen.height <= 540 ? 2 : 1 ); + + const params = [ [], + [ 128, 3, .45 ], // mode 1 + [ 128, 4, .225 ], // mode 2 + [ 96, 6, .225 ], // mode 3 + [ 80, 6, .225 ], // mode 4 + [ 80, 6, .125 ], // mode 5 + [ 64, 6, .125 ], // mode 6 + [ 48, 8, .125 ], // mode 7 + [ 24, 16, .125 ], // mode 8 + ]; + + // use custom LED parameters if set, or the default parameters for the current mode + const customParams = this._ledParams, + [ maxLeds, spaceVRatio, spaceHRatio ] = customParams || params[ this._mode ]; + + let ledCount, spaceV, + analyzerHeight = this._analyzerHeight; + + if ( customParams ) { const minHeight = 2 * dPR; let blockHeight; ledCount = maxLeds + 1; @@ -954,6 +1093,72 @@ export default class AudioMotionAnalyzer { ]; } + /** + * Generate the X-axis and radial scales in auxiliary canvases + */ + _createScales() { + const freqLabels = [ 16, 31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 ], + canvas = this._canvasCtx.canvas, + scaleX = this._scaleX, + scaleR = this._scaleR, + canvasX = scaleX.canvas, + canvasR = scaleR.canvas, + scaleHeight = Math.min( canvas.width, canvas.height ) * .03 | 0; // circular scale height (radial mode) + + // in radial stereo mode, the scale is positioned exactly between both channels, by making the canvas a bit larger than the central diameter + canvasR.width = canvasR.height = ( this._radius << 1 ) + ( this._stereo * scaleHeight ); + + const radius = canvasR.width >> 1, // this is also used as the center X and Y coordinates of the circular scale canvas + radialY = radius - scaleHeight * .7; // vertical position of text labels in the circular scale + + // helper function + const radialLabel = ( x, label ) => { + const angle = TAU * ( x / canvas.width ), + adjAng = angle - HALF_PI, // rotate angles so 0 is at the top + posX = radialY * Math.cos( adjAng ), + posY = radialY * Math.sin( adjAng ); + + scaleR.save(); + scaleR.translate( radius + posX, radius + posY ); + scaleR.rotate( angle ); + scaleR.fillText( label, 0, 0 ); + scaleR.restore(); + } + + // clear scale canvas + canvasX.width |= 0; + + scaleX.fillStyle = scaleR.strokeStyle = '#000c'; + scaleX.fillRect( 0, 0, canvasX.width, canvasX.height ); + + scaleR.arc( radius, radius, radius - scaleHeight / 2, 0, TAU ); + scaleR.lineWidth = scaleHeight; + scaleR.stroke(); + + scaleX.fillStyle = scaleR.fillStyle = '#fff'; + scaleX.font = `${ canvasX.height >> 1 }px sans-serif`; + scaleR.font = `${ scaleHeight >> 1 }px sans-serif`; + scaleX.textAlign = scaleR.textAlign = 'center'; + + for ( const freq of freqLabels ) { + const label = ( freq >= 1000 ) ? `${ freq / 1000 }k` : freq, + x = this._logWidth * ( Math.log10( freq ) - this._minLog ); + + if ( x >= 0 && x <= this._analyzerWidth ) { + scaleX.fillText( label, this._initialX + x, canvasX.height * .75 ); + if ( x < this._analyzerWidth ) // avoid wrapping-around the last label and overlapping the first one + radialLabel( x, label ); + + if ( this._mirror ) { + scaleX.fillText( label, ( this._initialX || canvas.width ) - x, canvasX.height * .75 ); + if ( x > 10 ) // avoid overlapping of first labels on mirror mode + radialLabel( -x, label ); + } + + } + } + } + /** * Redraw the canvas * this is called 60 times per second by requestAnimationFrame() @@ -1440,6 +1645,16 @@ export default class AudioMotionAnalyzer { this._runId = requestAnimationFrame( timestamp => this._draw( timestamp ) ); } + /** + * Return the FFT data bin (array index) which represents a given frequency + */ + _freqToBin( freq, rounding = 'round' ) { + const max = this._analyzer[0].frequencyBinCount - 1, + bin = Math[ rounding ]( freq * this.fftSize / this.audioCtx.sampleRate ); + + return bin < max ? bin : max; + } + /** * Generate currently selected gradient */ @@ -1529,261 +1744,7 @@ export default class AudioMotionAnalyzer { } /** - * Generate the X-axis and radial scales in auxiliary canvases - */ - _createScales() { - const freqLabels = [ 16, 31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 ], - canvas = this._canvasCtx.canvas, - scaleX = this._scaleX, - scaleR = this._scaleR, - canvasX = scaleX.canvas, - canvasR = scaleR.canvas, - scaleHeight = Math.min( canvas.width, canvas.height ) * .03 | 0; // circular scale height (radial mode) - - // in radial stereo mode, the scale is positioned exactly between both channels, by making the canvas a bit larger than the central diameter - canvasR.width = canvasR.height = ( this._radius << 1 ) + ( this._stereo * scaleHeight ); - - const radius = canvasR.width >> 1, // this is also used as the center X and Y coordinates of the circular scale canvas - radialY = radius - scaleHeight * .7; // vertical position of text labels in the circular scale - - // helper function - const radialLabel = ( x, label ) => { - const angle = TAU * ( x / canvas.width ), - adjAng = angle - HALF_PI, // rotate angles so 0 is at the top - posX = radialY * Math.cos( adjAng ), - posY = radialY * Math.sin( adjAng ); - - scaleR.save(); - scaleR.translate( radius + posX, radius + posY ); - scaleR.rotate( angle ); - scaleR.fillText( label, 0, 0 ); - scaleR.restore(); - } - - // clear scale canvas - canvasX.width |= 0; - - scaleX.fillStyle = scaleR.strokeStyle = '#000c'; - scaleX.fillRect( 0, 0, canvasX.width, canvasX.height ); - - scaleR.arc( radius, radius, radius - scaleHeight / 2, 0, TAU ); - scaleR.lineWidth = scaleHeight; - scaleR.stroke(); - - scaleX.fillStyle = scaleR.fillStyle = '#fff'; - scaleX.font = `${ canvasX.height >> 1 }px sans-serif`; - scaleR.font = `${ scaleHeight >> 1 }px sans-serif`; - scaleX.textAlign = scaleR.textAlign = 'center'; - - for ( const freq of freqLabels ) { - const label = ( freq >= 1000 ) ? `${ freq / 1000 }k` : freq, - x = this._logWidth * ( Math.log10( freq ) - this._minLog ); - - if ( x >= 0 && x <= this._analyzerWidth ) { - scaleX.fillText( label, this._initialX + x, canvasX.height * .75 ); - if ( x < this._analyzerWidth ) // avoid wrapping-around the last label and overlapping the first one - radialLabel( x, label ); - - if ( this._mirror ) { - scaleX.fillText( label, ( this._initialX || canvas.width ) - x, canvasX.height * .75 ); - if ( x > 10 ) // avoid overlapping of first labels on mirror mode - radialLabel( -x, label ); - } - - } - } - } - - /** - * Precalculate the actual X-coordinate on screen for each analyzer bar - * - * Since the frequency scale is logarithmic, each position in the X-axis actually represents a power of 10. - * To improve performace, the position of each frequency is calculated in advance and stored in an array. - * Canvas space usage is optimized to accommodate exactly the frequency range the user needs. - * Positions need to be recalculated whenever the frequency range, FFT size or canvas size change. - * - * +-------------------------- canvas --------------------------+ - * | | - * |-------------------|-----|-------------|-------------------!-------------------|------|------------| - * 1 10 | 100 1K 10K | 100K (Hz) - * (10^0) (10^1) | (10^2) (10^3) (10^4) | (10^5) - * |-------------|<--- logWidth ---->|--------------------------| - * minFreq--> 20 (pixels) 22K <--maxFreq - * (10^1.3) (10^4.34) - * minLog - */ - _calcBars() { - - const bars = this._bars = []; // initialize object property - - if ( ! this._ready ) - return; - - // helper functions - const binToFreq = bin => bin * this.audioCtx.sampleRate / this.fftSize || 1; // returns 1 for bin 0 - const barsPush = ( posX, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi ) => bars.push( { posX, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi, peak: [0,0], hold: [0], value: [0] } ); - - const analyzerWidth = this._analyzerWidth, - initialX = this._initialX, - maxFreq = this._maxFreq, - minFreq = this._minFreq; - - let minLog, logWidth; - - if ( this._isOctaveBands ) { - - // generate a 11-octave 24-tone equal tempered scale (16Hz to 33kHz) - - /* - A simple linear interpolation is used to obtain an approximate amplitude value for the desired frequency - from available FFT data, like so: - - h = hLo + ( hHi - hLo ) * ( f - fLo ) / ( fHi - fLo ) - \___________________________/ - | - ratio - where: - - f - desired frequency - h - amplitude of desired frequency - fLo - frequency represented by the lower FFT bin - fHi - frequency represented by the higher FFT bin - hLo - amplitude of fLo - hHi - amplitude of fHi - - ratio is calculated in advance here, to reduce computational complexity during real-time rendering in the _draw() function - */ - - let temperedScale = []; - - for ( let octave = 0; octave < 11; octave++ ) { - for ( let note = 0; note < 24; note++ ) { - - const freq = C0 * ROOT24 ** ( octave * 24 + note ), - bin = this._freqToBin( freq, 'floor' ), - binFreq = binToFreq( bin ), - nextFreq = binToFreq( bin + 1 ), - ratio = ( freq - binFreq ) / ( nextFreq - binFreq ); - - temperedScale.push( { freq, bin, ratio } ); - } - } - - // generate the frequency bands according to current analyzer settings - - const steps = [0,1,2,3,4,6,8,12,24][ this._mode ]; // number of notes grouped per band for each mode - - for ( let index = 0; index < temperedScale.length; index += steps ) { - let { freq: freqLo, bin: binLo, ratio: ratioLo } = temperedScale[ index ], // band start - { freq: freqHi, bin: binHi, ratio: ratioHi } = temperedScale[ index + steps - 1 ]; // band end - - const nBars = bars.length, - prevBar = bars[ nBars - 1 ]; - - // if the ending frequency is out of range, we're done here - if ( freqHi > maxFreq || binHi >= this.fftSize / 2 ) { - prevBar.binHi++; // add an extra bin to the last bar, to fully include the last valid band - prevBar.ratioHi = 0; // disable interpolation - prevBar.freqHi = binToFreq( prevBar.binHi ); // update ending frequency - break; - } - - // is the starting frequency in the selected range? - if ( freqLo >= minFreq ) { - if ( nBars > 0 ) { - const diff = binLo - prevBar.binHi; - - // check if we skipped any available FFT bins since the last bar - if ( diff > 1 ) { - // allocate half of the unused bins to the previous bar - prevBar.binHi = binLo - ( diff >> 1 ); - prevBar.ratioHi = 0; - prevBar.freqHi = binToFreq( prevBar.binHi ); // update ending frequency - - // if the previous bar doesn't share any bins with other bars, no need for interpolation - if ( nBars > 1 && prevBar.binHi > prevBar.binLo && prevBar.binLo > bars[ nBars - 2 ].binHi ) { - prevBar.ratioLo = 0; - prevBar.freqLo = binToFreq( prevBar.binLo ); // update starting frequency - } - - // start the current bar at the bin following the last allocated bin - binLo = prevBar.binHi + 1; - } - - // if the lower bin is not shared with the ending frequency nor the previous bar, no need to interpolate it - if ( binHi > binLo && binLo > prevBar.binHi ) { - ratioLo = 0; - freqLo = binToFreq( binLo ); - } - } - - barsPush( 0, binLo, binHi, freqLo, freqHi, ratioLo, ratioHi ); - } - } - - this._barWidth = analyzerWidth / bars.length; - - bars.forEach( ( bar, index ) => bar.posX = initialX + index * this._barWidth ); - - minLog = Math.log10( bars[0].freqLo ); - logWidth = analyzerWidth / ( Math.log10( bars[ bars.length - 1 ].freqHi ) - minLog ); - } - else { - - // Discrete frequencies modes - - this._barWidth = 1; - - minLog = Math.log10( minFreq ); - logWidth = analyzerWidth / ( Math.log10( maxFreq ) - minLog ); - - const minIndex = this._freqToBin( minFreq, 'floor' ), - maxIndex = this._freqToBin( maxFreq ); - - let lastPos = -999; - - for ( let i = minIndex; i <= maxIndex; i++ ) { - const freq = binToFreq( i ), // frequency represented by this index - pos = initialX + Math.round( logWidth * ( Math.log10( freq ) - minLog ) ); // avoid fractionary pixel values - - // if it's on a different X-coordinate, create a new bar for this frequency - if ( pos > lastPos ) { - barsPush( pos, i, i, freq, freq, 0, 0 ); - lastPos = pos; - } // otherwise, add this frequency to the last bar's range - else if ( bars.length ) { - bars[ bars.length - 1 ].binHi = i; - bars[ bars.length - 1 ].freqHi = freq; - } - } - } - - // save these for scale generation - this._minLog = minLog; - this._logWidth = logWidth; - - // update internal variables - this._calcAux(); - - // generate the X-axis and radial scales - this._createScales(); - - // update LED properties - this._calcLeds(); - } - - /** - * Return the FFT data bin (array index) which represents a given frequency - */ - _freqToBin( freq, rounding = 'round' ) { - const max = this._analyzer[0].frequencyBinCount - 1, - bin = Math[ rounding ]( freq * this.fftSize / this.audioCtx.sampleRate ); - - return bin < max ? bin : max; - } - - /** - * Internal function to change canvas dimensions on demand + * Internal function to change canvas dimensions on demand */ _setCanvas( reason ) { // if initialization is not finished, quit @@ -1859,41 +1820,41 @@ export default class AudioMotionAnalyzer { // settings defaults const defaults = { - mode : 0, + alphaBars : false, + barSpace : 0.1, + bgAlpha : 0.7, fftSize : 8192, - minFreq : 20, - maxFreq : 22000, - smoothing : 0.5, + fillAlpha : 1, gradient : 'classic', - minDecibels : -85, - maxDecibels : -25, - showBgColor : true, - showScaleX : true, - showScaleY : false, - showPeaks : true, - showFPS : false, ledBars : false, - lumiBars : false, + lineWidth : 0, loRes : false, - reflexRatio : 0, + lumiBars : false, + maxDecibels : -25, + maxFreq : 22000, + minDecibels : -85, + minFreq : 20, + mirror : 0, + mode : 0, + outlineBars : false, + overlay : false, + radial : false, reflexAlpha : 0.15, reflexBright : 1, reflexFit : true, - lineWidth : 0, - fillAlpha : 1, - barSpace : 0.1, - overlay : false, - bgAlpha : 0.7, - radial : false, + reflexRatio : 0, + showBgColor : true, + showFPS : false, + showPeaks : true, + showScaleX : true, + showScaleY : false, + smoothing : 0.5, spinSpeed : 0, - stereo : false, splitGradient: false, start : true, - volume : 1, - mirror : 0, + stereo : false, useCanvas : true, - alphaBars : false, - outlineBars : false + volume : 1, }; // callback functions properties From 4e5dc06feb6abba986767854e50c8543d99dc8ca Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sat, 2 Oct 2021 21:40:14 -0300 Subject: [PATCH 12/16] Improve check for preset string on `getEnergy()`. --- src/audioMotion-analyzer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 79c03cf..1608dd4 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -645,7 +645,7 @@ export default class AudioMotionAnalyzer { return this._energy.val; // if startFreq is a string, check for presets - if ( startFreq != ( startFreq | 0 ) ) { + if ( startFreq != +startFreq ) { if ( startFreq == 'peak' ) return this._energy.peak; From d0c807839409ccd224bf1bc988ed71a114f0c750 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sat, 2 Oct 2021 23:43:05 -0300 Subject: [PATCH 13/16] Update demos. --- demo/fluid.html | 4 ++-- demo/fluid.js | 6 +++--- demo/multi.html | 32 +++++++++++++++----------------- demo/multi.js | 14 ++++---------- demo/overlay.html | 16 +++++++++------- demo/overlay.js | 6 +++--- demo/styles.css | 27 +++++++++++++++------------ 7 files changed, 51 insertions(+), 54 deletions(-) diff --git a/demo/fluid.html b/demo/fluid.html index 63748e3..cecb88a 100644 --- a/demo/fluid.html +++ b/demo/fluid.html @@ -200,12 +200,12 @@

| fl
Switches & read-only flags + - @@ -213,7 +213,7 @@

| fl
isAlphaBars - isLedDisplay + isLedBars isLumiBars isOctaveBands isOutlineBars diff --git a/demo/fluid.js b/demo/fluid.js index 365a73d..522bb71 100644 --- a/demo/fluid.js +++ b/demo/fluid.js @@ -21,11 +21,11 @@ const presets = [ mode: 3, barSpace: .4, gradient: 'classic', + ledBars: true, lumiBars: false, radial: false, reflexRatio: 0, showBgColor: true, - showLeds: true, showPeaks: true } }, @@ -50,9 +50,9 @@ const presets = [ mode: 5, barSpace: .1, gradient: 'prism', + ledBars: false, radial: true, showBgColor: true, - showLeds: false, showPeaks: true, spinSpeed: 1, overlay: true @@ -64,6 +64,7 @@ const presets = [ mode: 5, barSpace: .25, gradient: 'rainbow', + ledBars: false, lumiBars: false, radial: false, reflexAlpha: .25, @@ -71,7 +72,6 @@ const presets = [ reflexFit: true, reflexRatio: .3, showBgColor: false, - showLeds: false, showPeaks: true, overlay: false } diff --git a/demo/multi.html b/demo/multi.html index 96a2d05..f8721ff 100644 --- a/demo/multi.html +++ b/demo/multi.html @@ -20,16 +20,12 @@

| mu

 

-
-
-
- -
-
-
-
-
+
+
+
+
+
@@ -126,7 +122,7 @@

| mu

- mode 10 only + mode 10 / outlineBars

+ + + + + + + +
+ - - - - - - -
diff --git a/demo/multi.js b/demo/multi.js index d6e78a8..0001bbd 100644 --- a/demo/multi.js +++ b/demo/multi.js @@ -51,11 +51,9 @@ document.getElementById('version').innerText = AudioMotionAnalyzer.version; audioMotion[0].setOptions({ mode: 3, - showLeds: true, + ledBars: true, showScaleY: true, - barSpace: 0.5, - width: 640, - height: 270 + barSpace: 0.5 }); audioMotion[1].setOptions({ @@ -66,9 +64,7 @@ audioMotion[1].setOptions({ showScaleX: false, showPeaks: false, lineWidth: 2, - fillAlpha: .3, - width: 320, - height: 145 + fillAlpha: .3 }); audioMotion[2].setOptions({ @@ -81,9 +77,7 @@ audioMotion[2].setOptions({ showPeaks: false, lumiBars: true, minDecibels: -80, - maxDecibels: -20, - width: 320, - height: 145 + maxDecibels: -20 }); // Analyzer selector diff --git a/demo/overlay.html b/demo/overlay.html index 489b148..6bb81be 100644 --- a/demo/overlay.html +++ b/demo/overlay.html @@ -126,7 +126,7 @@

| ov
- mode 10 only + mode 10 / outlineBars

+ + + + + + + + - - - - - -
diff --git a/demo/overlay.js b/demo/overlay.js index 7eccae8..ebd5717 100644 --- a/demo/overlay.js +++ b/demo/overlay.js @@ -23,11 +23,11 @@ const presets = [ barSpace: .5, bgAlpha: .7, gradient: 'classic', + ledBars: true, lumiBars: false, radial: false, reflexRatio: 0, showBgColor: true, - showLeds: true, showPeaks: true, overlay: true } @@ -57,10 +57,10 @@ const presets = [ barSpace: .1, bgAlpha: .5, gradient: 'prism', + ledBars: false, maxFreq: 16000, radial: true, showBgColor: true, - showLeds: false, showPeaks: true, spinSpeed: 2, overlay: true @@ -72,13 +72,13 @@ const presets = [ mode: 5, barSpace: .25, bgAlpha: .5, + ledBars: false, lumiBars: false, radial: false, reflexAlpha: .5, reflexFit: true, reflexRatio: .3, showBgColor: false, - showLeds: false, showPeaks: true, overlay: true } diff --git a/demo/styles.css b/demo/styles.css index 8cbc748..5bbadf9 100644 --- a/demo/styles.css +++ b/demo/styles.css @@ -19,6 +19,10 @@ a:not([class]):hover { filter: drop-shadow(.07em .07em .14em #f0fc) drop-shadow( -.07em -.07em .14em #f0fc); } +audio { + height: 40px; +} + button { margin: 5px; padding: 6px; @@ -208,10 +212,6 @@ ul { background-size: cover; } -#container1 { - margin-bottom: 15px; -} - .center { text-align: center; } @@ -242,16 +242,19 @@ ul { justify-content: space-evenly; } -.main { - display: inline-block; - margin-right: 20px; - width: 640px; +.multi-block { + display: grid; + grid-gap: 20px; + grid-template: 1fr 1fr / 1fr 1fr 1fr; + height: 340px; + margin-bottom: 10px; } -.aside { - display: inline-block; - vertical-align: top; - width: 320px; +.colspan-2 { + grid-column: span 2; +} +.rowspan-2 { + grid-row: span 2; } .selector { From 18e854c511b1abd4968beaf2a9ee578a2795ffd8 Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Tue, 5 Oct 2021 20:04:38 -0300 Subject: [PATCH 14/16] Version bump for beta release; update changelog. --- Changelog.md | 80 +++++++++++++++++++++++-------------- package.json | 2 +- src/audioMotion-analyzer.js | 4 +- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/Changelog.md b/Changelog.md index dc2300d..8dfe560 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,28 @@ Changelog ========= +## version 3.6.0-beta.0 (2021-10-05) + +### Added: + ++ `alphaBars` effect, which is similar to `lumiBars` but preserves bars' amplitudes and also works on discrete frequencies mode and radial visualization; ++ `outlineBars` effect, which extends the usage of `lineWidth` and `fillAlpha` to octave bands modes; ++ `isAlphaBars` and `isOutlineBars` read-only properties. + +### Changed: + ++ `showLeds` and `isLedDisplay` **have been deprecated** in favor of `ledBars` and `isLedBars`, for consistency. + +### Fixed: + ++ [`getEnergy()`](README.md#getenergy-preset-startfreq-endfreq-) would not accept a fractionary starting frequency. + +### Improved: + ++ Regenerate the current gradient if/when it is re-registered [(#21)](https://github.com/hvianna/audioMotion-analyzer/issues/21); ++ The fluid demo now shows the status of read-only flags (`isLedBars`, etc) for better visualization of the interactions between different options. + + ## version 3.5.1 (2021-09-10) + Removed named tuples from the TS type definitions file, for improved compatibility [(#20)](https://github.com/hvianna/audioMotion-analyzer/issues/20). @@ -8,13 +30,13 @@ Changelog ## version 3.5.0 (2021-07-15) -### Added: {docsify-ignore} +### Added: + [`getBars()`](README.md#getbars) method, which provides real-time analyzer data; + [`useCanvas`](README.md#usecanvas-boolean) property to disable rendering to the canvas - thanks **@davay42** for [suggesting this feature](https://github.com/hvianna/audioMotion-analyzer/issues/19); + A tool to view/debug the generated octave bands - see the `/tools` folder. -### Improved: {docsify-ignore} +### Improved: + Fine-tuned generation of octave bands; + Improved FFT data interpolation; @@ -25,11 +47,11 @@ Changelog ## version 3.4.0 (2021-05-29) -### Added: {docsify-ignore} +### Added: + [`fsElement`](README.md#fselement-htmlelement-object) constructor option, for easily handling fullscreen on any element. -### Fixed and improved: {docsify-ignore} +### Fixed and improved: + Fixed radial analyzer too wide in vertical containers; + Fixed out of bounds bar in mode 0 (discrete frequencies) with `mirror` set to -1; @@ -39,11 +61,11 @@ Changelog ## version 3.3.0 (2021-05-03) -### Added: {docsify-ignore} +### Added: + New horizontal mirroring effect; see [`mirror`](README.md#mirror-number) property - thanks **@lexterror** for [suggesting this feature](https://github.com/hvianna/audioMotion-analyzer/issues/16). -### Improvements: {docsify-ignore} +### Improvements: + `colorStops` type definition updated for improved compatibility ([see #17](https://github.com/hvianna/audioMotion-analyzer/pull/17)) - props to [@Staijn1](https://github.com/Staijn1). @@ -55,14 +77,14 @@ Changelog ## version 3.2.0 (2021-04-03) -### Added: {docsify-ignore} +### Added: + [`getEnergy()`](README.md#getenergy-preset-startfreq-endfreq-) method - see it in action on [this pen](https://codepen.io/hvianna/pen/poNmVYo) - thanks **@Staijn1** for [suggesting this feature](https://github.com/hvianna/audioMotion.js/issues/12#issuecomment-733045035); + [`setLedParams()`](README.md#setledparams-params-) method, which allows you to customize the look of the LEDs - you can try it in the [fluid demo](https://audiomotion.dev/demo/fluid.html); + [`connectSpeakers`](README.md#connectspeakers-boolean) constructor option - thanks **@evoyy** for the [suggestion](https://github.com/hvianna/audioMotion-analyzer/issues/13); + [`connectedTo`](README.md#connectedto-array-read-only) read only property. -### Improvements: {docsify-ignore} +### Improvements: + When passing an AudioNode in the `source` property of constructor, it's no longer necessary to explicitly provide the `audioCtx`, as it will be inferred from the source node - thanks [@evoyy](https://github.com/evoyy) for this idea; + Disconnecting the output from the speakers no longer prevents the analyzer from working on Chromium-based browsers; @@ -71,7 +93,7 @@ Changelog + Refactored some code for improved legibility, performance and file size (still below 20kB minified! :sunglasses:); + Added compatibility with *standardized-audio-context* library - thanks [@richclingman](https://github.com/richclingman) for reporting this issue. -### Changed: {docsify-ignore} +### Changed: + `energy` and `peakEnergy` properties have been **deprecated and will be removed in the next major release** - use `getEnergy()` and `getEnergy('peak')` instead; + FPS display font size is now scaled relatively to the canvas height; @@ -80,18 +102,18 @@ Changelog ## version 3.1.0 (2021-02-27) -### Added: {docsify-ignore} +### Added: + TypeScript definition file (props to [@alex-greff](https://github.com/alex-greff)). -### Improvements: {docsify-ignore} +### Improvements: + Generate only the currently selected gradient on mode/gradient changes. ## version 3.0.0 (2020-11-28) -### BREAKING CHANGES: {docsify-ignore} +### BREAKING CHANGES: + The `analyzer` object is no longer exposed - use the new [`connectInput()`](README.md#connectinput-source-) method to connect all audio sources and [`connectOutput()`](README.md#connectoutput-node-) to connect the analyzer output to other nodes; + `audioSource` property has been renamed to [`connectedSources`](README.md#connectedsources-array), which now returns an **array** of all connected audio sources; @@ -101,7 +123,7 @@ Changelog + `showScale` property has been renamed to [`showScaleX`](README.md#showscalex-boolean); + `version` is now a **static** property and should always be accessed as [`AudioMotionAnalyzer.version`](README.md#audiomotionanalyzerversion-string-read-only). -### New features: {docsify-ignore} +### New features: + Dual channel (stereo) analyzer option; + Built-in volume control; @@ -118,20 +140,20 @@ Changelog + [`splitGradient`](README.md#splitgradient-boolean) + [`volume`](README.md#volume-number) -### Improvements: {docsify-ignore} +### Improvements: + Automatically unlock/resume the AudioContext on first user click, so you don't need to handle this in your code anymore; + Improved FFT data interpolation on low frequencies (especially noticeable in 1/12th and 1/24th octave bands); + Corrected initial amplitude of line / area graph. -### Fixed: {docsify-ignore} +### Fixed: + A compatibility issue that could cause `reflexRatio` not to work in some environments. ## version 2.5.0 (2020-10-07) -### Improvements: {docsify-ignore} +### Improvements: + Behavior of the [`onCanvasResize`](README.md#oncanvasresize-function) callback is now consistent across different browsers. Changes worth of note: 1. on fullscreen changes, **only a `'fschange'` *reason* will be reported** to the callback function - no more redundant `'resize'` calls; @@ -147,14 +169,14 @@ Changelog ## version 2.4.0 (2020-07-18) -### Added: {docsify-ignore} +### Added: + New **Radial** visualization - see [`radial`](README.md#radial-boolean) and [`spinSpeed`](README.md#spinspeed-number) properties and try them in the [demos](https://audiomotion.dev/demo)! - thanks **@inglesuniversal** for [suggesting this feature](https://github.com/hvianna/audioMotion.js/issues/6); + [`showScaleY`](README.md#showscaley-boolean) property for displaying the level (dB) scale on the analyzer's vertical axis; + [`energy`](README.md#energy-number-read-only) and [`peakEnergy`](README.md#peakenergy-number-read-only) read-only properties; + [Known issues](README.md#known-issues) section added to the documentation. -### Changed: {docsify-ignore} +### Changed: + [`setOptions()`](README.md#setoptions-options-) called with no argument now **resets all configuration options to their default values** (it used to raise an error); + The LED effect code has been refactored to improve appearance and compatibility with other (future) effects; @@ -165,12 +187,12 @@ Changelog ## version 2.3.0 (2020-06-08) -### Added: {docsify-ignore} +### Added: + [`binToFreq()`](README.md#bintofreq-bin-) and [`freqToBin()`](README.md#freqtobin-frequency-rounding-) methods; + [`reflexBright`](README.md#reflexbright-number) property, which allows to adjust the brightness of the reflected image. -### Changed: {docsify-ignore} +### Changed: + Reverted the change to `reflexAlpha` introduced in [v2.2.1](https://github.com/hvianna/audioMotion-analyzer/releases/tag/2.2.1) + Removed the forced black layer off the reflection background. @@ -178,7 +200,7 @@ Changelog ## version 2.2.1 (2020-05-31) -### Changed: {docsify-ignore} +### Changed: + ~~Improved the Reflex effect in [`overlay`](README.md#overlay-boolean) mode - the [`reflexAlpha`](README.md#reflexalpha-number) property is now used to adjust the opacity of a dark layer applied *over* the reflection area, which prevents undesired transparency of the reflection itself @@ -186,33 +208,33 @@ and creates a consistent effect, whether overlay mode is on or off~~ **(reverted + The package source code has been moved from the `dist` to the `src` folder. -### Fixed: {docsify-ignore} +### Fixed: + Prevent showing leds below the 0 level, when both reflex and overlay are active. ## version 2.2.0 (2020-05-19) -### Added: {docsify-ignore} +### Added: + New [`overlay`](README.md#overlay-boolean) and [`bgAlpha`](README.md#bgalpha-number) properties allow displaying the analyzer over other contents. [Check out the new demo!](https://audiomotion.dev/demo/overlay.html) -### Changed: {docsify-ignore} +### Changed: + Corrected the documentation for the [`registerGradient()`](README.md#registergradient-name-options-) method, which stated the `bgColor` property was required (it has always been optional). ## version 2.1.0 (2020-04-06) -### Added: {docsify-ignore} +### Added: + Customizable Reflex effect - see [`reflexRatio`](README.md#reflexratio-number) and try it in the [demo](https://audiomotion.dev/demo/fluid.html). ## version 2.0.0 (2020-03-24) -### Added: {docsify-ignore} +### Added: + New [`lineWidth`](README.md#linewidth-number) and [`fillAlpha`](README.md#fillalpha-number) properties for [mode 10](README.md#mode-number) customization, so it can now work as an area graph (default), a line graph or a combination of both; + New [`barSpace`](README.md#barspace-number) property for customizable bar spacing in octave bands modes; @@ -220,7 +242,7 @@ and creates a consistent effect, whether overlay mode is on or off~~ **(reverted + Custom [error codes](README.md#custom-errors); + New [`version`](README.md#version-string-read-only) property; -### Changed: {docsify-ignore} +### Changed: + Increased default spacing between bars in octave bands modes - to get the previous look, set [`barSpace`](README.md#barspace-number) to **1**; + Improved accuracy when positioning the X-axis scale labels in octave bands modes; @@ -229,13 +251,13 @@ and creates a consistent effect, whether overlay mode is on or off~~ **(reverted + Several functions were refactored for improved legibility, memory usage and performance; + Improved documentation and demos; -### Fixed: {docsify-ignore} +### Fixed: + The multi-instance demo should now work on browsers other than Firefox (it now uses a shared audio context); + `isFullscreen` property now correctly reads `false` (instead of `undefined`) when the analyzer is not in fullscreen (*potentially breaking change*); + Setting one of the callback functions to `undefined` with `setOptions()` now properly unregisters the callback (*potentially breaking change*); -### API breaking changes: {docsify-ignore} +### API breaking changes: + `audioCtx`, `analyzer`, `canvas` and `canvasCtx` objects are now read-only (`canvasCtx` properties may be safely modified while inside the callback for `onCanvasDraw`); + `frame` and `time` properties are not exposed anymore, as they are intended for internal use only; diff --git a/package.json b/package.json index b7b332a..559b88c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "audiomotion-analyzer", "description": "High-resolution real-time graphic audio spectrum analyzer JavaScript module with no dependencies.", - "version": "3.5.1", + "version": "3.6.0-beta.0", "main": "./src/audioMotion-analyzer.js", "module": "./src/audioMotion-analyzer.js", "types": "./src/index.d.ts", diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index 1608dd4..ac85333 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -2,12 +2,12 @@ * audioMotion-analyzer * High-resolution real-time graphic audio spectrum analyzer JS module * - * @version 3.5.1 + * @version 3.6.0-beta.0 * @author Henrique Avila Vianna * @license AGPL-3.0-or-later */ -const VERSION = '3.5.1'; +const VERSION = '3.6.0-beta.0'; // internal constants const TAU = 2 * Math.PI, From 386963e7dcd87b2624ecf72674209a885027eb7c Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Wed, 6 Oct 2021 21:43:36 -0300 Subject: [PATCH 15/16] Update documentation. --- README.md | 144 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 100 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2c1a621..5dcf2dd 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,11 @@ What users are saying: ## Live code examples - [Quick and easy spectrum analyzer](https://codepen.io/hvianna/pen/pobMLNL) -- [Complete visualization options](https://codepen.io/hvianna/pen/LYREBYQ) - [Using microphone input](https://codepen.io/hvianna/pen/VwKZgEE) -- [Custom callback function](https://codepen.io/hvianna/pen/LYZwdvG) - [Creating additional effects with `getEnergy()`](https://codepen.io/hvianna/pen/poNmVYo) - [No canvas example](https://codepen.io/hvianna/pen/ZEKWWJb) (create your own visualization using analyzer data) - [Integration with Pizzicato library](https://codesandbox.io/s/9y6qb) - see [this discussion](https://github.com/hvianna/audioMotion-analyzer/issues/10) for more info +- [See more code examples on CodePen](https://codepen.io/collection/ABbbKr) ## Usage @@ -111,6 +110,7 @@ Valid properties and default values are shown below. Properties marked as *constructor only* can only be set by the constructor call, the others can also be set anytime via [`setOptions()`](#setoptions-options-) method. options = {
+  [alphaBars](#alphabars-boolean): **false**,
  [audioCtx](#audioctx-audiocontext-object): *undefined*, // constructor only
  [barSpace](#barspace-number): **0.1**,
  [bgAlpha](#bgalpha-number): **0.7**,
@@ -120,6 +120,7 @@ options = {
  [fsElement](#fselement-htmlelement-object): *undefined*, // constructor only
  [gradient](#gradient-string): **'classic'**,
  [height](#height-number): *undefined*,
+  [ledBars](#ledbars-boolean): **false**,
  [lineWidth](#linewidth-number): **0**,
  [loRes](#lores-boolean): **false**,
  [lumiBars](#lumibars-boolean): **false**,
@@ -131,6 +132,7 @@ options = {
  [mode](#mode-number): **0**,
  [onCanvasDraw](#oncanvasdraw-function): *undefined*,
  [onCanvasResize](#oncanvasresize-function): *undefined*,
+  [outlineBars](#outlinebars-boolean): **false**,
  [overlay](#overlay-boolean): **false**,
  [radial](#radial-boolean): **false**,
  [reflexAlpha](#reflexalpha-number): **0.15**,
@@ -139,7 +141,7 @@ options = {
  [reflexRatio](#reflexratio-number): **0**,
  [showBgColor](#showbgcolor-boolean): **true**,
  [showFPS](#showfps-boolean): **false**,
-  [showLeds](#showleds-boolean): **false**,
+  [showLeds](#showleds-deprecated): **false**, // DEPRECATED - use ledBars instead
  [showPeaks](#showpeaks-boolean): **true**,
  [showScaleX](#showscalex-boolean): **true**,
  [showScaleY](#showscaley-boolean): **false**,
@@ -216,6 +218,20 @@ Defaults to **true**, so the analyzer will start running right after initializat ## Properties +### `alphaBars` *boolean* + +*Available since v3.6.0* + +When set to *true* each bar's amplitude affects its opacity, i.e., higher bars are rendered more opaque while shorter bars are more transparent. + +This is similar to the [`lumiBars`](#lumibars-boolean) effect, but bars' amplitudes are preserved and it also works on **Discrete** [mode](#mode-number) and [radial](#radial-boolean) visualization. + +For effect priority when combined with other settings, see [`isAlphaBars`](#isalphabars-boolean-read-only). + +Defaults to **false**. + +!> [See related known issue](#alphabars-and-fillalpha-wont-work-with-radial-on-firefox) + ### `audioCtx` *AudioContext object* *(Read only)* [*AudioContext*](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) used by **audioMotion-analyzer**. @@ -247,7 +263,7 @@ See also the [fluid demo](/demo/fluid.html) and the [multi-instance demo](/demo/ *Available since v2.0.0* -Customize the spacing between bars in [octave bands modes](#mode-number). +Customize the spacing between bars in **octave bands** [modes](#mode-number). Use a value between 0 and 1 for spacing proportional to the band width. Values >= 1 will be considered as a literal number of pixels. @@ -255,7 +271,7 @@ For example, `barSpace = 0.5` will use half the width available to each band for On the other hand, `barSpace = 2` will set a fixed spacing of 2 pixels, independent of the width of bars. Prefer proportional spacing to obtain consistent results among different resolutions and screen sizes. -`barSpace = 0` will effectively show contiguous bars, except when [`showLeds`](#showleds-boolean) is *true*, in which case a minimum spacing is enforced +`barSpace = 0` will effectively show contiguous bars, except when [`ledBars`](#ledbars-boolean) is *true*, in which case a minimum spacing is enforced (this can be customized via [`setLedParams()`](#setledparams-params-) method). Defaults to **0.1**. @@ -296,9 +312,7 @@ See also [`connectOutput()`](#connectoutput-node-). ### `energy` **(DEPRECATED)** -**This property will be removed in version 4.0.0** - -Use [`getEnergy()`](#getenergy-preset-startfreq-endfreq-) instead. +**This property will be removed in version 4.0.0** - Use [`getEnergy()`](#getenergy-preset-startfreq-endfreq-) instead. ### `fftSize` *number* @@ -313,15 +327,17 @@ Defaults to **8192**. *Available since v2.0.0* -Opacity of the area fill in **Line / Area graph** visualization ([`mode`](#mode-number) 10). +Opacity of the area fill in **Graph** [mode](#mode-number), or inner fill of bars in **octave bands** modes when [`outlineBars`](#outlinebars-boolean) is *true*. It must be a number between 0 (completely transparent) and 1 (completely opaque). -Please note that this affects only the area fill. The line (when [`lineWidth`](#linewidth-number) > 0) is always drawn at full opacity. +Please note that the line stroke (when [`lineWidth`](#linewidth-number) > 0) is always drawn at full opacity, regardless of the `fillAlpha` value. + +Also, for octave bands modes, [`alphaBars`](#alphabars-boolean) set to *true* takes precedence over `fillAlpha`. Defaults to **1**. -!> [See related known issue](#fillalpha-and-radial-mode-on-firefox) +!> [See related known issue](#alphabars-and-fillalpha-wont-work-with-radial-on-firefox) ### `fps` *number* *(Read only)* @@ -355,47 +371,77 @@ Nominal dimensions of the analyzer. If one or both of these are `undefined`, the analyzer will try to adjust to the container's width and/or height. If the container's width and/or height are 0 (inline elements), a reference size of **640 x 270 pixels** will be used to replace the missing dimension(s). -This should be considered the minimum dimensions for proper visualization of all available modes with the [LED effect](#showleds-boolean) on. +This should be considered the minimum dimensions for proper visualization of all available modes with the [LED effect](#ledbars-boolean) on. You can set both values at once using the [`setCanvasSize()`](#setcanvassize-width-height-) method. ?> You can read the actual canvas dimensions at any time directly from the [`canvas`](#canvas-htmlcanvaselement-object-read-only) object. +### `isAlphaBars` *boolean* *(Read only)* + +*Available since v3.6.0* + +***true*** when alpha bars are effectively being displayed, i.e., [`alphaBars`](#alphabars-boolean) is set to *true* and [`mode`](#mode-number) is set to discrete frequencies +or one of the octave bands modes, in which case [`lumiBars`](#lumibars-boolean) must be set to *false* or [`radial`](#radial-boolean) must be set to *true*. + ### `isFullscreen` *boolean* *(Read only)* -*true* when the analyzer is being displayed in fullscreen, or *false* otherwise. +***true*** when the analyzer is being displayed in fullscreen, or ***false*** otherwise. See [`toggleFullscreen()`](#togglefullscreen). -### `isLedDisplay` *boolean* *(Read only)* +### `isLedBars` *boolean* *(Read only)* -*Available since v3.0.0* +*Available since v3.6.0* (formerly `isLedDisplay`) + +***true*** when LED bars are effectively being displayed, i.e., [`ledBars`](#ledBars-boolean) is set to *true* and [`mode`](#mode-number) is set to an octave bands mode and [`radial`](#radial-boolean) is *false*. -*true* when the LED effect is effectively being displayed, i.e., [`showLeds`](#showleds-boolean) is set to *true* and [`mode`](#mode-number) is set to one of the octave bands modes and [`radial`](#radial-boolean) is *false*. +### `isLedDisplay` **(DEPRECATED)** + +**This property will be removed in version 4.0.0** - Use [`isLedBars`](#isledbars-boolean-read-only) instead. ### `isLumiBars` *boolean* *(Read only)* *Available since v3.0.0* -*true* when the luminance bars effect is effectively being displayed, i.e., [`lumiBars`](#lumibars-boolean) is set to *true* and [`mode`](#mode-number) is set to one of the octave bands modes and [`radial`](#radial-boolean) is *false*. +***true*** when luminance bars are effectively being displayed, i.e., [`lumiBars`](#lumibars-boolean) is set to *true* and [`mode`](#mode-number) is set to an octave bands mode and [`radial`](#radial-boolean) is *false*. ### `isOctaveBands` *boolean* *(Read only)* *Available since v3.0.0* -*true* when [`mode`](#mode-number) is set to one of the octave bands modes. +***true*** when [`mode`](#mode-number) is set to one of the octave bands modes. ### `isOn` *boolean* *(Read only)* -*true* if the analyzer process is running, or *false* if it's stopped. +***true*** if the analyzer process is running, or *false* if it's stopped. See [`toggleAnalyzer()`](#toggleanalyzer-boolean-). +### `isOutlineBars` *boolean* *(Read only)* + +*Available since v3.6.0* + +***true*** when outlined bars are effectively being displayed, i.e., [`outlineBars`](#outlinebars-boolean) is set to *true*, [`mode`](#mode-number) is set to +one of the octave bands modes and both [`ledBars`](#ledbars-boolean) and [`lumiBars`](#lumibars-boolean) are set to *false*, or [`radial`](#radial-boolean) is set to *true*. + +### `ledBars` *boolean* + +*Available since v3.6.0* (formerly `showLeds`) + +*true* to activate a vintage LEDs display effect. Only effective for **octave bands** [modes](#mode-number). + +This effect can be customized via [`setLedParams()`](#setledparams-params-) method. + +For effect priority when combined with other settings, see [`isLedBars`](#isledbars-boolean-read-only). + +Defaults to **false**. + ### `lineWidth` *number* *Available since v2.0.0* -Line width for the **Line / Area graph** visualization ([`mode`](#mode-number) 10). +Line width for **Graph** [mode](#mode-number), or outline stroke in **octave bands** modes when [`outlineBars`](#outlinebars-boolean) is *true*. For the line to be distinguishable, set also [`fillAlpha`](#fillalpha-number) < 1. @@ -422,10 +468,14 @@ This will prevent the canvas size from changing, when switching the low resoluti *Available since v1.1.0* -This is only effective for [visualization modes](#mode-number) 1 to 8 (octave bands). +This is only effective for **octave bands** [modes](#mode-number). When set to *true* all analyzer bars will be displayed at full height with varying luminance (opacity, actually) instead. +`lumiBars` takes precedence over [`alphaBars`](#alphabars-boolean) and [`outlineBars`](#outlinebars-boolean), except in [`radial`](#radial-boolean) visualization. + +For effect priority when combined with other settings, see [`isLumiBars`](#islumibars-boolean-read-only). + Defaults to **false**. ### `maxDecibels` *number* @@ -472,9 +522,9 @@ Defaults to **0**. Current visualization mode. -+ **Discrete frequencies** mode provides the highest resolution, allowing you to visualize individual frequencies provided by the [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform); ++ **Discrete** mode provides the highest resolution, allowing you to visualize individual frequencies amplitudes as provided by the [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) computation; + **Octave bands** modes display wider vertical bars, each one representing the *n*th part of an octave, based on a [24-tone equal tempered scale](https://en.wikipedia.org/wiki/Quarter_tone); -+ **Line / Area graph** mode uses the discrete frequencies data to draw a filled shape and/or a continuous line (see [`fillAlpha`](#fillalpha-number) and [`lineWidth`](#linewidth-number) properties). ++ **Graph** mode uses the discrete data points to draw a continuous line and/or filled area graph (see [`fillAlpha`](#fillalpha-number) and [`lineWidth`](#linewidth-number) properties). mode | description | notes ------:|:-------------:|------ @@ -488,10 +538,20 @@ mode | description | notes 7 | Half octave bands | 8 | Full octave bands | 9 | *(not valid)* | *reserved* -10 | Line / Area graph | *added in v1.1.0* +10 | Graph | *added in v1.1.0* Defaults to **0**. +### `outlineBars` *boolean* + +*Available since v3.6.0* + +When *true* and [`mode`](#mode-number) is set to one of the **octave bands** modes, draws outlined bars with customizable [`fillAlpha`](#fillalpha-number) and [`lineWidth`](#linewidth-number). + +For effect priority when combined with other settings, see [`isOutlineBars`](#isoutlinebars-boolean-read-only). + +Defaults to **false**. + ### `overlay` *boolean* *Available since v2.2.0* @@ -506,9 +566,7 @@ Defaults to **false**. ### `peakEnergy` **(DEPRECATED)** -**This property will be removed in version 4.0.0** - -Use [`getEnergy('peak')`](#getenergy-preset-startfreq-endfreq-) instead. +**This property will be removed in version 4.0.0** - Use [`getEnergy('peak')`](#getenergy-preset-startfreq-endfreq-) instead. ### `pixelRatio` *number* *(Read only)* @@ -525,14 +583,14 @@ When [`loRes`](#lores-boolean) is *true* `pixelRatio` is halved, i.e. **0.5** fo When *true*, the spectrum analyzer is rendered as a circle, with radial frequency bars spreading from the center of the canvas. -When radial mode is active, [`lumiBars`](#lumibars-boolean) and [`showLeds`](#showleds-boolean) have no effect, and -also [`showPeaks`](#showpeaks-boolean) has no effect in **Line / Area graph** mode. +When radial mode is active, [`ledBars`](#ledbars-boolean) and [`lumiBars`](#lumibars-boolean) have no effect, and +also [`showPeaks`](#showpeaks-boolean) has no effect in radial **Graph** mode. See also [`spinSpeed`](#spinspeed-number). Defaults to **false**. -!> [See related known issue](#fillalpha-and-radial-mode-on-firefox) +!> [See related known issue](#alphabars-and-fillalpha-wont-work-with-radial-on-firefox) ### `reflexAlpha` *number* @@ -589,20 +647,16 @@ or transparent when [`overlay`](#overlay-boolean) is ***true***. Defaults to **true**. -?> Please note that when [`overlay`](#overlay-boolean) is ***false*** and [`showLeds`](#showleds-boolean) is ***true***, the background color will always be black, +?> Please note that when [`overlay`](#overlay-boolean) is ***false*** and [`ledBars`](#ledbars-boolean) is ***true***, the background color will always be black, and setting `showBgColor` to ***true*** will make the "unlit" LEDs visible instead. ### `showFPS` *boolean* *true* to display the current frame rate. Defaults to **false**. -### `showLeds` *boolean* - -*true* to activate a vintage LEDs display effect. Only effective for [visualization modes](#mode-number) 1 to 8 (octave bands). +### `showLeds` **(DEPRECATED)** -This effect can be customized via [`setLedParams()`](#setledparams-params-) method. - -Defaults to **false**. +**This property will be removed in version 4.0.0** - Use [`ledBars`](#ledbars-boolean) instead. ### `showPeaks` *boolean* @@ -915,7 +969,7 @@ Sets the desired frequency range. Values are expressed in Hz (Hertz). *Available since v3.2.0* -Customize parameters used to create the [LEDs display effect](#showleds-boolean). +Customize parameters used to create the [LEDs display effect](#ledbars-boolean). `params` should be an object with the following structure: @@ -1002,9 +1056,9 @@ ERR_UNKNOWN_GRADIENT | User tried to [select a gradient](#gradient-string) [`reflexBright`](#reflexbright-number) feature relies on the [`filter`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter) property of the Canvas API, which is [currently not supported in some browsers](https://caniuse.com/#feat=mdn-api_canvasrenderingcontext2d_filter) (notably, Opera and Safari). -### fillAlpha and radial mode on Firefox {docsify-ignore} +### alphaBars and fillAlpha won't work with Radial on Firefox {docsify-ignore} -On Firefox, [`fillAlpha`](#fillalpha-number) may not work properly for [`radial`](#radial-boolean) visualization, due to [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1164912). +On Firefox, [`alphaBars`](#alphaBars-boolean) and [`fillAlpha`](#fillalpha-number) won't work with [`radial`](#radial-boolean) visualization when using hardware acceleration, due to [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1164912). ### Visualization of live streams won't work on Safari {docsify-ignore} @@ -1030,13 +1084,15 @@ See [Changelog.md](Changelog.md) ## Get in touch! -If you create something cool with this project, or simply think it's useful, I would like to know! Please drop me an e-mail at hvianna@gmail.com +If you create something cool with **audioMotion-analyzer**, or simply think it's useful, I would love to know! Please drop me an e-mail at hvianna@gmail.com -If you have a feature request or code suggestion, please see [CONTRIBUTING.md](CONTRIBUTING.md) +For feature requests, suggestions or feedback, please see the [CONTRIBUTING.md](CONTRIBUTING.md) file. -And if you're feeling generous you can [buy me a coffee on Ko-fi](https://ko-fi.com/Q5Q6157GZ) :grin::coffee: +And if you're feeling generous, maybe: -[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Q5Q6157GZ) +* [Buy me a coffee](https://ko-fi.com/Q5Q6157GZ) on Ko-fi ☕😁 +* Gift me something from my [Bandcamp wishlist](https://bandcamp.com/henriquevianna/wishlist) 🎁🥰 +* Tip me via [Brave Rewards](https://brave.com/brave-rewards/) using Brave browser 🤓 ## License From 710df5a74c245e654cf3b0d39090d5b589c4da9c Mon Sep 17 00:00:00 2001 From: Henrique Vianna Date: Sun, 10 Oct 2021 15:45:16 -0300 Subject: [PATCH 16/16] Version bump; update changelog. --- Changelog.md | 14 +++++++------- package.json | 2 +- src/audioMotion-analyzer.js | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8dfe560..38ff538 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,26 +1,26 @@ Changelog ========= -## version 3.6.0-beta.0 (2021-10-05) +## version 3.6.0 (2021-10-10) ### Added: -+ `alphaBars` effect, which is similar to `lumiBars` but preserves bars' amplitudes and also works on discrete frequencies mode and radial visualization; -+ `outlineBars` effect, which extends the usage of `lineWidth` and `fillAlpha` to octave bands modes; -+ `isAlphaBars` and `isOutlineBars` read-only properties. ++ [`alphaBars`](README.md#alphabars-boolean) effect, which is similar to `lumiBars` but preserves bars' amplitudes and also works on discrete frequencies mode and radial visualization; ++ [`outlineBars`](README.md#outlinebars-boolean) effect, which extends the usage of `lineWidth` and `fillAlpha` to octave bands modes; ++ [`isAlphaBars`](README.md#isalphabars-boolean-read-only) and [`isOutlineBars`](README.md#isoutlinebars-boolean-read-only) read-only properties. ### Changed: -+ `showLeds` and `isLedDisplay` **have been deprecated** in favor of `ledBars` and `isLedBars`, for consistency. ++ `showLeds` and `isLedDisplay` **have been deprecated** in favor of [`ledBars`](README.md#ledbars-boolean) and [`isLedBars`](README.md#isledbars-boolean-read-only), for consistent naming of effects. ### Fixed: -+ [`getEnergy()`](README.md#getenergy-preset-startfreq-endfreq-) would not accept a fractionary starting frequency. ++ [`getEnergy()`](README.md#getenergy-preset-startfreq-endfreq-) would not accept a fractionary initial frequency. ### Improved: + Regenerate the current gradient if/when it is re-registered [(#21)](https://github.com/hvianna/audioMotion-analyzer/issues/21); -+ The fluid demo now shows the status of read-only flags (`isLedBars`, etc) for better visualization of the interactions between different options. ++ The [fluid demo](/demo/fluid.html) now shows the status of read-only flags, for better visualization of interactions between different properties. ## version 3.5.1 (2021-09-10) diff --git a/package.json b/package.json index 559b88c..7184c3e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "audiomotion-analyzer", "description": "High-resolution real-time graphic audio spectrum analyzer JavaScript module with no dependencies.", - "version": "3.6.0-beta.0", + "version": "3.6.0", "main": "./src/audioMotion-analyzer.js", "module": "./src/audioMotion-analyzer.js", "types": "./src/index.d.ts", diff --git a/src/audioMotion-analyzer.js b/src/audioMotion-analyzer.js index ac85333..d10b530 100644 --- a/src/audioMotion-analyzer.js +++ b/src/audioMotion-analyzer.js @@ -2,12 +2,12 @@ * audioMotion-analyzer * High-resolution real-time graphic audio spectrum analyzer JS module * - * @version 3.6.0-beta.0 + * @version 3.6.0 * @author Henrique Avila Vianna * @license AGPL-3.0-or-later */ -const VERSION = '3.6.0-beta.0'; +const VERSION = '3.6.0'; // internal constants const TAU = 2 * Math.PI,