-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
202 lines (174 loc) · 7.21 KB
/
index.html
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
198
199
200
201
202
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="index.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/APlayer.min.css">
<title>Document</title>
</head>
<body>
<div className='visu' id='visu'>
<div id='player'></div>
<img
src="https://github.com/yadPe/yadpe.github.io/blob/visualizer/src/components/pages/audiovisualizer/cursor.png?raw=true"
id="cursorImg" />
<div id="newBrightnessFilter" class="background filter layer">
<canvas id="visualizer"></canvas>
<div id="blurFilter" class="background filter">
<div id="saturateFilter" class="background filter layer"></div>
<div id="background" class="background layer"></div>
<canvas id="particles"></canvas>
<div id="brightnessFilterLeft" class="brightnessFilter layer"></div>
<div id="brightnessFilterRight" class="brightnessFilter layer"></div>
</div>
</div>
</div>
<div id="vidtop-content">
<div class="vid-info">
<h1>Try to move your cursor over the spectrum</h1>
<p>Audio visualization using the canvas API, a Web Worker, the AudioContext
API, some sprite batching technics and APLayer for the player.
<p>Two canvas tags are used to render the image, the first one is controlled by the main process that is in
charge of playing the audio and applying effect in real time on it, while animating the spectrum. The second
one is controlled by a web worker and only takes care of the particles effect, it can animate up to 3000
particles using sprite batching, limiting the amount of gpu calls per frame.
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/APlayer.min.js"></script>
<script type="text/js-worker">
const randomNum = (min, max) => Math.random() * (max - min) + min;
const convertRange = (OldValue, OldMax, OldMin, NewMax, NewMin) => {
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
}
let canvas, ctx, canvasHeight = 800, canvasWidth = 800, particlesToAnimate, speedRatio = 1;
const imgArr = [], particles = [];
console.log('worker: ini')
self.addEventListener('message', e => { // eslint-disable-line no-restricted-globals
const message = e.data.msg;
const data = e.data;
switch (message) {
case 'ini':
console.log('Worker INIT')
init(data);
break;
case 'addParticles':
addParticles(data.amount);
break;
case 'resize':
resizeCanvas(data.newSize);
break;
case 'updateSpeedRatio':
speedRatio = data.newRatio;
break;
default:
postMessage({ error: `Unknown command: '${message}'` });
console.error(`Unknown command: '${message}'`);
break;
}
});
const init = async data => {
console.log('Worker: received init ')
canvas = data.canvas;
canvas.height = canvasHeight;
canvas.width = canvasWidth;
ctx = canvas.getContext("2d");
await createSprites(data.particulesSize);
render();
console.log('Worker: init done')
}
const resizeCanvas = size => {
canvas.height = canvasHeight = size.height;
canvas.width = canvasWidth = size.width;
}
const createSprites = range => {
console.log('Worker: begin sprite creation')
for (let i = range.min; i <= range.max; i++) {
// set the canvas to the size of the image
const size = i;
canvas.width = size * 2;
canvas.height = size * 2;
// draw the image onto the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(255, 255, 255," + 1 + ")";
ctx.arc(size, size, size, 0, 2 * Math.PI);
ctx.fill();
// get the ImageData for the image.
const imageData = ctx.getImageData(0, 0, size * 2, size * 2);
// get the pixel component data from the image Data.
const imagePixData = imageData.data;
imgArr.push({
data: imagePixData,
width: size * 2,
height: size * 2
});
}
console.log(`Worker: done sprite calculation, sprite generated: ${imgArr.length}`)
return true
}
const addParticles = n => {
for (let i = 0; i < n; i++) {
const index = Math.floor(randomNum(0, imgArr.length));
particles.push({
x: randomNum(canvasWidth / 2 - 300, canvasWidth / 2 + 300),
y: randomNum(canvasHeight + 10, canvasHeight + 100),
speed: randomNum(0.1, 0.5),
dx: randomNum(-1.3, 1.3),
dy: -1,
alpha: 1,
width: imgArr[index].width,
height: imgArr[index].height,
data: imgArr[index].data
});
}
}
const render = () => {
// drawing
particlesToAnimate = particles.length;
if (particlesToAnimate > 2500) {
particles.shift();
particlesToAnimate = particles.length;
}
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// create new Image data
const canvasData = ctx.createImageData(canvasWidth, canvasHeight),
// get the pixel data
cData = canvasData.data;
// iterate over the opbects
for (let nObject = 0; nObject < particlesToAnimate; nObject++) {
const entity = particles[nObject];
entity.x += (entity.dx * entity.speed) * speedRatio;
entity.y += (entity.dy * entity.speed) * speedRatio;
// now iterate over the image we stored
for (let w = 0; w < entity.width; w++) {
for (let h = 0; h < entity.height; h++) {
// make sure the edges of the image are still inside the canvas
if (
entity.x + w < canvasWidth &&
entity.x + w > 0 &&
entity.y + h > 0 &&
entity.y + h < canvasHeight
) {
// get the position pixel from the image canvas
const iData = (h * entity.width + w) * 4;
// get the position of the data we will write to on our main canvas
const pData = (~~(entity.x + w) + ~~(entity.y + h) * canvasWidth) * 4;
// copy the r/g/b/ and alpha values to our main canvas from
// our image canvas data.
if (entity.data[iData + 3] > 160) { // magic number to get rid of black artifacs
cData[pData] = entity.data[iData];
cData[pData + 1] = entity.data[iData + 1];
cData[pData + 2] = entity.data[iData + 2];
cData[pData + 3] = convertRange(nObject, particlesToAnimate, 0, 255, 0);
}
}
}
}
}
ctx.putImageData(canvasData, 0, 0);
requestAnimationFrame(render);
}
</script>
<script src="index.js"></script>
</body>
</html>