audiomotion-analyzer 4.0.0-beta.4 → 4.0.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
@@ -27,19 +27,19 @@ What users are saying:
27
27
 
28
28
  ## Features
29
29
 
30
- + High-resolution real-time dual channel audio spectrum analyzer
31
- + Logarithmic, linear and perceptual (Bark/Mel) frequency scales, with customizable range
30
+ + Dual-channel high-resolution real-time audio spectrum analyzer
31
+ + Logarithmic, linear and perceptual (Bark and Mel) frequency scales, with customizable range
32
32
  + Visualization of discrete FFT frequencies or up to 240 frequency bands (supports ANSI and equal-tempered octave bands)
33
33
  + Decibel and linear amplitude scales, with customizable sensitivity
34
- + A, B, C, D and ITU-R 468 weighting filters
35
- + Optional effects: LED bars, luminance bars, mirroring and reflection, radial spectrum
36
- + Comes with 3 predefined color gradients - easily add your own!
34
+ + Optional A, B, C, D and ITU-R 468 weighting filters
35
+ + Additional effects: LED bars, luminance bars, mirroring and reflection, radial spectrum
36
+ + Choose from 5 built-in color gradients or easily add your own!
37
37
  + Fullscreen support, ready for retina / HiDPI displays
38
38
  + Zero-dependency native ES6+ module (ESM), \~25kB minified
39
39
 
40
40
  ## Online demos
41
41
 
