-
Notifications
You must be signed in to change notification settings - Fork 0
/
reference.js
112 lines (102 loc) · 4.28 KB
/
reference.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Set up audio context
window.AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
/**
* Retrieves audio from an external source, the initializes the drawing function
* @param {String} url the url of the audio we'd like to fetch
*/
const drawAudio = url => {
fetch(url)
.then(response => response.arrayBuffer())
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
.then(audioBuffer => draw(normalizeData(filterData(audioBuffer))));
};
/**
* Filters the AudioBuffer retrieved from an external source
* @param {AudioBuffer} audioBuffer the AudioBuffer from drawAudio()
* @returns {Array} an array of floating point numbers
*/
const filterData = audioBuffer => {
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
const samples = 70; // Number of samples we want to have in our final data set
const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
const filteredData = [];
for (let i = 0; i < samples; i++) {
let blockStart = blockSize * i; // the location of the first sample in the block
let sum = 0;
for (let j = 0; j < blockSize; j++) {
sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
}
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
}
return filteredData;
};
/**
* Normalizes the audio data to make a cleaner illustration
* @param {Array} filteredData the data from filterData()
* @returns {Array} an normalized array of floating point numbers
*/
const normalizeData = filteredData => {
const multiplier = Math.pow(Math.max(...filteredData), -1);
return filteredData.map(n => n * multiplier);
}
/**
* Draws the audio file into a canvas element.
* @param {Array} normalizedData The filtered array returned from filterData()
* @returns {Array} a normalized array of data
*/
const draw = normalizedData => {
// set up the canvas
const canvas = document.querySelector("canvas");
const dpr = window.devicePixelRatio || 1;
const padding = 20;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = (canvas.offsetHeight + padding * 2) * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.translate(0, canvas.offsetHeight / 2 + padding); // set Y = 0 to be in the middle of the canvas
// draw the line segments
const width = canvas.offsetWidth / normalizedData.length;
for (let i = 0; i < normalizedData.length; i++) {
const x = width * i;
let height = normalizedData[i] * canvas.offsetHeight - padding;
if (height < 0) {
height = 0;
} else if (height > canvas.offsetHeight / 2) {
height = height > canvas.offsetHeight / 2;
}
drawLineSegment(ctx, x, height, width, (i + 1) % 2);
}
};
/**
* A utility function for drawing our line segments
* @param {AudioContext} ctx the audio context
* @param {number} x the x coordinate of the beginning of the line segment
* @param {number} height the desired height of the line segment
* @param {number} width the desired width of the line segment
* @param {boolean} isEven whether or not the segmented is even-numbered
*/
const drawLineSegment = (ctx, x, height, width, isEven) => {
ctx.lineWidth = 1; // how thick the line is
ctx.strokeStyle = "#fff"; // what color our line is
ctx.beginPath();
height = isEven ? height : -height;
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.arc(x + width / 2, height, width / 2, Math.PI, 0, isEven);
ctx.lineTo(x + width, 0);
ctx.stroke();
};
drawAudio('https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/shoptalk-clip.mp3');