├── CNAME
├── Index.html
├── README.md
├── Visualizer.PNG
└── scripts
├── OrbitControls.js
├── Site.js
├── jquery-2.1.1.js
└── three.js
/CNAME:
--------------------------------------------------------------------------------
1 | HTML5AudioVisualizer.raathigesh.com
2 |
--------------------------------------------------------------------------------
/Index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HTML5AudioVisualizer
2 | ====================
3 | Simple audio visualizer using HTML 5 Web Audio API and the awesome Three.js library.
4 |
5 | DEMO : http://html5audiovisualizer.azurewebsites.net/
6 |
7 | 
8 |
--------------------------------------------------------------------------------
/Visualizer.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/HTML5AudioVisualizer/01490d1c0e1c5ac754d4971bda86ade55df79a2d/Visualizer.PNG
--------------------------------------------------------------------------------
/scripts/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | */
7 |
8 | THREE.OrbitControls = function ( object, domElement ) {
9 |
10 | this.object = object;
11 | this.domElement = ( domElement !== undefined ) ? domElement : document;
12 |
13 | // API
14 |
15 | this.enabled = true;
16 |
17 | this.center = new THREE.Vector3();
18 |
19 | this.userZoom = true;
20 | this.userZoomSpeed = 1.0;
21 |
22 | this.userRotate = true;
23 | this.userRotateSpeed = 1.0;
24 |
25 | this.userPan = true;
26 | this.userPanSpeed = 2.0;
27 |
28 | this.autoRotate = false;
29 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
30 |
31 | this.minPolarAngle = 0; // radians
32 | this.maxPolarAngle = Math.PI; // radians
33 |
34 | this.minDistance = 0;
35 | this.maxDistance = Infinity;
36 |
37 | // 65 /*A*/, 83 /*S*/, 68 /*D*/
38 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 };
39 |
40 | // internals
41 |
42 | var scope = this;
43 |
44 | var EPS = 0.000001;
45 | var PIXELS_PER_ROUND = 1800;
46 |
47 | var rotateStart = new THREE.Vector2();
48 | var rotateEnd = new THREE.Vector2();
49 | var rotateDelta = new THREE.Vector2();
50 |
51 | var zoomStart = new THREE.Vector2();
52 | var zoomEnd = new THREE.Vector2();
53 | var zoomDelta = new THREE.Vector2();
54 |
55 | var phiDelta = 0;
56 | var thetaDelta = 0;
57 | var scale = 1;
58 |
59 | var lastPosition = new THREE.Vector3();
60 |
61 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };
62 | var state = STATE.NONE;
63 |
64 | // events
65 |
66 | var changeEvent = { type: 'change' };
67 |
68 |
69 | this.rotateLeft = function ( angle ) {
70 |
71 | if ( angle === undefined ) {
72 |
73 | angle = getAutoRotationAngle();
74 |
75 | }
76 |
77 | thetaDelta -= angle;
78 |
79 | };
80 |
81 | this.rotateRight = function ( angle ) {
82 |
83 | if ( angle === undefined ) {
84 |
85 | angle = getAutoRotationAngle();
86 |
87 | }
88 |
89 | thetaDelta += angle;
90 |
91 | };
92 |
93 | this.rotateUp = function ( angle ) {
94 |
95 | if ( angle === undefined ) {
96 |
97 | angle = getAutoRotationAngle();
98 |
99 | }
100 |
101 | phiDelta -= angle;
102 |
103 | };
104 |
105 | this.rotateDown = function ( angle ) {
106 |
107 | if ( angle === undefined ) {
108 |
109 | angle = getAutoRotationAngle();
110 |
111 | }
112 |
113 | phiDelta += angle;
114 |
115 | };
116 |
117 | this.zoomIn = function ( zoomScale ) {
118 |
119 | if ( zoomScale === undefined ) {
120 |
121 | zoomScale = getZoomScale();
122 |
123 | }
124 |
125 | scale /= zoomScale;
126 |
127 | };
128 |
129 | this.zoomOut = function ( zoomScale ) {
130 |
131 | if ( zoomScale === undefined ) {
132 |
133 | zoomScale = getZoomScale();
134 |
135 | }
136 |
137 | scale *= zoomScale;
138 |
139 | };
140 |
141 | this.pan = function ( distance ) {
142 |
143 | distance.transformDirection( this.object.matrix );
144 | distance.multiplyScalar( scope.userPanSpeed );
145 |
146 | this.object.position.add( distance );
147 | this.center.add( distance );
148 |
149 | };
150 |
151 | this.update = function () {
152 |
153 | var position = this.object.position;
154 | var offset = position.clone().sub( this.center );
155 |
156 | // angle from z-axis around y-axis
157 |
158 | var theta = Math.atan2( offset.x, offset.z );
159 |
160 | // angle from y-axis
161 |
162 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
163 |
164 | if ( this.autoRotate ) {
165 |
166 | this.rotateLeft( getAutoRotationAngle() );
167 |
168 | }
169 |
170 | theta += thetaDelta;
171 | phi += phiDelta;
172 |
173 | // restrict phi to be between desired limits
174 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
175 |
176 | // restrict phi to be betwee EPS and PI-EPS
177 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
178 |
179 | var radius = offset.length() * scale;
180 |
181 | // restrict radius to be between desired limits
182 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
183 |
184 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
185 | offset.y = radius * Math.cos( phi );
186 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
187 |
188 | position.copy( this.center ).add( offset );
189 |
190 | this.object.lookAt( this.center );
191 |
192 | thetaDelta = 0;
193 | phiDelta = 0;
194 | scale = 1;
195 |
196 | if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
197 |
198 | this.dispatchEvent( changeEvent );
199 |
200 | lastPosition.copy( this.object.position );
201 |
202 | }
203 |
204 | };
205 |
206 |
207 | function getAutoRotationAngle() {
208 |
209 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
210 |
211 | }
212 |
213 | function getZoomScale() {
214 |
215 | return Math.pow( 0.95, scope.userZoomSpeed );
216 |
217 | }
218 |
219 | function onMouseDown( event ) {
220 |
221 | if ( scope.enabled === false ) return;
222 | if ( scope.userRotate === false ) return;
223 |
224 | event.preventDefault();
225 |
226 | if ( state === STATE.NONE )
227 | {
228 | if ( event.button === 0 )
229 | state = STATE.ROTATE;
230 | if ( event.button === 1 )
231 | state = STATE.ZOOM;
232 | if ( event.button === 2 )
233 | state = STATE.PAN;
234 | }
235 |
236 |
237 | if ( state === STATE.ROTATE ) {
238 |
239 | //state = STATE.ROTATE;
240 |
241 | rotateStart.set( event.clientX, event.clientY );
242 |
243 | } else if ( state === STATE.ZOOM ) {
244 |
245 | //state = STATE.ZOOM;
246 |
247 | zoomStart.set( event.clientX, event.clientY );
248 |
249 | } else if ( state === STATE.PAN ) {
250 |
251 | //state = STATE.PAN;
252 |
253 | }
254 |
255 | document.addEventListener( 'mousemove', onMouseMove, false );
256 | document.addEventListener( 'mouseup', onMouseUp, false );
257 |
258 | }
259 |
260 | function onMouseMove( event ) {
261 |
262 | if ( scope.enabled === false ) return;
263 |
264 | event.preventDefault();
265 |
266 |
267 |
268 | if ( state === STATE.ROTATE ) {
269 |
270 | rotateEnd.set( event.clientX, event.clientY );
271 | rotateDelta.subVectors( rotateEnd, rotateStart );
272 |
273 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed );
274 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed );
275 |
276 | rotateStart.copy( rotateEnd );
277 |
278 | } else if ( state === STATE.ZOOM ) {
279 |
280 | zoomEnd.set( event.clientX, event.clientY );
281 | zoomDelta.subVectors( zoomEnd, zoomStart );
282 |
283 | if ( zoomDelta.y > 0 ) {
284 |
285 | scope.zoomIn();
286 |
287 | } else {
288 |
289 | scope.zoomOut();
290 |
291 | }
292 |
293 | zoomStart.copy( zoomEnd );
294 |
295 | } else if ( state === STATE.PAN ) {
296 |
297 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
298 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
299 |
300 | scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) );
301 |
302 | }
303 |
304 | }
305 |
306 | function onMouseUp( event ) {
307 |
308 | if ( scope.enabled === false ) return;
309 | if ( scope.userRotate === false ) return;
310 |
311 | document.removeEventListener( 'mousemove', onMouseMove, false );
312 | document.removeEventListener( 'mouseup', onMouseUp, false );
313 |
314 | state = STATE.NONE;
315 |
316 | }
317 |
318 | function onMouseWheel( event ) {
319 |
320 | if ( scope.enabled === false ) return;
321 | if ( scope.userZoom === false ) return;
322 |
323 | var delta = 0;
324 |
325 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
326 |
327 | delta = event.wheelDelta;
328 |
329 | } else if ( event.detail ) { // Firefox
330 |
331 | delta = - event.detail;
332 |
333 | }
334 |
335 | if ( delta > 0 ) {
336 |
337 | scope.zoomOut();
338 |
339 | } else {
340 |
341 | scope.zoomIn();
342 |
343 | }
344 |
345 | }
346 |
347 | function onKeyDown( event ) {
348 |
349 | if ( scope.enabled === false ) return;
350 | if ( scope.userPan === false ) return;
351 |
352 | switch ( event.keyCode ) {
353 |
354 | /*case scope.keys.UP:
355 | scope.pan( new THREE.Vector3( 0, 1, 0 ) );
356 | break;
357 | case scope.keys.BOTTOM:
358 | scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
359 | break;
360 | case scope.keys.LEFT:
361 | scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
362 | break;
363 | case scope.keys.RIGHT:
364 | scope.pan( new THREE.Vector3( 1, 0, 0 ) );
365 | break;
366 | */
367 | case scope.keys.ROTATE:
368 | state = STATE.ROTATE;
369 | break;
370 | case scope.keys.ZOOM:
371 | state = STATE.ZOOM;
372 | break;
373 | case scope.keys.PAN:
374 | state = STATE.PAN;
375 | break;
376 |
377 | }
378 |
379 | }
380 |
381 | function onKeyUp( event ) {
382 |
383 | switch ( event.keyCode ) {
384 |
385 | case scope.keys.ROTATE:
386 | case scope.keys.ZOOM:
387 | case scope.keys.PAN:
388 | state = STATE.NONE;
389 | break;
390 | }
391 |
392 | }
393 |
394 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
395 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
396 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
397 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
398 | window.addEventListener( 'keydown', onKeyDown, false );
399 | window.addEventListener( 'keyup', onKeyUp, false );
400 |
401 | };
402 |
403 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
404 |
--------------------------------------------------------------------------------
/scripts/Site.js:
--------------------------------------------------------------------------------
1 | /*
2 | Audio Visualizer by Raathigeshan.
3 | http://raathigesh.com/
4 | */
5 |
6 | var visualizer;
7 |
8 | $(document).ready(function () {
9 | visualizer = new AudioVisualizer();
10 | visualizer.initialize();
11 | visualizer.createBars();
12 | visualizer.setupAudioProcessing();
13 | visualizer.getAudio();
14 | visualizer.handleDrop();
15 | });
16 |
17 |
18 | function AudioVisualizer() {
19 | //constants
20 | this.numberOfBars = 60;
21 |
22 | //Rendering
23 | this.scene;
24 | this.camera;
25 | this.renderer;
26 | this.controls;
27 |
28 | //bars
29 | this.bars = new Array();
30 |
31 | //audio
32 | this.javascriptNode;
33 | this.audioContext;
34 | this.sourceBuffer;
35 | this.analyser;
36 | }
37 |
38 | //initialize the visualizer elements
39 | AudioVisualizer.prototype.initialize = function () {
40 | //generate a ThreeJS Scene
41 | this.scene = new THREE.Scene();
42 |
43 | //get the width and height
44 | var WIDTH = window.innerWidth,
45 | HEIGHT = window.innerHeight;
46 |
47 | //get the renderer
48 | this.renderer = new THREE.WebGLRenderer({ antialias: true });
49 | this.renderer.setSize(WIDTH, HEIGHT);
50 |
51 | //append the rederer to the body
52 | document.body.appendChild(this.renderer.domElement);
53 |
54 | //create and add camera
55 | this.camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 0.1, 20000);
56 | this.camera.position.set(0, 45, 0);
57 | this.scene.add(this.camera);
58 |
59 | var that = this;
60 |
61 | //update renderer size, aspect ratio and projection matrix on resize
62 | window.addEventListener('resize', function () {
63 |
64 | var WIDTH = window.innerWidth,
65 | HEIGHT = window.innerHeight;
66 |
67 | that.renderer.setSize(WIDTH, HEIGHT);
68 |
69 | that.camera.aspect = WIDTH / HEIGHT;
70 | that.camera.updateProjectionMatrix();
71 |
72 | });
73 |
74 | //background color of the scene
75 | this.renderer.setClearColor(0x333F47, 1);
76 |
77 | //create a light and add it to the scene
78 | var light = new THREE.PointLight(0xffffff);
79 | light.position.set(-100, 200, 100);
80 | this.scene.add(light);
81 |
82 | //Add interation capability to the scene
83 | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
84 | };
85 |
86 | //create the bars required to show the visualization
87 | AudioVisualizer.prototype.createBars = function () {
88 |
89 | //iterate and create bars
90 | for (var i = 0; i < this.numberOfBars; i++) {
91 |
92 | //create a bar
93 | var barGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
94 |
95 | //create a material
96 | var material = new THREE.MeshPhongMaterial({
97 | color: this.getRandomColor(),
98 | ambient: 0x808080,
99 | specular: 0xffffff
100 | });
101 |
102 | //create the geometry and set the initial position
103 | this.bars[i] = new THREE.Mesh(barGeometry, material);
104 | this.bars[i].position.set(i - this.numberOfBars/2, 0, 0);
105 |
106 | //add the created bar to the scene
107 | this.scene.add(this.bars[i]);
108 | }
109 | };
110 |
111 | AudioVisualizer.prototype.setupAudioProcessing = function () {
112 | //get the audio context
113 | this.audioContext = new AudioContext();
114 |
115 | //create the javascript node
116 | this.javascriptNode = this.audioContext.createScriptProcessor(2048, 1, 1);
117 | this.javascriptNode.connect(this.audioContext.destination);
118 |
119 | //create the source buffer
120 | this.sourceBuffer = this.audioContext.createBufferSource();
121 |
122 | //create the analyser node
123 | this.analyser = this.audioContext.createAnalyser();
124 | this.analyser.smoothingTimeConstant = 0.3;
125 | this.analyser.fftSize = 512;
126 |
127 | //connect source to analyser
128 | this.sourceBuffer.connect(this.analyser);
129 |
130 | //analyser to speakers
131 | this.analyser.connect(this.javascriptNode);
132 |
133 | //connect source to analyser
134 | this.sourceBuffer.connect(this.audioContext.destination);
135 |
136 | var that = this;
137 |
138 | //this is where we animates the bars
139 | this.javascriptNode.onaudioprocess = function () {
140 |
141 | // get the average for the first channel
142 | var array = new Uint8Array(that.analyser.frequencyBinCount);
143 | that.analyser.getByteFrequencyData(array);
144 |
145 | //render the scene and update controls
146 | visualizer.renderer.render(visualizer.scene, visualizer.camera);
147 | visualizer.controls.update();
148 |
149 | var step = Math.round(array.length / visualizer.numberOfBars);
150 |
151 | //Iterate through the bars and scale the z axis
152 | for (var i = 0; i < visualizer.numberOfBars; i++) {
153 | var value = array[i * step] / 4;
154 | value = value < 1 ? 1 : value;
155 | visualizer.bars[i].scale.z = value;
156 | }
157 | }
158 |
159 | };
160 |
161 | //get the default audio from the server
162 | AudioVisualizer.prototype.getAudio = function () {
163 | var request = new XMLHttpRequest();
164 | request.open("GET", "Asset/Aathi-StarMusiQ.Com.mp3", true);
165 | request.responseType = "arraybuffer";
166 | request.send();
167 | var that = this;
168 | request.onload = function () {
169 | //that.start(request.response);
170 | }
171 | };
172 |
173 | //start the audio processing
174 | AudioVisualizer.prototype.start = function (buffer) {
175 | this.audioContext.decodeAudioData(buffer, decodeAudioDataSuccess, decodeAudioDataFailed);
176 | var that = this;
177 |
178 | function decodeAudioDataSuccess(decodedBuffer) {
179 | that.sourceBuffer.buffer = decodedBuffer
180 | that.sourceBuffer.start(0);
181 | }
182 |
183 | function decodeAudioDataFailed() {
184 | debugger
185 | }
186 | };
187 |
188 | //util method to get random colors to make stuff interesting
189 | AudioVisualizer.prototype.getRandomColor = function () {
190 | var letters = '0123456789ABCDEF'.split('');
191 | var color = '#';
192 | for (var i = 0; i < 6; i++) {
193 | color += letters[Math.floor(Math.random() * 16)];
194 | }
195 | return color;
196 | };
197 |
198 | AudioVisualizer.prototype.handleDrop = function () {
199 | //drag Enter
200 | document.body.addEventListener("dragenter", function () {
201 |
202 | }, false);
203 |
204 | //drag over
205 | document.body.addEventListener("dragover", function (e) {
206 | e.stopPropagation();
207 | e.preventDefault();
208 | e.dataTransfer.dropEffect = 'copy';
209 | }, false);
210 |
211 | //drag leave
212 | document.body.addEventListener("dragleave", function () {
213 |
214 | }, false);
215 |
216 | //drop
217 | document.body.addEventListener("drop", function (e) {
218 | e.stopPropagation();
219 |
220 | e.preventDefault();
221 |
222 | //get the file
223 | var file = e.dataTransfer.files[0];
224 | var fileName = file.name;
225 |
226 | $("#guide").text("Playing " + fileName);
227 |
228 | var fileReader = new FileReader();
229 |
230 | fileReader.onload = function (e) {
231 | var fileResult = e.target.result;
232 | visualizer.start(fileResult);
233 | };
234 |
235 | fileReader.onerror = function (e) {
236 | debugger
237 | };
238 |
239 | fileReader.readAsArrayBuffer(file);
240 | }, false);
241 | }
242 |
243 |
--------------------------------------------------------------------------------