42
- [![demo-animation](img/demo.gif)](https://audiomotion.dev/demo/)
42
+ [![demo-animation](img/demo.webp)](https://audiomotion.dev/demo/)
43
43
 
44
44
  ?> https://audiomotion.dev/demo/
45
45
 
@@ -161,7 +161,6 @@ options = {<br>
161
161
  &emsp;&emsp;[spinSpeed](#spinspeed-number): **0**,<br>
162
162
  &emsp;&emsp;[splitGradient](#splitgradient-boolean): **false**,<br>
163
163
  &emsp;&emsp;[start](#start-boolean): **true**,<br>
164
- &emsp;&emsp;[stereo](#stereo-deprecated-boolean): **false**, // DEPRECATED - use channelLayout instead<br>
165
164
  &emsp;&emsp;[useCanvas](#usecanvas-boolean): **true**,<br>
166
165
  &emsp;&emsp;[volume](#volume-number): **1**,<br>
167
166
  &emsp;&emsp;[weightingFilter](#weightingFilter-string): **''**<br>
@@ -236,7 +235,7 @@ Defaults to **true**, so the analyzer will start running right after initializat
236
235
 
237
236
  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.
238
237
 
239
- 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.
238
+ 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) spectrum.
240
239
 
241
240
  For effect priority when combined with other settings, see [`isAlphaBars`](#isalphabars-boolean-read-only).
242
241
 
@@ -248,12 +247,17 @@ Defaults to **false**.
248
247
 
249
248
  *Available since v4.0.0*
250
249
 
251
- When set to *true* uses ANSI/IEC preferred frequencies to generate the bands for [octave bands modes](#mode-number).
250
+ When set to *true*, ANSI/IEC preferred frequencies are used to generate the bands for **octave bands** modes (see [`mode`](#mode-number)).
252
251
  The preferred base-10 scale is used to compute the center and bandedge frequencies, as specified in the [ANSI S1.11-2004 standard](https://archive.org/details/gov.law.ansi.s1.11.2004).
253
252
 
254
- The default is to use the [equal temperament scale](http://hyperphysics.phy-astr.gsu.edu/hbase/Music/et.html), so that in 1/12 octave bands
253
+ When *false*, bands are based on the [equal-tempered scale](http://hyperphysics.phy-astr.gsu.edu/hbase/Music/et.html), so that in 1/12 octave bands
255
254
  the center of each band is perfectly tuned to a musical note.
256
255
 
256
+ ansiBands | bands standard | octaves' center frequencies
257
+ ----------|----------------|----------------------------
258
+ false | Equal temperament (A-440 Hz) | ![scale-log-equal-temperament](img/scale-log-equal-temperament.png)
259
+ true | ANSI S1.11-2004 | ![scale-log-ansi](img/scale-log-ansi.png)
260
+
257
261
  Defaults to **false**.
258
262
 
259
263
  ### `audioCtx` *AudioContext object* *(Read only)*
@@ -324,11 +328,11 @@ Defaults to **0.7**.
324
328
 
325
329
  Defines the number and layout of analyzer channels.
326
330
 
327
- channelLayout | description
328
- -------------- |------------
329
- 'single' | Single channel analyzer, representing the combined output of both left and right channels.
330
- 'dualVertical' | Dual channel analyzer, with left channel shown at the top and right channel at the bottom.
331
- 'dualCombined' | Left and right channel graphs are shown overlaid. Works best with semi-transparent **Graph** [`mode`](#mode-number) or [`outlineBars`](#outlinebars-boolean).
331
+ channelLayout | description
332
+ ----------------|------------
333
+ 'single' | Single channel analyzer, representing the combined output of both left and right channels.
334
+ 'dual-combined' | Dual channel analyzer, with both channel graphs overlaid. Works best with semi-transparent **Graph** [`mode`](#mode-number) or [`outlineBars`](#outlinebars-boolean).
335
+ 'dual-vertical' | Left channel shown at the top half of the canvas and right channel at the bottom.
332
336
 
333
337
  !> When a *dual* layout is selected, any mono (single channel) audio source connected to the analyzer will output sound only from the left speaker,
334
338
  unless a stereo source is simultaneously connected to the analyzer, which will force the mono input to be upmixed to stereo.
@@ -386,16 +390,16 @@ Current frame rate.
386
390
 
387
391
  Scale used to represent frequencies in the horizontal axis.
388
392
 
389
- frequencyScale | description
390
- ---------------|------------
391
- 'bark' | [Bark scale](https://en.wikipedia.org/wiki/Bark_scale)
392
- 'linear' | Linear scale
393
- 'log' | Logarithmic scale
394
- 'mel' | [Mel scale](https://en.wikipedia.org/wiki/Mel_scale)
393
+ frequencyScale | description | scale preview (20Hz - 22kHz range)
394
+ ---------------|-------------|-----------------------------------
395
+ 'bark' | [Bark scale](https://en.wikipedia.org/wiki/Bark_scale) | ![scale-bark](img/scale-bark.png)
396
+ 'linear' | Linear scale | ![scale-linear](img/scale-linear.png)
397
+ 'log' | Logarithmic scale | ![scale-log-ansi](img/scale-log-ansi.png)
398
+ 'mel' | [Mel scale](https://en.wikipedia.org/wiki/Mel_scale) | ![scale-mel](img/scale-mel.png)
395
399
 
396
- Logarithmic scale is required to visualize proper [octave bands](#mode-number) and it's also recommended when using [`noteLabels`](#notelabels-boolean).
400
+ Logarithmic scale allows visualization of proper **octave bands** (see [`mode`](#mode-number)) and it's also recommended when using [`noteLabels`](#notelabels-boolean).
397
401
 
398
- *Bark* and *Mel* are perceptual pitch scales which provide better visualization of midrange and high frequencies, especially in the [discrete frequencies mode](#mode-number).
402
+ *Bark* and *Mel* are perceptual pitch scales, which provide better visualization of mid-range to high frequencies.
399
403
 
400
404
  Defaults to **'log'**.
401
405
 
@@ -416,9 +420,23 @@ Canvas dimensions used during fullscreen mode. These take the current pixel rati
416
420
 
417
421
  Name of the color gradient used for analyzer graphs.
418
422
 
419
- It must be a built-in or [registered](#registergradient-name-options-) gradient name. Built-in gradients are *'classic'*, *'prism'* and *'rainbow'*.
423
+ It must be a built-in or registered gradient name (see [`registerGradient()`](#registergradient-name-options-)).
424
+
425
+ `gradient` sets the gradient for both analyzer channels, but its read value represents only the gradient on the left (or single) channel.
426
+
427
+ When using a dual [`channelLayout`](#channellayout-string), use [`gradientLeft`](#gradientleft-string) and [`gradientRight`](#gradientright-string) to set/read the gradient on each channel individually.
428
+
429
+ Built-in gradients are shown below:
420
430
 
421
- See also [`gradientLeft`](#gradientleft-string) and [`gradientRight`](#gradientright-string).
431
+ gradient | preview
432
+ ------------|---------
433
+ 'classic' | ![classic](img/gradient-classic.png)
434
+ 'orangered' | ![orangered](img/gradient-orangered.png)
435
+ 'prism' | ![prism](img/gradient-prism.png)
436
+ 'rainbow' | ![rainbow](img/gradient-rainbow.png)
437
+ 'steelblue' | ![steelblue](img/gradient-steelblue.png)
438
+
439
+ See also [`splitGradient`](#splitgradient-boolean).
422
440
 
423
441
  Defaults to **'classic'**.
424
442
 
@@ -427,11 +445,13 @@ Defaults to **'classic'**.
427
445
 
428
446
  *Available since v4.0.0*
429
447
 
430
- When using a dual [`channelLayout`](#channellayout-string), different gradients can be selected for the left and right channels.
448
+ Select gradients for the left and right analyzer channels independently, for use with a dual [`channelLayout`](#channellayout-string).
449
+
450
+ **_Single_** channel layout will use the gradient selected by `gradientLeft`.
431
451
 
432
- When [`channelLayout`](#channellayout-string) is set to *'single'*, the gradient selected by `gradientLeft` is used.
452
+ For **_dual-combined_** channel layout or [`radial`](#radial-boolean) spectrum, only the background color defined by `gradientLeft` will be applied when [`showBgColor`](#showbgcolor-boolean) is *true*.
433
453
 
434
- The [`gradient`](#gradient-string) property can be used as a shorthand to set the same gradient for both channels. Its read value returns only the left (or single) channel gradient though.
454
+ See also [`gradient`](#gradient-string) and [`splitGradient`](#splitgradient-boolean).
435
455
 
436
456
  ### `height` *number*
437
457
  ### `width` *number*
@@ -567,7 +587,7 @@ This is only effective for [bands modes](#mode-number).
567
587
 
568
588
  When set to *true* all analyzer bars will be displayed at full height with varying luminance (opacity, actually) instead.
569
589
 
570
- `lumiBars` takes precedence over [`alphaBars`](#alphabars-boolean) and [`outlineBars`](#outlinebars-boolean), except in [`radial`](#radial-boolean) visualization.
590
+ `lumiBars` takes precedence over [`alphaBars`](#alphabars-boolean) and [`outlineBars`](#outlinebars-boolean), except on [`radial`](#radial-boolean) spectrum.
571
591
 
572
592
  For effect priority when combined with other settings, see [`isLumiBars`](#islumibars-boolean-read-only).
573
593
 
@@ -620,22 +640,24 @@ Visualization mode.
620
640
 
621
641
  mode | description | notes
622
642
  ----:|:-----------:|------
623
- 0 | Discrete frequencies |
624
- 1 | 1/24th octave bands or 240 bands |
625
- 2 | 1/12th octave bands or 120 bands |
626
- 3 | 1/8th octave bands or 80 bands |
627
- 4 | 1/6th octave bands or 60 bands |
628
- 5 | 1/4th octave bands or 40 bands |
629
- 6 | 1/3rd octave bands or 30 bands |
630
- 7 | Half octave bands or 20 bands |
631
- 8 | Full octave bands or 10 bands |
643
+ 0 | Discrete frequencies | *default*
644
+ 1 | 1/24th octave bands or 240 bands | *use 'log' `frequencyScale` for octave bands*
645
+ 2 | 1/12th octave bands or 120 bands | *use 'log' `frequencyScale` for octave bands*
646
+ 3 | 1/8th octave bands or 80 bands | *use 'log' `frequencyScale` for octave bands*
647
+ 4 | 1/6th octave bands or 60 bands | *use 'log' `frequencyScale` for octave bands*
648
+ 5 | 1/4th octave bands or 40 bands | *use 'log' `frequencyScale` for octave bands*
649
+ 6 | 1/3rd octave bands or 30 bands | *use 'log' `frequencyScale` for octave bands*
650
+ 7 | Half octave bands or 20 bands | *use 'log' `frequencyScale` for octave bands*
651
+ 8 | Full octave bands or 10 bands | *use 'log' `frequencyScale` for octave bands*
632
652
  9 | *(not valid)* | *reserved*
633
653
  10 | Graph | *added in v1.1.0*
634
654
 
635
655
  + **Mode 0** provides the highest resolution, allowing you to visualize individual frequencies as provided by the [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) computation;
636
- + **Modes 1 - 8** divide the frequency spectrum in bands; when using the default **logarithmic** [frequency scale](#frequencyscale-string), each band represents the *n*th part of an octave (see also [`ansiBands`](#ansibands-boolean)); otherwise, a fixed number of bands is used for each mode;
656
+ + **Modes 1 - 8** divide the frequency spectrum in bands; when using the default **logarithmic** [`frequencyScale`](#frequencyscale-string), each band represents the *n*th part of an octave; otherwise, a fixed number of bands is used for each mode;
637
657
  + **Mode 10** uses the discrete FFT data points to draw a continuous line and/or a filled area graph (see [`fillAlpha`](#fillalpha-number) and [`lineWidth`](#linewidth-number) properties).
638
658
 
659
+ See also [`ansiBands`](#ansibands-boolean).
660
+
639
661
  Defaults to **0**.
640
662
 
641
663
  ### `noteLabels` *boolean*
@@ -685,10 +707,10 @@ You can refer to this value to adjust any additional drawings done in the canvas
685
707
 
686
708
  When *true*, the spectrum analyzer is rendered in a circular shape, with radial frequency bars spreading from its center.
687
709
 
688
- In radial visualization, [`ledBars`](#ledbars-boolean) and [`lumiBars`](#lumibars-boolean) effects are disabled, and
710
+ On radial spectrum, [`ledBars`](#ledbars-boolean) and [`lumiBars`](#lumibars-boolean) effects are disabled, and
689
711
  [`showPeaks`](#showpeaks-boolean) has no effect for [**Graph** mode](#mode-number).
690
712
 
691
- When [`channelLayout`](#channellayout-string) is set to *'dualVertical'*, a larger diameter is used and the right channel bars are rendered towards the center of the analyzer.
713
+ When [`channelLayout`](#channellayout-string) is set to *'dual-vertical'*, a larger diameter is used and the right channel bars are rendered towards the center of the analyzer.
692
714
 
693
715
  See also [`spinSpeed`](#spinspeed-number).
694
716
 
@@ -749,6 +771,8 @@ Opacity can be adjusted via [`bgAlpha`](#bgalpha-number) property, when [`overla
749
771
  If ***false***, the canvas background will be painted black when [`overlay`](#overlay-boolean) is ***false***,
750
772
  or transparent when [`overlay`](#overlay-boolean) is ***true***.
751
773
 
774
+ See also [`registerGradient()`](#registergradient-name-options-).
775
+
752
776
  Defaults to **true**.
753
777
 
754
778
  ?> Please note that when [`overlay`](#overlay-boolean) is ***false*** and [`ledBars`](#ledbars-boolean) is ***true***, the background color will always be black,
@@ -809,13 +833,15 @@ Defaults to **0**.
809
833
 
810
834
  *Available since v3.0.0*
811
835
 
812
- When *true*, the gradient will be split between both channels, so each channel will have different colors. If *false*, both channels will use the full gradient.
836
+ When set to *true* and [`channelLayout`](#channellayout-string) is **_dual-vertical_**, the gradient will be split between channels.
837
+
838
+ When *false*, both channels will use the full gradient.
813
839
 
814
- | splitGradient: *true* | splitGradient: *false* |
840
+ | gradient: *'classic'* - splitGradient: *false* | gradient: *'classic'* - splitGradient: *true* |
815
841
  |:--:|:--:|
816
- | ![split-on](img/splitGradient_on.png) | ![split-off](img/splitGradient_off.png) |
842
+ | ![split-off](img/splitGradient_off.png) | ![split-on](img/splitGradient_on.png) |
817
843
 
818
- This option has no effect on horizontal gradients (except in [`radial`](#radial-boolean) visualization - see note in [`registerGradient()`](#registergradient-name-options-)), or when [`channelLayout`](#channellayout-string) is set to *'single'* or *'dualCombined'*.
844
+ This option has no effect on horizontal gradients, except on [`radial`](#radial-boolean) spectrum - see note in [`registerGradient()`](#registergradient-name-options-).
819
845
 
820
846
  Defaults to **false**.
821
847
 
@@ -892,11 +918,15 @@ Since this is a static property, you should always access it as `AudioMotionAnal
892
918
 
893
919
  ### `onCanvasDraw` *function*
894
920
 
895
- If defined, this function will be called after rendering each frame.
921
+ If defined, this function will be called after **audioMotion-analyzer** finishes rendering each animation frame.
896
922
 
897
- The audioMotion object will be passed as an argument to the callback function.
923
+ The callback function is passed two arguments: an *AudioMotionAnalyzer* object, and an object with the following properties:
924
+ - `timestamp`, a [*DOMHighResTimeStamp*](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp)
925
+ which indicates the elapsed time in milliseconds since the analyzer started running;
926
+ - `canvasGradients`, an array of [*CanvasGradient*](https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient])
927
+ objects currently in use on the left (or single) and right analyzer channels.
898
928
 
899
- Canvas properties `fillStyle` and `strokeStyle` will be set to the current gradient when the function is called.
929
+ The canvas properties `fillStyle` and `strokeStyle` will be set to the left/single channel gradient before the function is called.
900
930
 
901
931
  Usage example:
902
932
 
@@ -909,16 +939,26 @@ const audioMotion = new AudioMotionAnalyzer(
909
939
  }
910
940
  );
911
941
 
912
- function drawCallback( instance ) {
913
- const ctx = instance.canvasCtx,
914
- baseSize = ( instance.isFullscreen ? 40 : 20 ) * instance.pixelRatio;
915
-
916
- // use the 'energy' value to increase the font size and make the logo pulse to the beat
942
+ function drawCallback( instance, info ) {
943
+ const baseSize = ( instance.isFullscreen ? 40 : 20 ) * instance.pixelRatio,
944
+ canvas = instance.canvas,
945
+ centerX = canvas.width / 2,
946
+ centerY = canvas.height / 2,
947
+ ctx = instance.canvasCtx,
948
+ maxHeight = centerY / 2,
949
+ maxWidth = centerX - baseSize * 5,
950
+ time = info.timestamp / 1e4;
951
+
952
+ // the energy value is used here to increase the font size and make the logo pulsate to the beat
917
953
  ctx.font = `${ baseSize + instance.getEnergy() * 25 * instance.pixelRatio }px Orbitron, sans-serif`;
918
954
 
919
- ctx.fillStyle = '#fff8';
955
+ // use the right-channel gradient to fill text
956
+ ctx.fillStyle = info.canvasGradients[1];
920
957
  ctx.textAlign = 'center';
921
- ctx.fillText( 'audioMotion', instance.canvas.width - baseSize * 8, baseSize * 2 );
958
+ ctx.globalCompositeOperation = 'lighter';
959
+
960
+ // the timestamp can be used to create effects and animations based on the elapsed time
961
+ ctx.fillText( 'audioMotion', centerX + maxWidth * Math.cos( time % Math.PI * 2 ), centerY + maxHeight * Math.sin( time % Math.PI * 16 ) );
922
962
  }
923
963
  ```
924
964
 
@@ -928,7 +968,7 @@ For more examples, see the fluid demo [source code](https://github.com/hvianna/a
928
968
 
929
969
  If defined, this function will be called whenever the canvas is resized.
930
970
 
931
- Two arguments are passed: a string with the reason why the function was called (see below) and the audioMotion object.
971
+ The callback function is passed two arguments: a string which indicates the reason that triggered the call (see below) and the *AudioMotionAnalyzer* object.
932
972
 
933
973
  Reason | Description
934
974
  -------|------------
@@ -1083,9 +1123,11 @@ const options = {
1083
1123
  audioMotion.registerGradient( 'myGradient', options );
1084
1124
  ```
1085
1125
 
1086
- ?> During [`radial`](#radial-boolean) visualization all gradients are rendered in radial direction, so the horizontal flag (`dir: 'h'`) has no effect.
1126
+ Check the [built-in **_'rainbow'_** gradient](#gradient-string) for an example of horizontal gradient.
1127
+
1128
+ **Note:** the horizontal flag (`dir: 'h'`) has no effect on [`radial`](#radial-boolean) spectrum, because in that mode all gradients are rendered in radial direction.
1087
1129
 
1088
- Additional information about [gradient color-stops](https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient/addColorStop).
1130
+ ?> Any gradient, including the built-in ones, may be modified by (re-)registering the same gradient name (names are case sensitive).
1089
1131
 
1090
1132
  ### `setCanvasSize( width, height )`
1091
1133
 
@@ -1190,7 +1232,7 @@ which is [currently not supported in some browsers](https://caniuse.com/#feat=md
1190
1232
 
1191
1233
  ### alphaBars and fillAlpha won't work with Radial on Firefox <!-- {docsify-ignore} -->
1192
1234
 
1193
- 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).
1235
+ On Firefox, [`alphaBars`](#alphaBars-boolean) and [`fillAlpha`](#fillalpha-number) won't work with [`radial`](#radial-boolean) spectrum when using hardware acceleration, due to [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1164912).
1194
1236
 
1195
1237
  ### Visualization of live streams won't work on Safari {docsify-ignore}
1196
1238
 
@@ -1238,11 +1280,11 @@ myAudio.crossOrigin = 'anonymous';
1238
1280
  Browser autoplay policy dictates that audio output can only be initiated by a user gesture, and this is enforced by WebAudio API
1239
1281
  by creating [*AudioContext*](#audioctx-audiocontext-object-read-only) objects in *suspended* mode.
1240
1282
 
1241
- **audioMotion-analyzer** tries to automatically start its audio context on the first click on the page.
1283
+ **audioMotion-analyzer** tries to automatically start its AudioContext on the first click on the page.
1242
1284
  However, if you're using an `audio` or `video` element with the `controls` property, clicks on those native media controls cannot be detected
1243
1285
  by JavaScript, so the audio will only be enabled if/when the user clicks somewhere else.
1244
1286
 
1245
- Two possible solutions: **1)** make **sure** your users have to click somewhere else before using the media controls,
1287
+ Two possible solutions are: **1)** ensure your users have to click somewhere else before using the media controls,
1246
1288
  like a "power on" button, or simply clicking to select a song from a list will do; or **2)** don't use the native
1247
1289
  controls at all, and create your own custom play and stop buttons. A very simple example:
1248
1290
 
@@ -1260,6 +1302,9 @@ document.getElementById('play').addEventListener( 'click', () => myAudio.play()
1260
1302
  document.getElementById('stop').addEventListener( 'click', () => myAudio.pause() );
1261
1303
  ```
1262
1304
 
1305
+ You can also prevent the _"The AudioContext was not allowed to start"_ warning message from appearing in the browser console, by instantiating
1306
+ your **audioMotion-analyzer** object within a function triggered by a user click. See the [minimal demo](/demo/minimal.html) code for an example.
1307
+
1263
1308
 
1264
1309
  ## References and acknowledgments
1265
1310
 
@@ -1272,7 +1317,9 @@ document.getElementById('stop').addEventListener( 'click', () => myAudio.pause()
1272
1317
  * [Equations for equal-tempered scale frequencies](http://pages.mtu.edu/~suits/NoteFreqCalcs.html)
1273
1318
  * [Making Audio Reactive Visuals](https://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/)
1274
1319
  * The font used in audioMotion's logo is [Orbitron](https://fonts.google.com/specimen/Orbitron) by Matt McInerney
1275
- * This documentation website is powered by [GitHub Pages](https://pages.github.com/), [docsify](https://docsify.js.org/) and [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable).
1320
+ * The _prism_ and _rainbow_ gradients use the [12-bit rainbow palette](https://iamkate.com/data/12-bit-rainbow/) by Kate Morley
1321
+ * The cover page animation was recorded with [ScreenToGif](https://github.com/NickeManarin/ScreenToGif) by Nicke Manarin
1322
+ * This documentation website is powered by [GitHub Pages](https://pages.github.com/), [docsify](https://docsify.js.org/) and [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable)
1276
1323
 
1277
1324
 
1278
1325
  ## Changelog
@@ -1282,18 +1329,20 @@ See [Changelog.md](Changelog.md)
1282
1329
 
1283
1330
  ## Contributing
1284
1331
 
1285
- If you want to send feedback, ask a question, or need help with something, please use the [**Discussions**](https://github.com/hvianna/audioMotion-analyzer/discussions) area on GitHub.
1332
+ I kindly request that you only [open an issue](https://github.com/hvianna/audioMotion-analyzer/issues) for submitting a **bug report**.
1286
1333
 
1287
- I would love to see your cool projects using **audioMotion-analyzer** -- post them in the *Show and tell* section of [Discussions](https://github.com/hvianna/audioMotion-analyzer/discussions)!
1334
+ If you need help integrating *audioMotion-analyzer* with your project, have ideas for **new features** or any other questions or feedback,
1335
+ please use the [**Discussions**](https://github.com/hvianna/audioMotion-analyzer/discussions) section on GitHub.
1288
1336
 
1289
- For **bug reports** and **feature requests**, feel free to [open an issue](https://github.com/hvianna/audioMotion-analyzer/issues).
1337
+ Additionally, I would love it if you could showcase your project using *audioMotion-analyzer* in [**Show and Tell**](https://github.com/hvianna/audioMotion-analyzer/discussions/categories/show-and-tell),
1338
+ and share your custom gradients with the community in [**Gradients**](https://github.com/hvianna/audioMotion-analyzer/discussions/categories/gradients)!
1290
1339
 
1291
- If you want to submit a **Pull Request**, please branch it off the project's `develop` branch.
1340
+ When submitting a **Pull Request**, please branch it off the project's `develop` branch.
1292
1341
 
1293
1342
  And if you're feeling generous, maybe:
1294
1343
 
1295
1344
  * [Buy me a coffee](https://ko-fi.com/Q5Q6157GZ) on Ko-fi ☕😁
1296
- * Gift me something from my [Bandcamp wishlist](https://bandcamp.com/henriquevianna/wishlist) 🎁🥰
1345
+ * Gift me something from my [Bandcamp wishlist](https://bandcamp.com/henriquevianna/wishlist) 🎁🎶🥰
1297
1346
  * Tip me via [Brave Rewards](https://brave.com/brave-rewards/) using Brave browser 🤓
1298
1347
 
1299
1348
 
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.0.0-beta.4",
4
+ "version": "4.0.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.0.0-beta.4
5
+ * @version 4.0.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.0.0-beta.4';
10
+ const VERSION = '4.0.0';
11
11
 
12
12
  // internal constants
13
13
  const TAU = 2 * Math.PI,
@@ -16,9 +16,9 @@ const TAU = 2 * Math.PI,
16
16
  C_1 = 8.17579892; // frequency for C -1
17
17
 
18
18
  const CANVAS_BACKGROUND_COLOR = '#000',
19
- CHANNEL_COMBINED = 'dualCombined',
19
+ CHANNEL_COMBINED = 'dual-combined',
20
20
  CHANNEL_SINGLE = 'single',
21
- CHANNEL_VERTICAL = 'dualVertical',
21
+ CHANNEL_VERTICAL = 'dual-vertical',
22
22
  GRADIENT_DEFAULT_BGCOLOR = '#111',
23
23
  FILTER_NONE = '',
24
24
  FILTER_A = 'A',
@@ -28,9 +28,6 @@ const CANVAS_BACKGROUND_COLOR = '#000',
28
28
  FILTER_468 = '468',
29
29
  FONT_FAMILY = 'sans-serif',
30
30
  FPS_COLOR = '#0f0',
31
- GRADIENT_CLASSIC = 'classic',
32
- GRADIENT_PRISM = 'prism',
33
- GRADIENT_RAINBOW = 'rainbow',
34
31
  LEDS_UNLIT_COLOR = '#7f7f7f22',
35
32
  REASON_CREATE = 'create',
36
33
  REASON_FSCHANGE = 'fschange',
@@ -47,6 +44,33 @@ const CANVAS_BACKGROUND_COLOR = '#000',
47
44
  SCALE_LOG = 'log',
48
45
  SCALE_MEL = 'mel';
49
46
 
47
+ // built-in gradients
48
+ const PRISM = [ '#a35', '#c66', '#e94', '#ed0', '#9d5', '#4d8', '#2cb', '#0bc', '#09c', '#36b' ],
49
+ GRADIENTS = [
50
+ [ 'classic', {
51
+ colorStops: [
52
+ 'hsl( 0, 100%, 50% )',
53
+ { pos: .6, color: 'hsl( 60, 100%, 50% )' },
54
+ 'hsl( 120, 100%, 50% )'
55
+ ]
56
+ }],
57
+ [ 'prism', {
58
+ colorStops: PRISM
59
+ }],
60
+ [ 'rainbow', {
61
+ dir: 'h',
62
+ colorStops: [ '#817', ...PRISM, '#639' ]
63
+ }],
64
+ [ 'orangered', {
65
+ bgColor: '#3e2f29',
66
+ colorStops: [ 'OrangeRed' ]
67
+ }],
68
+ [ 'steelblue', {
69
+ bgColor: '#222c35',
70
+ colorStops: [ 'SteelBlue' ]
71
+ }]
72
+ ];
73
+
50
74
  // custom error messages
51
75
  const ERR_AUDIO_CONTEXT_FAIL = [ 'ERR_AUDIO_CONTEXT_FAIL', 'Could not create audio context. Web Audio API not supported?' ],
52
76
  ERR_INVALID_AUDIO_CONTEXT = [ 'ERR_INVALID_AUDIO_CONTEXT', 'Provided audio context is not valid' ],
@@ -88,38 +112,12 @@ export default class AudioMotionAnalyzer {
88
112
 
89
113
  // Initialize internal gradient objects
90
114
  this._gradients = {}; // registered gradients
91
- this._gradientNames = []; // names of the currently selected gradients for channels 0 and 1
115
+ this._selectedGrads = []; // names of the currently selected gradients for channels 0 and 1
92
116
  this._canvasGradients = []; // actual CanvasGradient objects for channels 0 and 1
93
117
 
94
118
  // Register built-in gradients
95
- this.registerGradient( GRADIENT_CLASSIC, {
96
- colorStops: [
97
- 'hsl( 0, 100%, 50% )',
98
- { pos: .6, color: 'hsl( 60, 100%, 50% )' },
99
- 'hsl( 120, 100%, 50% )'
100
- ]
101
- });
102
- this.registerGradient( GRADIENT_PRISM, {
103
- colorStops: [
104
- 'hsl( 0, 100%, 50% )',
105
- 'hsl( 60, 100%, 50% )',
106
- 'hsl( 120, 100%, 50% )',
107
- 'hsl( 180, 100%, 50% )',
108
- 'hsl( 240, 100%, 50% )'
109
- ]
110
- });
111
- this.registerGradient( GRADIENT_RAINBOW, {
112
- dir: 'h',
113
- colorStops: [
114
- 'hsl( 0, 100%, 50% )',
115
- 'hsl( 60, 100%, 50% )',
116
- 'hsl( 120, 100%, 50% )',
117
- 'hsl( 180, 100%, 47% )',
118
- 'hsl( 240, 100%, 58% )',
119
- 'hsl( 300, 100%, 50% )',
120
- 'hsl( 360, 100%, 50% )'
121
- ]
122
- });
119
+ for ( const [ name, options ] of GRADIENTS )
120
+ this.registerGradient( name, options );
123
121
 
124
122
  // Set container
125
123
  this._container = container || document.body;
@@ -315,8 +313,8 @@ export default class AudioMotionAnalyzer {
315
313
  return this._chLayout;
316
314
  }
317
315
  set channelLayout( value ) {
318
- const MODES = [ CHANNEL_SINGLE, CHANNEL_VERTICAL, CHANNEL_COMBINED ];
319
- this._chLayout = MODES[ Math.max( 0, MODES.findIndex( el => el.toLowerCase() == ( '' + value ).toLowerCase() ) ) ];
316
+ const LAYOUTS = [ CHANNEL_SINGLE, CHANNEL_VERTICAL, CHANNEL_COMBINED ];
317
+ this._chLayout = LAYOUTS[ Math.max( 0, LAYOUTS.indexOf( ( '' + value ).toLowerCase() ) ) ];
320
318
 
321
319
  // update node connections
322
320
  this._input.disconnect();
@@ -354,21 +352,21 @@ export default class AudioMotionAnalyzer {
354
352
  }
355
353
 
356
354
  get gradient() {
357
- return this._gradientNames[0];
355
+ return this._selectedGrads[0];
358
356
  }
359
357
  set gradient( value ) {
360
358
  this._setGradient( value );
361
359
  }
362
360
 
363
361
  get gradientLeft() {
364
- return this._gradientNames[0];
362
+ return this._selectedGrads[0];
365
363
  }
366
364
  set gradientLeft( value ) {
367
365
  this._setGradient( value, 0 );
368
366
  }
369
367
 
370
368
  get gradientRight() {
371
- return this._gradientNames[1];
369
+ return this._selectedGrads[1];
372
370
  }
373
371
  set gradientRight( value ) {
374
372
  this._setGradient( value, 1 );
@@ -808,7 +806,7 @@ export default class AudioMotionAnalyzer {
808
806
  };
809
807
 
810
808
  // if the registered gradient is one of the currently selected gradients, regenerate them
811
- if ( this._gradientNames.includes( name ) )
809
+ if ( this._selectedGrads.includes( name ) )
812
810
  this._makeGrad();
813
811
  }
814
812
 
@@ -1126,7 +1124,7 @@ export default class AudioMotionAnalyzer {
1126
1124
  [ binLo, ratioLo ] = calcRatio( freqLo ),
1127
1125
  [ binHi, ratioHi ] = calcRatio( freqHi );
1128
1126
 
1129
- barsPush( { posX, freq, freqLo, freqHi, binLo, binHi, ratioLo, ratioHi } );
1127
+ barsPush( { posX: initialX + posX, freq, freqLo, freqHi, binLo, binHi, ratioLo, ratioHi } );
1130
1128
  }
1131
1129
 
1132
1130
  }
@@ -1353,11 +1351,15 @@ export default class AudioMotionAnalyzer {
1353
1351
  * this is called 60 times per second by requestAnimationFrame()
1354
1352
  */
1355
1353
  _draw( timestamp ) {
1356
- const ctx = this._canvasCtx,
1354
+ const barSpace = this._barSpace,
1355
+ barSpacePx = this._barSpacePx,
1356
+ ctx = this._canvasCtx,
1357
1357
  canvas = ctx.canvas,
1358
1358
  canvasX = this._scaleX.canvas,
1359
1359
  canvasR = this._scaleR.canvas,
1360
+ canvasGradients= this._canvasGradients,
1360
1361
  energy = this._energy,
1362
+ fillAlpha = this.fillAlpha,
1361
1363
  mode = this._mode,
1362
1364
  isAlphaBars = this._isAlphaBars,
1363
1365
  isLedDisplay = this._isLedDisplay,
@@ -1365,6 +1367,7 @@ export default class AudioMotionAnalyzer {
1365
1367
  isLumiBars = this._isLumiBars,
1366
1368
  isBandsMode = this._isBandsMode,
1367
1369
  isOutline = this._isOutline,
1370
+ isOverlay = this.overlay,
1368
1371
  isRadial = this._radial,
1369
1372
  channelLayout = this._chLayout,
1370
1373
  lineWidth = +this.lineWidth, // make sure the damn thing is a number!
@@ -1378,6 +1381,7 @@ export default class AudioMotionAnalyzer {
1378
1381
  centerX = canvas.width >> 1,
1379
1382
  centerY = canvas.height >> 1,
1380
1383
  radius = this._radius,
1384
+ showBgColor = this.showBgColor,
1381
1385
  maxBarHeight = isRadial ? Math.min( centerX, centerY ) - radius : analyzerHeight,
1382
1386
  maxdB = this.maxDecibels,
1383
1387
  mindB = this.minDecibels,
@@ -1461,15 +1465,12 @@ export default class AudioMotionAnalyzer {
1461
1465
  const [ ledCount, ledSpaceH, ledSpaceV, ledHeight ] = this._leds || [];
1462
1466
  const ledPosY = height => Math.max( 0, ( height * ledCount | 0 ) * ( ledHeight + ledSpaceV ) - ledSpaceV );
1463
1467
 
1464
- // select background color
1465
- const bgColor = ( ! this.showBgColor || isLedDisplay && ! this.overlay ) ? '#000' : this._gradients[ this._gradientNames[0] ].bgColor;
1466
-
1467
1468
  // compute the effective bar width, considering the selected bar spacing
1468
1469
  // if led effect is active, ensure at least the spacing from led definitions
1469
- let width = this._barWidth - ( ! isBandsMode ? 0 : Math.max( isLedDisplay ? ledSpaceH : 0, this._barSpacePx ) );
1470
+ let width = this._barWidth - ( ! isBandsMode ? 0 : Math.max( isLedDisplay ? ledSpaceH : 0, barSpacePx ) );
1470
1471
 
1471
1472
  // make sure width is integer for pixel accurate calculation, when no bar spacing is required
1472
- if ( this._barSpace == 0 && ! isLedDisplay )
1473
+ if ( barSpace == 0 && ! isLedDisplay )
1473
1474
  width |= 0;
1474
1475
 
1475
1476
  let currentEnergy = 0;
@@ -1481,24 +1482,26 @@ export default class AudioMotionAnalyzer {
1481
1482
 
1482
1483
  const channelTop = channelLayout == CHANNEL_VERTICAL ? channelHeight * channel + channelGap * channel : 0,
1483
1484
  channelBottom = channelTop + channelHeight,
1484
- analyzerBottom = channelTop + analyzerHeight - ( isLedDisplay && ! this._maximizeLeds ? ledSpaceV : 0 );
1485
+ analyzerBottom = channelTop + analyzerHeight - ( isLedDisplay && ! this._maximizeLeds ? ledSpaceV : 0 ),
1486
+ bgColor = ( ! showBgColor || isLedDisplay && ! isOverlay ) ? '#000' : this._gradients[ this._selectedGrads[ channel ] ].bgColor,
1487
+ mustClear = channel == 0 || ! isRadial && channelLayout != CHANNEL_COMBINED;
1485
1488
 
1486
1489
  if ( useCanvas ) {
1487
1490
  // clear the channel area, if in overlay mode
1488
1491
  // this is done per channel to clear any residue below 0 off the top channel (especially in line graph mode with lineWidth > 1)
1489
- if ( this.overlay && ( ! isRadial || channel == 0 ) )
1492
+ if ( isOverlay && mustClear )
1490
1493
  ctx.clearRect( 0, channelTop - channelGap, canvas.width, channelHeight + channelGap );
1491
1494
 
1492
1495
  // fill the analyzer background if needed (not overlay or overlay + showBgColor)
1493
- if ( ! this.overlay || this.showBgColor ) {
1494
- if ( this.overlay )
1496
+ if ( ! isOverlay || showBgColor ) {
1497
+ if ( isOverlay )
1495
1498
  ctx.globalAlpha = this.bgAlpha;
1496
1499
 
1497
1500
  ctx.fillStyle = bgColor;
1498
1501
 
1499
1502
  // exclude the reflection area when overlay is true and reflexAlpha == 1 (avoids alpha over alpha difference, in case bgAlpha < 1)
1500
- if ( ! isRadial && channelLayout != CHANNEL_COMBINED || channel == 0 )
1501
- ctx.fillRect( initialX, channelTop - channelGap, analyzerWidth, ( this.overlay && this.reflexAlpha == 1 ? analyzerHeight : channelHeight ) + channelGap );
1503
+ if ( mustClear )
1504
+ ctx.fillRect( initialX, channelTop - channelGap, analyzerWidth, ( isOverlay && this.reflexAlpha == 1 ? analyzerHeight : channelHeight ) + channelGap );
1502
1505
 
1503
1506
  ctx.globalAlpha = 1;
1504
1507
  }
@@ -1556,7 +1559,7 @@ export default class AudioMotionAnalyzer {
1556
1559
  ctx.lineWidth = isOutline ? Math.min( lineWidth, width / 2 ) : lineWidth;
1557
1560
 
1558
1561
  // set selected gradient for fill and stroke
1559
- ctx.fillStyle = ctx.strokeStyle = this._canvasGradients[ channel ];
1562
+ ctx.fillStyle = ctx.strokeStyle = canvasGradients[ channel ];
1560
1563
  } // if ( useCanvas )
1561
1564
 
1562
1565
  // get a new array of data from the FFT
@@ -1622,7 +1625,7 @@ export default class AudioMotionAnalyzer {
1622
1625
  if ( isLumiBars || isAlphaBars )
1623
1626
  ctx.globalAlpha = barHeight;
1624
1627
  else if ( isOutline )
1625
- ctx.globalAlpha = this.fillAlpha;
1628
+ ctx.globalAlpha = fillAlpha;
1626
1629
 
1627
1630
  // compute actual bar height on screen
1628
1631
  barHeight = isLedDisplay ? ledPosY( barHeight ) : barHeight * maxBarHeight | 0;
@@ -1671,9 +1674,9 @@ export default class AudioMotionAnalyzer {
1671
1674
  else {
1672
1675
  if ( mode > 0 ) {
1673
1676
  if ( isLedDisplay )
1674
- posX += Math.max( ledSpaceH / 2, this._barSpacePx / 2 );
1677
+ posX += Math.max( ledSpaceH / 2, barSpacePx / 2 );
1675
1678
  else {
1676
- if ( this._barSpace == 0 ) {
1679
+ if ( barSpace == 0 ) {
1677
1680
  posX |= 0;
1678
1681
  if ( i > 0 && posX > this._bars[ i - 1 ].posX + width ) {
1679
1682
  posX--;
@@ -1681,14 +1684,14 @@ export default class AudioMotionAnalyzer {
1681
1684
  }
1682
1685
  }
1683
1686
  else
1684
- posX += this._barSpacePx / 2;
1687
+ posX += barSpacePx / 2;
1685
1688
  }
1686
1689
  }
1687
1690
 
1688
1691
  if ( isLedDisplay ) {
1689
1692
  const x = posX + width / 2;
1690
1693
  // draw "unlit" leds
1691
- if ( this.showBgColor && ! this.overlay ) {
1694
+ if ( showBgColor && ! isOverlay ) {
1692
1695
  const alpha = ctx.globalAlpha;
1693
1696
  ctx.beginPath();
1694
1697
  ctx.moveTo( x, channelTop );
@@ -1770,7 +1773,7 @@ export default class AudioMotionAnalyzer {
1770
1773
  if ( lineWidth > 0 )
1771
1774
  ctx.stroke();
1772
1775
 
1773
- if ( this.fillAlpha > 0 ) {
1776
+ if ( fillAlpha > 0 ) {
1774
1777
  if ( isRadial ) {
1775
1778
  // exclude the center circle from the fill area
1776
1779
  ctx.moveTo( centerX + radius, centerY );
@@ -1781,7 +1784,7 @@ export default class AudioMotionAnalyzer {
1781
1784
  ctx.lineTo( initialX, analyzerBottom );
1782
1785
  }
1783
1786
 
1784
- ctx.globalAlpha = this.fillAlpha;
1787
+ ctx.globalAlpha = fillAlpha;
1785
1788
  ctx.fill();
1786
1789
  ctx.globalAlpha = 1;
1787
1790
  }
@@ -1876,8 +1879,8 @@ export default class AudioMotionAnalyzer {
1876
1879
  // call callback function, if defined
1877
1880
  if ( this.onCanvasDraw ) {
1878
1881
  ctx.save();
1879
- ctx.fillStyle = ctx.strokeStyle = this._canvasGradients[0];
1880
- this.onCanvasDraw( this );
1882
+ ctx.fillStyle = ctx.strokeStyle = canvasGradients[0];
1883
+ this.onCanvasDraw( this, { timestamp, canvasGradients } );
1881
1884
  ctx.restore();
1882
1885
  }
1883
1886
 
@@ -1921,9 +1924,10 @@ export default class AudioMotionAnalyzer {
1921
1924
 
1922
1925
  const ctx = this._canvasCtx,
1923
1926
  canvas = ctx.canvas,
1927
+ channelLayout = this._chLayout,
1924
1928
  isLumiBars = this._isLumiBars,
1925
1929
  isRadial = this._radial,
1926
- gradientHeight = isLumiBars ? canvas.height : canvas.height * ( 1 - this._reflexRatio * ( this._chLayout != CHANNEL_VERTICAL ) ) | 0,
1930
+ gradientHeight = isLumiBars ? canvas.height : canvas.height * ( 1 - this._reflexRatio * ( channelLayout != CHANNEL_VERTICAL ) ) | 0,
1927
1931
  // for vertical stereo we keep the full canvas height and handle the reflex areas while generating the color stops
1928
1932
  analyzerRatio = 1 - this._reflexRatio,
1929
1933
  initialX = this._initialX;
@@ -1934,29 +1938,28 @@ export default class AudioMotionAnalyzer {
1934
1938
  maxRadius = Math.min( centerX, centerY ),
1935
1939
  radius = this._radius;
1936
1940
 
1937
- for ( let chn = 0; chn < 2; chn++ ) {
1938
- const currGradient = this._gradients[ this._gradientNames[ chn ] ],
1941
+ for ( const channel of [0,1] ) {
1942
+ const currGradient = this._gradients[ this._selectedGrads[ channel ] ],
1939
1943
  colorStops = currGradient.colorStops,
1940
1944
  isHorizontal = currGradient.dir == 'h';
1941
1945
 
1942
1946
  let grad;
1943
1947
 
1944
1948
  if ( isRadial )
1945
- grad = ctx.createRadialGradient( centerX, centerY, maxRadius, centerX, centerY, radius - ( maxRadius - radius ) * ( this._chLayout == CHANNEL_VERTICAL ) );
1949
+ grad = ctx.createRadialGradient( centerX, centerY, maxRadius, centerX, centerY, radius - ( maxRadius - radius ) * ( channelLayout == CHANNEL_VERTICAL ) );
1946
1950
  else
1947
1951
  grad = ctx.createLinearGradient( ...( isHorizontal ? [ initialX, 0, initialX + this._analyzerWidth, 0 ] : [ 0, 0, 0, gradientHeight ] ) );
1948
1952
 
1949
1953
  if ( colorStops ) {
1950
- const dual = this._chLayout == CHANNEL_VERTICAL && ! this._splitGradient && ( ! isHorizontal || isRadial );
1954
+ const dual = channelLayout == CHANNEL_VERTICAL && ! this._splitGradient && ( ! isHorizontal || isRadial );
1951
1955
 
1952
1956
  // helper function
1953
1957
  const addColorStop = ( offset, colorInfo ) => grad.addColorStop( offset, colorInfo.color || colorInfo );
1954
1958
 
1955
- for ( let channel = 0; channel < 1 + dual; channel++ ) {
1956
- colorStops.forEach( ( colorInfo, index ) => {
1959
+ for ( let channelArea = 0; channelArea < 1 + dual; channelArea++ ) {
1957
1960
 
1961
+ colorStops.forEach( ( colorInfo, index ) => {
1958
1962
  const maxIndex = colorStops.length - 1;
1959
-
1960
1963
  let offset = colorInfo.pos !== undefined ? colorInfo.pos : index / Math.max( 1, maxIndex );
1961
1964
 
1962
1965
  // in dual mode (not split), use half the original offset for each channel
@@ -1964,15 +1967,15 @@ export default class AudioMotionAnalyzer {
1964
1967
  offset /= 2;
1965
1968
 
1966
1969
  // constrain the offset within the useful analyzer areas (avoid reflex areas)
1967
- if ( this._chLayout == CHANNEL_VERTICAL && ! isLumiBars && ! isRadial && ! isHorizontal ) {
1970
+ if ( channelLayout == CHANNEL_VERTICAL && ! isLumiBars && ! isRadial && ! isHorizontal ) {
1968
1971
  offset *= analyzerRatio;
1969
1972
  // skip the first reflex area in split mode
1970
1973
  if ( ! dual && offset > .5 * analyzerRatio )
1971
1974
  offset += .5 * this._reflexRatio;
1972
1975
  }
1973
1976
 
1974
- // only for non-split gradient
1975
- if ( channel == 1 ) {
1977
+ // only for dual-vertical non-split gradient (creates full gradient on both halves of the canvas)
1978
+ if ( channelArea == 1 ) {
1976
1979
  // add colors in reverse order if radial or lumi are active
1977
1980
  if ( isRadial || isLumiBars ) {
1978
1981
  const revIndex = maxIndex - index;
@@ -1992,14 +1995,14 @@ export default class AudioMotionAnalyzer {
1992
1995
  addColorStop( offset, colorInfo );
1993
1996
 
1994
1997
  // create additional color stop at the end of first channel to prevent bleeding
1995
- if ( this._chLayout == CHANNEL_VERTICAL && index == maxIndex && offset < .5 )
1998
+ if ( channelLayout == CHANNEL_VERTICAL && index == maxIndex && offset < .5 )
1996
1999
  addColorStop( .5, colorInfo );
1997
2000
  });
1998
- }
2001
+ } // for ( let channelArea = 0; channelArea < 1 + dual; channelArea++ )
1999
2002
  }
2000
2003
 
2001
- this._canvasGradients[ chn ] = grad;
2002
- } // for (chn)
2004
+ this._canvasGradients[ channel ] = grad;
2005
+ } // for ( const channel of [0,1] )
2003
2006
  }
2004
2007
 
2005
2008
  /**
@@ -2104,11 +2107,11 @@ export default class AudioMotionAnalyzer {
2104
2107
  throw new AudioMotionError( ERR_UNKNOWN_GRADIENT, name );
2105
2108
 
2106
2109
  if ( ! [0,1].includes( channel ) ) {
2107
- this._gradientNames[1] = name;
2110
+ this._selectedGrads[1] = name;
2108
2111
  channel = 0;
2109
2112
  }
2110
2113
 
2111
- this._gradientNames[ channel ] = name;
2114
+ this._selectedGrads[ channel ] = name;
2112
2115
  this._makeGrad();
2113
2116
  }
2114
2117
 
@@ -2127,7 +2130,7 @@ export default class AudioMotionAnalyzer {
2127
2130
  fftSize : 8192,
2128
2131
  fillAlpha : 1,
2129
2132
  frequencyScale : SCALE_LOG,
2130
- gradient : GRADIENT_CLASSIC,
2133
+ gradient : GRADIENTS[0][0],
2131
2134
  ledBars : false,
2132
2135
  linearAmplitude: false,
2133
2136
  linearBoost : 1,
package/src/index.d.ts CHANGED
@@ -1,5 +1,14 @@
1
- type OnCanvasDrawFunction = (instance: AudioMotionAnalyzer) => unknown;
2
- type OnCanvasResizeFunction = (
1
+ export type OnCanvasDrawFunction = (
2
+ instance: AudioMotionAnalyzer,
3
+ info: CanvasDrawInfo
4
+ ) => unknown;
5
+
6
+ export type CanvasDrawInfo = {
7
+ timestamp: DOMHighResTimeStamp,
8
+ canvasGradients: CanvasGradient[]
9
+ }
10
+
11
+ export type OnCanvasResizeFunction = (
3
12
  reason: CanvasResizeReason,
4
13
  instance: AudioMotionAnalyzer
5
14
  ) => unknown;
@@ -74,7 +83,7 @@ export interface ConstructorOptions extends Options {
74
83
  source?: HTMLMediaElement | AudioNode;
75
84
  }
76
85
 
77
- export type ChannelLayout = "single" | "dualVertical" | "dualCombined";
86
+ export type ChannelLayout = "single" | "dual-vertical" | "dual-combined";
78
87
 
79
88
  export type EnergyPreset = "peak" | "bass" | "lowMid" | "mid" | "highMid" | "treble";
80
89