-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvisualiser.js
More file actions
197 lines (172 loc) · 7.04 KB
/
Copy pathvisualiser.js
File metadata and controls
197 lines (172 loc) · 7.04 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
'use strict';
// Initialise variables related to the audio player.
let audioElement = document.getElementById('audio-player');
let audioContext = new AudioContext();
let audioSource = audioContext.createMediaElementSource(audioElement);
let audioAnalyser = audioContext.createAnalyser();
audioSource.connect(audioAnalyser);
audioSource.connect(audioContext.destination);
let scaler = document.getElementById('scaler');
let scalerLabel = document.getElementById('scaler-label');
let fullscreenButton = document.getElementById('fullscreen-button');
let controls = document.getElementById('controls');
// Initialise the waveform data.
const dataPointCount = 1024;
audioAnalyser.fftSize = dataPointCount;
let waveformData = new Float32Array(dataPointCount);
// Initialise variables related to the canvas.
let canvas = document.getElementById('visualiser');
let canvasContext = canvas.getContext('2d');
// Initialise the waveform to 0 at all points.
let topPath = new Array(dataPointCount);
let bottomPath = new Array(dataPointCount);
for (let i = 0; i < dataPointCount; ++i) {
topPath[i] = 0.0;
bottomPath[i] = 0.0;
}
// Assign the constant speed factors by which the waveform rises and falls.
const waveformRise = 0.4;
const waveformFall = 0.2;
function renderFrame() {
// Build the waveform path to be rendered later.
audioAnalyser.getFloatTimeDomainData(waveformData);
for (let i = 0; i < dataPointCount; ++i) {
let topValue = 0.0;
let bottomValue = 0.0;
let currentValue = waveformData[i];
if (currentValue < 0.0) {
topValue = currentValue;
} else {
bottomValue = currentValue;
}
// Allow the waveform to rise faster than it falls.
if (topValue < topPath[i]) {
topPath[i] = (topValue - topPath[i]) * waveformRise + topPath[i];
} else {
topPath[i] = (topValue - topPath[i]) * waveformFall + topPath[i];
}
if (bottomValue > bottomPath[i]) {
bottomPath[i] = (bottomValue - bottomPath[i]) * waveformRise + bottomPath[i];
} else {
bottomPath[i] = (bottomValue - bottomPath[i]) * waveformFall + bottomPath[i];
}
}
// Scale the waveform amplitude such that volume is not a factor.
let volumeScaleFactor = (audioElement.volume > 0.01) ? (1.0 / audioElement.volume) : 1.0;
// Scale the waveform amplitude based on a user-adjustable slider.
let amplitudeScaleFactor = scaler.value / 100.0;
let newLabel = 'Amplitude Scale Factor: ' + amplitudeScaleFactor.toFixed(3);
if (scalerLabel.textContent != newLabel) scalerLabel.textContent = newLabel;
// Begin drawing to the canvas, first by saving the current canvas state.
canvasContext.save();
// Cover the canvas with a transparent background colour to fade what currently exists.
canvasContext.fillStyle = 'rgba(80%, 86%, 92%, 0.75)';
canvasContext.globalCompositeOperation = 'multiply';
canvasContext.fillRect(0, 0, canvas.width, canvas.height);
canvasContext.globalCompositeOperation = 'source-over';
// Draw the paths in 'topPath' and 'bottomPath' as one solid object.
let canvasVerticalCentre = canvas.height * 0.5;
canvasContext.beginPath();
canvasContext.moveTo(0, canvasVerticalCentre);
for (let i = 0; i < dataPointCount; ++i) {
let x = (i / (dataPointCount - 1)) * canvas.width;
canvasContext.lineTo(x, canvasVerticalCentre + topPath[i] * canvasVerticalCentre
* amplitudeScaleFactor * volumeScaleFactor);
}
for (let i = dataPointCount - 1; i >= 0; --i) {
let x = (i / (dataPointCount - 1)) * canvas.width;
canvasContext.lineTo(x, canvasVerticalCentre + bottomPath[i] * canvasVerticalCentre
* amplitudeScaleFactor * volumeScaleFactor);
}
canvasContext.fillStyle = '#EEFFEE';
// Add a shadow, as a glow effect.
canvasContext.shadowBlur = 8;
canvasContext.shadowColor = 'rgba(40%, 100%, 80%, 0.2)';
// Perform the drawing.
canvasContext.fill();
// Restore the canvas to its original state.
canvasContext.restore();
// Request that this function be called again when appropriate.
window.requestAnimationFrame(renderFrame);
}
// Handle changes to the file input.
let audioFileInput = document.getElementById('audio-file-input');
function audioFileChanged() {
if (audioFileInput.files.length) {
audioElement.src = URL.createObjectURL(audioFileInput.files[0]);
}
}
// If a file is already specified from a previous page view, load it immediately.
audioFileChanged();
window.onresize = function(event) {
// Resize the canvas to match the new page size.
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Blank the canvas with the black background colour.
canvasContext.save();
canvasContext.fillStyle = '#030408';
canvasContext.globalCompositeOperation = 'source-over';
canvasContext.fillRect(0, 0, canvas.width, canvas.height);
canvasContext.restore();
}
// Resize the canvas to match the initial window size.
window.onresize();
// Hide the mouse cursor and controls if the mouse is stationary over the canvas for a while.
let inactivityTimeout;
const inactivityTimeoutPeriod = 2500;
document.body.onmousemove = function(event) {
// The cursor has just moved, so display everything and reset the timeout.
if (document.documentElement.style.cursor != 'default') {
document.documentElement.style.cursor = 'default';
}
if (controls.style.display != '') {
controls.style.display = '';
}
clearTimeout(inactivityTimeout);
inactivityTimeout = setTimeout(hideCursorAndControls, inactivityTimeoutPeriod);
}
function hideCursorAndControls() {
// If the cursor is over the controls, don't hide anything -- reset the timer.
if (document.body.querySelector(':hover') == controls) {
inactivityTimeout = setTimeout(hideCursorAndControls, inactivityTimeoutPeriod);
// Otherwise, hide the cursor and controls via CSS.
} else {
document.documentElement.style.cursor = 'none';
controls.style.display = 'none';
}
}
// Initialise the inactivity timer from the beginning.
inactivityTimeout = setTimeout(hideCursorAndControls, inactivityTimeoutPeriod);
// Handle the fullscreen toggle across all supported modern browsers.
function toggleFullscreen() {
if ( !document.fullscreenElement
&& !document.webkitFullscreenElement
&& !document.mozFullScreenElement
&& !document.msFullscreenElement)
{
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.msRequestFullscreen) {
document.documentElement.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
// Toggle fullscreen either via the dedicated button or double clicking the canvas.
fullscreenButton.onclick = toggleFullscreen;
canvas.ondblclick = toggleFullscreen;
// Start the rendering loop.
window.requestAnimationFrame(renderFrame);