├── 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 | ![](https://raw.githubusercontent.com/Raathigesh/HTML5AudioVisualizer/master/Visualizer.PNG) 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 | --------------------------------------------------------------------------------