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 +116 -67
- package/package.json +1 -1
- package/src/audioMotion-analyzer.js +86 -83
- package/src/index.d.ts +12 -3
package/README.md
CHANGED
|
@@ -27,19 +27,19 @@ What users are saying:
|
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
29
|
|
|
30
|
-
+
|
|
31
|
-
+ Logarithmic, linear and perceptual (Bark
|
|
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
|
-
+
|
|
36
|
-
+
|
|
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
|
-
[](https://audiomotion.dev/demo/)
|
|
43
43
|
|
|
44
44
|
?> https://audiomotion.dev/demo/
|
|
45
45
|
|
|
@@ -161,7 +161,6 @@ options = {<br>
|
|
|
161
161
|
  [spinSpeed](#spinspeed-number): **0**,<br>
|
|
162
162
|
  [splitGradient](#splitgradient-boolean): **false**,<br>
|
|
163
163
|
  [start](#start-boolean): **true**,<br>
|
|
164
|
-
  [stereo](#stereo-deprecated-boolean): **false**, // DEPRECATED - use channelLayout instead<br>
|
|
165
164
|
  [useCanvas](#usecanvas-boolean): **true**,<br>
|
|
166
165
|
  [volume](#volume-number): **1**,<br>
|
|
167
166
|
  [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)
|
|
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
|
|
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
|
-
|
|
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) | 
|
|
259
|
+
true | ANSI S1.11-2004 | 
|
|
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
|
|
328
|
-
|
|
329
|
-
'single'
|
|
330
|
-
'
|
|
331
|
-
'
|
|
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) | 
|
|
396
|
+
'linear' | Linear scale | 
|
|
397
|
+
'log' | Logarithmic scale | 
|
|
398
|
+
'mel' | [Mel scale](https://en.wikipedia.org/wiki/Mel_scale) | 
|
|
395
399
|
|
|
396
|
-
Logarithmic scale
|
|
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
|
|
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 [
|
|
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
|
-
|
|
431
|
+
gradient | preview
|
|
432
|
+
------------|---------
|
|
433
|
+
'classic' | 
|
|
434
|
+
'orangered' | 
|
|
435
|
+
'prism' | 
|
|
436
|
+
'rainbow' | 
|
|
437
|
+
'steelblue' | 
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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** [
|
|
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
|
-
|
|
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 *'
|
|
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
|
|
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: *
|
|
840
|
+
| gradient: *'classic'* - splitGradient: *false* | gradient: *'classic'* - splitGradient: *true* |
|
|
815
841
|
|:--:|:--:|
|
|
816
|
-
|  |  |
|
|
817
843
|
|
|
818
|
-
This option has no effect on horizontal gradients
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
|
|
955
|
+
// use the right-channel gradient to fill text
|
|
956
|
+
ctx.fillStyle = info.canvasGradients[1];
|
|
920
957
|
ctx.textAlign = 'center';
|
|
921
|
-
ctx.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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)**
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 = '
|
|
19
|
+
CHANNEL_COMBINED = 'dual-combined',
|
|
20
20
|
CHANNEL_SINGLE = 'single',
|
|
21
|
-
CHANNEL_VERTICAL = '
|
|
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.
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
319
|
-
this._chLayout =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 ( !
|
|
1494
|
-
if (
|
|
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 (
|
|
1501
|
-
ctx.fillRect( initialX, channelTop - channelGap, analyzerWidth, (
|
|
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 =
|
|
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 =
|
|
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,
|
|
1677
|
+
posX += Math.max( ledSpaceH / 2, barSpacePx / 2 );
|
|
1675
1678
|
else {
|
|
1676
|
-
if (
|
|
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 +=
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 * (
|
|
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 (
|
|
1938
|
-
const currGradient = this._gradients[ this.
|
|
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 ) * (
|
|
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 =
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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[
|
|
2002
|
-
} // for (
|
|
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.
|
|
2110
|
+
this._selectedGrads[1] = name;
|
|
2108
2111
|
channel = 0;
|
|
2109
2112
|
}
|
|
2110
2113
|
|
|
2111
|
-
this.
|
|
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 :
|
|
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 = (
|
|
2
|
-
|
|
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" | "
|
|
86
|
+
export type ChannelLayout = "single" | "dual-vertical" | "dual-combined";
|
|
78
87
|
|
|
79
88
|
export type EnergyPreset = "peak" | "bass" | "lowMid" | "mid" | "highMid" | "treble";
|
|
80
89
|
|