Skip to content

Commit c26ae48

Browse files
author
Sammy Sammon
committed
Added perceived pitch rendering
1 parent e524e04 commit c26ae48

File tree

2 files changed

+64
-22
lines changed

2 files changed

+64
-22
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
<body style="background-color: #252833">
2525
<input id="fileinput" style="color:white;" type="file" accept="audio/*" />
2626
<audio id="player"></audio>
27-
<canvas style="padding:0; margin:0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);" id="canvas" width="920px" height="45px"></canvas>
27+
<canvas style="padding:0; margin:0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);" id="canvas" width="920px" height="50px"></canvas>
2828
</body>
2929
</html>

visualizer.js

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//Note: bins needs to be a power of 2
2-
let bins = 64;
2+
let displayBins = 512;
33
let backgroundColour = "#262626";
44
let barColour = "#F1910C";
55
let songFont = "15px 'Open Sans'";
@@ -8,6 +8,8 @@ let songFont = "15px 'Open Sans'";
88
//it past 96. If your audio stream is quiet though, you'll want to reduce this.
99
let floorLevel = 96;
1010

11+
let drawPitch = true;
12+
1113
//Can't touch this
1214
let audioContext;
1315
let audioBuffer;
@@ -19,6 +21,9 @@ let freqLookup = [];
1921
let canvasContext;
2022
let canvasWidth;
2123
let canvasHeight;
24+
let multiplier;
25+
26+
let magicConstant = 42; //Meaning of everything. I don't know why this works.
2227

2328
function initializeVisualizer(canvasElement, audioElement) {
2429
try {
@@ -41,7 +46,8 @@ function setupAudioApi(audioElement) {
4146

4247
audioAnalyserNode = audioContext.createAnalyser();
4348
//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));
4551

4652
src.connect(audioAnalyserNode);
4753
audioAnalyserNode.connect(audioContext.destination);
@@ -61,19 +67,15 @@ function initCanvas(canvasElement) {
6167
}
6268

6369
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));
6571
}
6672

6773
function initFreqLookupTable() {
68-
let lastPoint = 0;
6974
let bins = audioAnalyserNode.frequencyBinCount;
70-
for(let i = 0; i < bins / 2; i++) {
75+
for (let i = 0; i < bins; i++) {
7176
//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);
7779
freqLookup.push(point);
7880
}
7981
}
@@ -94,18 +96,58 @@ function paint() {
9496
audioAnalyserNode.getByteFrequencyData(data);
9597
canvasContext.fillStyle = barColour;
9698

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+
108104
canvasContext.fillStyle = 'white';
109105
//Note: the 15's here need to be changed if you change the font size
110106
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);
111153
}

0 commit comments

Comments
 (0)