1
1
//Note: bins needs to be a power of 2
2
- let bins = 64 ;
2
+ let displayBins = 512 ;
3
3
let backgroundColour = "#262626" ;
4
4
let barColour = "#F1910C" ;
5
5
let songFont = "15px 'Open Sans'" ;
@@ -8,6 +8,8 @@ let songFont = "15px 'Open Sans'";
8
8
//it past 96. If your audio stream is quiet though, you'll want to reduce this.
9
9
let floorLevel = 96 ;
10
10
11
+ let drawPitch = true ;
12
+
11
13
//Can't touch this
12
14
let audioContext ;
13
15
let audioBuffer ;
@@ -19,6 +21,9 @@ let freqLookup = [];
19
21
let canvasContext ;
20
22
let canvasWidth ;
21
23
let canvasHeight ;
24
+ let multiplier ;
25
+
26
+ let magicConstant = 42 ; //Meaning of everything. I don't know why this works.
22
27
23
28
function initializeVisualizer ( canvasElement , audioElement ) {
24
29
try {
@@ -41,7 +46,8 @@ function setupAudioApi(audioElement) {
41
46
42
47
audioAnalyserNode = audioContext . createAnalyser ( ) ;
43
48
//FFT node takes in 2 samples per bin, and we internally use 2 samples per bin
44
- audioAnalyserNode . fftSize = bins * 4 ;
49
+ audioAnalyserNode . fftSize = drawPitch ? 8192 : displayBins * 2 ;
50
+ multiplier = Math . pow ( 2 , Math . log2 ( displayBins ) / ( 22050 / magicConstant ) ) ;
45
51
46
52
src . connect ( audioAnalyserNode ) ;
47
53
audioAnalyserNode . connect ( audioContext . destination ) ;
@@ -61,19 +67,15 @@ function initCanvas(canvasElement) {
61
67
}
62
68
63
69
function getFreqPoint ( start , stop , n , binCount ) {
64
- return start * Math . pow ( stop / start , n / ( binCount - 1 ) ) ;
70
+ return start * Math . exp ( ( Math . log ( stop ) - Math . log ( start ) ) * ( n / binCount ) ) ;
65
71
}
66
72
67
73
function initFreqLookupTable ( ) {
68
- let lastPoint = 0 ;
69
74
let bins = audioAnalyserNode . frequencyBinCount ;
70
- for ( let i = 0 ; i < bins / 2 ; i ++ ) {
75
+ for ( let i = 0 ; i < bins ; i ++ ) {
71
76
//Scale to perceived frequency distribution
72
- let newFreq = getFreqPoint ( 20 , 20000 , i * 2 , bins ) ;
73
- let point = Math . floor ( bins * newFreq / 20000 ) ;
74
- while ( point <= lastPoint )
75
- point ++ ;
76
- lastPoint = point ;
77
+ let freqStart = getFreqPoint ( 1 , 22025 , i , bins ) ;
78
+ let point = Math . floor ( bins * freqStart / 22025 ) ;
77
79
freqLookup . push ( point ) ;
78
80
}
79
81
}
@@ -94,18 +96,58 @@ function paint() {
94
96
audioAnalyserNode . getByteFrequencyData ( data ) ;
95
97
canvasContext . fillStyle = barColour ;
96
98
97
- for ( let i = 0 ; i < bins ; i ++ ) {
98
- let point = freqLookup [ i ] ;
99
- //Pretty much any volume will push it over [floorLevel] so we set that as the bottom threshold
100
- //I suspect I should be doing a logarithmic space for the volume as well
101
- let height = Math . max ( 0 , ( data [ point ] - floorLevel ) ) ;
102
- //Scale to the height of the bar
103
- //Since we change the base level in the previous operations, 256 should be changed to 160 (i think) if we want it to go all the way to the top
104
- height = ( height / ( 256 - floorLevel ) ) * canvasHeight * 0.8 ;
105
- let width = Math . ceil ( canvasWidth / ( ( bins / 2 ) - 1 ) ) ;
106
- canvasContext . fillRect ( i * width , canvasHeight - height , width , height ) ;
107
- }
99
+ if ( drawPitch )
100
+ paintLogBins ( bins , data )
101
+ else
102
+ paintBins ( bins , data ) ;
103
+
108
104
canvasContext . fillStyle = 'white' ;
109
105
//Note: the 15's here need to be changed if you change the font size
110
106
canvasContext . fillText ( songText , canvasWidth / 2 - textSize . width / 2 , canvasHeight / 2 - 15 / 2 + 15 ) ;
107
+ }
108
+
109
+ //Inclusive lower, exclusive upper except with stop == start
110
+ function averageRegion ( data , start , stop ) {
111
+ if ( stop <= start )
112
+ return data [ start ] ;
113
+
114
+ let sum = 0 ;
115
+ for ( let i = start ; i < stop ; i ++ ) {
116
+ sum += data [ i ] ;
117
+ }
118
+ return sum / ( stop - start ) ;
119
+ }
120
+
121
+ function paintBins ( bins , data ) {
122
+ let step = bins / displayBins ;
123
+ for ( let i = 0 ; i < displayBins ; i ++ ) {
124
+ let lower = i * step ;
125
+ let upper = ( i + 1 ) * step - 1 ;
126
+ let binValue = averageRegion ( data , lower , upper ) ;
127
+
128
+ paintSingleBin ( binValue , i ) ;
129
+ }
130
+ }
131
+
132
+ function paintLogBins ( bins , data ) {
133
+ let lastFrequency = magicConstant / multiplier ;
134
+ for ( let i = 0 ; i < displayBins ; i ++ ) {
135
+ let thisFreq = lastFrequency * multiplier ;
136
+ lastFrequency = thisFreq ;
137
+ let binIndex = Math . floor ( bins * thisFreq / 22050 ) ;
138
+ let binValue = data [ binIndex ] ;
139
+
140
+ paintSingleBin ( binValue , i ) ;
141
+ }
142
+ }
143
+
144
+ function paintSingleBin ( binValue , i ) {
145
+ //Pretty much any volume will push it over [floorLevel] so we set that as the bottom threshold
146
+ //I suspect I should be doing a logarithmic space for the volume as well
147
+ let height = Math . max ( 0 , ( binValue - floorLevel ) ) ;
148
+ //Scale to the height of the bar
149
+ //Since we change the base level in the previous operations, 256 should be changed to 160 (i think) if we want it to go all the way to the top
150
+ height = ( height / ( 256 - floorLevel ) ) * canvasHeight * 0.8 ;
151
+ let width = Math . ceil ( canvasWidth / ( displayBins - 1 ) ) ;
152
+ canvasContext . fillRect ( i * width , canvasHeight - height , width , height ) ;
111
153
}
0 commit comments