audiomotion-analyzer 4.5.0-beta.1 → 4.5.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 +80 -17
- package/dist/index.js +76 -21
- package/package.json +4 -5
- package/src/audioMotion-analyzer.js +82 -27
- package/src/index.d.ts +12 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
**audioMotion-analyzer** is a high-resolution real-time audio spectrum analyzer built upon **Web Audio** and **Canvas** JavaScript APIs.
|
|
5
5
|
|
|
6
|
-
It was originally conceived as part of my full-featured
|
|
6
|
+
It was originally conceived as part of my full-featured media player called [**audioMotion**](https://audiomotion.app), but I later decided
|
|
7
7
|
to make the spectrum analyzer available as a self-contained module, so other developers could use it in their own JS projects.
|
|
8
8
|
|
|
9
9
|
My goal is to make this the best looking, most accurate and customizable spectrum analyzer around, in a small-footprint and high-performance package.
|
|
@@ -35,7 +35,7 @@ What users are saying:
|
|
|
35
35
|
+ Additional effects: LED bars, luminance bars, mirroring and reflection, radial spectrum
|
|
36
36
|
+ Choose from 5 built-in color gradients or easily add your own!
|
|
37
37
|
+ Fullscreen support, ready for retina / HiDPI displays
|
|
38
|
-
+ Zero-dependency native ES6+ module (ESM), \~
|
|
38
|
+
+ Zero-dependency native ES6+ module (ESM), \~30kB minified
|
|
39
39
|
|
|
40
40
|
## Online demos
|
|
41
41
|
|
|
@@ -57,7 +57,27 @@ What users are saying:
|
|
|
57
57
|
|
|
58
58
|
## Usage
|
|
59
59
|
|
|
60
|
-
###
|
|
60
|
+
### Node.js project
|
|
61
|
+
|
|
62
|
+
Install via npm:
|
|
63
|
+
|
|
64
|
+
```console
|
|
65
|
+
npm i audiomotion-analyzer
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use ES6 import:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import AudioMotionAnalyzer from 'audiomotion-analyzer';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or CommonJS require:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const { AudioMotionAnalyzer } = require('audioMotion-analyzer');
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### In the browser using native ES6 module (ESM)
|
|
61
81
|
|
|
62
82
|
Load from Skypack CDN:
|
|
63
83
|
|
|
@@ -70,19 +90,17 @@ Load from Skypack CDN:
|
|
|
70
90
|
|
|
71
91
|
Or download the [latest version](https://github.com/hvianna/audioMotion-analyzer/releases) and copy the `audioMotion-analyzer.js` file from the `src/` folder into your project folder.
|
|
72
92
|
|
|
73
|
-
###
|
|
93
|
+
### In the browser using global variable
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
Load from Unpkg CDN:
|
|
76
96
|
|
|
77
|
-
```
|
|
78
|
-
|
|
97
|
+
```html
|
|
98
|
+
<script src="https://unpkg.com/audiomotion-analyzer/dist"></script>
|
|
99
|
+
<script>
|
|
100
|
+
// available as AudioMotionAnalyzer global
|
|
101
|
+
</script>
|
|
79
102
|
```
|
|
80
103
|
|
|
81
|
-
Use ES6 import syntax:
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
import AudioMotionAnalyzer from 'audiomotion-analyzer';
|
|
85
|
-
```
|
|
86
104
|
|
|
87
105
|
## Constructor
|
|
88
106
|
|
|
@@ -133,6 +151,7 @@ options = {<br>
|
|
|
133
151
|
  [channelLayout](#channellayout-string): **'single'**,<br>
|
|
134
152
|
  [colorMode](#colormode-string): **'gradient'**,<br>
|
|
135
153
|
  [connectSpeakers](#connectspeakers-boolean): **true**, // constructor only<br>
|
|
154
|
+
  [fadePeaks](#fadepeaks-boolean): **false**,<br>
|
|
136
155
|
  [fftSize](#fftsize-number): **8192**,<br>
|
|
137
156
|
  [fillAlpha](#fillalpha-number): **1**,<br>
|
|
138
157
|
  [frequencyScale](#frequencyscale-string): **'log'**,<br>
|
|
@@ -140,7 +159,7 @@ options = {<br>
|
|
|
140
159
|
  [gradient](#gradient-string): **'classic'**,<br>
|
|
141
160
|
  [gradientLeft](#gradientleft-string): *undefined*,<br>
|
|
142
161
|
  [gradientRight](#gradientright-string): *undefined*,<br>
|
|
143
|
-
  [gravity](#gravity-number): **
|
|
162
|
+
  [gravity](#gravity-number): **3.8**,<br>
|
|
144
163
|
  [height](#height-number): *undefined*,<br>
|
|
145
164
|
  [ledBars](#ledbars-boolean): **false**,<br>
|
|
146
165
|
  [linearAmplitude](#linearamplitude-boolean): **false**,<br>
|
|
@@ -160,6 +179,8 @@ options = {<br>
|
|
|
160
179
|
  [onCanvasResize](#oncanvasresize-function): *undefined*,<br>
|
|
161
180
|
  [outlineBars](#outlinebars-boolean): **false**,<br>
|
|
162
181
|
  [overlay](#overlay-boolean): **false**,<br>
|
|
182
|
+
  [peakFadeTime](#peakfadetime-number): **750**,<br>
|
|
183
|
+
  [peakHoldTime](#peakholdtime-number): **500**,<br>
|
|
163
184
|
  [peakLine](#peakline-boolean): **false**,<br>
|
|
164
185
|
  [radial](#radial-boolean): **false**,<br>
|
|
165
186
|
  [radialInvert](#radialinvert-boolean): **false**,<br>
|
|
@@ -401,6 +422,18 @@ By default, **audioMotion-analyzer** is connected to the *AudioContext* `destina
|
|
|
401
422
|
|
|
402
423
|
See also [`connectOutput()`](#connectoutput-node-).
|
|
403
424
|
|
|
425
|
+
### `fadePeaks` *boolean*
|
|
426
|
+
|
|
427
|
+
*Available since v4.5.0*
|
|
428
|
+
|
|
429
|
+
When *true*, peaks fade out instead of falling down. It has no effect when [`peakLine`](#peakline-boolean) is active.
|
|
430
|
+
|
|
431
|
+
Fade time can be customized via [`peakFadeTime`](#peakfadetime-number).
|
|
432
|
+
|
|
433
|
+
See also [`peakHoldTime`](#peakholdtime-number) and [`showPeaks`](#showpeaks-boolean).
|
|
434
|
+
|
|
435
|
+
Defaults to **false**.
|
|
436
|
+
|
|
404
437
|
### `fftSize` *number*
|
|
405
438
|
|
|
406
439
|
Number of samples used for the FFT performed by the [*AnalyzerNode*](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode).
|
|
@@ -503,11 +536,17 @@ See also [`gradient`](#gradient-string) and [`splitGradient`](#splitgradient-boo
|
|
|
503
536
|
|
|
504
537
|
*Available since v4.5.0*
|
|
505
538
|
|
|
506
|
-
|
|
539
|
+
Customize the acceleration of falling peaks.
|
|
507
540
|
|
|
508
|
-
It must be a number greater than zero. Invalid values are ignored and no error is thrown.
|
|
541
|
+
It must be a number **greater than zero,** representing _thousands of pixels per second squared_. Invalid values are ignored and no error is thrown.
|
|
509
542
|
|
|
510
|
-
|
|
543
|
+
With the default value and analyzer height of 1080px, a peak at maximum amplitude takes approximately 750ms to fall to zero.
|
|
544
|
+
|
|
545
|
+
You can use the [peak drop analysis tool](/tools/peak-drop.html) to see the decay curve for different values of gravity.
|
|
546
|
+
|
|
547
|
+
See also [`peakHoldTime`](#peakholdtime-number) and [`showPeaks`](#showpeaks-boolean).
|
|
548
|
+
|
|
549
|
+
Defaults to **3.8**.
|
|
511
550
|
|
|
512
551
|
### `height` *number*
|
|
513
552
|
### `width` *number*
|
|
@@ -779,6 +818,30 @@ Defaults to **false**.
|
|
|
779
818
|
|
|
780
819
|
?> In order to keep elements other than the canvas visible in fullscreen, you'll need to set the [`fsElement`](#fselement-htmlelement-object) property in the [constructor](#constructor) options.
|
|
781
820
|
|
|
821
|
+
### `peakFadeTime` *number*
|
|
822
|
+
|
|
823
|
+
*Available since v4.5.0*
|
|
824
|
+
|
|
825
|
+
Time in milliseconds for peaks to completely fade out, when [`fadePeaks`](#fadepeaks-boolean) is active.
|
|
826
|
+
|
|
827
|
+
It must be a number greater than or equal to zero. Invalid values are ignored and no error is thrown.
|
|
828
|
+
|
|
829
|
+
See also [`peakHoldTime`](#peakholdtime-number) and [`showPeaks`](#showpeaks-boolean).
|
|
830
|
+
|
|
831
|
+
Defaults to **750**.
|
|
832
|
+
|
|
833
|
+
### `peakHoldTime` *number*
|
|
834
|
+
|
|
835
|
+
*Available since v4.5.0*
|
|
836
|
+
|
|
837
|
+
Time in milliseconds for peaks to hold their value before they begin to fall or fade.
|
|
838
|
+
|
|
839
|
+
It must be a number greater than or equal to zero. Invalid values are ignored and no error is thrown.
|
|
840
|
+
|
|
841
|
+
See also [`fadePeaks`](#fadepeaks-boolean), [`gravity`](#gravity-number), [`peakFadeTime`](#peakfadetime-number) and [`showPeaks`](#showpeaks-boolean).
|
|
842
|
+
|
|
843
|
+
Defaults to **500**.
|
|
844
|
+
|
|
782
845
|
### `peakLine` *boolean*
|
|
783
846
|
|
|
784
847
|
*Available since v4.2.0*
|
|
@@ -919,7 +982,7 @@ and setting `showBgColor` to ***true*** will make the "unlit" LEDs visible inste
|
|
|
919
982
|
|
|
920
983
|
*true* to show amplitude peaks.
|
|
921
984
|
|
|
922
|
-
See also [`gravity`](#gravity-number) and [`peakLine`](#peakline-boolean).
|
|
985
|
+
See also [`gravity`](#gravity-number), [`peakFadeTime`](#peakfadetime-number), [`peakHoldTime`](#peakholdtime-number) and [`peakLine`](#peakline-boolean).
|
|
923
986
|
|
|
924
987
|
Defaults to **true**.
|
|
925
988
|
|
package/dist/index.js
CHANGED
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
* audioMotion-analyzer
|
|
22
22
|
* High-resolution real-time graphic audio spectrum analyzer JS module
|
|
23
23
|
*
|
|
24
|
-
* @version 4.5.0
|
|
24
|
+
* @version 4.5.0
|
|
25
25
|
* @author Henrique Avila Vianna <hvianna@gmail.com> <https://henriquevianna.com>
|
|
26
26
|
* @license AGPL-3.0-or-later
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
const VERSION = '4.5.0
|
|
29
|
+
const VERSION = '4.5.0';
|
|
30
30
|
|
|
31
31
|
// internal constants
|
|
32
32
|
const PI = Math.PI,
|
|
@@ -104,11 +104,12 @@
|
|
|
104
104
|
bgAlpha: 0.7,
|
|
105
105
|
channelLayout: CHANNEL_SINGLE,
|
|
106
106
|
colorMode: COLOR_GRADIENT,
|
|
107
|
+
fadePeaks: false,
|
|
107
108
|
fftSize: 8192,
|
|
108
109
|
fillAlpha: 1,
|
|
109
110
|
frequencyScale: SCALE_LOG,
|
|
110
111
|
gradient: GRADIENTS[0][0],
|
|
111
|
-
gravity:
|
|
112
|
+
gravity: 3.8,
|
|
112
113
|
height: undefined,
|
|
113
114
|
ledBars: false,
|
|
114
115
|
linearAmplitude: false,
|
|
@@ -126,6 +127,8 @@
|
|
|
126
127
|
noteLabels: false,
|
|
127
128
|
outlineBars: false,
|
|
128
129
|
overlay: false,
|
|
130
|
+
peakFadeTime: 750,
|
|
131
|
+
peakHoldTime: 500,
|
|
129
132
|
peakLine: false,
|
|
130
133
|
radial: false,
|
|
131
134
|
radialInvert: false,
|
|
@@ -443,6 +446,12 @@
|
|
|
443
446
|
set colorMode(value) {
|
|
444
447
|
this._colorMode = validateFromList(value, [COLOR_GRADIENT, COLOR_BAR_INDEX, COLOR_BAR_LEVEL]);
|
|
445
448
|
}
|
|
449
|
+
get fadePeaks() {
|
|
450
|
+
return this._fadePeaks;
|
|
451
|
+
}
|
|
452
|
+
set fadePeaks(value) {
|
|
453
|
+
this._fadePeaks = !!value;
|
|
454
|
+
}
|
|
446
455
|
get fftSize() {
|
|
447
456
|
return this._analyzer[0].fftSize;
|
|
448
457
|
}
|
|
@@ -599,6 +608,18 @@
|
|
|
599
608
|
this._outlineBars = !!value;
|
|
600
609
|
this._calcBars();
|
|
601
610
|
}
|
|
611
|
+
get peakFadeTime() {
|
|
612
|
+
return this._peakFadeTime;
|
|
613
|
+
}
|
|
614
|
+
set peakFadeTime(value) {
|
|
615
|
+
this._peakFadeTime = value >= 0 ? +value : this._peakFadeTime || DEFAULT_SETTINGS.peakFadeTime;
|
|
616
|
+
}
|
|
617
|
+
get peakHoldTime() {
|
|
618
|
+
return this._peakHoldTime;
|
|
619
|
+
}
|
|
620
|
+
set peakHoldTime(value) {
|
|
621
|
+
this._peakHoldTime = +value || 0;
|
|
622
|
+
}
|
|
602
623
|
get peakLine() {
|
|
603
624
|
return this._peakLine;
|
|
604
625
|
}
|
|
@@ -1223,12 +1244,27 @@
|
|
|
1223
1244
|
* unitWidth
|
|
1224
1245
|
*/
|
|
1225
1246
|
|
|
1226
|
-
// helper function
|
|
1227
|
-
// bar object:
|
|
1247
|
+
// helper function to add a bar to the bars array
|
|
1248
|
+
// bar object format:
|
|
1249
|
+
// {
|
|
1250
|
+
// posX,
|
|
1251
|
+
// freq,
|
|
1252
|
+
// freqLo,
|
|
1253
|
+
// freqHi,
|
|
1254
|
+
// binLo,
|
|
1255
|
+
// binHi,
|
|
1256
|
+
// ratioLo,
|
|
1257
|
+
// ratioHi,
|
|
1258
|
+
// peak, // peak value
|
|
1259
|
+
// hold, // peak hold frames (negative value indicates peak falling / fading)
|
|
1260
|
+
// alpha, // peak alpha (used by fadePeaks)
|
|
1261
|
+
// value // current bar value
|
|
1262
|
+
// }
|
|
1228
1263
|
const barsPush = args => bars.push({
|
|
1229
1264
|
...args,
|
|
1230
1265
|
peak: [0, 0],
|
|
1231
1266
|
hold: [0],
|
|
1267
|
+
alpha: [0],
|
|
1232
1268
|
value: [0]
|
|
1233
1269
|
});
|
|
1234
1270
|
|
|
@@ -1720,9 +1756,9 @@
|
|
|
1720
1756
|
_colorMode,
|
|
1721
1757
|
_ctx,
|
|
1722
1758
|
_energy,
|
|
1759
|
+
_fadePeaks,
|
|
1723
1760
|
fillAlpha,
|
|
1724
1761
|
_fps,
|
|
1725
|
-
_gravity,
|
|
1726
1762
|
_linearAmplitude,
|
|
1727
1763
|
_lineWidth,
|
|
1728
1764
|
maxDecibels,
|
|
@@ -1738,8 +1774,10 @@
|
|
|
1738
1774
|
} = this,
|
|
1739
1775
|
canvasX = this._scaleX.canvas,
|
|
1740
1776
|
canvasR = this._scaleR.canvas,
|
|
1741
|
-
|
|
1742
|
-
|
|
1777
|
+
fadeFrames = _fps * this._peakFadeTime / 1e3,
|
|
1778
|
+
fpsSquared = _fps ** 2,
|
|
1779
|
+
gravity = this._gravity * 1e3,
|
|
1780
|
+
holdFrames = _fps * this._peakHoldTime / 1e3,
|
|
1743
1781
|
isDualCombined = _chLayout == CHANNEL_COMBINED,
|
|
1744
1782
|
isDualHorizontal = _chLayout == CHANNEL_HORIZONTAL,
|
|
1745
1783
|
isDualVertical = _chLayout == CHANNEL_VERTICAL,
|
|
@@ -1749,6 +1787,8 @@
|
|
|
1749
1787
|
finalX = initialX + analyzerWidth,
|
|
1750
1788
|
showPeakLine = showPeaks && this._peakLine && _mode == MODE_GRAPH,
|
|
1751
1789
|
maxBarHeight = _radial ? outerRadius - innerRadius : analyzerHeight,
|
|
1790
|
+
nominalMaxHeight = maxBarHeight / this._pixelRatio,
|
|
1791
|
+
// for consistent gravity on lo-res or hi-dpi
|
|
1752
1792
|
dbRange = maxDecibels - minDecibels,
|
|
1753
1793
|
[ledCount, ledSpaceH, ledSpaceV, ledHeight] = this._leds || [];
|
|
1754
1794
|
if (_energy.val > 0 && _fps > 0) this._spinAngle += this._spinSpeed * TAU / 60 / _fps; // spinSpeed * angle increment per frame for 1 RPM
|
|
@@ -1853,7 +1893,8 @@
|
|
|
1853
1893
|
_energy.val = newVal;
|
|
1854
1894
|
if (_energy.peak > 0) {
|
|
1855
1895
|
_energy.hold--;
|
|
1856
|
-
if (_energy.hold < 0) _energy.peak += _energy.hold
|
|
1896
|
+
if (_energy.hold < 0) _energy.peak += _energy.hold * gravity / fpsSquared / canvas.height * this._pixelRatio;
|
|
1897
|
+
// TO-DO: replace `canvas.height * this._pixelRatio` with `maxNominalHeight` when implementing dual-channel energy
|
|
1857
1898
|
}
|
|
1858
1899
|
if (newVal >= _energy.peak) {
|
|
1859
1900
|
_energy.peak = newVal;
|
|
@@ -2067,23 +2108,32 @@
|
|
|
2067
2108
|
currentEnergy += barValue;
|
|
2068
2109
|
|
|
2069
2110
|
// update bar peak
|
|
2070
|
-
if (bar.peak[channel] > 0) {
|
|
2111
|
+
if (bar.peak[channel] > 0 && bar.alpha[channel] > 0) {
|
|
2071
2112
|
bar.hold[channel]--;
|
|
2072
|
-
// if hold is negative,
|
|
2073
|
-
if (bar.hold[channel] < 0)
|
|
2113
|
+
// if hold is negative, start peak drop or fade out
|
|
2114
|
+
if (bar.hold[channel] < 0) {
|
|
2115
|
+
if (_fadePeaks && !showPeakLine) {
|
|
2116
|
+
const initialAlpha = !isAlpha || isOutline && _lineWidth > 0 ? 1 : isAlpha ? bar.peak[channel] : fillAlpha;
|
|
2117
|
+
bar.alpha[channel] = initialAlpha * (1 + bar.hold[channel] / fadeFrames); // hold is negative, so this is <= 1
|
|
2118
|
+
} else bar.peak[channel] += bar.hold[channel] * gravity / fpsSquared / nominalMaxHeight;
|
|
2119
|
+
// make sure the peak value is reset when using fadePeaks
|
|
2120
|
+
if (bar.alpha[channel] <= 0) bar.peak[channel] = 0;
|
|
2121
|
+
}
|
|
2074
2122
|
}
|
|
2075
2123
|
|
|
2076
2124
|
// check if it's a new peak for this bar
|
|
2077
2125
|
if (barValue >= bar.peak[channel]) {
|
|
2078
2126
|
bar.peak[channel] = barValue;
|
|
2079
2127
|
bar.hold[channel] = holdFrames;
|
|
2128
|
+
// check whether isAlpha or isOutline are active to start the peak alpha with the proper value
|
|
2129
|
+
bar.alpha[channel] = !isAlpha || isOutline && _lineWidth > 0 ? 1 : isAlpha ? barValue : fillAlpha;
|
|
2080
2130
|
}
|
|
2081
2131
|
|
|
2082
2132
|
// if not using the canvas, move earlier to the next bar
|
|
2083
2133
|
if (!useCanvas) continue;
|
|
2084
2134
|
|
|
2085
2135
|
// set opacity for bar effects
|
|
2086
|
-
|
|
2136
|
+
_ctx.globalAlpha = isLumi || isAlpha ? barValue : isOutline ? fillAlpha : 1;
|
|
2087
2137
|
|
|
2088
2138
|
// set fillStyle and strokeStyle for the current bar
|
|
2089
2139
|
setBarColor(barValue, barIndex);
|
|
@@ -2167,23 +2217,28 @@
|
|
|
2167
2217
|
}
|
|
2168
2218
|
|
|
2169
2219
|
// Draw peak
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2220
|
+
const peakValue = bar.peak[channel],
|
|
2221
|
+
peakAlpha = bar.alpha[channel];
|
|
2222
|
+
if (peakValue > 0 && peakAlpha > 0 && showPeaks && !showPeakLine && !isLumi && posX >= initialX && posX < finalX) {
|
|
2223
|
+
// set opacity for peak
|
|
2224
|
+
if (_fadePeaks) _ctx.globalAlpha = peakAlpha;else if (isOutline && _lineWidth > 0)
|
|
2225
|
+
// when lineWidth == 0 ctx.globalAlpha remains set to `fillAlpha`
|
|
2226
|
+
_ctx.globalAlpha = 1;else if (isAlpha)
|
|
2227
|
+
// isAlpha (alpha based on peak value) supersedes fillAlpha if lineWidth == 0
|
|
2228
|
+
_ctx.globalAlpha = peakValue;
|
|
2174
2229
|
|
|
2175
2230
|
// select the peak color for 'bar-level' colorMode or 'trueLeds'
|
|
2176
|
-
if (_colorMode == COLOR_BAR_LEVEL || isTrueLeds) setBarColor(
|
|
2231
|
+
if (_colorMode == COLOR_BAR_LEVEL || isTrueLeds) setBarColor(peakValue);
|
|
2177
2232
|
|
|
2178
2233
|
// render peak according to current mode / effect
|
|
2179
2234
|
if (isLeds) {
|
|
2180
|
-
const ledPeak = ledPosY(
|
|
2235
|
+
const ledPeak = ledPosY(peakValue);
|
|
2181
2236
|
if (ledPeak >= ledSpaceV)
|
|
2182
2237
|
// avoid peak below first led
|
|
2183
2238
|
_ctx.fillRect(posX, analyzerBottom - ledPeak, width, ledHeight);
|
|
2184
|
-
} else if (!_radial) _ctx.fillRect(posX, analyzerBottom -
|
|
2239
|
+
} else if (!_radial) _ctx.fillRect(posX, analyzerBottom - peakValue * maxBarHeight, width, 2);else if (_mode != MODE_GRAPH) {
|
|
2185
2240
|
// radial (peaks for graph mode are done by the peakLine code)
|
|
2186
|
-
const y =
|
|
2241
|
+
const y = peakValue * maxBarHeight;
|
|
2187
2242
|
radialPoly(posX, y, width, !this._radialInvert || isDualVertical || y + innerRadius >= 2 ? -2 : 2);
|
|
2188
2243
|
}
|
|
2189
2244
|
}
|
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.5.0
|
|
4
|
+
"version": "4.5.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./src/audioMotion-analyzer.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
@@ -43,9 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://audiomotion.dev",
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@babel/cli": "^7.24.
|
|
47
|
-
"@babel/core": "^7.24.
|
|
48
|
-
"@babel/plugin-transform-modules-umd": "^7.24.1"
|
|
49
|
-
"@babel/preset-env": "^7.24.3"
|
|
46
|
+
"@babel/cli": "^7.24.5",
|
|
47
|
+
"@babel/core": "^7.24.5",
|
|
48
|
+
"@babel/plugin-transform-modules-umd": "^7.24.1"
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -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.5.0
|
|
5
|
+
* @version 4.5.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.5.0
|
|
10
|
+
const VERSION = '4.5.0';
|
|
11
11
|
|
|
12
12
|
// internal constants
|
|
13
13
|
const PI = Math.PI,
|
|
@@ -88,11 +88,12 @@ const DEFAULT_SETTINGS = {
|
|
|
88
88
|
bgAlpha : 0.7,
|
|
89
89
|
channelLayout : CHANNEL_SINGLE,
|
|
90
90
|
colorMode : COLOR_GRADIENT,
|
|
91
|
+
fadePeaks : false,
|
|
91
92
|
fftSize : 8192,
|
|
92
93
|
fillAlpha : 1,
|
|
93
94
|
frequencyScale : SCALE_LOG,
|
|
94
95
|
gradient : GRADIENTS[0][0],
|
|
95
|
-
gravity :
|
|
96
|
+
gravity : 3.8,
|
|
96
97
|
height : undefined,
|
|
97
98
|
ledBars : false,
|
|
98
99
|
linearAmplitude: false,
|
|
@@ -110,6 +111,8 @@ const DEFAULT_SETTINGS = {
|
|
|
110
111
|
noteLabels : false,
|
|
111
112
|
outlineBars : false,
|
|
112
113
|
overlay : false,
|
|
114
|
+
peakFadeTime : 750,
|
|
115
|
+
peakHoldTime : 500,
|
|
113
116
|
peakLine : false,
|
|
114
117
|
radial : false,
|
|
115
118
|
radialInvert : false,
|
|
@@ -446,6 +449,13 @@ class AudioMotionAnalyzer {
|
|
|
446
449
|
this._colorMode = validateFromList( value, [ COLOR_GRADIENT, COLOR_BAR_INDEX, COLOR_BAR_LEVEL ] );
|
|
447
450
|
}
|
|
448
451
|
|
|
452
|
+
get fadePeaks() {
|
|
453
|
+
return this._fadePeaks;
|
|
454
|
+
}
|
|
455
|
+
set fadePeaks( value ) {
|
|
456
|
+
this._fadePeaks = !! value;
|
|
457
|
+
}
|
|
458
|
+
|
|
449
459
|
get fftSize() {
|
|
450
460
|
return this._analyzer[0].fftSize;
|
|
451
461
|
}
|
|
@@ -633,6 +643,20 @@ class AudioMotionAnalyzer {
|
|
|
633
643
|
this._calcBars();
|
|
634
644
|
}
|
|
635
645
|
|
|
646
|
+
get peakFadeTime() {
|
|
647
|
+
return this._peakFadeTime;
|
|
648
|
+
}
|
|
649
|
+
set peakFadeTime( value ) {
|
|
650
|
+
this._peakFadeTime = value >= 0 ? +value : this._peakFadeTime || DEFAULT_SETTINGS.peakFadeTime;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
get peakHoldTime() {
|
|
654
|
+
return this._peakHoldTime;
|
|
655
|
+
}
|
|
656
|
+
set peakHoldTime( value ) {
|
|
657
|
+
this._peakHoldTime = +value || 0;
|
|
658
|
+
}
|
|
659
|
+
|
|
636
660
|
get peakLine() {
|
|
637
661
|
return this._peakLine;
|
|
638
662
|
}
|
|
@@ -1289,9 +1313,23 @@ class AudioMotionAnalyzer {
|
|
|
1289
1313
|
* unitWidth
|
|
1290
1314
|
*/
|
|
1291
1315
|
|
|
1292
|
-
// helper function
|
|
1293
|
-
// bar object:
|
|
1294
|
-
|
|
1316
|
+
// helper function to add a bar to the bars array
|
|
1317
|
+
// bar object format:
|
|
1318
|
+
// {
|
|
1319
|
+
// posX,
|
|
1320
|
+
// freq,
|
|
1321
|
+
// freqLo,
|
|
1322
|
+
// freqHi,
|
|
1323
|
+
// binLo,
|
|
1324
|
+
// binHi,
|
|
1325
|
+
// ratioLo,
|
|
1326
|
+
// ratioHi,
|
|
1327
|
+
// peak, // peak value
|
|
1328
|
+
// hold, // peak hold frames (negative value indicates peak falling / fading)
|
|
1329
|
+
// alpha, // peak alpha (used by fadePeaks)
|
|
1330
|
+
// value // current bar value
|
|
1331
|
+
// }
|
|
1332
|
+
const barsPush = args => bars.push( { ...args, peak: [0,0], hold: [0], alpha: [0], value: [0] } );
|
|
1295
1333
|
|
|
1296
1334
|
/*
|
|
1297
1335
|
A simple interpolation is used to obtain an approximate amplitude value for any given frequency,
|
|
@@ -1773,9 +1811,9 @@ class AudioMotionAnalyzer {
|
|
|
1773
1811
|
_colorMode,
|
|
1774
1812
|
_ctx,
|
|
1775
1813
|
_energy,
|
|
1814
|
+
_fadePeaks,
|
|
1776
1815
|
fillAlpha,
|
|
1777
1816
|
_fps,
|
|
1778
|
-
_gravity,
|
|
1779
1817
|
_linearAmplitude,
|
|
1780
1818
|
_lineWidth,
|
|
1781
1819
|
maxDecibels,
|
|
@@ -1791,7 +1829,10 @@ class AudioMotionAnalyzer {
|
|
|
1791
1829
|
|
|
1792
1830
|
canvasX = this._scaleX.canvas,
|
|
1793
1831
|
canvasR = this._scaleR.canvas,
|
|
1794
|
-
|
|
1832
|
+
fadeFrames = _fps * this._peakFadeTime / 1e3,
|
|
1833
|
+
fpsSquared = _fps ** 2,
|
|
1834
|
+
gravity = this._gravity * 1e3,
|
|
1835
|
+
holdFrames = _fps * this._peakHoldTime / 1e3,
|
|
1795
1836
|
isDualCombined = _chLayout == CHANNEL_COMBINED,
|
|
1796
1837
|
isDualHorizontal = _chLayout == CHANNEL_HORIZONTAL,
|
|
1797
1838
|
isDualVertical = _chLayout == CHANNEL_VERTICAL,
|
|
@@ -1801,6 +1842,7 @@ class AudioMotionAnalyzer {
|
|
|
1801
1842
|
finalX = initialX + analyzerWidth,
|
|
1802
1843
|
showPeakLine = showPeaks && this._peakLine && _mode == MODE_GRAPH,
|
|
1803
1844
|
maxBarHeight = _radial ? outerRadius - innerRadius : analyzerHeight,
|
|
1845
|
+
nominalMaxHeight = maxBarHeight / this._pixelRatio, // for consistent gravity on lo-res or hi-dpi
|
|
1804
1846
|
dbRange = maxDecibels - minDecibels,
|
|
1805
1847
|
[ ledCount, ledSpaceH, ledSpaceV, ledHeight ] = this._leds || [];
|
|
1806
1848
|
|
|
@@ -1918,7 +1960,8 @@ class AudioMotionAnalyzer {
|
|
|
1918
1960
|
if ( _energy.peak > 0 ) {
|
|
1919
1961
|
_energy.hold--;
|
|
1920
1962
|
if ( _energy.hold < 0 )
|
|
1921
|
-
_energy.peak += _energy.hold
|
|
1963
|
+
_energy.peak += _energy.hold * gravity / fpsSquared / canvas.height * this._pixelRatio;
|
|
1964
|
+
// TO-DO: replace `canvas.height * this._pixelRatio` with `maxNominalHeight` when implementing dual-channel energy
|
|
1922
1965
|
}
|
|
1923
1966
|
if ( newVal >= _energy.peak ) {
|
|
1924
1967
|
_energy.peak = newVal;
|
|
@@ -2146,17 +2189,28 @@ class AudioMotionAnalyzer {
|
|
|
2146
2189
|
currentEnergy += barValue;
|
|
2147
2190
|
|
|
2148
2191
|
// update bar peak
|
|
2149
|
-
if ( bar.peak[ channel ] > 0 ) {
|
|
2192
|
+
if ( bar.peak[ channel ] > 0 && bar.alpha[ channel ] > 0 ) {
|
|
2150
2193
|
bar.hold[ channel ]--;
|
|
2151
|
-
// if hold is negative,
|
|
2152
|
-
if ( bar.hold[ channel ] < 0 )
|
|
2153
|
-
|
|
2194
|
+
// if hold is negative, start peak drop or fade out
|
|
2195
|
+
if ( bar.hold[ channel ] < 0 ) {
|
|
2196
|
+
if ( _fadePeaks && ! showPeakLine ) {
|
|
2197
|
+
const initialAlpha = ! isAlpha || ( isOutline && _lineWidth > 0 ) ? 1 : isAlpha ? bar.peak[ channel ] : fillAlpha;
|
|
2198
|
+
bar.alpha[ channel ] = initialAlpha * ( 1 + bar.hold[ channel ] / fadeFrames ); // hold is negative, so this is <= 1
|
|
2199
|
+
}
|
|
2200
|
+
else
|
|
2201
|
+
bar.peak[ channel ] += bar.hold[ channel ] * gravity / fpsSquared / nominalMaxHeight;
|
|
2202
|
+
// make sure the peak value is reset when using fadePeaks
|
|
2203
|
+
if ( bar.alpha[ channel ] <= 0 )
|
|
2204
|
+
bar.peak[ channel ] = 0;
|
|
2205
|
+
}
|
|
2154
2206
|
}
|
|
2155
2207
|
|
|
2156
2208
|
// check if it's a new peak for this bar
|
|
2157
2209
|
if ( barValue >= bar.peak[ channel ] ) {
|
|
2158
2210
|
bar.peak[ channel ] = barValue;
|
|
2159
2211
|
bar.hold[ channel ] = holdFrames;
|
|
2212
|
+
// check whether isAlpha or isOutline are active to start the peak alpha with the proper value
|
|
2213
|
+
bar.alpha[ channel ] = ! isAlpha || ( isOutline && _lineWidth > 0 ) ? 1 : isAlpha ? barValue : fillAlpha;
|
|
2160
2214
|
}
|
|
2161
2215
|
|
|
2162
2216
|
// if not using the canvas, move earlier to the next bar
|
|
@@ -2164,10 +2218,7 @@ class AudioMotionAnalyzer {
|
|
|
2164
2218
|
continue;
|
|
2165
2219
|
|
|
2166
2220
|
// set opacity for bar effects
|
|
2167
|
-
|
|
2168
|
-
_ctx.globalAlpha = barValue;
|
|
2169
|
-
else if ( isOutline )
|
|
2170
|
-
_ctx.globalAlpha = fillAlpha;
|
|
2221
|
+
_ctx.globalAlpha = ( isLumi || isAlpha ) ? barValue : ( isOutline ) ? fillAlpha : 1;
|
|
2171
2222
|
|
|
2172
2223
|
// set fillStyle and strokeStyle for the current bar
|
|
2173
2224
|
setBarColor( barValue, barIndex );
|
|
@@ -2263,28 +2314,32 @@ class AudioMotionAnalyzer {
|
|
|
2263
2314
|
}
|
|
2264
2315
|
|
|
2265
2316
|
// Draw peak
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2317
|
+
const peakValue = bar.peak[ channel ],
|
|
2318
|
+
peakAlpha = bar.alpha[ channel ];
|
|
2319
|
+
|
|
2320
|
+
if ( peakValue > 0 && peakAlpha > 0 && showPeaks && ! showPeakLine && ! isLumi && posX >= initialX && posX < finalX ) {
|
|
2321
|
+
// set opacity for peak
|
|
2322
|
+
if ( _fadePeaks )
|
|
2323
|
+
_ctx.globalAlpha = peakAlpha;
|
|
2324
|
+
else if ( isOutline && _lineWidth > 0 ) // when lineWidth == 0 ctx.globalAlpha remains set to `fillAlpha`
|
|
2270
2325
|
_ctx.globalAlpha = 1;
|
|
2271
|
-
else if ( isAlpha )
|
|
2272
|
-
_ctx.globalAlpha =
|
|
2326
|
+
else if ( isAlpha ) // isAlpha (alpha based on peak value) supersedes fillAlpha if lineWidth == 0
|
|
2327
|
+
_ctx.globalAlpha = peakValue;
|
|
2273
2328
|
|
|
2274
2329
|
// select the peak color for 'bar-level' colorMode or 'trueLeds'
|
|
2275
2330
|
if ( _colorMode == COLOR_BAR_LEVEL || isTrueLeds )
|
|
2276
|
-
setBarColor(
|
|
2331
|
+
setBarColor( peakValue );
|
|
2277
2332
|
|
|
2278
2333
|
// render peak according to current mode / effect
|
|
2279
2334
|
if ( isLeds ) {
|
|
2280
|
-
const ledPeak = ledPosY(
|
|
2335
|
+
const ledPeak = ledPosY( peakValue );
|
|
2281
2336
|
if ( ledPeak >= ledSpaceV ) // avoid peak below first led
|
|
2282
2337
|
_ctx.fillRect( posX, analyzerBottom - ledPeak, width, ledHeight );
|
|
2283
2338
|
}
|
|
2284
2339
|
else if ( ! _radial )
|
|
2285
|
-
_ctx.fillRect( posX, analyzerBottom -
|
|
2340
|
+
_ctx.fillRect( posX, analyzerBottom - peakValue * maxBarHeight, width, 2 );
|
|
2286
2341
|
else if ( _mode != MODE_GRAPH ) { // radial (peaks for graph mode are done by the peakLine code)
|
|
2287
|
-
const y =
|
|
2342
|
+
const y = peakValue * maxBarHeight;
|
|
2288
2343
|
radialPoly( posX, y, width, ! this._radialInvert || isDualVertical || y + innerRadius >= 2 ? -2 : 2 );
|
|
2289
2344
|
}
|
|
2290
2345
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface Options {
|
|
|
22
22
|
bgAlpha?: number;
|
|
23
23
|
channelLayout?: ChannelLayout;
|
|
24
24
|
colorMode?: ColorMode;
|
|
25
|
+
fadePeaks?: boolean;
|
|
25
26
|
fftSize?: number;
|
|
26
27
|
fillAlpha?: number;
|
|
27
28
|
frequencyScale?: FrequencyScale;
|
|
@@ -48,6 +49,8 @@ export interface Options {
|
|
|
48
49
|
onCanvasResize?: OnCanvasResizeFunction;
|
|
49
50
|
outlineBars?: boolean;
|
|
50
51
|
overlay?: boolean;
|
|
52
|
+
peakFadeTime?: number;
|
|
53
|
+
peakHoldTime?: number;
|
|
51
54
|
peakLine?: boolean;
|
|
52
55
|
radial?: boolean;
|
|
53
56
|
radialInvert?: boolean;
|
|
@@ -144,6 +147,9 @@ declare class AudioMotionAnalyzer {
|
|
|
144
147
|
get connectedSources(): AudioNode[];
|
|
145
148
|
get connectedTo(): AudioNode[];
|
|
146
149
|
|
|
150
|
+
get fadePeaks(): boolean;
|
|
151
|
+
set fadePeaks(value: boolean);
|
|
152
|
+
|
|
147
153
|
get fftSize(): number;
|
|
148
154
|
set fftSize(value: number);
|
|
149
155
|
|
|
@@ -234,6 +240,12 @@ declare class AudioMotionAnalyzer {
|
|
|
234
240
|
|
|
235
241
|
public overlay: boolean;
|
|
236
242
|
|
|
243
|
+
get peakFadeTime(): number;
|
|
244
|
+
set peakFadeTime(value: number);
|
|
245
|
+
|
|
246
|
+
get peakHoldTime(): number;
|
|
247
|
+
set peakHoldTime(value: number);
|
|
248
|
+
|
|
237
249
|
get peakLine(): boolean;
|
|
238
250
|
set peakLine(value: boolean);
|
|
239
251
|
|