audiomotion-analyzer 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,13 +86,20 @@ import AudioMotionAnalyzer from 'audiomotion-analyzer';
86
86
 
87
87
  ## Constructor
88
88
 
89
- `new AudioMotionAnalyzer( [container], [{options}] )`
89
+ ```js
90
+ new AudioMotionAnalyzer()
91
+ new AudioMotionAnalyzer( container )
92
+ new AudioMotionAnalyzer( container, {options} )
93
+ new AudioMotionAnalyzer( {options} )
94
+ ```
90
95
 
91
96
  Creates a new instance of **audioMotion-analyzer**.
92
97
 
93
- The analyzer canvas will be appended to the HTML element referenced by `container`, unless you set [`useCanvas: false`](#usecanvas-boolean) in the options.
98
+ `container` is the DOM element into which the canvas created for the analyzer should be inserted.
94
99
 
95
- If `container` is undefined, the document's body will be used instead.
100
+ If not defined, defaults to `document.body`, unless [`canvas`](#canvas-htmlcanvaselement-object) is defined in the options, in which case its parent element will be considered the container.
101
+
102
+ `options` must be an [Options object](#options-object).
96
103
 
97
104
  Usage example:
98
105
 
@@ -107,11 +114,14 @@ const audioMotion = new AudioMotionAnalyzer(
107
114
 
108
115
  This will insert the analyzer canvas inside the *#container* element and start the visualization of audio coming from the *#audio* element.
109
116
 
117
+ ?> By default, audioMotion will try to use all available container space for the canvas. To prevent it from growing indefinitely, you must either constrain the dimensions of the container via CSS or explicitly define [`height`](#height-number) and/or [`width`](#width-number) properties in the constructor [options](#options-object).
118
+
110
119
  ### Options object
111
120
 
112
121
  Valid properties and default values are shown below.
113
122
 
114
- 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.
123
+ Properties marked as *constructor only* can only be set in the constructor call, the others can also be set anytime via [`setOptions()`](#setoptions-options-) method or
124
+ directly as [properties](#properties) of the audioMotion instance.
115
125
 
116
126
  options = {<br>
117
127
  &emsp;&emsp;[alphaBars](#alphabars-boolean): **false**,<br>
@@ -119,6 +129,7 @@ options = {<br>
119
129
  &emsp;&emsp;[audioCtx](#audioctx-audiocontext-object): *undefined*, // constructor only<br>
120
130
  &emsp;&emsp;[barSpace](#barspace-number): **0.1**,<br>
121
131
  &emsp;&emsp;[bgAlpha](#bgalpha-number): **0.7**,<br>
132
+ &emsp;&emsp;[canvas](#canvas-htmlcanvaselement-object): *undefined*, // constructor only<br>
122
133
  &emsp;&emsp;[channelLayout](#channellayout-string): **'single'**,<br>
123
134
  &emsp;&emsp;[colorMode](#colormode-string): **'gradient'**,<br>
124
135
  &emsp;&emsp;[connectSpeakers](#connectspeakers-boolean): **true**, // constructor only<br>
@@ -150,6 +161,8 @@ options = {<br>
150
161
  &emsp;&emsp;[overlay](#overlay-boolean): **false**,<br>
151
162
  &emsp;&emsp;[peakLine](#peakline-boolean): **false**,<br>
152
163
  &emsp;&emsp;[radial](#radial-boolean): **false**,<br>
164
+ &emsp;&emsp;[radialInvert](#radialinvert-boolean): **false**,<br>
165
+ &emsp;&emsp;[radius](#radius-number): **0.3**,<br>
153
166
  &emsp;&emsp;[reflexAlpha](#reflexalpha-number): **0.15**,<br>
154
167
  &emsp;&emsp;[reflexBright](#reflexbright-number): **1**,<br>
155
168
  &emsp;&emsp;[reflexFit](#reflexfit-boolean): **true**,<br>
@@ -164,7 +177,7 @@ options = {<br>
164
177
  &emsp;&emsp;[source](#source-htmlmediaelement-or-audionode-object): *undefined*, // constructor only<br>
165
178
  &emsp;&emsp;[spinSpeed](#spinspeed-number): **0**,<br>
166
179
  &emsp;&emsp;[splitGradient](#splitgradient-boolean): **false**,<br>
167
- &emsp;&emsp;[start](#start-boolean): **true**,<br>
180
+ &emsp;&emsp;[start](#start-boolean): **true**, // constructor only<br>
168
181
  &emsp;&emsp;[trueLeds](#trueleds-boolean): **false**,<br>
169
182
  &emsp;&emsp;[useCanvas](#usecanvas-boolean): **true**,<br>
170
183
  &emsp;&emsp;[volume](#volume-number): **1**,<br>
@@ -187,6 +200,14 @@ If neither is defined, a new audio context will be created. After instantiation,
187
200
 
188
201
  See [this live code](https://codesandbox.io/s/9y6qb) and the [multi-instance demo](/demo/multi.html) for more usage examples.
189
202
 
203
+ #### `canvas` *HTMLCanvasElement object*
204
+
205
+ *Available since v4.4.0*
206
+
207
+ Allows you to provide an existing [*Canvas*](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) where audioMotion should render its visualizations.
208
+
209
+ If not defined, a new canvas will be created. After instantiation, you can obtain its reference from the [`canvas`](#canvas-htmlcanvaselement-object-read-only) read-only property.
210
+
190
211
  #### `connectSpeakers` *boolean*
191
212
 
192
213
  *Available since v3.2.0*
@@ -321,11 +342,13 @@ Defaults to **0.7**.
321
342
 
322
343
  ### `canvas` *HTMLCanvasElement object* *(Read only)*
323
344
 
324
- [*Canvas*](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) element created by audioMotion.
345
+ [*Canvas*](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) element where audioMotion renders its visualizations.
346
+
347
+ See also the [`canvas`](#canvas-htmlcanvaselement-object) constructor option.
325
348
 
326
349
  ### `canvasCtx` *CanvasRenderingContext2D object* *(Read only)*
327
350
 
328
- [2D rendering context](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) used for drawing in audioMotion's *Canvas*.
351
+ [2D rendering context](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) used for drawing in audioMotion's [`canvas`](#canvas-htmlcanvaselement-object-read-only).
329
352
 
330
353
  ### `channelLayout` *string*
331
354
 
@@ -480,13 +503,14 @@ See also [`gradient`](#gradient-string) and [`splitGradient`](#splitgradient-boo
480
503
 
481
504
  Nominal dimensions of the analyzer.
482
505
 
483
- If one or both of these are `undefined`, the analyzer will try to adjust to the container's width and/or height.
484
- 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).
485
- This should be considered the minimum dimensions for proper visualization of all available modes and effects.
506
+ Setting one or both properties to **_undefined_** (default) will trigger the fluid/responsive behavior and the analyzer will try to adjust to the container's height and/or width.
507
+ In that case, it's important that you constrain the dimensions of the container via CSS to prevent the canvas from growing indefinitely.
486
508
 
487
509
  You can set both values at once using the [`setCanvasSize()`](#setcanvassize-width-height-) method.
488
510
 
489
- ?> You can read the actual canvas dimensions at any time directly from the [`canvas`](#canvas-htmlcanvaselement-object-read-only) object.
511
+ See also [`onCanvasResize`](#oncanvasresize-function).
512
+
513
+ ?> The actual dimensions of the canvas may differ from these values, depending on the device's [pixelRatio](#pixelratio-number-read-only), the [`loRes`](#lores-boolean) setting and while in fullscreen. For the actual pixel values, read `height` and `width` directly from the [`canvas`](#canvas-htmlcanvaselement-object-read-only) object.
490
514
 
491
515
  ### `isAlphaBars` *boolean* *(Read only)*
492
516
 
@@ -771,12 +795,36 @@ In radial view, [`ledBars`](#ledbars-boolean) and [`lumiBars`](#lumibars-boolean
771
795
 
772
796
  When [`channelLayout`](#channellayout-string) is set to *'dual-vertical'*, graphs for the right channel are rendered towards the center of the screen.
773
797
 
774
- See also [`spinSpeed`](#spinspeed-number).
798
+ See also [`radialInvert`](#radialinvert-boolean), [`radius`](#radius-number) and [`spinSpeed`](#spinspeed-number).
775
799
 
776
800
  Defaults to **false**.
777
801
 
778
802
  !> [See related known issue](#alphabars-and-fillalpha-wont-work-with-radial-on-firefox)
779
803
 
804
+ ### `radialInvert` *boolean*
805
+
806
+ *Available since v4.4.0*
807
+
808
+ When set to *true* (and [`radial`](#radial-boolean) is also *true*) creates a radial spectrum with maximum size and bars growing towards the center of the screen.
809
+
810
+ This property has no effect when [`channelLayout`](#channellayout-string) is set to *'dual-vertical'*.
811
+
812
+ See also [`radius`](#radius-number).
813
+
814
+ Defaults to **false**.
815
+
816
+ ### `radius` *number*
817
+
818
+ *Available since v4.4.0*
819
+
820
+ Defines the internal radius of [`radial`](#radial-boolean) spectrum. It should be a number between **0** and **1**.
821
+
822
+ This property has no effect when [`channelLayout`](#channellayout-string) is set to *'dual-vertical'*.
823
+
824
+ When [`radialInvert`](#radialinvert-boolean) is *true*, this property controls how close to the center of the screen the bars can get.
825
+
826
+ Defaults to **0.3**.
827
+
780
828
  ### `reflexAlpha` *number*
781
829
 
782
830
  *Available since v2.1.0*
@@ -1213,6 +1261,19 @@ Please note that preset names are case-sensitive. If the specified preset is not
1213
1261
 
1214
1262
  Use this method inside your callback function to create additional visual effects. See the [fluid demo](/demo/fluid.html) or [this pen](https://codepen.io/hvianna/pen/poNmVYo) for examples.
1215
1263
 
1264
+ ### `getOptions( [ignore] )`
1265
+
1266
+ *Available since v4.4.0*
1267
+
1268
+ Returns an [**Options object**](#options-object) with all the current analyzer settings.
1269
+
1270
+ `ignore` can be a single property name or an array of property names that should not be included in the returned object.
1271
+
1272
+ Callbacks and [constructor-specific properties](#constructor-specific-options) are NOT included in the object.
1273
+
1274
+ ?> If the same [gradient](#gradient-string) is selected for both channels, only the `gradient` property is included in the object; otherwise, only `gradientLeft` and `gradientRight` are included (not `gradient`). If 'gradient' is added to `ignore`, none of the gradient properties will be included.
1275
+
1276
+ See also [`setOptions()`](#setoptions-options-).
1216
1277
 
1217
1278
  ### `registerGradient( name, options )`
1218
1279
 
@@ -1290,10 +1351,12 @@ You can try different values in the [fluid demo](https://audiomotion.dev/demo/fl
1290
1351
 
1291
1352
  Shorthand method for setting several analyzer [properties](#properties) at once.
1292
1353
 
1293
- See **[Options object](#options-object)** for object structure and default values.
1354
+ `options` must be an [**Options object**](#options-object).
1294
1355
 
1295
1356
  ?> If called with no argument (or `options` is *undefined*), resets all configuration options to their default values.
1296
1357
 
1358
+ See also [`getOptions()`](#getoptions-ignore-).
1359
+
1297
1360
  ### `setSensitivity( minDecibels, maxDecibels )`
1298
1361
 
1299
1362
  Adjust the analyzer's sensitivity. See [`minDecibels`](#mindecibels-number) and [`maxDecibels`](#maxdecibels-number) properties.
@@ -1456,7 +1519,7 @@ See [Changelog.md](Changelog.md)
1456
1519
 
1457
1520
  ## Contributing
1458
1521
 
1459
- I kindly request that you only [open an issue](https://github.com/hvianna/audioMotion-analyzer/issues) for submitting a **bug report**.
1522
+ I kindly request that you only [open an issue](https://github.com/hvianna/audioMotion-analyzer/issues) for submitting **bug reports**.
1460
1523
 
1461
1524
  If you need help integrating *audioMotion-analyzer* with your project, have ideas for **new features** or any other questions or feedback,
1462
1525
  please use the [**Discussions**](https://github.com/hvianna/audioMotion-analyzer/discussions) section on GitHub.
@@ -1475,5 +1538,5 @@ And if you're feeling generous, maybe:
1475
1538
 
1476
1539
  ## License
1477
1540
 
1478
- audioMotion-analyzer copyright (c) 2018-2023 [Henrique Avila Vianna](https://henriquevianna.com)<br>
1541
+ audioMotion-analyzer copyright (c) 2018-2024 [Henrique Avila Vianna](https://henriquevianna.com)<br>
1479
1542
  Licensed under the [GNU Affero General Public License, version 3 or later](https://www.gnu.org/licenses/agpl.html).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "audiomotion-analyzer",
3
3
  "description": "High-resolution real-time graphic audio spectrum analyzer JavaScript module with no dependencies.",
4
- "version": "4.3.0",
4
+ "version": "4.4.0",
5
5
  "main": "./src/audioMotion-analyzer.js",
6
6
  "module": "./src/audioMotion-analyzer.js",
7
7
  "types": "./src/index.d.ts",
@@ -2,12 +2,12 @@
2
2
  * audioMotion-analyzer
3
3
  * High-resolution real-time graphic audio spectrum analyzer JS module
4
4
  *
5
- * @version 4.3.0
5
+ * @version 4.4.0
6
6
  * @author Henrique Avila Vianna <hvianna@gmail.com> <https://henriquevianna.com>
7
7
  * @license AGPL-3.0-or-later
8
8
  */
9
9
 
10
- const VERSION = '4.3.0';
10
+ const VERSION = '4.4.0';
11
11
 
12
12
  // internal constants
13
13
  const PI = Math.PI,
@@ -80,6 +80,60 @@ const PRISM = [ '#a35', '#c66', '#e94', '#ed0', '#9d5', '#4d8', '#2cb', '#0bc',
80
80
  }]
81
81
  ];
82
82
 
83
+ // settings defaults
84
+ const DEFAULT_SETTINGS = {
85
+ alphaBars : false,
86
+ ansiBands : false,
87
+ barSpace : 0.1,
88
+ bgAlpha : 0.7,
89
+ channelLayout : CHANNEL_SINGLE,
90
+ colorMode : COLOR_GRADIENT,
91
+ fftSize : 8192,
92
+ fillAlpha : 1,
93
+ frequencyScale : SCALE_LOG,
94
+ gradient : GRADIENTS[0][0],
95
+ height : undefined,
96
+ ledBars : false,
97
+ linearAmplitude: false,
98
+ linearBoost : 1,
99
+ lineWidth : 0,
100
+ loRes : false,
101
+ lumiBars : false,
102
+ maxDecibels : -25,
103
+ maxFPS : 0,
104
+ maxFreq : 22000,
105
+ minDecibels : -85,
106
+ minFreq : 20,
107
+ mirror : 0,
108
+ mode : 0,
109
+ noteLabels : false,
110
+ outlineBars : false,
111
+ overlay : false,
112
+ peakLine : false,
113
+ radial : false,
114
+ radialInvert : false,
115
+ radius : 0.3,
116
+ reflexAlpha : 0.15,
117
+ reflexBright : 1,
118
+ reflexFit : true,
119
+ reflexRatio : 0,
120
+ roundBars : false,
121
+ showBgColor : true,
122
+ showFPS : false,
123
+ showPeaks : true,
124
+ showScaleX : true,
125
+ showScaleY : false,
126
+ smoothing : 0.5,
127
+ spinSpeed : 0,
128
+ splitGradient : false,
129
+ start : true,
130
+ trueLeds : false,
131
+ useCanvas : true,
132
+ volume : 1,
133
+ weightingFilter: FILTER_NONE,
134
+ width : undefined
135
+ };
136
+
83
137
  // custom error messages
84
138
  const ERR_AUDIO_CONTEXT_FAIL = [ 'ERR_AUDIO_CONTEXT_FAIL', 'Could not create audio context. Web Audio API not supported?' ],
85
139
  ERR_INVALID_AUDIO_CONTEXT = [ 'ERR_INVALID_AUDIO_CONTEXT', 'Provided audio context is not valid' ],
@@ -104,6 +158,13 @@ class AudioMotionError extends Error {
104
158
  // helper function - output deprecation warning message on console
105
159
  const deprecate = ( name, alternative ) => console.warn( `${name} is deprecated. Use ${alternative} instead.` );
106
160
 
161
+ // helper function - check if a given object is empty (also returns `true` on null, undefined or any non-object value)
162
+ const isEmpty = obj => {
163
+ for ( const p in obj )
164
+ return false;
165
+ return true;
166
+ }
167
+
107
168
  // helper function - validate a given value with an array of strings (by default, all lowercase)
108
169
  // returns the validated value, or the first element of `list` if `value` is not found in the array
109
170
  const validateFromList = ( value, list, modifier = 'toLowerCase' ) => list[ Math.max( 0, list.indexOf( ( '' + value )[ modifier ]() ) ) ];
@@ -152,12 +213,26 @@ export default class AudioMotionAnalyzer {
152
213
  this._selectedGrads = []; // names of the currently selected gradients for channels 0 and 1
153
214
  this._sources = []; // input nodes
154
215
 
216
+ // Check if options object passed as first argument
217
+ if ( ! ( container instanceof Element ) ) {
218
+ if ( isEmpty( options ) && ! isEmpty( container ) )
219
+ options = container;
220
+ container = null;
221
+ }
222
+
223
+ this._ownCanvas = ! ( options.canvas instanceof HTMLCanvasElement );
224
+
225
+ // Create a new canvas or use the one provided by the user
226
+ const canvas = this._ownCanvas ? document.createElement('canvas') : options.canvas;
227
+ canvas.style = 'max-width: 100%;';
228
+ this._ctx = canvas.getContext('2d');
229
+
155
230
  // Register built-in gradients
156
231
  for ( const [ name, options ] of GRADIENTS )
157
232
  this.registerGradient( name, options );
158
233
 
159
234
  // Set container
160
- this._container = container || document.body;
235
+ this._container = container || ( ! this._ownCanvas && canvas.parentElement ) || document.body;
161
236
 
162
237
  // Make sure we have minimal width and height dimensions in case of an inline container
163
238
  this._defaultWidth = this._container.clientWidth || 640;
@@ -225,11 +300,6 @@ export default class AudioMotionAnalyzer {
225
300
  if ( options.connectSpeakers !== false )
226
301
  this.connectOutput();
227
302
 
228
- // create analyzer canvas
229
- const canvas = document.createElement('canvas');
230
- canvas.style = 'max-width: 100%;';
231
- this._ctx = canvas.getContext('2d');
232
-
233
303
  // create auxiliary canvases for the X-axis and radial scale labels
234
304
  for ( const ctx of [ '_scaleX', '_scaleR' ] )
235
305
  this[ ctx ] = document.createElement('canvas').getContext('2d');
@@ -310,8 +380,8 @@ export default class AudioMotionAnalyzer {
310
380
  // Set configuration options and use defaults for any missing properties
311
381
  this._setProps( options, true );
312
382
 
313
- // add canvas to the container
314
- if ( this.useCanvas )
383
+ // Add canvas to the container (only when canvas not provided by user)
384
+ if ( this.useCanvas && this._ownCanvas )
315
385
  this._container.appendChild( canvas );
316
386
 
317
387
  // Finish canvas setup
@@ -571,6 +641,24 @@ export default class AudioMotionAnalyzer {
571
641
  this._makeGrad();
572
642
  }
573
643
 
644
+ get radialInvert() {
645
+ return this._radialInvert;
646
+ }
647
+ set radialInvert( value ) {
648
+ this._radialInvert = !! value;
649
+ this._calcBars();
650
+ this._makeGrad();
651
+ }
652
+
653
+ get radius() {
654
+ return this._radius;
655
+ }
656
+ set radius( value ) {
657
+ this._radius = +value || 0;
658
+ this._calcBars();
659
+ this._makeGrad();
660
+ }
661
+
574
662
  get reflexRatio() {
575
663
  return this._reflexRatio;
576
664
  }
@@ -777,7 +865,7 @@ export default class AudioMotionAnalyzer {
777
865
  if ( ! this._ready )
778
866
  return;
779
867
 
780
- const { audioCtx, canvas, _controller, _input, _merger, _observer, _ownContext, _splitter } = this;
868
+ const { audioCtx, canvas, _controller, _input, _merger, _observer, _ownCanvas, _ownContext, _splitter } = this;
781
869
 
782
870
  this._destroyed = true;
783
871
  this._ready = false;
@@ -804,8 +892,9 @@ export default class AudioMotionAnalyzer {
804
892
  if ( _ownContext )
805
893
  audioCtx.close();
806
894
 
807
- // remove canvas from the DOM
808
- canvas.remove();
895
+ // remove canvas from the DOM (if not provided by the user)
896
+ if ( _ownCanvas )
897
+ canvas.remove();
809
898
 
810
899
  // reset flags
811
900
  this._calcBars();
@@ -909,6 +998,29 @@ export default class AudioMotionAnalyzer {
909
998
  return energy / ( endBin - startBin + 1 ) / chnCount;
910
999
  }
911
1000
 
1001
+ /**
1002
+ * Returns current analyzer settings in object format
1003
+ *
1004
+ * @param [{string|array}] a property name or an array of property names to not include in the returned object
1005
+ * @returns {object} Options object
1006
+ */
1007
+ getOptions( ignore ) {
1008
+ if ( ! Array.isArray( ignore ) )
1009
+ ignore = [ ignore ];
1010
+ let options = {};
1011
+ for ( const prop of Object.keys( DEFAULT_SETTINGS ) ) {
1012
+ if ( ! ignore.includes( prop ) ) {
1013
+ if ( prop == 'gradient' && this.gradientLeft != this.gradientRight ) {
1014
+ options.gradientLeft = this.gradientLeft;
1015
+ options.gradientRight = this.gradientRight;
1016
+ }
1017
+ else if ( prop != 'start' )
1018
+ options[ prop ] = this[ prop ];
1019
+ }
1020
+ }
1021
+ return options;
1022
+ }
1023
+
912
1024
  /**
913
1025
  * Registers a custom gradient
914
1026
  *
@@ -1115,7 +1227,7 @@ export default class AudioMotionAnalyzer {
1115
1227
  return;
1116
1228
  }
1117
1229
 
1118
- const { _ansiBands, _barSpace, canvas, _chLayout, _maxFreq, _minFreq, _mirror, _mode, _radial, _reflexRatio } = this,
1230
+ const { _ansiBands, _barSpace, canvas, _chLayout, _maxFreq, _minFreq, _mirror, _mode, _radial, _radialInvert, _reflexRatio } = this,
1119
1231
  centerX = canvas.width >> 1,
1120
1232
  centerY = canvas.height >> 1,
1121
1233
  isDualVertical = _chLayout == CHANNEL_VERTICAL && ! _radial,
@@ -1145,9 +1257,13 @@ export default class AudioMotionAnalyzer {
1145
1257
  // TODO: improve this, make it configurable?
1146
1258
  channelGap = isDualVertical ? canvas.height - channelHeight * 2 : 0,
1147
1259
 
1148
- initialX = centerX * ( _mirror == -1 && ! isDualHorizontal && ! _radial ),
1149
- innerRadius = Math.min( canvas.width, canvas.height ) * ( _chLayout == CHANNEL_VERTICAL ? .375 : .125 ) | 0,
1150
- outerRadius = Math.min( centerX, centerY );
1260
+ initialX = centerX * ( _mirror == -1 && ! isDualHorizontal && ! _radial );
1261
+
1262
+ let innerRadius = Math.min( canvas.width, canvas.height ) * .375 * ( _chLayout == CHANNEL_VERTICAL ? 1 : this._radius ) | 0,
1263
+ outerRadius = Math.min( centerX, centerY );
1264
+
1265
+ if ( _radialInvert && _chLayout != CHANNEL_VERTICAL )
1266
+ [ innerRadius, outerRadius ] = [ outerRadius, innerRadius ];
1151
1267
 
1152
1268
  /**
1153
1269
  * CREATE ANALYZER BANDS
@@ -1479,8 +1595,9 @@ export default class AudioMotionAnalyzer {
1479
1595
  freqLabels = [],
1480
1596
  isDualHorizontal = this._chLayout == CHANNEL_HORIZONTAL,
1481
1597
  isDualVertical = this._chLayout == CHANNEL_VERTICAL,
1598
+ minDimension = Math.min( canvas.width, canvas.height ),
1482
1599
  scale = [ 'C',, 'D',, 'E', 'F',, 'G',, 'A',, 'B' ], // for note labels (no sharp notes)
1483
- scaleHeight = Math.min( canvas.width, canvas.height ) / 34 | 0, // circular scale height (radial mode)
1600
+ scaleHeight = minDimension / 34 | 0, // circular scale height (radial mode)
1484
1601
  fontSizeX = canvasX.height >> 1,
1485
1602
  fontSizeR = scaleHeight >> 1,
1486
1603
  labelWidthX = fontSizeX * ( _noteLabels ? .7 : 1.5 ),
@@ -1510,7 +1627,7 @@ export default class AudioMotionAnalyzer {
1510
1627
  }
1511
1628
 
1512
1629
  // in radial dual-vertical layout, the scale is positioned exactly between both channels, by making the canvas a bit larger than the inner diameter
1513
- canvasR.width = canvasR.height = ( innerRadius << 1 ) + ( isDualVertical * scaleHeight );
1630
+ canvasR.width = canvasR.height = Math.max( minDimension * .15, ( innerRadius << 1 ) + ( isDualVertical * scaleHeight ) );
1514
1631
 
1515
1632
  const centerR = canvasR.width >> 1,
1516
1633
  radialY = centerR - scaleHeight * .7; // vertical position of text labels in the circular scale
@@ -1678,8 +1795,8 @@ export default class AudioMotionAnalyzer {
1678
1795
  dbRange = maxDecibels - minDecibels,
1679
1796
  [ ledCount, ledSpaceH, ledSpaceV, ledHeight ] = this._leds || [];
1680
1797
 
1681
- if ( _energy.val > 0 )
1682
- this._spinAngle += this._spinSpeed * TAU / ( 60 * _fps ); // spinSpeed * angle increment per frame for 1 RPM
1798
+ if ( _energy.val > 0 && _fps > 0 )
1799
+ this._spinAngle += this._spinSpeed * TAU / 60 / _fps; // spinSpeed * angle increment per frame for 1 RPM
1683
1800
 
1684
1801
  /* HELPER FUNCTIONS */
1685
1802
 
@@ -2157,8 +2274,10 @@ export default class AudioMotionAnalyzer {
2157
2274
  }
2158
2275
  else if ( ! _radial )
2159
2276
  _ctx.fillRect( posX, analyzerBottom - peak * maxBarHeight, width, 2 );
2160
- else if ( _mode != MODE_GRAPH ) // radial - peaks for graph mode are done by the peak line code
2161
- radialPoly( posX, peak * maxBarHeight, width, -2 );
2277
+ else if ( _mode != MODE_GRAPH ) { // radial (peaks for graph mode are done by the peakLine code)
2278
+ const y = peak * maxBarHeight;
2279
+ radialPoly( posX, y, width, ! this._radialInvert || isDualVertical || y + innerRadius >= 2 ? -2 : 2 );
2280
+ }
2162
2281
  }
2163
2282
 
2164
2283
  } // for ( let barIndex = 0; barIndex < nBars; barIndex++ )
@@ -2278,7 +2397,7 @@ export default class AudioMotionAnalyzer {
2278
2397
  if ( this.onCanvasDraw ) {
2279
2398
  _ctx.save();
2280
2399
  _ctx.fillStyle = _ctx.strokeStyle = _canvasGradients[0];
2281
- this.onCanvasDraw( this, { timestamp, _canvasGradients } );
2400
+ this.onCanvasDraw( this, { timestamp, canvasGradients: _canvasGradients } );
2282
2401
  _ctx.restore();
2283
2402
  }
2284
2403
  }
@@ -2437,8 +2556,8 @@ export default class AudioMotionAnalyzer {
2437
2556
  this._fsWidth = screenWidth;
2438
2557
  this._fsHeight = screenHeight;
2439
2558
 
2440
- // if canvas dimensions haven't changed, quit
2441
- if ( canvas.width == newWidth && canvas.height == newHeight )
2559
+ // if this is not the constructor call and canvas dimensions haven't changed, quit
2560
+ if ( reason != REASON_CREATE && canvas.width == newWidth && canvas.height == newHeight )
2442
2561
  return;
2443
2562
 
2444
2563
  // apply new dimensions
@@ -2497,68 +2616,17 @@ export default class AudioMotionAnalyzer {
2497
2616
  * Set object properties
2498
2617
  */
2499
2618
  _setProps( options, useDefaults ) {
2500
-
2501
- // settings defaults
2502
- const defaults = {
2503
- alphaBars : false,
2504
- ansiBands : false,
2505
- barSpace : 0.1,
2506
- bgAlpha : 0.7,
2507
- channelLayout : CHANNEL_SINGLE,
2508
- colorMode : COLOR_GRADIENT,
2509
- fftSize : 8192,
2510
- fillAlpha : 1,
2511
- frequencyScale : SCALE_LOG,
2512
- gradient : GRADIENTS[0][0],
2513
- ledBars : false,
2514
- linearAmplitude: false,
2515
- linearBoost : 1,
2516
- lineWidth : 0,
2517
- loRes : false,
2518
- lumiBars : false,
2519
- maxDecibels : -25,
2520
- maxFPS : 0,
2521
- maxFreq : 22000,
2522
- minDecibels : -85,
2523
- minFreq : 20,
2524
- mirror : 0,
2525
- mode : 0,
2526
- noteLabels : false,
2527
- outlineBars : false,
2528
- overlay : false,
2529
- peakLine : false,
2530
- radial : false,
2531
- reflexAlpha : 0.15,
2532
- reflexBright : 1,
2533
- reflexFit : true,
2534
- reflexRatio : 0,
2535
- roundBars : false,
2536
- showBgColor : true,
2537
- showFPS : false,
2538
- showPeaks : true,
2539
- showScaleX : true,
2540
- showScaleY : false,
2541
- smoothing : 0.5,
2542
- spinSpeed : 0,
2543
- splitGradient : false,
2544
- start : true,
2545
- trueLeds : false,
2546
- useCanvas : true,
2547
- volume : 1,
2548
- weightingFilter: FILTER_NONE
2549
- };
2550
-
2551
2619
  // callback functions properties
2552
2620
  const callbacks = [ 'onCanvasDraw', 'onCanvasResize' ];
2553
2621
 
2554
- // properties undefined by default
2555
- const defaultUndefined = [ 'gradientLeft', 'gradientRight', 'height', 'width', 'stereo' ];
2622
+ // properties not in the defaults (`stereo` is deprecated)
2623
+ const extraProps = [ 'gradientLeft', 'gradientRight', 'stereo' ];
2556
2624
 
2557
2625
  // build an array of valid properties; `start` is not an actual property and is handled after setting everything else
2558
- const validProps = Object.keys( defaults ).filter( e => e != 'start' ).concat( callbacks, defaultUndefined );
2626
+ const validProps = Object.keys( DEFAULT_SETTINGS ).filter( e => e != 'start' ).concat( callbacks, extraProps );
2559
2627
 
2560
2628
  if ( useDefaults || options === undefined )
2561
- options = { ...defaults, ...options }; // merge options with defaults
2629
+ options = { ...DEFAULT_SETTINGS, ...options }; // merge options with defaults
2562
2630
 
2563
2631
  for ( const prop of Object.keys( options ) ) {
2564
2632
  if ( callbacks.includes( prop ) && typeof options[ prop ] !== 'function' ) // check invalid callback
@@ -2567,6 +2635,7 @@ export default class AudioMotionAnalyzer {
2567
2635
  this[ prop ] = options[ prop ];
2568
2636
  }
2569
2637
 
2638
+ // deprecated - move this to the constructor in the next major release (`start` should be constructor-specific)
2570
2639
  if ( options.start !== undefined )
2571
2640
  this.toggleAnalyzer( options.start );
2572
2641
  }
package/src/index.d.ts CHANGED
@@ -49,6 +49,8 @@ export interface Options {
49
49
  overlay?: boolean;
50
50
  peakLine?: boolean;
51
51
  radial?: boolean;
52
+ radialInvert?: boolean;
53
+ radius?: number;
52
54
  reflexAlpha?: number;
53
55
  reflexBright?: number;
54
56
  reflexFit?: boolean;
@@ -83,6 +85,7 @@ export interface AnalyzerBarData {
83
85
 
84
86
  export interface ConstructorOptions extends Options {
85
87
  audioCtx?: AudioContext;
88
+ canvas?: HTMLCanvasElement;
86
89
  connectSpeakers?: boolean;
87
90
  fsElement?: HTMLElement;
88
91
  source?: HTMLMediaElement | AudioNode;
@@ -114,6 +117,7 @@ export interface LedParameters {
114
117
 
115
118
  declare class AudioMotionAnalyzer {
116
119
  constructor(container?: HTMLElement, options?: ConstructorOptions);
120
+ constructor(options?: ConstructorOptions);
117
121
 
118
122
  get alphaBars(): boolean;
119
123
  set alphaBars(value: boolean);
@@ -234,6 +238,12 @@ declare class AudioMotionAnalyzer {
234
238
  get radial(): boolean;
235
239
  set radial(value: boolean);
236
240
 
241
+ get radialInvert(): boolean;
242
+ set radialInvert(value: boolean);
243
+
244
+ get radius(): number;
245
+ set radius(value: number);
246
+
237
247
  public reflexAlpha: number;
238
248
  public reflexBright: number;
239
249
  public reflexFit: boolean;
@@ -292,6 +302,8 @@ declare class AudioMotionAnalyzer {
292
302
  public getEnergy(preset?: EnergyPreset): number;
293
303
  public getEnergy(startFreq: number, endFreq?: number): number;
294
304
 
305
+ public getOptions(ignore?: string | string[]): Options;
306
+
295
307
  public registerGradient(name: string, options: GradientOptions): void;
296
308
 
297
309
  public setCanvasSize(width: number, height: number): void;