forked from CreateJS/SoundJS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path07_WebAudioNodeInsertion.html
More file actions
278 lines (228 loc) · 11.2 KB
/
07_WebAudioNodeInsertion.html
File metadata and controls
278 lines (228 loc) · 11.2 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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<!DOCTYPE html>
<html>
<head>
<title>SoundJS: Web Audio Node Insertion</title>
<link href="../_assets/css/shared.css" rel="stylesheet" type="text/css"/>
<link href="../_assets/css/examples.css" rel="stylesheet" type="text/css"/>
<link href="../_assets/css/soundjs.css" rel="stylesheet" type="text/css"/>
<script src="../_assets/js/examples.js"></script>
</head>
<body onload="init()">
<header class="SoundJS">
<h1>Web Audio Music Visualizer</h1>
<p>This example shows a web audio exclusive demo that examines music data as
it is played.</p>
</header>
<div id="error">
<h2>Sorry!</h2>
<p>SoundJS or Web Audio is not currently supported in your browser.
Either you are using a browser that does not support web audio, or you
are trying to open this locally.
Because web audio uses XHR, it cannot be opened as a local file and must
be hosted on a server.</p>
<p>Please <a href="http://github.com/CreateJS/SoundJS/issues" target="_blank">log a bug</a>
if you are not opening this locally and your browser supports web audio.
Thank you.</p>
</div>
<div><canvas style="background: black" id="testCanvas" width="960" height="400"></canvas></div>
<script type="text/javascript" src="../_assets/libs/easeljs-NEXT.min.js"></script>
<script type="text/javascript" src="../lib/soundjs-NEXT.js"></script>
<!-- We also provide hosted minified versions of all CreateJS libraries.
http://code.createjs.com -->
<script id="editable">
// global constants
var FFTSIZE = 32; // number of samples for the analyser node FFT, min 32
var TICK_FREQ = 20; // how often to run the tick function, in milliseconds
var CIRCLES = 8; // the number of circles to draw. This is also the amount to break the files into, so FFTSIZE/2 needs to divide by this evenly
var RADIUS_FACTOR = 40; // the radius of the circles, factored for which ring we are drawing
var MIN_RADIUS = 1; // the minimum radius of each circle
var HUE_VARIANCE = 120; // amount hue can vary by
var COLOR_CHANGE_THRESHOLD = 15; // amount of change before we change color
var WAVE_EMIT_THRESHOLD = 15; // amount of positive change before we emit a wave
var WAVE_SCALE = 0.03; // amount to scale wave per tick
var WAVE_RADIUS = 180; // the radius the wave images will be drawn with
// global variables
var stage; // the stage we draw everything to
var w, h; // store the width and height of the canvas
var centerX, centerY; // variables to hold the center point, so that tick is quicker
var messageField; // Message display field
var assetsPath = "../_assets/audio/"; // Create a single item to load.
var src = assetsPath + "05-Binrpilot-Underground.mp3"; // set up our source
var soundInstance; // the sound instance we create
var analyserNode; // the analyser node that allows us to visualize the audio
var freqFloatData, freqByteData, timeByteData; // arrays to retrieve data from analyserNode
var circles = {}; // object has of circles shapes
var circleHue = 300; // the base color hue used when drawing circles, which can change
var waves = new createjs.Container(); // container to store waves we draw coming off of circles
var circleFreqChunk; // The chunk of freqByteData array that is computed per circle
var dataAverage = [42, 42, 42, 42]; // an array recording data for the last 4 ticks
var waveImgs = []; // array of wave images with different stroke thicknesses
function init() {
// Web Audio only demo, so we register just the WebAudioPlugin and if that fails, display fail message
if (!createjs.Sound.registerPlugins([createjs.WebAudioPlugin])) {
document.getElementById("error").style.display = "block";
return;
}
stage = new createjs.Stage("testCanvas");
// Store the width and height, so we only have to access this data once (quicker)
h = stage.canvas.height;
w = stage.canvas.width;
// calculate the center point, so we only have to do this math once (quicker)
centerX = w >> 1;
centerY = h >> 1;
// a message on our stage that we use to let the user know what is going on. Useful when preloading.
messageField = new createjs.Text("Loading Audio", "bold 24px Arial", "#FFFFFF");
messageField.maxWidth = w;
messageField.textAlign = "center"; // NOTE this puts the registration point of the textField at the center
messageField.x = centerX;
messageField.y = centerY;
stage.addChild(messageField);
stage.update(); //update the stage to show text
createjs.Sound.on("fileload", handleLoad, this); // add an event listener for when load is completed
createjs.Sound.registerSound(src); // register sound, which will preload automatically
}
function handleLoad(evt) {
// get the context. NOTE to connect to existing nodes we need to work in the same context.
var context = createjs.Sound.activePlugin.context;
// create an analyser node
analyserNode = context.createAnalyser();
analyserNode.fftSize = FFTSIZE; //The size of the FFT used for frequency-domain analysis. This must be a power of two
analyserNode.smoothingTimeConstant = 0.85; //A value from 0 -> 1 where 0 represents no time averaging with the last analysis frame
analyserNode.connect(context.destination); // connect to the context.destination, which outputs the audio
// attach visualizer node to our existing dynamicsCompressorNode, which was connected to context.destination
var dynamicsNode = createjs.Sound.activePlugin.dynamicsCompressorNode;
dynamicsNode.disconnect(); // disconnect from destination
dynamicsNode.connect(analyserNode);
// set up the arrays that we use to retrieve the analyserNode data
freqFloatData = new Float32Array(analyserNode.frequencyBinCount);
freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
timeByteData = new Uint8Array(analyserNode.frequencyBinCount);
// calculate the number of array elements that represent each circle
circleFreqChunk = analyserNode.frequencyBinCount / CIRCLES;
// enable touch interactions if supported on the current device, and display appropriate message
if (createjs.Touch.enable(stage)) {
messageField.text = "Touch to start";
// wrap our sound playing in a click event so we can be played on mobile devices
stage.addEventListener("stagemousedown", startPlayback);
stage.update(); //update the stage to show text
} else {
startPlayback();
}
}
// this will start our playback in response to a user click, allowing this demo to work on mobile devices
function startPlayback(evt) {
// we only start once, so remove the click/touch listener
stage.removeEventListener("stagemousedown", startPlayback);
if (soundInstance) {
return;
} // if this is defined, we've already started playing. This is very unlikely to happen.
// we're starting, so we can remove the message
stage.removeChild(messageField);
// start playing the sound we just loaded, looping indefinitely
soundInstance = createjs.Sound.play(src, {loop: -1});
// testing function that allows a quick stop
/*stage.addEventListener("stagemousedown", function(){
createjs.Ticker.removeEventListener("tick", tick);
createjs.Sound.stop();
});*/
// create circles so they are persistent
for (var i = 0; i < CIRCLES; i++) {
var circle = circles[i] = new createjs.Shape();
// set the composite operation so we can blend our image colors
circle.compositeOperation = "lighter";
stage.addChild(circle);
}
// add waves container to stage
stage.addChild(waves);
// start the tick and point it at the window so we can do some work before updating the stage:
createjs.Ticker.addEventListener("tick", tick);
createjs.Ticker.setInterval(TICK_FREQ);
}
function tick(evt) {
analyserNode.getFloatFrequencyData(freqFloatData); // this gives us the dBs
analyserNode.getByteFrequencyData(freqByteData); // this gives us the frequency
analyserNode.getByteTimeDomainData(timeByteData); // this gives us the waveform
var lastRadius = 0; // we use this to store the radius of the last circle, making them relative to each other
// run through our array from last to first, 0 will evaluate to false (quicker)
for (var i = 0; i < CIRCLES; i++) {
var freqSum = 0;
var timeSum = 0;
for (var x = circleFreqChunk; x; x--) {
var index = (CIRCLES - i) * circleFreqChunk - x;
freqSum += freqByteData[index];
timeSum += timeByteData[index];
}
freqSum = freqSum / circleFreqChunk / 256; // gives us a percentage out of the total possible value
timeSum = timeSum / circleFreqChunk / 256; // gives us a percentage out of the total possible value
// NOTE in testing it was determined that i 1 thru 4 stay 0's most of the time
// draw circle
lastRadius += freqSum * RADIUS_FACTOR + MIN_RADIUS;
var color = createjs.Graphics.getHSL((i / CIRCLES * HUE_VARIANCE + circleHue) % 360, 100, 50);
var g = new createjs.Graphics().beginFill(color).drawCircle(centerX, centerY, lastRadius).endFill();
circles[i].graphics = g;
}
// update our dataAverage, by removing the first element and pushing in the new last element
dataAverage.shift();
dataAverage.push(lastRadius);
// get our average data for the last 3 ticks
var dataSum = 0;
for (var i = dataAverage.length - 1; i; i--) {
dataSum += dataAverage[i - 1];
}
dataSum = dataSum / (dataAverage.length - 1);
// calculate latest change
var dataDiff = dataAverage[dataAverage.length - 1] - dataSum;
// change color based on large enough changes
if (dataDiff > COLOR_CHANGE_THRESHOLD) {
circleHue = circleHue + dataDiff;
}
// emit a wave for large enough changes
if (dataDiff > WAVE_EMIT_THRESHOLD) {
// create the wave, and center it on screen:
var wave = new createjs.Bitmap(getWaveImg(dataDiff * 0.1 + 1));
wave.x = centerX;
wave.y = centerY;
wave.regX = wave.regY = WAVE_RADIUS;
// set the expansion speed as a factor of the value difference:
wave.speed = dataDiff * 0.1 + 1;
// set the initial scale:
wave.scaleX = wave.scaleY = lastRadius / WAVE_RADIUS;
// add new wave to our waves container
waves.addChild(wave);
}
// animate all of our waves by scaling them up by a fixed about
var maxR = Math.sqrt(w * w + h * h) * 0.5; // the maximum radius for the waves.
for (var i = waves.getNumChildren() - 1; i > -1; i--) {
wave = waves.getChildAt(i);
wave.scaleX = wave.scaleY = wave.scaleX + wave.speed * 0.02;
// check if it is offstage and therefore not visible, if so remove it
if (wave.scaleX * WAVE_RADIUS > maxR) {
waves.removeChildAt(i);
}
}
// draw the updates to stage
stage.update();
}
function getWaveImg(thickness) {
// floor the thickness so we only have to deal with integer values:
thickness |= 0;
if (thickness < 1) {
return null;
}
// if we already have an image with the right thickness, return it:
if (waveImgs[thickness]) {
return waveImgs[thickness];
}
// otherwise, draw the wave into a Shape instance:
var waveShape = new createjs.Shape();
waveShape.graphics.setStrokeStyle(thickness).beginStroke("#FFF").drawCircle(0, 0, WAVE_RADIUS);
// cache it to create a bitmap version of the shape:
var r = WAVE_RADIUS + thickness;
waveShape.cache(-r, -r, r * 2, r * 2);
// save the image into our list, and return it:
waveImgs[thickness] = waveShape.cacheCanvas
return waveShape.cacheCanvas;
}
</script>
</body>
</html>