├── spec ├── plugin_fft.js ├── lib │ ├── .DS_Store │ ├── 440hz_100amp.mp3 │ ├── 440hz_100amp.ogg │ └── jasmine-1.1.0 │ │ ├── jasmine_favicon.png │ │ ├── MIT.LICENSE │ │ ├── jasmine.css │ │ └── jasmine-html.js ├── index.html ├── support.js ├── kick.js └── dancer.js ├── .gitignore ├── examples ├── lib │ ├── test.ogg │ ├── Detector.js │ ├── Stats.js │ ├── Tween.js │ └── physics.js ├── songs │ ├── tonetest.mp3 │ ├── tonetest.ogg │ ├── zircon_devils_spirit.mp3 │ └── zircon_devils_spirit.ogg ├── fft │ ├── images │ │ ├── particle_blue.png │ │ ├── particle_green.png │ │ ├── particle_pink.png │ │ ├── particle_white.png │ │ ├── particle_orange.png │ │ └── particle_yellow.png │ ├── css │ │ ├── League_Gothic-webfont.eot │ │ ├── League_Gothic-webfont.ttf │ │ ├── League_Gothic-webfont.woff │ │ └── dancer.css │ ├── index.html │ └── js │ │ └── player.js ├── waveform │ ├── images │ │ ├── particle_blue.png │ │ ├── particle_green.png │ │ ├── particle_pink.png │ │ ├── particle_white.png │ │ ├── particle_orange.png │ │ └── particle_yellow.png │ ├── css │ │ ├── League_Gothic-webfont.eot │ │ ├── League_Gothic-webfont.ttf │ │ ├── League_Gothic-webfont.woff │ │ └── dancer.css │ ├── index.html │ └── js │ │ └── player.js ├── audio_element │ ├── images │ │ ├── particle_blue.png │ │ ├── particle_green.png │ │ ├── particle_pink.png │ │ ├── particle_white.png │ │ ├── particle_orange.png │ │ └── particle_yellow.png │ ├── css │ │ ├── League_Gothic-webfont.eot │ │ ├── League_Gothic-webfont.ttf │ │ ├── League_Gothic-webfont.woff │ │ └── dancer.css │ ├── index.html │ └── js │ │ └── player.js └── single_song_demo │ ├── images │ ├── particle_blue.png │ ├── particle_green.png │ ├── particle_pink.png │ ├── particle_white.png │ ├── particle_orange.png │ └── particle_yellow.png │ ├── css │ ├── League_Gothic-webfont.eot │ ├── League_Gothic-webfont.ttf │ ├── League_Gothic-webfont.woff │ └── dancer.css │ ├── js │ ├── scene.js │ └── player.js │ └── index.html ├── lib ├── soundmanager2.swf ├── fft.js └── flash_detect.js ├── package.json ├── plugins ├── dancer.fft.js └── dancer.waveform.js ├── LICENSE ├── Gruntfile.js ├── src ├── kick.js ├── adapterMoz.js ├── support.js ├── adapterWebAudio.js ├── adapterFlash.js └── dancer.js ├── README.md └── dancer.min.js /spec/plugin_fft.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.swp 3 | node_modules 4 | -------------------------------------------------------------------------------- /spec/lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/spec/lib/.DS_Store -------------------------------------------------------------------------------- /examples/lib/test.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/lib/test.ogg -------------------------------------------------------------------------------- /lib/soundmanager2.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/lib/soundmanager2.swf -------------------------------------------------------------------------------- /spec/lib/440hz_100amp.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/spec/lib/440hz_100amp.mp3 -------------------------------------------------------------------------------- /spec/lib/440hz_100amp.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/spec/lib/440hz_100amp.ogg -------------------------------------------------------------------------------- /examples/songs/tonetest.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/songs/tonetest.mp3 -------------------------------------------------------------------------------- /examples/songs/tonetest.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/songs/tonetest.ogg -------------------------------------------------------------------------------- /examples/fft/images/particle_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_blue.png -------------------------------------------------------------------------------- /examples/fft/images/particle_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_green.png -------------------------------------------------------------------------------- /examples/fft/images/particle_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_pink.png -------------------------------------------------------------------------------- /examples/fft/images/particle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_white.png -------------------------------------------------------------------------------- /examples/fft/images/particle_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_orange.png -------------------------------------------------------------------------------- /examples/fft/images/particle_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/images/particle_yellow.png -------------------------------------------------------------------------------- /examples/songs/zircon_devils_spirit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/songs/zircon_devils_spirit.mp3 -------------------------------------------------------------------------------- /examples/songs/zircon_devils_spirit.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/songs/zircon_devils_spirit.ogg -------------------------------------------------------------------------------- /examples/fft/css/League_Gothic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/css/League_Gothic-webfont.eot -------------------------------------------------------------------------------- /examples/fft/css/League_Gothic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/css/League_Gothic-webfont.ttf -------------------------------------------------------------------------------- /examples/fft/css/League_Gothic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/fft/css/League_Gothic-webfont.woff -------------------------------------------------------------------------------- /examples/waveform/images/particle_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_blue.png -------------------------------------------------------------------------------- /examples/waveform/images/particle_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_green.png -------------------------------------------------------------------------------- /examples/waveform/images/particle_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_pink.png -------------------------------------------------------------------------------- /examples/waveform/images/particle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_white.png -------------------------------------------------------------------------------- /spec/lib/jasmine-1.1.0/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/spec/lib/jasmine-1.1.0/jasmine_favicon.png -------------------------------------------------------------------------------- /examples/waveform/images/particle_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_orange.png -------------------------------------------------------------------------------- /examples/waveform/images/particle_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/images/particle_yellow.png -------------------------------------------------------------------------------- /examples/audio_element/images/particle_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_blue.png -------------------------------------------------------------------------------- /examples/audio_element/images/particle_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_green.png -------------------------------------------------------------------------------- /examples/audio_element/images/particle_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_pink.png -------------------------------------------------------------------------------- /examples/audio_element/images/particle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_white.png -------------------------------------------------------------------------------- /examples/waveform/css/League_Gothic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/css/League_Gothic-webfont.eot -------------------------------------------------------------------------------- /examples/waveform/css/League_Gothic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/css/League_Gothic-webfont.ttf -------------------------------------------------------------------------------- /examples/waveform/css/League_Gothic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/waveform/css/League_Gothic-webfont.woff -------------------------------------------------------------------------------- /examples/audio_element/images/particle_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_orange.png -------------------------------------------------------------------------------- /examples/audio_element/images/particle_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/images/particle_yellow.png -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_blue.png -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_green.png -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_pink.png -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_white.png -------------------------------------------------------------------------------- /examples/audio_element/css/League_Gothic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/css/League_Gothic-webfont.eot -------------------------------------------------------------------------------- /examples/audio_element/css/League_Gothic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/css/League_Gothic-webfont.ttf -------------------------------------------------------------------------------- /examples/audio_element/css/League_Gothic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/audio_element/css/League_Gothic-webfont.woff -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_orange.png -------------------------------------------------------------------------------- /examples/single_song_demo/images/particle_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/images/particle_yellow.png -------------------------------------------------------------------------------- /examples/single_song_demo/css/League_Gothic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/css/League_Gothic-webfont.eot -------------------------------------------------------------------------------- /examples/single_song_demo/css/League_Gothic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/css/League_Gothic-webfont.ttf -------------------------------------------------------------------------------- /examples/single_song_demo/css/League_Gothic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenGrider/dancer.js/master/examples/single_song_demo/css/League_Gothic-webfont.woff -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dancer", 3 | "description": "dancer.js is a high-level audio API, usable with the Web Audio API, Mozilla's Audio Data API and flash fallback, designed to make sweet visualizations.", 4 | "version": "0.4.0", 5 | "homepage": "https://github.com/jsantell/dancer.js", 6 | "author": { 7 | "name": "Jordan Santell", 8 | "url": "http://jsantell.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/jsantell/dancer.js" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/jsantell/dancer.js/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/jsantell/dancer.js/blob/master/LICENSE" 21 | } 22 | ], 23 | "devDependencies": { 24 | "grunt": "0.4.2", 25 | "grunt-contrib-uglify": "0.3.2", 26 | "grunt-contrib-concat": "0.3.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugins/dancer.fft.js: -------------------------------------------------------------------------------- 1 | /* 2 | * FFT plugin for dancer.js 3 | * 4 | * Usage of frequencies being 2px with 1px spacing: 5 | * 6 | * var dancer = new Dancer('song.ogg'), 7 | * canvas = document.getElementById('fftcanvas'); 8 | * dancer.fft( canvas, { width: 2, spacing: 1 }); 9 | */ 10 | 11 | (function() { 12 | Dancer.addPlugin( 'fft', function( canvasEl, options ) { 13 | options = options || {}; 14 | var 15 | ctx = canvasEl.getContext( '2d' ), 16 | h = canvasEl.height, 17 | w = canvasEl.width, 18 | width = options.width || 1, 19 | spacing = options.spacing || 0, 20 | count = options.count || 512; 21 | 22 | ctx.fillStyle = options.fillStyle || "white"; 23 | 24 | this.bind( 'update', function() { 25 | var spectrum = this.getSpectrum(); 26 | ctx.clearRect( 0, 0, w, h ); 27 | for ( var i = 0, l = spectrum.length; i < l && i < count; i++ ) { 28 | ctx.fillRect( i * ( spacing + width ), h, width, -spectrum[ i ] * h ); 29 | } 30 | }); 31 | 32 | return this; 33 | }); 34 | })(); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jordan Santell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.1.0/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /plugins/dancer.waveform.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Waveform plugin for dancer.js 3 | * 4 | * var dancer = new Dancer('song.ogg'), 5 | * canvas = document.getElementById('waveform'); 6 | * dancer.waveform( canvas, { strokeStyle: '#ff0077' }); 7 | */ 8 | 9 | (function() { 10 | Dancer.addPlugin( 'waveform', function( canvasEl, options ) { 11 | options = options || {}; 12 | var 13 | ctx = canvasEl.getContext( '2d' ), 14 | h = canvasEl.height, 15 | w = canvasEl.width, 16 | width = options.width || 2, 17 | spacing = options.spacing || 0, 18 | count = options.count || 1024; 19 | 20 | ctx.lineWidth = options.strokeWidth || 1; 21 | ctx.strokeStyle = options.strokeStyle || "white"; 22 | 23 | this.bind( 'update', function() { 24 | var waveform = this.getWaveform(); 25 | ctx.clearRect( 0, 0, w, h ); 26 | ctx.beginPath(); 27 | ctx.moveTo( 0, h / 2 ); 28 | for ( var i = 0, l = waveform.length; i < l && i < count; i++ ) { 29 | ctx.lineTo( i * ( spacing + width ), ( h / 2 ) + waveform[ i ] * ( h / 2 )); 30 | } 31 | ctx.stroke(); 32 | ctx.closePath(); 33 | }); 34 | 35 | return this; 36 | }); 37 | })(); 38 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var banner = '/*\n' + 4 | ' * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 5 | ' * <%= pkg.homepage %>\n' + 6 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n' + 7 | ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' + 8 | ' */\n'; 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | concat: { 12 | options: { 13 | banner: banner 14 | }, 15 | build: { 16 | src: [ 17 | 'src/dancer.js', 18 | 'src/support.js', 19 | 'src/kick.js', 20 | 'src/adapterWebAudio.js', 21 | 'src/adapterMoz.js', 22 | 'src/adapterFlash.js', 23 | 'lib/fft.js', 24 | 'lib/flash_detect.js' 25 | ], 26 | dest: 'dancer.js' 27 | } 28 | }, 29 | uglify: { 30 | options: { 31 | banner: banner 32 | }, 33 | build: { 34 | src: ['dancer.js'], 35 | dest: 'dancer.min.js' 36 | } 37 | }, 38 | watch: { 39 | scripts: { 40 | files: '', 41 | tasks: 'concat min' 42 | } 43 | } 44 | }); 45 | 46 | grunt.loadNpmTasks('grunt-contrib-uglify'); 47 | grunt.loadNpmTasks('grunt-contrib-concat'); 48 | grunt.registerTask('default', ['concat','uglify']); 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /examples/fft/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

dancer.js

10 |

JavaScript Audio Library

11 |

Simple FFT

12 | 16 |
17 |
Loading . . .
18 | 19 |
20 | Now playing... 21 |

Zircon - Devil's Spirit

22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/waveform/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

dancer.js

10 |

JavaScript Audio Library

11 |

Simple Waveform

12 | 16 |
17 |
Loading . . .
18 | 19 |
20 | Now playing... 21 |

Zircon - Devil's Spirit

22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/audio_element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

dancer.js

10 |

JavaScript Audio Library

11 |

Simple Waveform

12 | 16 |
17 | 21 |
Loading . . .
22 | 23 |
24 | Now playing... 25 |

Zircon - Devil's Spirit

26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/fft/js/player.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var 4 | AUDIO_FILE = '../songs/zircon_devils_spirit', 5 | fft = document.getElementById( 'fft' ), 6 | ctx = fft.getContext( '2d' ), 7 | dancer, kick; 8 | 9 | /* 10 | * Dancer.js magic 11 | */ 12 | Dancer.setOptions({ 13 | flashSWF : '../../lib/soundmanager2.swf', 14 | flashJS : '../../lib/soundmanager2.js' 15 | }); 16 | 17 | dancer = new Dancer(); 18 | kick = dancer.createKick({ 19 | onKick: function () { 20 | ctx.fillStyle = '#ff0077'; 21 | }, 22 | offKick: function () { 23 | ctx.fillStyle = '#666'; 24 | } 25 | }).on(); 26 | 27 | dancer 28 | .fft( fft, { fillStyle: '#666' }) 29 | .load({ src: AUDIO_FILE, codecs: [ 'ogg', 'mp3' ]}); 30 | 31 | Dancer.isSupported() || loaded(); 32 | !dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded(); 33 | 34 | /* 35 | * Loading 36 | */ 37 | 38 | function loaded () { 39 | var 40 | loading = document.getElementById( 'loading' ), 41 | anchor = document.createElement('A'), 42 | supported = Dancer.isSupported(), 43 | p; 44 | 45 | anchor.appendChild( document.createTextNode( supported ? 'Play!' : 'Close' ) ); 46 | anchor.setAttribute( 'href', '#' ); 47 | loading.innerHTML = ''; 48 | loading.appendChild( anchor ); 49 | 50 | if ( !supported ) { 51 | p = document.createElement('P'); 52 | p.appendChild( document.createTextNode( 'Your browser does not currently support either Web Audio API or Audio Data API. The audio may play, but the visualizers will not move to the music; check out the latest Chrome or Firefox browsers!' ) ); 53 | loading.appendChild( p ); 54 | } 55 | 56 | anchor.addEventListener( 'click', function () { 57 | dancer.play(); 58 | document.getElementById('loading').style.display = 'none'; 59 | }); 60 | } 61 | 62 | // For debugging 63 | window.dancer = dancer; 64 | 65 | })(); 66 | -------------------------------------------------------------------------------- /examples/audio_element/js/player.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var 4 | audio = document.getElementById( 'audio' ), 5 | waveform = document.getElementById( 'waveform' ), 6 | ctx = waveform.getContext( '2d' ), 7 | dancer, kick; 8 | 9 | /* 10 | * Dancer.js magic 11 | */ 12 | Dancer.setOptions({ 13 | flashSWF : '../../lib/soundmanager2.swf', 14 | flashJS : '../../lib/soundmanager2.js' 15 | }); 16 | 17 | dancer = new Dancer(); 18 | kick = dancer.createKick({ 19 | onKick: function () { 20 | ctx.strokeStyle = '#ff0077'; 21 | }, 22 | offKick: function () { 23 | ctx.strokeStyle = '#666'; 24 | } 25 | }).on(); 26 | 27 | dancer 28 | .load( audio ) 29 | .waveform( waveform, { strokeStyle: '#666', strokeWidth: 2 }); 30 | 31 | Dancer.isSupported() || loaded(); 32 | !dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded(); 33 | 34 | /* 35 | * Loading 36 | */ 37 | 38 | function loaded () { 39 | var 40 | loading = document.getElementById( 'loading' ), 41 | anchor = document.createElement('A'), 42 | supported = Dancer.isSupported(), 43 | p; 44 | 45 | anchor.appendChild( document.createTextNode( supported ? 'Play!' : 'Close' ) ); 46 | anchor.setAttribute( 'href', '#' ); 47 | loading.innerHTML = ''; 48 | loading.appendChild( anchor ); 49 | 50 | if ( !supported ) { 51 | p = document.createElement('P'); 52 | p.appendChild( document.createTextNode( 'Your browser does not currently support either Web Audio API or Audio Data API. The audio may play, but the visualizers will not move to the music; check out the latest Chrome or Firefox browsers!' ) ); 53 | loading.appendChild( p ); 54 | } 55 | 56 | anchor.addEventListener( 'click', function () { 57 | dancer.play(); 58 | document.getElementById('loading').style.display = 'none'; 59 | }); 60 | } 61 | 62 | // For debugging 63 | window.dancer = dancer; 64 | 65 | })(); 66 | -------------------------------------------------------------------------------- /examples/waveform/js/player.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var 4 | AUDIO_FILE = '../songs/tonetest', 5 | waveform = document.getElementById( 'waveform' ), 6 | ctx = waveform.getContext( '2d' ), 7 | dancer, kick; 8 | 9 | /* 10 | * Dancer.js magic 11 | */ 12 | Dancer.setOptions({ 13 | flashSWF : '../../lib/soundmanager2.swf', 14 | flashJS : '../../lib/soundmanager2.js' 15 | }); 16 | 17 | dancer = new Dancer(); 18 | kick = dancer.createKick({ 19 | onKick: function () { 20 | ctx.strokeStyle = '#ff0077'; 21 | }, 22 | offKick: function () { 23 | ctx.strokeStyle = '#666'; 24 | } 25 | }).on(); 26 | 27 | dancer 28 | .load({ src: AUDIO_FILE, codecs: [ 'ogg', 'mp3' ]}) 29 | .waveform( waveform, { strokeStyle: '#666', strokeWidth: 2 }); 30 | 31 | Dancer.isSupported() || loaded(); 32 | !dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded(); 33 | 34 | /* 35 | * Loading 36 | */ 37 | 38 | function loaded () { 39 | var 40 | loading = document.getElementById( 'loading' ), 41 | anchor = document.createElement('A'), 42 | supported = Dancer.isSupported(), 43 | p; 44 | 45 | anchor.appendChild( document.createTextNode( supported ? 'Play!' : 'Close' ) ); 46 | anchor.setAttribute( 'href', '#' ); 47 | loading.innerHTML = ''; 48 | loading.appendChild( anchor ); 49 | 50 | if ( !supported ) { 51 | p = document.createElement('P'); 52 | p.appendChild( document.createTextNode( 'Your browser does not currently support either Web Audio API or Audio Data API. The audio may play, but the visualizers will not move to the music; check out the latest Chrome or Firefox browsers!' ) ); 53 | loading.appendChild( p ); 54 | } 55 | 56 | anchor.addEventListener( 'click', function () { 57 | dancer.play(); 58 | document.getElementById('loading').style.display = 'none'; 59 | }); 60 | } 61 | 62 | // For debugging 63 | window.dancer = dancer; 64 | 65 | })(); 66 | -------------------------------------------------------------------------------- /examples/lib/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | Detector = { 7 | 8 | canvas: !! window.CanvasRenderingContext2D, 9 | webgl: ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(), 10 | workers: !! window.Worker, 11 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 12 | 13 | getWebGLErrorMessage: function () { 14 | 15 | var element = document.createElement( 'div' ); 16 | element.id = 'webgl-error-message'; 17 | element.style.fontFamily = 'monospace'; 18 | element.style.fontSize = '13px'; 19 | element.style.fontWeight = 'normal'; 20 | element.style.textAlign = 'center'; 21 | element.style.background = '#fff'; 22 | element.style.color = '#000'; 23 | element.style.padding = '1.5em'; 24 | element.style.width = '400px'; 25 | element.style.margin = '5em auto 0'; 26 | 27 | if ( ! this.webgl ) { 28 | 29 | element.innerHTML = window.WebGLRenderingContext ? [ 30 | 'Your graphics card does not seem to support WebGL.
', 31 | 'Find out how to get it here.' 32 | ].join( '\n' ) : [ 33 | 'Your browser does not seem to support WebGL.
', 34 | 'Find out how to get it here.' 35 | ].join( '\n' ); 36 | 37 | } 38 | 39 | return element; 40 | 41 | }, 42 | 43 | addGetWebGLMessage: function ( parameters ) { 44 | 45 | var parent, id, element; 46 | 47 | parameters = parameters || {}; 48 | 49 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 50 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 51 | 52 | element = Detector.getWebGLErrorMessage(); 53 | element.id = id; 54 | 55 | parent.appendChild( element ); 56 | 57 | } 58 | 59 | }; 60 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/single_song_demo/js/scene.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var container; 3 | var renderer, particle; 4 | var mouseX = 0, mouseY = 0; 5 | 6 | var stats = new Stats(); 7 | stats.domElement.id = 'stats'; 8 | document.getElementById('info').insertBefore( stats.domElement, document.getElementById('togglefft') ); 9 | 10 | var windowHalfX = window.innerWidth / 2; 11 | var windowHalfY = window.innerHeight / 2; 12 | 13 | // Expose these for the demo 14 | window.rotateSpeed = 1; 15 | window.scene = new THREE.Scene(); 16 | window.group = new THREE.Object3D(); 17 | window.camera; 18 | 19 | init(); 20 | animate(); 21 | 22 | function init() { 23 | container = document.createElement( 'div' ); 24 | document.body.appendChild( container ); 25 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 3000 ); 26 | camera.position.z = 1000; 27 | 28 | scene.add( camera ); 29 | scene.add( group ); 30 | 31 | renderer = new THREE.CanvasRenderer(); 32 | renderer.setSize( window.innerWidth, window.innerHeight ); 33 | container.appendChild( renderer.domElement ); 34 | 35 | document.addEventListener( 'mousemove', onDocumentMouseMove, false ); 36 | document.addEventListener( 'touchstart', onDocumentTouch, false ); 37 | document.addEventListener( 'touchmove', onDocumentTouch, false ); 38 | } 39 | 40 | function onDocumentMouseMove( event ) { 41 | mouseX = event.clientX - windowHalfX; 42 | mouseY = event.clientY - windowHalfY; 43 | } 44 | 45 | function onDocumentTouch( event ) { 46 | if ( event.touches.length == 1 ) { 47 | event.preventDefault(); 48 | mouseX = event.touches[ 0 ].pageX - windowHalfX; 49 | mouseY = event.touches[ 0 ].pageY - windowHalfY; 50 | } 51 | } 52 | 53 | function animate() { 54 | requestAnimationFrame( animate ); 55 | render(); 56 | stats.update(); 57 | } 58 | 59 | var t = 0; 60 | function render() { 61 | camera.position.x = Math.sin(t * 0.005 * rotateSpeed) * 1000; 62 | camera.position.z = Math.cos(t * 0.005 * rotateSpeed) * 1000; 63 | camera.position.y += ( - mouseY - camera.position.y ) * 0.01; 64 | camera.lookAt( scene.position ); 65 | t++; 66 | renderer.render( scene, camera ); 67 | } 68 | })(); 69 | -------------------------------------------------------------------------------- /src/kick.js: -------------------------------------------------------------------------------- 1 | (function ( undefined ) { 2 | var Kick = function ( dancer, o ) { 3 | o = o || {}; 4 | this.dancer = dancer; 5 | this.frequency = o.frequency !== undefined ? o.frequency : [ 0, 10 ]; 6 | this.threshold = o.threshold !== undefined ? o.threshold : 0.3; 7 | this.decay = o.decay !== undefined ? o.decay : 0.02; 8 | this.onKick = o.onKick; 9 | this.offKick = o.offKick; 10 | this.isOn = false; 11 | this.currentThreshold = this.threshold; 12 | 13 | var _this = this; 14 | this.dancer.bind( 'update', function () { 15 | _this.onUpdate(); 16 | }); 17 | }; 18 | 19 | Kick.prototype = { 20 | on : function () { 21 | this.isOn = true; 22 | return this; 23 | }, 24 | off : function () { 25 | this.isOn = false; 26 | return this; 27 | }, 28 | 29 | set : function ( o ) { 30 | o = o || {}; 31 | this.frequency = o.frequency !== undefined ? o.frequency : this.frequency; 32 | this.threshold = o.threshold !== undefined ? o.threshold : this.threshold; 33 | this.decay = o.decay !== undefined ? o.decay : this.decay; 34 | this.onKick = o.onKick || this.onKick; 35 | this.offKick = o.offKick || this.offKick; 36 | }, 37 | 38 | onUpdate : function () { 39 | if ( !this.isOn ) { return; } 40 | var magnitude = this.maxAmplitude( this.frequency ); 41 | if ( magnitude >= this.currentThreshold && 42 | magnitude >= this.threshold ) { 43 | this.currentThreshold = magnitude; 44 | this.onKick && this.onKick.call( this.dancer, magnitude ); 45 | } else { 46 | this.offKick && this.offKick.call( this.dancer, magnitude ); 47 | this.currentThreshold -= this.decay; 48 | } 49 | }, 50 | maxAmplitude : function ( frequency ) { 51 | var 52 | max = 0, 53 | fft = this.dancer.getSpectrum(); 54 | 55 | // Sloppy array check 56 | if ( !frequency.length ) { 57 | return frequency < fft.length ? 58 | fft[ ~~frequency ] : 59 | null; 60 | } 61 | 62 | for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) { 63 | if ( fft[ i ] > max ) { max = fft[ i ]; } 64 | } 65 | return max; 66 | } 67 | }; 68 | 69 | window.Dancer.Kick = Kick; 70 | })(); 71 | -------------------------------------------------------------------------------- /examples/single_song_demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

dancer.js

10 |

JavaScript Audio Library

11 | toggle FFT spectrum 12 | 13 | 17 |
18 |
19 | Now playing... 20 |

Zircon - Devil's Spirit

21 |
22 |
Loading . . .
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/fft/css/dancer.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('League_Gothic-webfont.eot'); 4 | src: url('League_Gothic-webfont.eot?') format('embedded-opentype'), 5 | url('League_Gothic-webfont.woff') format('woff'), 6 | url('League_Gothic-webfont.ttf') format('opentype'); 7 | font-weight: bold; 8 | font-style: normal; 9 | } 10 | body { 11 | background-color: #000; 12 | margin: 0; 13 | padding: 0; 14 | font-family: Arial; 15 | } 16 | #info { 17 | width: 100%; 18 | position: absolute; 19 | top: 0; 20 | height: 52px; 21 | background-color: #111; 22 | color: #ccc; 23 | font-family: 'League Gothic', arial, sans-serif; 24 | } 25 | #info h1 { 26 | margin: 0 0 0 20px; 27 | float: left; 28 | font-size: 40px; 29 | color: #ff0077; 30 | } 31 | #info h2 { 32 | margin: 14px 0 0 5px; 33 | float: left; 34 | font-size: 25px; 35 | } 36 | 37 | #info h3 { 38 | margin: 19px 0 0 15px; 39 | float: left; 40 | font-size: 20px; 41 | } 42 | 43 | #info ul { 44 | float:right; 45 | display-icon-type:none; 46 | } 47 | 48 | #info li { 49 | display:block; 50 | float:left; 51 | margin-right: 15px; 52 | } 53 | 54 | #info li a { 55 | color: #ff0077; 56 | margin-left: 5px; 57 | } 58 | 59 | #loading { 60 | font-size: 60px; 61 | color: #fff; 62 | width: 500px; 63 | margin-left: auto; 64 | margin-right: auto; 65 | margin-top: 100px; 66 | background-color: #111; 67 | padding: 10px; 68 | -webkit-border-radius: 10px; 69 | -moz-border-radius: 10px; 70 | border-radius: 10px; 71 | opacity: 0.9; 72 | border: 1px solid #ccc; 73 | text-align: center; 74 | } 75 | 76 | #loading a { 77 | color: #fff; 78 | text-shadow: 0 0 10px #fff; 79 | text-decoration: none; 80 | } 81 | 82 | #loading p{ 83 | color: #fff; 84 | font-size: 18px; 85 | } 86 | 87 | #fft { 88 | display: block; 89 | margin: 0 auto; 90 | } 91 | 92 | #stats { 93 | margin: 0 0 0 10px; 94 | float: left; 95 | } 96 | 97 | #songinfo { 98 | position: absolute; 99 | bottom: 0; 100 | left: 0; 101 | background-color: #111; 102 | } 103 | 104 | #songinfo span, #songinfo h3 { 105 | color: #ccc; 106 | font-size: 12px; 107 | margin: 5px 30px 5px 30px; 108 | padding: 0; 109 | } 110 | 111 | #songinfo a { 112 | color: #ff0077; 113 | } 114 | -------------------------------------------------------------------------------- /examples/waveform/css/dancer.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('League_Gothic-webfont.eot'); 4 | src: url('League_Gothic-webfont.eot?') format('embedded-opentype'), 5 | url('League_Gothic-webfont.woff') format('woff'), 6 | url('League_Gothic-webfont.ttf') format('opentype'); 7 | font-weight: bold; 8 | font-style: normal; 9 | } 10 | body { 11 | background-color: #000; 12 | margin: 0; 13 | padding: 0; 14 | font-family: Arial; 15 | } 16 | #info { 17 | width: 100%; 18 | position: absolute; 19 | top: 0; 20 | height: 52px; 21 | background-color: #111; 22 | color: #ccc; 23 | font-family: 'League Gothic', arial, sans-serif; 24 | } 25 | #info h1 { 26 | margin: 0 0 0 20px; 27 | float: left; 28 | font-size: 40px; 29 | color: #ff0077; 30 | } 31 | #info h2 { 32 | margin: 14px 0 0 5px; 33 | float: left; 34 | font-size: 25px; 35 | } 36 | 37 | #info h3 { 38 | margin: 19px 0 0 15px; 39 | float: left; 40 | font-size: 20px; 41 | } 42 | 43 | #info ul { 44 | float:right; 45 | display-icon-type:none; 46 | } 47 | 48 | #info li { 49 | display:block; 50 | float:left; 51 | margin-right: 15px; 52 | } 53 | 54 | #info li a { 55 | color: #ff0077; 56 | margin-left: 5px; 57 | } 58 | 59 | #loading { 60 | font-size: 60px; 61 | color: #fff; 62 | width: 500px; 63 | margin-left: auto; 64 | margin-right: auto; 65 | margin-top: 100px; 66 | background-color: #111; 67 | padding: 10px; 68 | -webkit-border-radius: 10px; 69 | -moz-border-radius: 10px; 70 | border-radius: 10px; 71 | opacity: 0.9; 72 | border: 1px solid #ccc; 73 | text-align: center; 74 | } 75 | 76 | #loading a { 77 | color: #fff; 78 | text-shadow: 0 0 10px #fff; 79 | text-decoration: none; 80 | } 81 | 82 | #loading p{ 83 | color: #fff; 84 | font-size: 18px; 85 | } 86 | 87 | #waveform { 88 | display: block; 89 | margin: 100px auto; 90 | } 91 | 92 | #stats { 93 | margin: 0 0 0 10px; 94 | float: left; 95 | } 96 | 97 | #songinfo { 98 | position: absolute; 99 | bottom: 0; 100 | left: 0; 101 | background-color: #111; 102 | } 103 | 104 | #songinfo span, #songinfo h3 { 105 | color: #ccc; 106 | font-size: 12px; 107 | margin: 5px 30px 5px 30px; 108 | padding: 0; 109 | } 110 | 111 | #songinfo a { 112 | color: #ff0077; 113 | } 114 | -------------------------------------------------------------------------------- /examples/single_song_demo/css/dancer.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('League_Gothic-webfont.eot'); 4 | src: url('League_Gothic-webfont.eot?') format('embedded-opentype'), 5 | url('League_Gothic-webfont.woff') format('woff'), 6 | url('League_Gothic-webfont.ttf') format('opentype'); 7 | font-weight: bold; 8 | font-style: normal; 9 | } 10 | body { 11 | background-color: #000000; /*#212426;*/ 12 | margin: 0; 13 | padding: 0; 14 | font-family: Arial; 15 | } 16 | #info { 17 | width: 100%; 18 | position: absolute; 19 | top: 0; 20 | height: 52px; 21 | background-color: #111; 22 | color: #ccc; 23 | font-family: 'League Gothic', arial, sans-serif; 24 | } 25 | #info h1 { 26 | margin: 0 0 0 20px; 27 | float: left; 28 | font-size: 40px; 29 | color: #ff0077; 30 | } 31 | #info h2 { 32 | margin: 14px 0 0 5px; 33 | float: left; 34 | font-size: 25px; 35 | } 36 | 37 | #info ul { 38 | float:right; 39 | display-icon-type:none; 40 | } 41 | 42 | #info li { 43 | display:block; 44 | float:left; 45 | margin-right: 15px; 46 | } 47 | 48 | #info li a { 49 | color: #ff0077; 50 | margin-left: 5px; 51 | } 52 | 53 | #loading { 54 | font-size: 60px; 55 | color: #fff; 56 | width: 500px; 57 | margin-top: 300px; 58 | position: absolute; 59 | background-color: #111; 60 | padding: 10px; 61 | -webkit-border-radius: 10px; 62 | -moz-border-radius: 10px; 63 | border-radius: 10px; 64 | opacity: 0.9; 65 | border: 1px solid #ccc; 66 | text-align: center; 67 | } 68 | 69 | #loading a { 70 | color: #fff; 71 | text-shadow: 0 0 10px #fff; 72 | text-decoration: none; 73 | } 74 | 75 | #loading p{ 76 | color: #fff; 77 | font-size: 18px; 78 | } 79 | 80 | #togglefft { 81 | float: left; 82 | margin: 20px 0 0 10px; 83 | color: #ff0077; 84 | } 85 | 86 | #fft { 87 | display: none; 88 | margin: 2px 0 0 10px; 89 | float: left; 90 | } 91 | 92 | #stats { 93 | margin: 0 0 0 10px; 94 | float: left; 95 | } 96 | 97 | #songinfo { 98 | position: absolute; 99 | bottom: 0; 100 | left: 0; 101 | background-color: #111; 102 | } 103 | 104 | #songinfo span, #songinfo h3 { 105 | color: #ccc; 106 | font-size: 12px; 107 | margin: 5px 30px 5px 30px; 108 | padding: 0; 109 | } 110 | 111 | #songinfo a { 112 | color: #ff0077; 113 | } 114 | -------------------------------------------------------------------------------- /examples/audio_element/css/dancer.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('League_Gothic-webfont.eot'); 4 | src: url('League_Gothic-webfont.eot?') format('embedded-opentype'), 5 | url('League_Gothic-webfont.woff') format('woff'), 6 | url('League_Gothic-webfont.ttf') format('opentype'); 7 | font-weight: bold; 8 | font-style: normal; 9 | } 10 | body { 11 | background-color: #000; 12 | margin: 0; 13 | padding: 0; 14 | font-family: Arial; 15 | } 16 | #info { 17 | width: 100%; 18 | position: absolute; 19 | top: 0; 20 | height: 52px; 21 | background-color: #111; 22 | color: #ccc; 23 | font-family: 'League Gothic', arial, sans-serif; 24 | } 25 | #info h1 { 26 | margin: 0 0 0 20px; 27 | float: left; 28 | font-size: 40px; 29 | color: #ff0077; 30 | } 31 | #info h2 { 32 | margin: 14px 0 0 5px; 33 | float: left; 34 | font-size: 25px; 35 | } 36 | 37 | #info h3 { 38 | margin: 19px 0 0 15px; 39 | float: left; 40 | font-size: 20px; 41 | } 42 | 43 | #info ul { 44 | float:right; 45 | display-icon-type:none; 46 | } 47 | 48 | #info li { 49 | display:block; 50 | float:left; 51 | margin-right: 15px; 52 | } 53 | 54 | #info li a { 55 | color: #ff0077; 56 | margin-left: 5px; 57 | } 58 | 59 | #loading { 60 | font-size: 60px; 61 | color: #fff; 62 | width: 500px; 63 | margin-left: auto; 64 | margin-right: auto; 65 | margin-top: 100px; 66 | background-color: #111; 67 | padding: 10px; 68 | -webkit-border-radius: 10px; 69 | -moz-border-radius: 10px; 70 | border-radius: 10px; 71 | opacity: 0.9; 72 | border: 1px solid #ccc; 73 | text-align: center; 74 | } 75 | 76 | #loading a { 77 | color: #fff; 78 | text-shadow: 0 0 10px #fff; 79 | text-decoration: none; 80 | } 81 | 82 | #loading p{ 83 | color: #fff; 84 | font-size: 18px; 85 | } 86 | 87 | #waveform { 88 | display: block; 89 | margin: 100px auto; 90 | } 91 | 92 | #stats { 93 | margin: 0 0 0 10px; 94 | float: left; 95 | } 96 | 97 | #songinfo { 98 | position: absolute; 99 | bottom: 0; 100 | left: 0; 101 | background-color: #111; 102 | } 103 | 104 | #songinfo span, #songinfo h3 { 105 | color: #ccc; 106 | font-size: 12px; 107 | margin: 5px 30px 5px 30px; 108 | padding: 0; 109 | } 110 | 111 | #songinfo a { 112 | color: #ff0077; 113 | } 114 | 115 | audio { 116 | width: 400px; 117 | display: block; 118 | margin: 100px auto; 119 | } 120 | -------------------------------------------------------------------------------- /examples/lib/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r8 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var h,a,n=0,o=0,i=Date.now(),u=i,p=i,l=0,q=1E3,r=0,e,j,f,b=[[16,16,48],[0,255,255]],m=0,s=1E3,t=0,d,k,g,c=[[16,48,16],[0,255,0]];h=document.createElement("div");h.style.cursor="pointer";h.style.width="80px";h.style.opacity="0.9";h.style.zIndex="10001";h.addEventListener("mousedown",function(a){a.preventDefault();n=(n+1)%2;n==0?(e.style.display="block",d.style.display="none"):(e.style.display="none",d.style.display="block")},!1);e=document.createElement("div");e.style.textAlign= 3 | "left";e.style.lineHeight="1.2em";e.style.backgroundColor="rgb("+Math.floor(b[0][0]/2)+","+Math.floor(b[0][1]/2)+","+Math.floor(b[0][2]/2)+")";e.style.padding="0 0 3px 3px";h.appendChild(e);j=document.createElement("div");j.style.fontFamily="Helvetica, Arial, sans-serif";j.style.fontSize="9px";j.style.color="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";j.style.fontWeight="bold";j.innerHTML="FPS";e.appendChild(j);f=document.createElement("div");f.style.position="relative";f.style.width="74px";f.style.height= 4 | "30px";f.style.backgroundColor="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";for(e.appendChild(f);f.children.length<74;)a=document.createElement("span"),a.style.width="1px",a.style.height="30px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+b[0][0]+","+b[0][1]+","+b[0][2]+")",f.appendChild(a);d=document.createElement("div");d.style.textAlign="left";d.style.lineHeight="1.2em";d.style.backgroundColor="rgb("+Math.floor(c[0][0]/2)+","+Math.floor(c[0][1]/2)+","+Math.floor(c[0][2]/2)+")";d.style.padding= 5 | "0 0 3px 3px";d.style.display="none";h.appendChild(d);k=document.createElement("div");k.style.fontFamily="Helvetica, Arial, sans-serif";k.style.fontSize="9px";k.style.color="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";k.style.fontWeight="bold";k.innerHTML="MS";d.appendChild(k);g=document.createElement("div");g.style.position="relative";g.style.width="74px";g.style.height="30px";g.style.backgroundColor="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";for(d.appendChild(g);g.children.length<74;)a=document.createElement("span"), 6 | a.style.width="1px",a.style.height=Math.random()*30+"px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+c[0][0]+","+c[0][1]+","+c[0][2]+")",g.appendChild(a);return{domElement:h,update:function(){i=Date.now();m=i-u;s=Math.min(s,m);t=Math.max(t,m);k.textContent=m+" MS ("+s+"-"+t+")";var a=Math.min(30,30-m/200*30);g.appendChild(g.firstChild).style.height=a+"px";u=i;o++;if(i>p+1E3)l=Math.round(o*1E3/(i-p)),q=Math.min(q,l),r=Math.max(r,l),j.textContent=l+" FPS ("+q+"-"+r+")",a=Math.min(30,30-l/ 7 | 100*30),f.appendChild(f.firstChild).style.height=a+"px",p=i,o=0}}}; 8 | 9 | -------------------------------------------------------------------------------- /src/adapterMoz.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var adapter = function ( dancer ) { 4 | this.dancer = dancer; 5 | this.audio = new Audio(); 6 | }; 7 | 8 | adapter.prototype = { 9 | 10 | load : function ( _source ) { 11 | var _this = this; 12 | this.audio = _source; 13 | 14 | this.isLoaded = false; 15 | this.progress = 0; 16 | 17 | if ( this.audio.readyState < 3 ) { 18 | this.audio.addEventListener( 'loadedmetadata', function () { 19 | getMetadata.call( _this ); 20 | }, false); 21 | } else { 22 | getMetadata.call( _this ); 23 | } 24 | 25 | this.audio.addEventListener( 'MozAudioAvailable', function ( e ) { 26 | _this.update( e ); 27 | }, false); 28 | 29 | this.audio.addEventListener( 'progress', function ( e ) { 30 | if ( e.currentTarget.duration ) { 31 | _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; 32 | } 33 | }, false); 34 | 35 | return this.audio; 36 | }, 37 | 38 | play : function () { 39 | this.audio.play(); 40 | this.isPlaying = true; 41 | }, 42 | 43 | pause : function () { 44 | this.audio.pause(); 45 | this.isPlaying = false; 46 | }, 47 | 48 | setVolume : function ( volume ) { 49 | this.audio.volume = volume; 50 | }, 51 | 52 | getVolume : function () { 53 | return this.audio.volume; 54 | }, 55 | 56 | getProgress : function () { 57 | return this.progress; 58 | }, 59 | 60 | getWaveform : function () { 61 | return this.signal; 62 | }, 63 | 64 | getSpectrum : function () { 65 | return this.fft.spectrum; 66 | }, 67 | 68 | getTime : function () { 69 | return this.audio.currentTime; 70 | }, 71 | 72 | update : function ( e ) { 73 | if ( !this.isPlaying || !this.isLoaded ) return; 74 | 75 | for ( var i = 0, j = this.fbLength / 2; i < j; i++ ) { 76 | this.signal[ i ] = ( e.frameBuffer[ 2 * i ] + e.frameBuffer[ 2 * i + 1 ] ) / 2; 77 | } 78 | 79 | this.fft.forward( this.signal ); 80 | this.dancer.trigger( 'update' ); 81 | } 82 | }; 83 | 84 | function getMetadata () { 85 | this.fbLength = this.audio.mozFrameBufferLength; 86 | this.channels = this.audio.mozChannels; 87 | this.rate = this.audio.mozSampleRate; 88 | this.fft = new FFT( this.fbLength / this.channels, this.rate ); 89 | this.signal = new Float32Array( this.fbLength / this.channels ); 90 | this.isLoaded = true; 91 | this.progress = 1; 92 | this.dancer.trigger( 'loaded' ); 93 | } 94 | 95 | Dancer.adapters.moz = adapter; 96 | 97 | })(); 98 | -------------------------------------------------------------------------------- /src/support.js: -------------------------------------------------------------------------------- 1 | (function ( Dancer ) { 2 | 3 | var CODECS = { 4 | 'mp3' : 'audio/mpeg;', 5 | 'ogg' : 'audio/ogg; codecs="vorbis"', 6 | 'wav' : 'audio/wav; codecs="1"', 7 | 'aac' : 'audio/mp4; codecs="mp4a.40.2"' 8 | }, 9 | audioEl = document.createElement( 'audio' ); 10 | 11 | Dancer.options = {}; 12 | 13 | Dancer.setOptions = function ( o ) { 14 | for ( var option in o ) { 15 | if ( o.hasOwnProperty( option ) ) { 16 | Dancer.options[ option ] = o[ option ]; 17 | } 18 | } 19 | }; 20 | 21 | Dancer.isSupported = function () { 22 | if ( !window.Float32Array || !window.Uint32Array ) { 23 | return null; 24 | } else if ( !isUnsupportedSafari() && ( window.AudioContext || window.webkitAudioContext )) { 25 | return 'webaudio'; 26 | } else if ( audioEl && audioEl.mozSetup ) { 27 | return 'audiodata'; 28 | } else if ( FlashDetect.versionAtLeast( 9 ) ) { 29 | return 'flash'; 30 | } else { 31 | return ''; 32 | } 33 | }; 34 | 35 | Dancer.canPlay = function ( type ) { 36 | var canPlay = audioEl.canPlayType; 37 | return !!( 38 | Dancer.isSupported() === 'flash' ? 39 | type.toLowerCase() === 'mp3' : 40 | audioEl.canPlayType && 41 | audioEl.canPlayType( CODECS[ type.toLowerCase() ] ).replace( /no/, '')); 42 | }; 43 | 44 | Dancer.addPlugin = function ( name, fn ) { 45 | if ( Dancer.prototype[ name ] === undefined ) { 46 | Dancer.prototype[ name ] = fn; 47 | } 48 | }; 49 | 50 | Dancer._makeSupportedPath = function ( source, codecs ) { 51 | if ( !codecs ) { return source; } 52 | 53 | for ( var i = 0; i < codecs.length; i++ ) { 54 | if ( Dancer.canPlay( codecs[ i ] ) ) { 55 | return source + '.' + codecs[ i ]; 56 | } 57 | } 58 | return source; 59 | }; 60 | 61 | Dancer._getAdapter = function ( instance ) { 62 | switch ( Dancer.isSupported() ) { 63 | case 'webaudio': 64 | return new Dancer.adapters.webaudio( instance ); 65 | case 'audiodata': 66 | return new Dancer.adapters.moz( instance ); 67 | case 'flash': 68 | return new Dancer.adapters.flash( instance ); 69 | default: 70 | return null; 71 | } 72 | }; 73 | 74 | Dancer._getMP3SrcFromAudio = function ( audioEl ) { 75 | var sources = audioEl.children; 76 | if ( audioEl.src ) { return audioEl.src; } 77 | for ( var i = sources.length; i--; ) { 78 | if (( sources[ i ].type || '' ).match( /audio\/mpeg/ )) return sources[ i ].src; 79 | } 80 | return null; 81 | }; 82 | 83 | // Browser detection is lame, but Safari 6 has Web Audio API, 84 | // but does not support processing audio from a Media Element Source 85 | // https://gist.github.com/3265344 86 | function isUnsupportedSafari () { 87 | var 88 | isApple = !!( navigator.vendor || '' ).match( /Apple/ ), 89 | version = navigator.userAgent.match( /Version\/([^ ]*)/ ); 90 | version = version ? parseFloat( version[ 1 ] ) : 0; 91 | return isApple && version <= 6; 92 | } 93 | 94 | })( window.Dancer ); 95 | -------------------------------------------------------------------------------- /src/adapterWebAudio.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var 3 | SAMPLE_SIZE = 2048, 4 | SAMPLE_RATE = 44100; 5 | 6 | var adapter = function ( dancer ) { 7 | this.dancer = dancer; 8 | this.audio = new Audio(); 9 | this.context = window.AudioContext ? 10 | new window.AudioContext() : 11 | new window.webkitAudioContext(); 12 | }; 13 | 14 | adapter.prototype = { 15 | 16 | load : function ( _source ) { 17 | var _this = this; 18 | this.audio = _source; 19 | 20 | this.isLoaded = false; 21 | this.progress = 0; 22 | 23 | if (!this.context.createScriptProcessor) { 24 | this.context.createScriptProcessor = this.context.createJavascriptNode; 25 | } 26 | this.proc = this.context.createScriptProcessor( SAMPLE_SIZE / 2, 1, 1 ); 27 | 28 | this.proc.onaudioprocess = function ( e ) { 29 | _this.update.call( _this, e ); 30 | }; 31 | if (!this.context.createGain) { 32 | this.context.createGain = this.context.createGainNode; 33 | } 34 | 35 | this.gain = this.context.createGain(); 36 | 37 | this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE ); 38 | this.signal = new Float32Array( SAMPLE_SIZE / 2 ); 39 | 40 | if ( this.audio.readyState < 3 ) { 41 | this.audio.addEventListener( 'canplay', function () { 42 | connectContext.call( _this ); 43 | }); 44 | } else { 45 | connectContext.call( _this ); 46 | } 47 | 48 | this.audio.addEventListener( 'progress', function ( e ) { 49 | if ( e.currentTarget.duration ) { 50 | _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; 51 | } 52 | }); 53 | 54 | return this.audio; 55 | }, 56 | 57 | play : function () { 58 | this.audio.play(); 59 | this.isPlaying = true; 60 | }, 61 | 62 | pause : function () { 63 | this.audio.pause(); 64 | this.isPlaying = false; 65 | }, 66 | 67 | setVolume : function ( volume ) { 68 | this.gain.gain.value = volume; 69 | }, 70 | 71 | getVolume : function () { 72 | return this.gain.gain.value; 73 | }, 74 | 75 | getProgress : function() { 76 | return this.progress; 77 | }, 78 | 79 | getWaveform : function () { 80 | return this.signal; 81 | }, 82 | 83 | getSpectrum : function () { 84 | return this.fft.spectrum; 85 | }, 86 | 87 | getTime : function () { 88 | return this.audio.currentTime; 89 | }, 90 | 91 | update : function ( e ) { 92 | if ( !this.isPlaying || !this.isLoaded ) return; 93 | 94 | var 95 | buffers = [], 96 | channels = e.inputBuffer.numberOfChannels, 97 | resolution = SAMPLE_SIZE / channels, 98 | sum = function ( prev, curr ) { 99 | return prev[ i ] + curr[ i ]; 100 | }, i; 101 | 102 | for ( i = channels; i--; ) { 103 | buffers.push( e.inputBuffer.getChannelData( i ) ); 104 | } 105 | 106 | for ( i = 0; i < resolution; i++ ) { 107 | this.signal[ i ] = channels > 1 ? 108 | buffers.reduce( sum ) / channels : 109 | buffers[ 0 ][ i ]; 110 | } 111 | 112 | this.fft.forward( this.signal ); 113 | this.dancer.trigger( 'update' ); 114 | } 115 | }; 116 | 117 | function connectContext () { 118 | this.source = this.context.createMediaElementSource( this.audio ); 119 | this.source.connect( this.proc ); 120 | this.source.connect( this.gain ); 121 | this.gain.connect( this.context.destination ); 122 | this.proc.connect( this.context.destination ); 123 | 124 | this.isLoaded = true; 125 | this.progress = 1; 126 | this.dancer.trigger( 'loaded' ); 127 | } 128 | 129 | Dancer.adapters.webaudio = adapter; 130 | 131 | })(); 132 | -------------------------------------------------------------------------------- /examples/lib/Tween.js: -------------------------------------------------------------------------------- 1 | // tween.js r5 - http://github.com/sole/tween.js 2 | var TWEEN=TWEEN||function(){var a,e,c=60,b=false,h=[],i;return{setFPS:function(f){c=f||60},start:function(f){arguments.length!=0&&this.setFPS(f);e=setInterval(this.update,1E3/c)},stop:function(){clearInterval(e)},setAutostart:function(f){(b=f)&&!e&&this.start()},add:function(f){h.push(f);b&&!e&&this.start()},getAll:function(){return h},removeAll:function(){h=[]},remove:function(f){a=h.indexOf(f);a!==-1&&h.splice(a,1)},update:function(f){a=0;i=h.length;for(f=f||Date.now();a1?1:d;j=n(d);for(g in c)a[g]=e[g]+c[g]*j;l!==null&&l.call(a,j);if(d==1){m!==null&&m.call(a);k!==null&&k.start();return false}return true}};TWEEN.Easing={Linear:{},Quadratic:{},Cubic:{},Quartic:{},Quintic:{},Sinusoidal:{},Exponential:{},Circular:{},Elastic:{},Back:{},Bounce:{}};TWEEN.Easing.Linear.EaseNone=function(a){return a}; 6 | TWEEN.Easing.Quadratic.EaseIn=function(a){return a*a};TWEEN.Easing.Quadratic.EaseOut=function(a){return-a*(a-2)};TWEEN.Easing.Quadratic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a;return-0.5*(--a*(a-2)-1)};TWEEN.Easing.Cubic.EaseIn=function(a){return a*a*a};TWEEN.Easing.Cubic.EaseOut=function(a){return--a*a*a+1};TWEEN.Easing.Cubic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a;return 0.5*((a-=2)*a*a+2)};TWEEN.Easing.Quartic.EaseIn=function(a){return a*a*a*a}; 7 | TWEEN.Easing.Quartic.EaseOut=function(a){return-(--a*a*a*a-1)};TWEEN.Easing.Quartic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a;return-0.5*((a-=2)*a*a*a-2)};TWEEN.Easing.Quintic.EaseIn=function(a){return a*a*a*a*a};TWEEN.Easing.Quintic.EaseOut=function(a){return(a-=1)*a*a*a*a+1};TWEEN.Easing.Quintic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a*a;return 0.5*((a-=2)*a*a*a*a+2)};TWEEN.Easing.Sinusoidal.EaseIn=function(a){return-Math.cos(a*Math.PI/2)+1}; 8 | TWEEN.Easing.Sinusoidal.EaseOut=function(a){return Math.sin(a*Math.PI/2)};TWEEN.Easing.Sinusoidal.EaseInOut=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};TWEEN.Easing.Exponential.EaseIn=function(a){return a==0?0:Math.pow(2,10*(a-1))};TWEEN.Easing.Exponential.EaseOut=function(a){return a==1?1:-Math.pow(2,-10*a)+1};TWEEN.Easing.Exponential.EaseInOut=function(a){if(a==0)return 0;if(a==1)return 1;if((a*=2)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*(a-1))+2)}; 9 | TWEEN.Easing.Circular.EaseIn=function(a){return-(Math.sqrt(1-a*a)-1)};TWEEN.Easing.Circular.EaseOut=function(a){return Math.sqrt(1- --a*a)};TWEEN.Easing.Circular.EaseInOut=function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)};TWEEN.Easing.Elastic.EaseIn=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);return-(c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/b))}; 10 | TWEEN.Easing.Elastic.EaseOut=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);return c*Math.pow(2,-10*a)*Math.sin((a-e)*2*Math.PI/b)+1}; 11 | TWEEN.Easing.Elastic.EaseInOut=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);if((a*=2)<1)return-0.5*c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/b);return c*Math.pow(2,-10*(a-=1))*Math.sin((a-e)*2*Math.PI/b)*0.5+1};TWEEN.Easing.Back.EaseIn=function(a){return a*a*(2.70158*a-1.70158)};TWEEN.Easing.Back.EaseOut=function(a){return(a-=1)*a*(2.70158*a+1.70158)+1}; 12 | TWEEN.Easing.Back.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*(3.5949095*a-2.5949095);return 0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)};TWEEN.Easing.Bounce.EaseIn=function(a){return 1-TWEEN.Easing.Bounce.EaseOut(1-a)};TWEEN.Easing.Bounce.EaseOut=function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375}; 13 | TWEEN.Easing.Bounce.EaseInOut=function(a){if(a<0.5)return TWEEN.Easing.Bounce.EaseIn(a*2)*0.5;return TWEEN.Easing.Bounce.EaseOut(a*2-1)*0.5+0.5}; 14 | -------------------------------------------------------------------------------- /spec/support.js: -------------------------------------------------------------------------------- 1 | describe('Support', function () { 2 | 3 | describe('addPlugin()', function () { 4 | it('Should add a method to the prototype if not in the chain', function () { 5 | var fn = jasmine.createSpy(); 6 | Dancer.addPlugin('pluginname', fn); 7 | dancer.pluginname('arggg'); 8 | expect(fn).toHaveBeenCalledWith('arggg'); 9 | }); 10 | 11 | it('Should pass the dancer instance as the "this" context', function () { 12 | Dancer.addPlugin('pluginname2', function() { return this; }); 13 | expect(dancer.pluginname2()).toBe(dancer); 14 | }); 15 | 16 | it('Should not allow a rebinding of a preexisting prototype method or plugin', function () { 17 | var 18 | origMethod = Dancer.prototype.play, 19 | newMethod = function() { }; 20 | Dancer.addPlugin('play', newMethod); 21 | Dancer.addPlugin('pluginname', newMethod); // Used in previous test 22 | expect(dancer.play).toBe(origMethod); 23 | expect(dancer.pluginname).not.toBe(newMethod); 24 | }); 25 | }); 26 | 27 | describe('isSupported()', function () { 28 | var webAudio = window.webkitAudioContext || window.AudioContext, 29 | audioData = window.Audio && (new window.Audio()).mozSetup ? window.Audio : null; 30 | 31 | it('Should return null if typed arrays are not present', function () { 32 | var 33 | f32 = window.Float32Array, 34 | u32 = window.Uint32Array; 35 | expect( !f32 || !u32 ).toBe( !Dancer.isSupported() ); 36 | window.Float32Array = null; 37 | window.Uint32Array = null; 38 | expect( Dancer.isSupported() ).toBeFalsy(); 39 | window.Float32Array = f32; 40 | window.Uint32Array = u32; 41 | }); 42 | 43 | it('Should test whether or not the browser supports Web Audio or Audio Data or flash', function () { 44 | expect(Dancer.isSupported()).toBeTruthy(); 45 | expect(!!webAudio).toBe(Dancer.isSupported()==='webaudio'); 46 | expect(!!audioData).toBe(Dancer.isSupported()==='audiodata'); 47 | expect(!!webAudio && !!audioData && FlashDetect.versionAtLeast(9)).toBe(Dancer.isSupported()==='flash'); 48 | }); 49 | }); 50 | 51 | describe('canPlay()', function () { 52 | it('Should return the correct support for current browser', function () { 53 | var audio = document.createElement('audio'), 54 | canMp3 = audio.canPlayType && audio.canPlayType('audio/mpeg;').replace(/no/,''), 55 | canOgg = audio.canPlayType && audio.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/,''), 56 | canWav = audio.canPlayType && audio.canPlayType('audio/wav; codecs="1"').replace(/no/,''), 57 | canAac = audio.canPlayType && audio.canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/,''); 58 | if ( Dancer.isSupported() === 'flash' ) { 59 | expect(Dancer.canPlay('MP3')).toBeTruthy(); 60 | expect(Dancer.canPlay('oGg')).toBeFalsy(); 61 | expect(Dancer.canPlay('WaV')).toBeFalsy(); 62 | expect(Dancer.canPlay('aac')).toBeFalsy(); 63 | } else { 64 | expect(Dancer.canPlay('MP3')).toEqual(!!canMp3); 65 | expect(Dancer.canPlay('oGg')).toEqual(!!canOgg); 66 | expect(Dancer.canPlay('WaV')).toEqual(!!canWav); 67 | expect(Dancer.canPlay('aac')).toEqual(!!canAac); 68 | } 69 | }); 70 | }); 71 | 72 | describe('_makeSupportedPath', function () { 73 | var 74 | pathWithExt = '/path/to/audio.ogg', 75 | pathWithoutExt = '/path/to/audio'; 76 | it('Should return a path unmodified if no codecs given', function () { 77 | expect(Dancer._makeSupportedPath( pathWithExt )).toEqual(pathWithExt); 78 | }); 79 | it('Should return a path with first usable codec when codecs given', function () { 80 | var otherValidCodec = Dancer.canPlay('wav') ? 'wav' : ( Dancer.canPlay('mp3') ? 'mp3' : 'ogg' ); 81 | if ( Dancer.isSupported() === 'flash' ) { 82 | expect(Dancer._makeSupportedPath( pathWithoutExt, [ 'ogg', 'wav', 'mp3' ] )).toEqual(pathWithoutExt + '.mp3') 83 | } else { 84 | expect(Dancer._makeSupportedPath( pathWithoutExt, [ 'bogus', 'codec', 'ogg' ] )).toEqual(pathWithoutExt + '.ogg'); 85 | expect(Dancer._makeSupportedPath( pathWithoutExt, [ otherValidCodec, 'ogg' ] )).toEqual(pathWithoutExt + '.' + otherValidCodec); 86 | } 87 | }); 88 | }); 89 | 90 | describe('setOptions()', function () { 91 | it('Should set options correctly', function () { 92 | Dancer.setOptions({ test1: 'cheeseburger', test2: 'megaman' }); 93 | Dancer.setOptions({ test1: 'cheeseburger' }); 94 | expect(Dancer.options.test1).toBe('cheeseburger'); 95 | expect(Dancer.options.test2).toBe('megaman'); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/adapterFlash.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var 3 | SAMPLE_SIZE = 1024, 4 | SAMPLE_RATE = 44100, 5 | smLoaded = false, 6 | smLoading = false, 7 | CONVERSION_COEFFICIENT = 0.93; 8 | 9 | var adapter = function ( dancer ) { 10 | this.dancer = dancer; 11 | this.wave_L = []; 12 | this.wave_R = []; 13 | this.spectrum = []; 14 | window.SM2_DEFER = true; 15 | }; 16 | 17 | adapter.prototype = { 18 | // `source` can be either an Audio element, if supported, or an object 19 | // either way, the path is stored in the `src` property 20 | load : function ( source ) { 21 | var _this = this; 22 | this.path = source ? source.src : this.path; 23 | 24 | this.isLoaded = false; 25 | this.progress = 0; 26 | 27 | !window.soundManager && !smLoading && loadSM.call( this ); 28 | 29 | if ( window.soundManager ) { 30 | this.audio = soundManager.createSound({ 31 | id : 'dancer' + Math.random() + '', 32 | url : this.path, 33 | stream : true, 34 | autoPlay : false, 35 | autoLoad : true, 36 | whileplaying : function () { 37 | _this.update(); 38 | }, 39 | whileloading : function () { 40 | _this.progress = this.bytesLoaded / this.bytesTotal; 41 | }, 42 | onload : function () { 43 | _this.fft = new FFT( SAMPLE_SIZE, SAMPLE_RATE ); 44 | _this.signal = new Float32Array( SAMPLE_SIZE ); 45 | _this.waveform = new Float32Array( SAMPLE_SIZE ); 46 | _this.isLoaded = true; 47 | _this.progress = 1; 48 | _this.dancer.trigger( 'loaded' ); 49 | } 50 | }); 51 | this.dancer.audio = this.audio; 52 | } 53 | 54 | // Returns audio if SM already loaded -- otherwise, 55 | // sets dancer instance's audio property after load 56 | return this.audio; 57 | }, 58 | 59 | play : function () { 60 | this.audio.play(); 61 | this.isPlaying = true; 62 | }, 63 | 64 | pause : function () { 65 | this.audio.pause(); 66 | this.isPlaying = false; 67 | }, 68 | 69 | setVolume : function ( volume ) { 70 | this.audio.setVolume( volume * 100 ); 71 | }, 72 | 73 | getVolume : function () { 74 | return this.audio.volume / 100; 75 | }, 76 | 77 | getProgress : function () { 78 | return this.progress; 79 | }, 80 | 81 | getWaveform : function () { 82 | return this.waveform; 83 | }, 84 | 85 | getSpectrum : function () { 86 | return this.fft.spectrum; 87 | }, 88 | 89 | getTime : function () { 90 | return this.audio.position / 1000; 91 | }, 92 | 93 | update : function () { 94 | if ( !this.isPlaying && !this.isLoaded ) return; 95 | this.wave_L = this.audio.waveformData.left; 96 | this.wave_R = this.audio.waveformData.right; 97 | var avg; 98 | for ( var i = 0, j = this.wave_L.length; i < j; i++ ) { 99 | avg = parseFloat(this.wave_L[ i ]) + parseFloat(this.wave_R[ i ]); 100 | this.waveform[ 2 * i ] = avg / 2; 101 | this.waveform[ i * 2 + 1 ] = avg / 2; 102 | this.signal[ 2 * i ] = avg * CONVERSION_COEFFICIENT; 103 | this.signal[ i * 2 + 1 ] = avg * CONVERSION_COEFFICIENT; 104 | } 105 | 106 | this.fft.forward( this.signal ); 107 | this.dancer.trigger( 'update' ); 108 | } 109 | }; 110 | 111 | function loadSM () { 112 | var adapter = this; 113 | smLoading = true; 114 | loadScript( Dancer.options.flashJS, function () { 115 | soundManager = new SoundManager(); 116 | soundManager.flashVersion = 9; 117 | soundManager.flash9Options.useWaveformData = true; 118 | soundManager.useWaveformData = true; 119 | soundManager.useHighPerformance = true; 120 | soundManager.useFastPolling = true; 121 | soundManager.multiShot = false; 122 | soundManager.debugMode = false; 123 | soundManager.debugFlash = false; 124 | soundManager.url = Dancer.options.flashSWF; 125 | soundManager.onready(function () { 126 | smLoaded = true; 127 | adapter.load(); 128 | }); 129 | soundManager.ontimeout(function(){ 130 | console.error( 'Error loading SoundManager2.swf' ); 131 | }); 132 | soundManager.beginDelayedInit(); 133 | }); 134 | } 135 | 136 | function loadScript ( url, callback ) { 137 | var 138 | script = document.createElement( 'script' ), 139 | appender = document.getElementsByTagName( 'script' )[0]; 140 | script.type = 'text/javascript'; 141 | script.src = url; 142 | script.onload = callback; 143 | appender.parentNode.insertBefore( script, appender ); 144 | } 145 | 146 | Dancer.adapters.flash = adapter; 147 | 148 | })(); 149 | -------------------------------------------------------------------------------- /src/dancer.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var Dancer = function () { 4 | this.audioAdapter = Dancer._getAdapter( this ); 5 | this.events = {}; 6 | this.sections = []; 7 | this.bind( 'update', update ); 8 | }; 9 | 10 | Dancer.version = '0.3.2'; 11 | Dancer.adapters = {}; 12 | 13 | Dancer.prototype = { 14 | 15 | load : function ( source ) { 16 | var path; 17 | 18 | // Loading an Audio element 19 | if ( source instanceof HTMLElement ) { 20 | this.source = source; 21 | if ( Dancer.isSupported() === 'flash' ) { 22 | this.source = { src: Dancer._getMP3SrcFromAudio( source ) }; 23 | } 24 | 25 | // Loading an object with src, [codecs] 26 | } else { 27 | this.source = window.Audio ? new Audio() : {}; 28 | this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); 29 | } 30 | 31 | this.audio = this.audioAdapter.load( this.source ); 32 | return this; 33 | }, 34 | 35 | /* Controls */ 36 | 37 | play : function () { 38 | this.audioAdapter.play(); 39 | return this; 40 | }, 41 | 42 | pause : function () { 43 | this.audioAdapter.pause(); 44 | return this; 45 | }, 46 | 47 | setVolume : function ( volume ) { 48 | this.audioAdapter.setVolume( volume ); 49 | return this; 50 | }, 51 | 52 | 53 | /* Actions */ 54 | 55 | createKick : function ( options ) { 56 | return new Dancer.Kick( this, options ); 57 | }, 58 | 59 | bind : function ( name, callback ) { 60 | if ( !this.events[ name ] ) { 61 | this.events[ name ] = []; 62 | } 63 | this.events[ name ].push( callback ); 64 | return this; 65 | }, 66 | 67 | unbind : function ( name ) { 68 | if ( this.events[ name ] ) { 69 | delete this.events[ name ]; 70 | } 71 | return this; 72 | }, 73 | 74 | trigger : function ( name ) { 75 | var _this = this; 76 | if ( this.events[ name ] ) { 77 | this.events[ name ].forEach(function( callback ) { 78 | callback.call( _this ); 79 | }); 80 | } 81 | return this; 82 | }, 83 | 84 | 85 | /* Getters */ 86 | 87 | getVolume : function () { 88 | return this.audioAdapter.getVolume(); 89 | }, 90 | 91 | getProgress : function () { 92 | return this.audioAdapter.getProgress(); 93 | }, 94 | 95 | getTime : function () { 96 | return this.audioAdapter.getTime(); 97 | }, 98 | 99 | // Returns the magnitude of a frequency or average over a range of frequencies 100 | getFrequency : function ( freq, endFreq ) { 101 | var sum = 0; 102 | if ( endFreq !== undefined ) { 103 | for ( var i = freq; i <= endFreq; i++ ) { 104 | sum += this.getSpectrum()[ i ]; 105 | } 106 | return sum / ( endFreq - freq + 1 ); 107 | } else { 108 | return this.getSpectrum()[ freq ]; 109 | } 110 | }, 111 | 112 | getWaveform : function () { 113 | return this.audioAdapter.getWaveform(); 114 | }, 115 | 116 | getSpectrum : function () { 117 | return this.audioAdapter.getSpectrum(); 118 | }, 119 | 120 | isLoaded : function () { 121 | return this.audioAdapter.isLoaded; 122 | }, 123 | 124 | isPlaying : function () { 125 | return this.audioAdapter.isPlaying; 126 | }, 127 | 128 | 129 | /* Sections */ 130 | 131 | after : function ( time, callback ) { 132 | var _this = this; 133 | this.sections.push({ 134 | condition : function () { 135 | return _this.getTime() > time; 136 | }, 137 | callback : callback 138 | }); 139 | return this; 140 | }, 141 | 142 | before : function ( time, callback ) { 143 | var _this = this; 144 | this.sections.push({ 145 | condition : function () { 146 | return _this.getTime() < time; 147 | }, 148 | callback : callback 149 | }); 150 | return this; 151 | }, 152 | 153 | between : function ( startTime, endTime, callback ) { 154 | var _this = this; 155 | this.sections.push({ 156 | condition : function () { 157 | return _this.getTime() > startTime && _this.getTime() < endTime; 158 | }, 159 | callback : callback 160 | }); 161 | return this; 162 | }, 163 | 164 | onceAt : function ( time, callback ) { 165 | var 166 | _this = this, 167 | thisSection = null; 168 | this.sections.push({ 169 | condition : function () { 170 | return _this.getTime() > time && !this.called; 171 | }, 172 | callback : function () { 173 | callback.call( this ); 174 | thisSection.called = true; 175 | }, 176 | called : false 177 | }); 178 | // Baking the section in the closure due to callback's this being the dancer instance 179 | thisSection = this.sections[ this.sections.length - 1 ]; 180 | return this; 181 | } 182 | }; 183 | 184 | function update () { 185 | for ( var i in this.sections ) { 186 | if ( this.sections[ i ].condition() ) 187 | this.sections[ i ].callback.call( this ); 188 | } 189 | } 190 | 191 | window.Dancer = Dancer; 192 | })(); 193 | -------------------------------------------------------------------------------- /lib/fft.js: -------------------------------------------------------------------------------- 1 | /* 2 | * DSP.js - a comprehensive digital signal processing library for javascript 3 | * 4 | * Created by Corban Brook on 2010-01-01. 5 | * Copyright 2010 Corban Brook. All rights reserved. 6 | * 7 | */ 8 | 9 | // Fourier Transform Module used by DFT, FFT, RFFT 10 | function FourierTransform(bufferSize, sampleRate) { 11 | this.bufferSize = bufferSize; 12 | this.sampleRate = sampleRate; 13 | this.bandwidth = 2 / bufferSize * sampleRate / 2; 14 | 15 | this.spectrum = new Float32Array(bufferSize/2); 16 | this.real = new Float32Array(bufferSize); 17 | this.imag = new Float32Array(bufferSize); 18 | 19 | this.peakBand = 0; 20 | this.peak = 0; 21 | 22 | /** 23 | * Calculates the *middle* frequency of an FFT band. 24 | * 25 | * @param {Number} index The index of the FFT band. 26 | * 27 | * @returns The middle frequency in Hz. 28 | */ 29 | this.getBandFrequency = function(index) { 30 | return this.bandwidth * index + this.bandwidth / 2; 31 | }; 32 | 33 | this.calculateSpectrum = function() { 34 | var spectrum = this.spectrum, 35 | real = this.real, 36 | imag = this.imag, 37 | bSi = 2 / this.bufferSize, 38 | sqrt = Math.sqrt, 39 | rval, 40 | ival, 41 | mag; 42 | 43 | for (var i = 0, N = bufferSize/2; i < N; i++) { 44 | rval = real[i]; 45 | ival = imag[i]; 46 | mag = bSi * sqrt(rval * rval + ival * ival); 47 | 48 | if (mag > this.peak) { 49 | this.peakBand = i; 50 | this.peak = mag; 51 | } 52 | 53 | spectrum[i] = mag; 54 | } 55 | }; 56 | } 57 | 58 | /** 59 | * FFT is a class for calculating the Discrete Fourier Transform of a signal 60 | * with the Fast Fourier Transform algorithm. 61 | * 62 | * @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2 63 | * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) 64 | * 65 | * @constructor 66 | */ 67 | function FFT(bufferSize, sampleRate) { 68 | FourierTransform.call(this, bufferSize, sampleRate); 69 | 70 | this.reverseTable = new Uint32Array(bufferSize); 71 | 72 | var limit = 1; 73 | var bit = bufferSize >> 1; 74 | 75 | var i; 76 | 77 | while (limit < bufferSize) { 78 | for (i = 0; i < limit; i++) { 79 | this.reverseTable[i + limit] = this.reverseTable[i] + bit; 80 | } 81 | 82 | limit = limit << 1; 83 | bit = bit >> 1; 84 | } 85 | 86 | this.sinTable = new Float32Array(bufferSize); 87 | this.cosTable = new Float32Array(bufferSize); 88 | 89 | for (i = 0; i < bufferSize; i++) { 90 | this.sinTable[i] = Math.sin(-Math.PI/i); 91 | this.cosTable[i] = Math.cos(-Math.PI/i); 92 | } 93 | } 94 | 95 | /** 96 | * Performs a forward transform on the sample buffer. 97 | * Converts a time domain signal to frequency domain spectra. 98 | * 99 | * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 100 | * 101 | * @returns The frequency spectrum array 102 | */ 103 | FFT.prototype.forward = function(buffer) { 104 | // Locally scope variables for speed up 105 | var bufferSize = this.bufferSize, 106 | cosTable = this.cosTable, 107 | sinTable = this.sinTable, 108 | reverseTable = this.reverseTable, 109 | real = this.real, 110 | imag = this.imag, 111 | spectrum = this.spectrum; 112 | 113 | var k = Math.floor(Math.log(bufferSize) / Math.LN2); 114 | 115 | if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; } 116 | if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; } 117 | 118 | var halfSize = 1, 119 | phaseShiftStepReal, 120 | phaseShiftStepImag, 121 | currentPhaseShiftReal, 122 | currentPhaseShiftImag, 123 | off, 124 | tr, 125 | ti, 126 | tmpReal, 127 | i; 128 | 129 | for (i = 0; i < bufferSize; i++) { 130 | real[i] = buffer[reverseTable[i]]; 131 | imag[i] = 0; 132 | } 133 | 134 | while (halfSize < bufferSize) { 135 | //phaseShiftStepReal = Math.cos(-Math.PI/halfSize); 136 | //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); 137 | phaseShiftStepReal = cosTable[halfSize]; 138 | phaseShiftStepImag = sinTable[halfSize]; 139 | 140 | currentPhaseShiftReal = 1; 141 | currentPhaseShiftImag = 0; 142 | 143 | for (var fftStep = 0; fftStep < halfSize; fftStep++) { 144 | i = fftStep; 145 | 146 | while (i < bufferSize) { 147 | off = i + halfSize; 148 | tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); 149 | ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); 150 | 151 | real[off] = real[i] - tr; 152 | imag[off] = imag[i] - ti; 153 | real[i] += tr; 154 | imag[i] += ti; 155 | 156 | i += halfSize << 1; 157 | } 158 | 159 | tmpReal = currentPhaseShiftReal; 160 | currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); 161 | currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); 162 | } 163 | 164 | halfSize = halfSize << 1; 165 | } 166 | 167 | return this.calculateSpectrum(); 168 | }; 169 | -------------------------------------------------------------------------------- /examples/single_song_demo/js/player.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var 4 | AUDIO_FILE = '../songs/zircon_devils_spirit', 5 | PARTICLE_COUNT = 250, 6 | MAX_PARTICLE_SIZE = 12, 7 | MIN_PARTICLE_SIZE = 2, 8 | GROWTH_RATE = 5, 9 | DECAY_RATE = 0.5, 10 | 11 | BEAM_RATE = 0.5, 12 | BEAM_COUNT = 20, 13 | 14 | GROWTH_VECTOR = new THREE.Vector3( GROWTH_RATE, GROWTH_RATE, GROWTH_RATE ), 15 | DECAY_VECTOR = new THREE.Vector3( DECAY_RATE, DECAY_RATE, DECAY_RATE ), 16 | beamGroup = new THREE.Object3D(), 17 | particles = group.children, 18 | colors = [ 0xaaee22, 0x04dbe5, 0xff0077, 0xffb412, 0xf6c83d ], 19 | t, dancer, kick; 20 | 21 | /* 22 | * Dancer.js magic 23 | */ 24 | 25 | Dancer.setOptions({ 26 | flashSWF : '../../lib/soundmanager2.swf', 27 | flashJS : '../../lib/soundmanager2.js' 28 | }); 29 | 30 | dancer = new Dancer(); 31 | kick = dancer.createKick({ 32 | onKick: function () { 33 | var i; 34 | if ( particles[ 0 ].scale.x > MAX_PARTICLE_SIZE ) { 35 | decay(); 36 | } else { 37 | for ( i = PARTICLE_COUNT; i--; ) { 38 | particles[ i ].scale.addSelf( GROWTH_VECTOR ); 39 | } 40 | } 41 | if ( !beamGroup.children[ 0 ].visible ) { 42 | for ( i = BEAM_COUNT; i--; ) { 43 | beamGroup.children[ i ].visible = true; 44 | } 45 | } 46 | }, 47 | offKick: decay 48 | }); 49 | 50 | dancer.onceAt( 0, function () { 51 | kick.on(); 52 | }).onceAt( 8.2, function () { 53 | scene.add( beamGroup ); 54 | }).after( 8.2, function () { 55 | beamGroup.rotation.x += BEAM_RATE; 56 | beamGroup.rotation.y += BEAM_RATE; 57 | }).onceAt( 50, function () { 58 | changeParticleMat( 'white' ); 59 | }).onceAt( 66.5, function () { 60 | changeParticleMat( 'pink' ); 61 | }).onceAt( 75, function () { 62 | changeParticleMat(); 63 | }).fft( document.getElementById( 'fft' ) ) 64 | .load({ src: AUDIO_FILE, codecs: [ 'ogg', 'mp3' ]}) 65 | 66 | Dancer.isSupported() || loaded(); 67 | !dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded(); 68 | 69 | /* 70 | * Three.js Setup 71 | */ 72 | 73 | function on () { 74 | for ( var i = PARTICLE_COUNT; i--; ) { 75 | particle = new THREE.Particle( newParticleMat() ); 76 | particle.position.x = Math.random() * 2000 - 1000; 77 | particle.position.y = Math.random() * 2000 - 1000; 78 | particle.position.z = Math.random() * 2000 - 1000; 79 | particle.scale.x = particle.scale.y = Math.random() * 10 + 5; 80 | group.add( particle ); 81 | } 82 | scene.add( group ); 83 | 84 | // Beam idea from http://www.airtightinteractive.com/demos/js/nebula/ 85 | var 86 | beamGeometry = new THREE.PlaneGeometry( 5000, 50, 1, 1 ), 87 | beamMaterial, beam; 88 | 89 | for ( i = BEAM_COUNT; i--; ) { 90 | beamMaterial = new THREE.MeshBasicMaterial({ 91 | opacity: 0.5, 92 | blending: THREE.AdditiveBlending, 93 | depthTest: false, 94 | color: colors[ ~~( Math.random() * 5 )] 95 | }); 96 | beam = new THREE.Mesh( beamGeometry, beamMaterial ); 97 | beam.doubleSided = true; 98 | beam.rotation.x = Math.random() * Math.PI; 99 | beam.rotation.y = Math.random() * Math.PI; 100 | beam.rotation.z = Math.random() * Math.PI; 101 | beamGroup.add( beam ); 102 | } 103 | } 104 | 105 | function decay () { 106 | if ( beamGroup.children[ 0 ].visible ) { 107 | for ( i = BEAM_COUNT; i--; ) { 108 | beamGroup.children[ i ].visible = false; 109 | } 110 | } 111 | 112 | for ( var i = PARTICLE_COUNT; i--; ) { 113 | if ( particles[i].scale.x - DECAY_RATE > MIN_PARTICLE_SIZE ) { 114 | particles[ i ].scale.subSelf( DECAY_VECTOR ); 115 | } 116 | } 117 | } 118 | 119 | function changeParticleMat ( color ) { 120 | var mat = newParticleMat( color ); 121 | for ( var i = PARTICLE_COUNT; i--; ) { 122 | if ( !color ) { 123 | mat = newParticleMat(); 124 | } 125 | particles[ i ].material = mat; 126 | } 127 | } 128 | 129 | function newParticleMat( color ) { 130 | var 131 | sprites = [ 'pink', 'orange', 'yellow', 'blue', 'green' ], 132 | sprite = color || sprites[ ~~( Math.random() * 5 )]; 133 | 134 | return new THREE.ParticleBasicMaterial({ 135 | blending: THREE.AdditiveBlending, 136 | size: MIN_PARTICLE_SIZE, 137 | map: THREE.ImageUtils.loadTexture('images/particle_' + sprite + '.png'), 138 | vertexColor: 0xFFFFFF 139 | }); 140 | } 141 | 142 | function loaded () { 143 | var 144 | loading = document.getElementById( 'loading' ), 145 | anchor = document.createElement('A'), 146 | supported = Dancer.isSupported(), 147 | p; 148 | 149 | anchor.appendChild( document.createTextNode( supported ? 'Play!' : 'Close' ) ); 150 | anchor.setAttribute( 'href', '#' ); 151 | loading.innerHTML = ''; 152 | loading.appendChild( anchor ); 153 | 154 | if ( !supported ) { 155 | p = document.createElement('P'); 156 | p.appendChild( document.createTextNode( 'Your browser does not currently support either Web Audio API or Audio Data API. The audio may play, but the visualizers will not move to the music; check out the latest Chrome or Firefox browsers!' ) ); 157 | loading.appendChild( p ); 158 | } 159 | 160 | anchor.addEventListener( 'click', function () { 161 | dancer.play(); 162 | document.getElementById('loading').style.display = 'none'; 163 | }, false ); 164 | 165 | } 166 | 167 | on(); 168 | 169 | // For debugging 170 | window.dancer = dancer; 171 | 172 | })(); 173 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.1.0/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 23 | #HTMLReporter .runningAlert { background-color: #666666; } 24 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 25 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 26 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 27 | #HTMLReporter .passingAlert { background-color: #a6b779; } 28 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 29 | #HTMLReporter .failingAlert { background-color: #cf867e; } 30 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 31 | #HTMLReporter .results { margin-top: 14px; } 32 | #HTMLReporter #details { display: none; } 33 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 34 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 35 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 36 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 37 | #HTMLReporter.showDetails .summary { display: none; } 38 | #HTMLReporter.showDetails #details { display: block; } 39 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 40 | #HTMLReporter .summary { margin-top: 14px; } 41 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 42 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 43 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 44 | #HTMLReporter .description + .suite { margin-top: 0; } 45 | #HTMLReporter .suite { margin-top: 14px; } 46 | #HTMLReporter .suite a { color: #333333; } 47 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 48 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 49 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 50 | #HTMLReporter .resultMessage span.result { display: block; } 51 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 52 | 53 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 54 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 55 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 56 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 57 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 58 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 59 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 60 | #TrivialReporter .runner.running { background-color: yellow; } 61 | #TrivialReporter .options { text-align: right; font-size: .8em; } 62 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 63 | #TrivialReporter .suite .suite { margin: 5px; } 64 | #TrivialReporter .suite.passed { background-color: #dfd; } 65 | #TrivialReporter .suite.failed { background-color: #fdd; } 66 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 67 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 68 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 69 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 70 | #TrivialReporter .spec.skipped { background-color: #bbb; } 71 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 72 | #TrivialReporter .passed { background-color: #cfc; display: none; } 73 | #TrivialReporter .failed { background-color: #fbb; } 74 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 75 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 76 | #TrivialReporter .resultMessage .mismatch { color: black; } 77 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 78 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 79 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 80 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 81 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 82 | -------------------------------------------------------------------------------- /spec/kick.js: -------------------------------------------------------------------------------- 1 | describe('Dancer.Kick', function () { 2 | 3 | var random = function (min, max) { return Math.random() * (max-min) + min }; 4 | 5 | describe('Init', function () { 6 | var kick; 7 | 8 | it('Should have correct defaults', function () { 9 | kick = dancer.createKick(); 10 | expect(kick.frequency.length).toEqual(2); 11 | expect(kick.frequency[0]).toEqual(0); 12 | expect(kick.frequency[1]).toEqual(10); 13 | expect(kick.threshold).toEqual(0.3); 14 | expect(kick.decay).toEqual(0.02); 15 | expect(kick.onKick).toBe(undefined); 16 | expect(kick.offKick).toBe(undefined); 17 | }); 18 | 19 | it('Should set options correctly', function () { 20 | var 21 | freqLow = ~~random(1, 5), 22 | freqHigh = ~~random(6, 15), 23 | threshold = random(0.2, 0.4), 24 | decay = random(0.01, 0.03), 25 | onKick = function () { }, 26 | offKick = function () { }; 27 | 28 | kick = dancer.createKick({ 29 | frequency : [ freqLow, freqHigh ], 30 | threshold : threshold, 31 | decay : decay, 32 | onKick : onKick, 33 | offKick : offKick 34 | }); 35 | 36 | expect(kick.frequency[0]).toEqual(freqLow); 37 | expect(kick.frequency[1]).toEqual(freqHigh); 38 | expect(kick.threshold).toEqual(threshold); 39 | expect(kick.decay).toEqual(decay); 40 | expect(kick.onKick).toBe(onKick); 41 | expect(kick.offKick).toBe(offKick); 42 | }); 43 | 44 | it('Should correctly set 0 attributes', function () { 45 | kick = dancer.createKick({ 46 | frequency: 0, 47 | threshold: 0, 48 | decay: 0 49 | }); 50 | expect(kick.frequency).toEqual(0); 51 | expect(kick.threshold).toEqual(0); 52 | expect(kick.decay).toEqual(0); 53 | }); 54 | 55 | it('Should have an internal reference to the dancer instance', function () { 56 | expect(kick.dancer).toBe(dancer); 57 | }); 58 | }); 59 | 60 | describe('Dancer.Kick.maxAmplitude() utility', function () { 61 | var 62 | kick = dancer.createKick(), 63 | fft = [ 0.95, 0.95, 0.9, 0.85, 1, 0.75, 0.7, 0.65, 0.6, 0.55 ], 64 | stubCtx = { dancer: { getSpectrum: function() { return fft; } } }, 65 | maxAmplitude = function ( freq ) { 66 | return kick.maxAmplitude.call( stubCtx, freq ); 67 | }; 68 | 69 | it('Should return frequency amplitude when single frequency given', function () { 70 | expect(maxAmplitude(5)).toEqual(0.75); 71 | expect(maxAmplitude(5.45)).toEqual(0.75); 72 | expect(maxAmplitude(0)).toEqual(0.95); 73 | expect(maxAmplitude(9)).toEqual(0.55); 74 | }); 75 | 76 | it('Should return null when passing out of range frequency', function () { 77 | expect(maxAmplitude(13)).toBeNull(); 78 | expect(maxAmplitude(20)).toBeNull(); 79 | expect(maxAmplitude(11)).toBeNull(); 80 | }); 81 | 82 | it('Should return the max amplitude within a frequency range', function () { 83 | expect(maxAmplitude([0, 10])).toEqual(1); 84 | expect(maxAmplitude([1, 3])).toEqual(0.95); 85 | expect(maxAmplitude([5, 8])).toEqual(0.75); 86 | }); 87 | }); 88 | 89 | describe('on() and off()', function () { 90 | var kick = dancer.createKick(); 91 | 92 | it('Should be off by default', function () { 93 | expect(kick.isOn).toBeFalsy(); 94 | }); 95 | 96 | it('Should turn on with on()', function () { 97 | kick.on(); 98 | expect(kick.isOn).toBeTruthy(); 99 | }); 100 | 101 | it('Should turn off with off()', function () { 102 | kick.off(); 103 | expect(kick.isOn).toBeFalsy(); 104 | }); 105 | }); 106 | 107 | describe('onUpdate()', function () { 108 | var kick, _onUpdate, registeredCallback; 109 | 110 | kick = dancer.createKick(); 111 | registered = dancer.events.update[ dancer.events.update.length - 1 ]; 112 | _onUpdate = kick.onUpdate; 113 | 114 | it('Should register a bridge update event in the dancer instance', function () { 115 | kick.onUpdate = jasmine.createSpy(); 116 | registered(); 117 | expect(kick.onUpdate).toHaveBeenCalled(); 118 | }); 119 | 120 | describe('onKick(), offKick() callbacks', function () { 121 | var 122 | fft = [ 0.5, 0.5, 0.5, 0.1, 0.35 ]; 123 | kick.dancer = { getSpectrum: function () { return fft } }; 124 | kick.frequency = 0; 125 | kick.threshold = 0.3; 126 | kick.decay = 0.01; 127 | kick.off(); 128 | 129 | beforeEach(function () { 130 | kick.onKick = jasmine.createSpy(); 131 | kick.offKick = jasmine.createSpy(); 132 | }); 133 | 134 | it('Should not call either onKick or offKick if kick is not "on"', function () { 135 | kick.onUpdate = _onUpdate; 136 | kick.onUpdate(); 137 | 138 | expect(kick.onKick).not.toHaveBeenCalled(); 139 | expect(kick.offKick).not.toHaveBeenCalled(); 140 | }); 141 | 142 | it('Should call onKick when amplitude > threshold and pass magnitude', function () { 143 | kick.on().onUpdate(); 144 | 145 | expect(kick.onKick).toHaveBeenCalledWith( fft[ kick.frequency ] ); 146 | expect(kick.offKick).not.toHaveBeenCalled(); 147 | }); 148 | 149 | it('Should call offKick when amplitude < threshold and pass magnitude', function () { 150 | kick.frequency = 3; 151 | kick.onUpdate(); 152 | 153 | expect(kick.onKick).not.toHaveBeenCalled(); 154 | expect(kick.offKick).toHaveBeenCalledWith( fft[ kick.frequency ] ); 155 | }); 156 | 157 | it('Should set currentThreshold to previous kick\'s amplitude', function () { 158 | kick.frequency = 0; 159 | kick.onUpdate(); 160 | 161 | expect(kick.currentThreshold).toEqual( fft[ kick.frequency ] ); 162 | }); 163 | 164 | it('Should call onKick when amplitude > threshold and current thresh w/ decay', function () { 165 | var 166 | updates = 0, 167 | kickCalled = false; 168 | 169 | kick.frequency = 4; 170 | kick.currentThreshold = 0.5; 171 | kick.onKick = function () { kickCalled = true; }; 172 | 173 | // currentThreshold should decay 15 times (0.01 decay) 174 | // at currentThreshold is 0.50 and the frequency is 0.35 175 | waitsFor(function () { 176 | kick.onUpdate(); 177 | if (!kickCalled) { updates++; } 178 | return kickCalled; 179 | }, 'Kick frequency was never > than decaying current threshold', 1000); 180 | runs(function () { 181 | expect(updates).toEqual(15); 182 | }); 183 | }); 184 | }); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /lib/flash_detect.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved. 3 | Code licensed under the BSD License: http://www.featureblend.com/license.txt 4 | Version: 1.0.4 5 | */ 6 | var FlashDetect = new function(){ 7 | var self = this; 8 | self.installed = false; 9 | self.raw = ""; 10 | self.major = -1; 11 | self.minor = -1; 12 | self.revision = -1; 13 | self.revisionStr = ""; 14 | var activeXDetectRules = [ 15 | { 16 | "name":"ShockwaveFlash.ShockwaveFlash.7", 17 | "version":function(obj){ 18 | return getActiveXVersion(obj); 19 | } 20 | }, 21 | { 22 | "name":"ShockwaveFlash.ShockwaveFlash.6", 23 | "version":function(obj){ 24 | var version = "6,0,21"; 25 | try{ 26 | obj.AllowScriptAccess = "always"; 27 | version = getActiveXVersion(obj); 28 | }catch(err){} 29 | return version; 30 | } 31 | }, 32 | { 33 | "name":"ShockwaveFlash.ShockwaveFlash", 34 | "version":function(obj){ 35 | return getActiveXVersion(obj); 36 | } 37 | } 38 | ]; 39 | /** 40 | * Extract the ActiveX version of the plugin. 41 | * 42 | * @param {Object} The flash ActiveX object. 43 | * @type String 44 | */ 45 | var getActiveXVersion = function(activeXObj){ 46 | var version = -1; 47 | try{ 48 | version = activeXObj.GetVariable("$version"); 49 | }catch(err){} 50 | return version; 51 | }; 52 | /** 53 | * Try and retrieve an ActiveX object having a specified name. 54 | * 55 | * @param {String} name The ActiveX object name lookup. 56 | * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true. 57 | * @type Object 58 | */ 59 | var getActiveXObject = function(name){ 60 | var obj = -1; 61 | try{ 62 | obj = new ActiveXObject(name); 63 | }catch(err){ 64 | obj = {activeXError:true}; 65 | } 66 | return obj; 67 | }; 68 | /** 69 | * Parse an ActiveX $version string into an object. 70 | * 71 | * @param {String} str The ActiveX Object GetVariable($version) return value. 72 | * @return An object having raw, major, minor, revision and revisionStr attributes. 73 | * @type Object 74 | */ 75 | var parseActiveXVersion = function(str){ 76 | var versionArray = str.split(",");//replace with regex 77 | return { 78 | "raw":str, 79 | "major":parseInt(versionArray[0].split(" ")[1], 10), 80 | "minor":parseInt(versionArray[1], 10), 81 | "revision":parseInt(versionArray[2], 10), 82 | "revisionStr":versionArray[2] 83 | }; 84 | }; 85 | /** 86 | * Parse a standard enabledPlugin.description into an object. 87 | * 88 | * @param {String} str The enabledPlugin.description value. 89 | * @return An object having raw, major, minor, revision and revisionStr attributes. 90 | * @type Object 91 | */ 92 | var parseStandardVersion = function(str){ 93 | var descParts = str.split(/ +/); 94 | var majorMinor = descParts[2].split(/\./); 95 | var revisionStr = descParts[3]; 96 | return { 97 | "raw":str, 98 | "major":parseInt(majorMinor[0], 10), 99 | "minor":parseInt(majorMinor[1], 10), 100 | "revisionStr":revisionStr, 101 | "revision":parseRevisionStrToInt(revisionStr) 102 | }; 103 | }; 104 | /** 105 | * Parse the plugin revision string into an integer. 106 | * 107 | * @param {String} The revision in string format. 108 | * @type Number 109 | */ 110 | var parseRevisionStrToInt = function(str){ 111 | return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision; 112 | }; 113 | /** 114 | * Is the major version greater than or equal to a specified version. 115 | * 116 | * @param {Number} version The minimum required major version. 117 | * @type Boolean 118 | */ 119 | self.majorAtLeast = function(version){ 120 | return self.major >= version; 121 | }; 122 | /** 123 | * Is the minor version greater than or equal to a specified version. 124 | * 125 | * @param {Number} version The minimum required minor version. 126 | * @type Boolean 127 | */ 128 | self.minorAtLeast = function(version){ 129 | return self.minor >= version; 130 | }; 131 | /** 132 | * Is the revision version greater than or equal to a specified version. 133 | * 134 | * @param {Number} version The minimum required revision version. 135 | * @type Boolean 136 | */ 137 | self.revisionAtLeast = function(version){ 138 | return self.revision >= version; 139 | }; 140 | /** 141 | * Is the version greater than or equal to a specified major, minor and revision. 142 | * 143 | * @param {Number} major The minimum required major version. 144 | * @param {Number} (Optional) minor The minimum required minor version. 145 | * @param {Number} (Optional) revision The minimum required revision version. 146 | * @type Boolean 147 | */ 148 | self.versionAtLeast = function(major){ 149 | var properties = [self.major, self.minor, self.revision]; 150 | var len = Math.min(properties.length, arguments.length); 151 | for(i=0; i=arguments[i]){ 153 | if(i+10){ 168 | var type = 'application/x-shockwave-flash'; 169 | var mimeTypes = navigator.mimeTypes; 170 | if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){ 171 | var version = mimeTypes[type].enabledPlugin.description; 172 | var versionObj = parseStandardVersion(version); 173 | self.raw = versionObj.raw; 174 | self.major = versionObj.major; 175 | self.minor = versionObj.minor; 176 | self.revisionStr = versionObj.revisionStr; 177 | self.revision = versionObj.revision; 178 | self.installed = true; 179 | } 180 | }else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){ 181 | var version = -1; 182 | for(var i=0; ij;j++)b=f[j],c=g[j],d=h*i(b*b+c*c),d>this.peak&&(this.peakBand=j,this.peak=d),e[j]=d}}function FFT(a,b){FourierTransform.call(this,a,b),this.reverseTable=new Uint32Array(a);for(var c,d=1,e=a>>1;a>d;){for(c=0;d>c;c++)this.reverseTable[c+d]=this.reverseTable[c]+e;d<<=1,e>>=1}for(this.sinTable=new Float32Array(a),this.cosTable=new Float32Array(a),c=0;a>c;c++)this.sinTable[c]=Math.sin(-Math.PI/c),this.cosTable[c]=Math.cos(-Math.PI/c)}!function(){function a(){for(var a in this.sections)this.sections[a].condition()&&this.sections[a].callback.call(this)}var b=function(){this.audioAdapter=b._getAdapter(this),this.events={},this.sections=[],this.bind("update",a)};b.version="0.3.2",b.adapters={},b.prototype={load:function(a){return a instanceof HTMLElement?(this.source=a,"flash"===b.isSupported()&&(this.source={src:b._getMP3SrcFromAudio(a)})):(this.source=window.Audio?new Audio:{},this.source.src=b._makeSupportedPath(a.src,a.codecs)),this.audio=this.audioAdapter.load(this.source),this},play:function(){return this.audioAdapter.play(),this},pause:function(){return this.audioAdapter.pause(),this},setVolume:function(a){return this.audioAdapter.setVolume(a),this},createKick:function(a){return new b.Kick(this,a)},bind:function(a,b){return this.events[a]||(this.events[a]=[]),this.events[a].push(b),this},unbind:function(a){return this.events[a]&&delete this.events[a],this},trigger:function(a){var b=this;return this.events[a]&&this.events[a].forEach(function(a){a.call(b)}),this},getVolume:function(){return this.audioAdapter.getVolume()},getProgress:function(){return this.audioAdapter.getProgress()},getTime:function(){return this.audioAdapter.getTime()},getFrequency:function(a,b){var c=0;if(void 0!==b){for(var d=a;b>=d;d++)c+=this.getSpectrum()[d];return c/(b-a+1)}return this.getSpectrum()[a]},getWaveform:function(){return this.audioAdapter.getWaveform()},getSpectrum:function(){return this.audioAdapter.getSpectrum()},isLoaded:function(){return this.audioAdapter.isLoaded},isPlaying:function(){return this.audioAdapter.isPlaying},after:function(a,b){var c=this;return this.sections.push({condition:function(){return c.getTime()>a},callback:b}),this},before:function(a,b){var c=this;return this.sections.push({condition:function(){return c.getTime()a&&d.getTime()a&&!this.called},callback:function(){b.call(this),d.called=!0},called:!1}),d=this.sections[this.sections.length-1],this}},window.Dancer=b}(),function(a){function b(){var a=!!(navigator.vendor||"").match(/Apple/),b=navigator.userAgent.match(/Version\/([^ ]*)/);return b=b?parseFloat(b[1]):0,a&&6>=b}var c={mp3:"audio/mpeg;",ogg:'audio/ogg; codecs="vorbis"',wav:'audio/wav; codecs="1"',aac:'audio/mp4; codecs="mp4a.40.2"'},d=document.createElement("audio");a.options={},a.setOptions=function(b){for(var c in b)b.hasOwnProperty(c)&&(a.options[c]=b[c])},a.isSupported=function(){return window.Float32Array&&window.Uint32Array?b()||!window.AudioContext&&!window.webkitAudioContext?d&&d.mozSetup?"audiodata":FlashDetect.versionAtLeast(9)?"flash":"":"webaudio":null},a.canPlay=function(b){d.canPlayType;return!!("flash"===a.isSupported()?"mp3"===b.toLowerCase():d.canPlayType&&d.canPlayType(c[b.toLowerCase()]).replace(/no/,""))},a.addPlugin=function(b,c){void 0===a.prototype[b]&&(a.prototype[b]=c)},a._makeSupportedPath=function(b,c){if(!c)return b;for(var d=0;d=this.currentThreshold&&a>=this.threshold?(this.currentThreshold=a,this.onKick&&this.onKick.call(this.dancer,a)):(this.offKick&&this.offKick.call(this.dancer,a),this.currentThreshold-=this.decay)}},maxAmplitude:function(a){var b=0,c=this.dancer.getSpectrum();if(!a.length)return a=d;d++)c[d]>b&&(b=c[d]);return b}},window.Dancer.Kick=b}(),function(){function a(){this.source=this.context.createMediaElementSource(this.audio),this.source.connect(this.proc),this.source.connect(this.gain),this.gain.connect(this.context.destination),this.proc.connect(this.context.destination),this.isLoaded=!0,this.progress=1,this.dancer.trigger("loaded")}var b=2048,c=44100,d=function(a){this.dancer=a,this.audio=new Audio,this.context=window.AudioContext?new window.AudioContext:new window.webkitAudioContext};d.prototype={load:function(d){var e=this;return this.audio=d,this.isLoaded=!1,this.progress=0,this.context.createScriptProcessor||(this.context.createScriptProcessor=this.context.createJavascriptNode),this.proc=this.context.createScriptProcessor(b/2,1,1),this.proc.onaudioprocess=function(a){e.update.call(e,a)},this.context.createGain||(this.context.createGain=this.context.createGainNode),this.gain=this.context.createGain(),this.fft=new FFT(b/2,c),this.signal=new Float32Array(b/2),this.audio.readyState<3?this.audio.addEventListener("canplay",function(){a.call(e)}):a.call(e),this.audio.addEventListener("progress",function(a){a.currentTarget.duration&&(e.progress=a.currentTarget.seekable.end(0)/a.currentTarget.duration)}),this.audio},play:function(){this.audio.play(),this.isPlaying=!0},pause:function(){this.audio.pause(),this.isPlaying=!1},setVolume:function(a){this.gain.gain.value=a},getVolume:function(){return this.gain.gain.value},getProgress:function(){return this.progress},getWaveform:function(){return this.signal},getSpectrum:function(){return this.fft.spectrum},getTime:function(){return this.audio.currentTime},update:function(a){if(this.isPlaying&&this.isLoaded){var c,d=[],e=a.inputBuffer.numberOfChannels,f=b/e,g=function(a,b){return a[c]+b[c]};for(c=e;c--;)d.push(a.inputBuffer.getChannelData(c));for(c=0;f>c;c++)this.signal[c]=e>1?d.reduce(g)/e:d[0][c];this.fft.forward(this.signal),this.dancer.trigger("update")}}},Dancer.adapters.webaudio=d}(),function(){function a(){this.fbLength=this.audio.mozFrameBufferLength,this.channels=this.audio.mozChannels,this.rate=this.audio.mozSampleRate,this.fft=new FFT(this.fbLength/this.channels,this.rate),this.signal=new Float32Array(this.fbLength/this.channels),this.isLoaded=!0,this.progress=1,this.dancer.trigger("loaded")}var b=function(a){this.dancer=a,this.audio=new Audio};b.prototype={load:function(b){var c=this;return this.audio=b,this.isLoaded=!1,this.progress=0,this.audio.readyState<3?this.audio.addEventListener("loadedmetadata",function(){a.call(c)},!1):a.call(c),this.audio.addEventListener("MozAudioAvailable",function(a){c.update(a)},!1),this.audio.addEventListener("progress",function(a){a.currentTarget.duration&&(c.progress=a.currentTarget.seekable.end(0)/a.currentTarget.duration)},!1),this.audio},play:function(){this.audio.play(),this.isPlaying=!0},pause:function(){this.audio.pause(),this.isPlaying=!1},setVolume:function(a){this.audio.volume=a},getVolume:function(){return this.audio.volume},getProgress:function(){return this.progress},getWaveform:function(){return this.signal},getSpectrum:function(){return this.fft.spectrum},getTime:function(){return this.audio.currentTime},update:function(a){if(this.isPlaying&&this.isLoaded){for(var b=0,c=this.fbLength/2;c>b;b++)this.signal[b]=(a.frameBuffer[2*b]+a.frameBuffer[2*b+1])/2;this.fft.forward(this.signal),this.dancer.trigger("update")}}},Dancer.adapters.moz=b}(),function(){function a(){var a=this;f=!0,b(Dancer.options.flashJS,function(){soundManager=new SoundManager,soundManager.flashVersion=9,soundManager.flash9Options.useWaveformData=!0,soundManager.useWaveformData=!0,soundManager.useHighPerformance=!0,soundManager.useFastPolling=!0,soundManager.multiShot=!1,soundManager.debugMode=!1,soundManager.debugFlash=!1,soundManager.url=Dancer.options.flashSWF,soundManager.onready(function(){e=!0,a.load()}),soundManager.ontimeout(function(){console.error("Error loading SoundManager2.swf")}),soundManager.beginDelayedInit()})}function b(a,b){var c=document.createElement("script"),d=document.getElementsByTagName("script")[0];c.type="text/javascript",c.src=a,c.onload=b,d.parentNode.insertBefore(c,d)}var c=1024,d=44100,e=!1,f=!1,g=.93,h=function(a){this.dancer=a,this.wave_L=[],this.wave_R=[],this.spectrum=[],window.SM2_DEFER=!0};h.prototype={load:function(b){var e=this;return this.path=b?b.src:this.path,this.isLoaded=!1,this.progress=0,!window.soundManager&&!f&&a.call(this),window.soundManager&&(this.audio=soundManager.createSound({id:"dancer"+Math.random(),url:this.path,stream:!0,autoPlay:!1,autoLoad:!0,whileplaying:function(){e.update()},whileloading:function(){e.progress=this.bytesLoaded/this.bytesTotal},onload:function(){e.fft=new FFT(c,d),e.signal=new Float32Array(c),e.waveform=new Float32Array(c),e.isLoaded=!0,e.progress=1,e.dancer.trigger("loaded")}}),this.dancer.audio=this.audio),this.audio},play:function(){this.audio.play(),this.isPlaying=!0},pause:function(){this.audio.pause(),this.isPlaying=!1},setVolume:function(a){this.audio.setVolume(100*a)},getVolume:function(){return this.audio.volume/100},getProgress:function(){return this.progress},getWaveform:function(){return this.waveform},getSpectrum:function(){return this.fft.spectrum},getTime:function(){return this.audio.position/1e3},update:function(){if(this.isPlaying||this.isLoaded){this.wave_L=this.audio.waveformData.left,this.wave_R=this.audio.waveformData.right;for(var a,b=0,c=this.wave_L.length;c>b;b++)a=parseFloat(this.wave_L[b])+parseFloat(this.wave_R[b]),this.waveform[2*b]=a/2,this.waveform[2*b+1]=a/2,this.signal[2*b]=a*g,this.signal[2*b+1]=a*g;this.fft.forward(this.signal),this.dancer.trigger("update")}}},Dancer.adapters.flash=h}(),FFT.prototype.forward=function(a){var b=this.bufferSize,c=this.cosTable,d=this.sinTable,e=this.reverseTable,f=this.real,g=this.imag,h=(this.spectrum,Math.floor(Math.log(b)/Math.LN2));if(Math.pow(2,h)!==b)throw"Invalid buffer size, must be a power of 2.";if(b!==a.length)throw"Supplied buffer is not the same size as defined FFT. FFT Size: "+b+" Buffer Size: "+a.length;var i,j,k,l,m,n,o,p,q,r=1;for(q=0;b>q;q++)f[q]=a[e[q]],g[q]=0;for(;b>r;){i=c[r],j=d[r],k=1,l=0;for(var s=0;r>s;s++){for(q=s;b>q;)m=q+r,n=k*f[m]-l*g[m],o=k*g[m]+l*f[m],f[m]=f[q]-n,g[m]=g[q]-o,f[q]+=n,g[q]+=o,q+=r<<1;p=k,k=p*i-l*j,l=p*j+l*i}r<<=1}return this.calculateSpectrum()};var FlashDetect=new function(){var a=this;a.installed=!1,a.raw="",a.major=-1,a.minor=-1,a.revision=-1,a.revisionStr="";var b=[{name:"ShockwaveFlash.ShockwaveFlash.7",version:function(a){return c(a)}},{name:"ShockwaveFlash.ShockwaveFlash.6",version:function(a){var b="6,0,21";try{a.AllowScriptAccess="always",b=c(a)}catch(d){}return b}},{name:"ShockwaveFlash.ShockwaveFlash",version:function(a){return c(a)}}],c=function(a){var b=-1;try{b=a.GetVariable("$version")}catch(c){}return b},d=function(a){var b=-1;try{b=new ActiveXObject(a)}catch(c){b={activeXError:!0}}return b},e=function(a){var b=a.split(",");return{raw:a,major:parseInt(b[0].split(" ")[1],10),minor:parseInt(b[1],10),revision:parseInt(b[2],10),revisionStr:b[2]}},f=function(a){var b=a.split(/ +/),c=b[2].split(/\./),d=b[3];return{raw:a,major:parseInt(c[0],10),minor:parseInt(c[1],10),revisionStr:d,revision:g(d)}},g=function(b){return parseInt(b.replace(/[a-zA-Z]/g,""),10)||a.revision};a.majorAtLeast=function(b){return a.major>=b},a.minorAtLeast=function(b){return a.minor>=b},a.revisionAtLeast=function(b){return a.revision>=b},a.versionAtLeast=function(){var b=[a.major,a.minor,a.revision],c=Math.min(b.length,arguments.length);for(i=0;c>i;i++){if(b[i]>=arguments[i]){if(c>i+1&&b[i]==arguments[i])continue;return!0}return!1}},a.FlashDetect=function(){if(navigator.plugins&&navigator.plugins.length>0){var c="application/x-shockwave-flash",g=navigator.mimeTypes;if(g&&g[c]&&g[c].enabledPlugin&&g[c].enabledPlugin.description){var h=g[c].enabledPlugin.description,i=f(h);a.raw=i.raw,a.major=i.major,a.minor=i.minor,a.revisionStr=i.revisionStr,a.revision=i.revision,a.installed=!0}}else if(-1==navigator.appVersion.indexOf("Mac")&&window.execScript)for(var h=-1,j=0;j= expected - tolerance; 28 | } 29 | }); 30 | }); 31 | 32 | 33 | describe('Core', function () { 34 | it('Should bind an update event', function () { 35 | expect(dancer.events.update).toBeDefined(); 36 | }); 37 | 38 | // Load tested implicitly throughout tests 39 | it("load() should return a dancer instance", function () { 40 | expect(loadReturn).toBe(dancer); 41 | }); 42 | 43 | it('should have an audio property with the audio element', function () { 44 | if ( Dancer.isSupported() !== 'flash' ) { 45 | expect(dancer.audio instanceof HTMLElement).toBeTruthy(); 46 | expect(dancer.audio.src.match(new RegExp(song))).toBeTruthy(); 47 | } else { 48 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 49 | runs(function () { 50 | expect(dancer.audio).toBeTruthy(); 51 | expect(dancer.audio.url.match(new RegExp(song))).toBeTruthy(); 52 | }); 53 | } 54 | }); 55 | }); 56 | 57 | describe('Controls', function () { 58 | // TODO Should probably check audio output via adapter, similar to getTime(); 59 | it("Should call adapter's play/pause method via dancer.play(), dancer.pause()", function () { 60 | spyOn(dancer.audioAdapter, 'play'); 61 | spyOn(dancer.audioAdapter, 'pause'); 62 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 63 | runs(function () { 64 | dancer.play(); 65 | expect(dancer.audioAdapter.play).toHaveBeenCalled(); 66 | dancer.pause(); 67 | expect(dancer.audioAdapter.pause).toHaveBeenCalled(); 68 | }); 69 | }); 70 | 71 | it("Should return dancer instance when calling dancer.play(), dancer.pause()", function() { 72 | var 73 | playReturn = dancer.play(), 74 | pauseReturn = dancer.pause(); 75 | expect(playReturn).toBe(dancer); 76 | expect(pauseReturn).toBe(dancer); 77 | }); 78 | 79 | it("The volume should default to 1", function () { 80 | expect(dancer.getVolume()).toEqual(1); 81 | }); 82 | 83 | it("Should change the volume with setVolume() and return `this`", function () { 84 | expect(dancer.setVolume(0.5)).toBe(dancer); 85 | expect(dancer.getVolume()).toBeWithin(0.5, 0.0001); 86 | dancer.setVolume(0.1); 87 | expect(dancer.getVolume()).toBeWithin(0.1, 0.0001); 88 | dancer.setVolume(1); 89 | }); 90 | 91 | it("Should return version from .version", function () { 92 | expect(Dancer.version.match(/\d+\.\d+\.\d+/)).toBeTruthy(); 93 | }); 94 | }); 95 | 96 | describe('createKick()', function () { 97 | var kick = dancer.createKick(); 98 | it("Should return a Dancer.Kick instance", function () { 99 | expect(kick instanceof Dancer.Kick).toBeTruthy(); 100 | }); 101 | }); 102 | 103 | describe('Getters', function () { 104 | 105 | describe('getProgress()', function () { 106 | 107 | it( "getProgress() should return a value from 0 to 1", function() { 108 | runs( function () { 109 | var checkProgress = setInterval(function () { 110 | if ( expect( dancer.getProgress() ).toBeWithin ) { 111 | expect( dancer.getProgress() ).toBeWithin( 0.5, 0.5 ); 112 | } 113 | }, 1); 114 | setTimeout( function () { clearInterval(checkProgress); }, waitForLoadTime ); 115 | }); 116 | }); 117 | 118 | }); 119 | 120 | describe('getTime()', function () { 121 | 122 | var currentTime; 123 | 124 | it("getTime() should increment by 1 second after 1 second", function () { 125 | currentTime = 0; 126 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 127 | runs(function () { 128 | dancer.play(); 129 | currentTime = dancer.getTime(); 130 | waits( 1000 ); 131 | runs(function () { 132 | expect(dancer.getTime()).toBeWithin(currentTime + 1.0, 0.1); 133 | dancer.pause(); 134 | }); 135 | }); 136 | }); 137 | 138 | it("getTime() should pause incrementing when pause()'d", function () { 139 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 140 | runs(function () { 141 | currentTime = dancer.getTime(); 142 | dancer.pause(); 143 | waits( 1000 ); 144 | runs(function () { 145 | expect(dancer.getTime()).toBeWithin(currentTime, 0.05); 146 | }); 147 | }); 148 | }); 149 | }); 150 | 151 | describe("getSpectrum()", function () { 152 | var s; 153 | 154 | it("should return a Float32Array(512)", function () { 155 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 156 | runs(function () { 157 | dancer.play(); 158 | s = dancer.getSpectrum(); 159 | expect(s.length).toEqual(512); 160 | expect(s instanceof Float32Array).toBeTruthy(); 161 | }); 162 | }); 163 | 164 | it("should return a correct amplitude for the 440hz pitch (11/1024)", function () { 165 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 166 | runs(function () { 167 | s= dancer.getSpectrum()[10]; 168 | expect(s).toBeWithin(0.8, 0.2); 169 | }); 170 | }); 171 | 172 | it("should return a correct amplitude for the 440hz pitch (51/1024)", function () { 173 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 174 | runs(function () { 175 | s = dancer.getSpectrum()[50]; 176 | expect(s).toBeLessThan(0.1); 177 | }); 178 | }); 179 | }); 180 | 181 | describe("getWaveform()", function () { 182 | it("should return a Float32Array(1024)", function () { 183 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 184 | runs(function () { 185 | expect(dancer.getWaveform().length).toBe(1024); 186 | expect(dancer.getWaveform() instanceof Float32Array ).toBeTruthy(); 187 | }); 188 | }); 189 | 190 | // This sine has 5 elements of -1 followed by 45 elements until 191 | // 5 elements of ~0.9999 and 45 elements back down.. 192 | it("Should return a sine wave", function () { 193 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 194 | runs(function () { 195 | var 196 | valley = null, last = -1, savedWave = [], wf = dancer.getWaveform(); 197 | for ( var i = 0; i < 200; i++ ) { 198 | savedWave.push(wf[i]); 199 | if ( last > -0.99 && wf[i] <= -0.99 && valley === null) { 200 | valley = i; 201 | } 202 | last = wf[i]; 203 | } 204 | // Check valley range 205 | expect(savedWave[valley]).toBeWithin(-1, 0.05); 206 | expect(savedWave[valley+3]).toBeWithin(-1, 0.05); 207 | 208 | expect(savedWave[valley+24]).toBeLessThan(savedWave[valley+28]); 209 | expect(savedWave[valley+28]).toBeWithin(0, 0.07); 210 | expect(savedWave[valley+32]).toBeGreaterThan(savedWave[valley+28]); 211 | 212 | // Check peak 213 | expect(savedWave[valley+51]).toBeWithin(1, 0.05); 214 | expect(savedWave[valley+54]).toBeWithin(1, 0.05); 215 | expect(savedWave[valley+58]).toBeLessThan(savedWave[valley+54]); 216 | }); 217 | }); 218 | }); 219 | 220 | describe("getFrequency()", function () { 221 | var f; 222 | 223 | it("should return a correct amplitude for the 440hz pitch (11/1024)", function () { 224 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 225 | runs(function () { 226 | f = dancer.getFrequency(10); 227 | expect(f).toBeGreaterThan(0.5); 228 | }); 229 | }); 230 | 231 | it("should return a correct amplitude for the 440hz pitch (51/1024)", function () { 232 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 233 | runs(function () { 234 | f = dancer.getFrequency(50); 235 | expect(f).toBeLessThan(0.1); 236 | }); 237 | }); 238 | 239 | it("Should return the average amplitude over a range of the 440hz pitch", function () { 240 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 241 | runs(function () { 242 | f = dancer.getFrequency(10, 50); 243 | expect(f).toBeWithin(0.06, 0.02); 244 | }); 245 | }); 246 | }); 247 | 248 | describe("isLoaded()", function () { 249 | // Also tested implicitly via other tests 250 | it("Should return adapter's loaded boolean from isLoaded()", function () { 251 | // Wait for song being loaded before messing with the adapter's load status 252 | waitsFor(songReady, 'Song was never loaded', waitForLoadTime); 253 | runs(function () { 254 | dancer.audioAdapter.isLoaded = false; 255 | expect(dancer.isLoaded()).toBeFalsy(); 256 | dancer.audioAdapter.isLoaded = true; 257 | expect(dancer.isLoaded()).toBeTruthy(); 258 | }); 259 | }); 260 | }); 261 | 262 | describe("isPlaying()", function () { 263 | // Relying on adapter implementation, possibly better way to spec 264 | it("Should be true if playing, false otherwise", function () { 265 | dancer.play(); 266 | expect(dancer.isPlaying()).toBeTruthy(); 267 | dancer.pause(); 268 | expect(dancer.isPlaying()).toBeFalsy(); 269 | dancer.play(); 270 | expect(dancer.isPlaying()).toBeTruthy(); 271 | }); 272 | }); 273 | }); 274 | 275 | describe('Sections', function () { 276 | var 277 | f1Count, f2Count, f3Count, f4Count, 278 | ret1, ret2, ret3, ret4, 279 | ctx1, ctx2, ctx3, ctx4, t, 280 | fn1 = function () { f1Count++; ctx1 = this; }, 281 | fn2 = function () { f2Count++; ctx2 = this; }, 282 | fn3 = function () { f3Count++; ctx3 = this; }, 283 | fn4 = function () { f4Count++; ctx4 = this; }; 284 | f1Count = f2Count = f3Count = f4Count = 0; 285 | ret1 = ret2 = ret3 = ret4 = ctx1 = ctx2 = ctx3 = ctx4 = null; 286 | 287 | describe('after()', function () { 288 | it("Should not call 'after' callback before time t", function () { 289 | t = dancer.getTime(); 290 | ret1 = dancer.after(t+1, fn1); 291 | waits(100); 292 | runs(function () { 293 | expect(f1Count).toBe(0); 294 | }); 295 | }); 296 | 297 | it("Should call 'after' callback after time t, repeatedly", function () { 298 | waitsFor(function () { 299 | return dancer.getTime() > t + 1.2; 300 | }, 'Wait for time to elapse 2s', 2000); 301 | runs(function () { 302 | expect(f1Count).toBeGreaterThan(1); 303 | }); 304 | }); 305 | 306 | it("Should return dance instance", function () { 307 | expect(ret1).toBe(dancer); 308 | }); 309 | 310 | it("Should have dance instance as the 'this' context in callback", function () { 311 | expect(ctx1).toBe(dancer); 312 | }); 313 | }); 314 | 315 | describe('before()', function () { 316 | it("Should call 'before' callback before time t, repeatedly", function () { 317 | t = dancer.getTime(); 318 | ret2 = dancer.before(t+1, fn2); 319 | waits(200); 320 | runs(function () { 321 | expect(f2Count).toBeGreaterThan(1); 322 | }); 323 | }); 324 | 325 | it("Should not call 'before' callback after time t", function () { 326 | var pauseCounting; 327 | waitsFor(function () { 328 | pauseCounting = f2Count; 329 | return dancer.getTime() > t + 1.2; 330 | }); 331 | runs(function () { 332 | waits(200); 333 | runs(function () { 334 | expect(f2Count).toBe(pauseCounting); 335 | }); 336 | }); 337 | }); 338 | 339 | it("Should return dance instance", function () { 340 | expect(ret2).toBe(dancer); 341 | }); 342 | 343 | it("Should have dance instance as the 'this' context in callback", function () { 344 | expect(ctx2).toBe(dancer); 345 | }); 346 | }); 347 | 348 | describe('between()', function () { 349 | it("Should not call 'between' callback before time t1", function () { 350 | t = dancer.getTime(); 351 | ret3 = dancer.between(t+1, t+3, fn3); 352 | waits(100); 353 | runs(function () { 354 | expect(f3Count).toBe(0); 355 | }); 356 | }); 357 | 358 | it("Should repeatedly call 'between' callback between time t1,t2", function () { 359 | waitsFor(function () { 360 | return dancer.getTime() > t + 1.05; 361 | }); 362 | runs(function () { 363 | waits(100); 364 | runs(function () { 365 | expect(f3Count).toBeGreaterThan(1); 366 | }); 367 | }); 368 | }); 369 | 370 | it("Should not call 'between' callback after time t2", function () { 371 | var pauseCounting; 372 | waitsFor(function () { 373 | pauseCounting = f3Count; 374 | return dancer.getTime() > t + 3.1; 375 | }); 376 | runs(function () { 377 | waits(200); 378 | runs(function () { 379 | expect(f3Count).toBe(pauseCounting); 380 | }); 381 | }); 382 | }); 383 | 384 | it("Should return dance instance", function () { 385 | expect(ret3).toBe(dancer); 386 | }); 387 | 388 | it("Should have dance instance as the 'this' context in callback", function () { 389 | expect(ctx3).toBe(dancer); 390 | }); 391 | }); 392 | 393 | describe('onceAt()', function () { 394 | it("Should only call 'onceAt' callback one time at time t", function () { 395 | t = dancer.getTime(); 396 | ret4 = dancer.onceAt(t+1, fn4); 397 | waits(100); 398 | runs(function () { 399 | expect(f4Count).toBe(0); 400 | }); 401 | waitsFor(function () { 402 | return dancer.getTime() > t+1.2; 403 | }); 404 | runs(function () { 405 | expect(f4Count).toBe(1); 406 | }); 407 | }); 408 | 409 | it("Should return dance instance", function () { 410 | expect(ret4).toBe(dancer); 411 | }); 412 | 413 | it("Should have dance instance as the 'this' context in callback", function () { 414 | expect(ctx4).toBe(dancer); 415 | }); 416 | 417 | }); 418 | }); 419 | 420 | describe('Pub/Sub Bindings', function () { 421 | var 422 | fn1 = jasmine.createSpy(), 423 | fn2 = jasmine.createSpy(), 424 | fn3 = jasmine.createSpy(), 425 | fn4 = jasmine.createSpy(); 426 | 427 | beforeEach(function () { 428 | dancer.bind('eventA', fn1); 429 | dancer.bind('eventA', fn2); 430 | dancer.bind('eventB', fn3); 431 | dancer.bind('update', fn4); 432 | }); 433 | 434 | describe('bind()', function () { 435 | it('Should bind several events under the same name and different names', function () { 436 | expect(dancer.events.eventA[0]).toBe(fn1); 437 | expect(dancer.events.eventA[1]).toBe(fn2); 438 | expect(dancer.events.eventB[0]).toBe(fn3); 439 | }); 440 | }); 441 | 442 | describe('unbind()', function () { 443 | it('Should unbind all events of a shared name', function () { 444 | dancer.unbind('eventA'); 445 | expect(dancer.events.eventA).toBeFalsy(); 446 | expect(dancer.events.eventB[0]).toBe(fn3); 447 | }); 448 | }); 449 | 450 | describe('trigger()', function () { 451 | it('Should call all events of a shared name, and no others', function () { 452 | dancer.trigger('eventA'); 453 | expect(fn1).toHaveBeenCalled(); 454 | expect(fn2).toHaveBeenCalled(); 455 | expect(fn3).not.toHaveBeenCalled(); 456 | }); 457 | }); 458 | 459 | describe('Update Trigger', function () { 460 | it('Should trigger update events as the audio plays', function () { 461 | waits(100); 462 | runs(function () { 463 | expect(fn4).toHaveBeenCalled(); 464 | }); 465 | }); 466 | }); 467 | }); 468 | 469 | it("Should pause the goddamn beeping", function() { 470 | dancer.pause(); 471 | runs(function () { 472 | expect(0).toBe(0); 473 | }); 474 | }); 475 | }); 476 | -------------------------------------------------------------------------------- /examples/lib/physics.js: -------------------------------------------------------------------------------- 1 | /* Allows safe, dyamic creation of namespaces. 2 | */ 3 | var Attraction, Behaviour, Collision, ConstantForce, EdgeBounce, EdgeWrap, Particle, Physics, Random, Spring, Vector, Wander, namespace, 4 | __hasProp = Object.prototype.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 6 | 7 | namespace = function(id) { 8 | var path, root, _i, _len, _ref, _ref2, _results; 9 | root = self; 10 | _ref = id.split('.'); 11 | _results = []; 12 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 13 | path = _ref[_i]; 14 | _results.push(root = (_ref2 = root[path]) != null ? _ref2 : root[path] = {}); 15 | } 16 | return _results; 17 | }; 18 | 19 | /* RequestAnimationFrame shim. 20 | */ 21 | 22 | (function() { 23 | var time, vendor, vendors, _i, _len; 24 | time = 0; 25 | vendors = ['ms', 'moz', 'webkit', 'o']; 26 | for (_i = 0, _len = vendors.length; _i < _len; _i++) { 27 | vendor = vendors[_i]; 28 | if (!(!window.requestAnimationFrame)) continue; 29 | window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']; 30 | window.cancelRequestAnimationFrame = window[vendor + 'CancelRequestAnimationFrame']; 31 | } 32 | if (!window.requestAnimationFrame) { 33 | window.requestAnimationFrame = function(callback, element) { 34 | var delta, now, old; 35 | now = new Date().getTime(); 36 | delta = Math.max(0, 16 - (now - old)); 37 | setTimeout((function() { 38 | return callback(time + delta); 39 | }), delta); 40 | return old = now + delta; 41 | }; 42 | } 43 | if (!window.cancelAnimationFrame) { 44 | return window.cancelAnimationFrame = function(id) { 45 | return clearTimeout(id); 46 | }; 47 | } 48 | })(); 49 | 50 | /* Random 51 | */ 52 | 53 | Random = function(min, max) { 54 | if (!(max != null)) { 55 | max = min; 56 | min = 0; 57 | } 58 | return min + Math.random() * (max - min); 59 | }; 60 | 61 | Random.int = function(min, max) { 62 | if (!(max != null)) { 63 | max = min; 64 | min = 0; 65 | } 66 | return Math.floor(min + Math.random() * (max - min)); 67 | }; 68 | 69 | Random.sign = function(prob) { 70 | if (prob == null) prob = 0.5; 71 | if (Math.random() < prob) { 72 | return 1; 73 | } else { 74 | return -1; 75 | } 76 | }; 77 | 78 | Random.bool = function(prob) { 79 | if (prob == null) prob = 0.5; 80 | return Math.random() < prob; 81 | }; 82 | 83 | Random.item = function(list) { 84 | return list[Math.floor(Math.random() * list.length)]; 85 | }; 86 | 87 | /* 2D Vector 88 | */ 89 | 90 | Vector = (function() { 91 | /* Adds two vectors and returns the product. 92 | */ 93 | Vector.add = function(v1, v2) { 94 | return new Vector(v1.x + v2.x, v1.y + v2.y); 95 | }; 96 | 97 | /* Subtracts v2 from v1 and returns the product. 98 | */ 99 | 100 | Vector.sub = function(v1, v2) { 101 | return new Vector(v1.x - v2.x, v1.y - v2.y); 102 | }; 103 | 104 | /* Projects one vector (v1) onto another (v2) 105 | */ 106 | 107 | Vector.project = function(v1, v2) { 108 | return v1.clone().scale((v1.dot(v2)) / v1.magSq()); 109 | }; 110 | 111 | /* Creates a new Vector instance. 112 | */ 113 | 114 | function Vector(x, y) { 115 | this.x = x != null ? x : 0.0; 116 | this.y = y != null ? y : 0.0; 117 | } 118 | 119 | /* Sets the components of this vector. 120 | */ 121 | 122 | Vector.prototype.set = function(x, y) { 123 | this.x = x; 124 | this.y = y; 125 | return this; 126 | }; 127 | 128 | /* Add a vector to this one. 129 | */ 130 | 131 | Vector.prototype.add = function(v) { 132 | this.x += v.x; 133 | this.y += v.y; 134 | return this; 135 | }; 136 | 137 | /* Subtracts a vector from this one. 138 | */ 139 | 140 | Vector.prototype.sub = function(v) { 141 | this.x -= v.x; 142 | this.y -= v.y; 143 | return this; 144 | }; 145 | 146 | /* Scales this vector by a value. 147 | */ 148 | 149 | Vector.prototype.scale = function(f) { 150 | this.x *= f; 151 | this.y *= f; 152 | return this; 153 | }; 154 | 155 | /* Computes the dot product between vectors. 156 | */ 157 | 158 | Vector.prototype.dot = function(v) { 159 | return this.x * v.x + this.y * v.y; 160 | }; 161 | 162 | /* Computes the cross product between vectors. 163 | */ 164 | 165 | Vector.prototype.cross = function(v) { 166 | return (this.x * v.y) - (this.y * v.x); 167 | }; 168 | 169 | /* Computes the magnitude (length). 170 | */ 171 | 172 | Vector.prototype.mag = function() { 173 | return Math.sqrt(this.x * this.x + this.y * this.y); 174 | }; 175 | 176 | /* Computes the squared magnitude (length). 177 | */ 178 | 179 | Vector.prototype.magSq = function() { 180 | return this.x * this.x + this.y * this.y; 181 | }; 182 | 183 | /* Computes the distance to another vector. 184 | */ 185 | 186 | Vector.prototype.dist = function(v) { 187 | var dx, dy; 188 | dx = v.x - this.x; 189 | dy = v.y - this.y; 190 | return Math.sqrt(dx * dx + dy * dy); 191 | }; 192 | 193 | /* Computes the squared distance to another vector. 194 | */ 195 | 196 | Vector.prototype.distSq = function(v) { 197 | var dx, dy; 198 | dx = v.x - this.x; 199 | dy = v.y - this.y; 200 | return dx * dx + dy * dy; 201 | }; 202 | 203 | /* Normalises the vector, making it a unit vector (of length 1). 204 | */ 205 | 206 | Vector.prototype.norm = function() { 207 | var m; 208 | m = Math.sqrt(this.x * this.x + this.y * this.y); 209 | this.x /= m; 210 | this.y /= m; 211 | return this; 212 | }; 213 | 214 | /* Limits the vector length to a given amount. 215 | */ 216 | 217 | Vector.prototype.limit = function(l) { 218 | var m, mSq; 219 | mSq = this.x * this.x + this.y * this.y; 220 | if (mSq > l * l) { 221 | m = Math.sqrt(mSq); 222 | this.x /= m; 223 | this.y /= m; 224 | this.x *= l; 225 | this.y *= l; 226 | return this; 227 | } 228 | }; 229 | 230 | /* Copies components from another vector. 231 | */ 232 | 233 | Vector.prototype.copy = function(v) { 234 | this.x = v.x; 235 | this.y = v.y; 236 | return this; 237 | }; 238 | 239 | /* Clones this vector to a new itentical one. 240 | */ 241 | 242 | Vector.prototype.clone = function() { 243 | return new Vector(this.x, this.y); 244 | }; 245 | 246 | /* Resets the vector to zero. 247 | */ 248 | 249 | Vector.prototype.clear = function() { 250 | this.x = 0.0; 251 | this.y = 0.0; 252 | return this; 253 | }; 254 | 255 | return Vector; 256 | 257 | })(); 258 | 259 | /* Particle 260 | */ 261 | 262 | Particle = (function() { 263 | 264 | function Particle(mass) { 265 | this.mass = mass != null ? mass : 1.0; 266 | this.setMass(this.mass); 267 | this.setRadius(1.0); 268 | this.fixed = false; 269 | this.behaviours = []; 270 | this.pos = new Vector(); 271 | this.vel = new Vector(); 272 | this.acc = new Vector(); 273 | this.old = { 274 | pos: new Vector(), 275 | vel: new Vector(), 276 | acc: new Vector() 277 | }; 278 | } 279 | 280 | /* Moves the particle to a given location vector. 281 | */ 282 | 283 | Particle.prototype.moveTo = function(pos) { 284 | this.pos.copy(pos); 285 | return this.old.pos.copy(pos); 286 | }; 287 | 288 | /* Sets the mass of the particle. 289 | */ 290 | 291 | Particle.prototype.setMass = function(mass) { 292 | this.mass = mass != null ? mass : 1.0; 293 | return this.massInv = 1.0 / this.mass; 294 | }; 295 | 296 | /* Sets the radius of the particle. 297 | */ 298 | 299 | Particle.prototype.setRadius = function(radius) { 300 | this.radius = radius != null ? radius : 1.0; 301 | return this.radiusSq = this.radius * this.radius; 302 | }; 303 | 304 | /* Applies all behaviours to derive new force. 305 | */ 306 | 307 | Particle.prototype.update = function(dt, index) { 308 | var behaviour, _i, _len, _ref, _results; 309 | if (!this.fixed) { 310 | _ref = this.behaviours; 311 | _results = []; 312 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 313 | behaviour = _ref[_i]; 314 | _results.push(behaviour.apply(this, dt, index)); 315 | } 316 | return _results; 317 | } 318 | }; 319 | 320 | return Particle; 321 | 322 | })(); 323 | 324 | /* Spring 325 | */ 326 | 327 | Spring = (function() { 328 | 329 | function Spring(p1, p2, restLength, stiffness) { 330 | this.p1 = p1; 331 | this.p2 = p2; 332 | this.restLength = restLength != null ? restLength : 100; 333 | this.stiffness = stiffness != null ? stiffness : 1.0; 334 | this._delta = new Vector(); 335 | } 336 | 337 | Spring.prototype.apply = function() { 338 | var dist, force; 339 | (this._delta.copy(this.p2.pos)).sub(this.p1.pos); 340 | dist = this._delta.mag() + 0.000001; 341 | force = (dist - this.restLength) / (dist * (this.p1.massInv + this.p2.massInv)) * this.stiffness; 342 | if (!this.p1.fixed) { 343 | this.p1.pos.add(this._delta.clone().scale(force * this.p1.massInv)); 344 | } 345 | if (!this.p2.fixed) { 346 | return this.p2.pos.add(this._delta.scale(-force * this.p2.massInv)); 347 | } 348 | }; 349 | 350 | return Spring; 351 | 352 | })(); 353 | 354 | /* Physics Engine 355 | */ 356 | 357 | Physics = (function() { 358 | 359 | function Physics(integrator) { 360 | this.integrator = integrator != null ? integrator : new Euler(); 361 | this.timestep = 1.0 / 60; 362 | this.viscosity = 0.005; 363 | this.behaviours = []; 364 | this._time = 0.0; 365 | this._step = 0.0; 366 | this._clock = null; 367 | this._buffer = 0.0; 368 | this._maxSteps = 4; 369 | this.particles = []; 370 | this.springs = []; 371 | } 372 | 373 | /* Performs a numerical integration step. 374 | */ 375 | 376 | Physics.prototype.integrate = function(dt) { 377 | var behaviour, drag, index, particle, spring, _i, _j, _len, _len2, _len3, _ref, _ref2, _ref3, _results; 378 | drag = 1.0 - this.viscosity; 379 | _ref = this.particles; 380 | for (index = 0, _len = _ref.length; index < _len; index++) { 381 | particle = _ref[index]; 382 | _ref2 = this.behaviours; 383 | for (_i = 0, _len2 = _ref2.length; _i < _len2; _i++) { 384 | behaviour = _ref2[_i]; 385 | behaviour.apply(particle, dt, index); 386 | } 387 | particle.update(dt, index); 388 | } 389 | this.integrator.integrate(this.particles, dt, drag); 390 | _ref3 = this.springs; 391 | _results = []; 392 | for (_j = 0, _len3 = _ref3.length; _j < _len3; _j++) { 393 | spring = _ref3[_j]; 394 | _results.push(spring.apply()); 395 | } 396 | return _results; 397 | }; 398 | 399 | /* Steps the system. 400 | */ 401 | 402 | Physics.prototype.step = function() { 403 | var delta, i, time; 404 | if (this._clock == null) this._clock = new Date().getTime(); 405 | time = new Date().getTime(); 406 | delta = time - this._clock; 407 | if (delta <= 0.0) return; 408 | delta *= 0.001; 409 | this._clock = time; 410 | this._buffer += delta; 411 | i = 0; 412 | while (this._buffer >= this.timestep && ++i < this._maxSteps) { 413 | this.integrate(this.timestep); 414 | this._buffer -= this.timestep; 415 | this._time += this.timestep; 416 | } 417 | return this._step = new Date().getTime() - time; 418 | }; 419 | 420 | /* Clean up after yourself. 421 | */ 422 | 423 | Physics.prototype.destroy = function() { 424 | this.integrator = null; 425 | this.particles = null; 426 | return this.springs = null; 427 | }; 428 | 429 | return Physics; 430 | 431 | })(); 432 | 433 | /* Behaviour 434 | */ 435 | 436 | Behaviour = (function() { 437 | 438 | Behaviour.GUID = 0; 439 | 440 | function Behaviour() { 441 | this.GUID = Behaviour.GUID++; 442 | this.interval = 1; 443 | } 444 | 445 | Behaviour.prototype.apply = function(p, dt, index) { 446 | var _name, _ref; 447 | return ((_ref = p[_name = '__behaviour' + this.GUID]) != null ? _ref : p[_name] = { 448 | counter: 0 449 | }).counter++; 450 | }; 451 | 452 | return Behaviour; 453 | 454 | })(); 455 | 456 | /* Attraction Behaviour 457 | */ 458 | 459 | Attraction = (function(_super) { 460 | 461 | __extends(Attraction, _super); 462 | 463 | function Attraction(target, radius, strength) { 464 | this.target = target != null ? target : new Vector(); 465 | this.radius = radius != null ? radius : 1000; 466 | this.strength = strength != null ? strength : 100.0; 467 | this._delta = new Vector(); 468 | this.setRadius(this.radius); 469 | Attraction.__super__.constructor.apply(this, arguments); 470 | } 471 | 472 | /* Sets the effective radius of the bahavious. 473 | */ 474 | 475 | Attraction.prototype.setRadius = function(radius) { 476 | this.radius = radius; 477 | return this.radiusSq = radius * radius; 478 | }; 479 | 480 | Attraction.prototype.apply = function(p, dt, index) { 481 | var distSq; 482 | (this._delta.copy(this.target)).sub(p.pos); 483 | distSq = this._delta.magSq(); 484 | if (distSq < this.radiusSq && distSq > 0.000001) { 485 | this._delta.norm().scale(1.0 - distSq / this.radiusSq); 486 | return p.acc.add(this._delta.scale(this.strength)); 487 | } 488 | }; 489 | 490 | return Attraction; 491 | 492 | })(Behaviour); 493 | 494 | /* Collision Behaviour 495 | */ 496 | 497 | Collision = (function(_super) { 498 | 499 | __extends(Collision, _super); 500 | 501 | function Collision(useMass, callback) { 502 | this.useMass = useMass != null ? useMass : true; 503 | this.callback = callback != null ? callback : null; 504 | this.pool = []; 505 | this._delta = new Vector(); 506 | Collision.__super__.constructor.apply(this, arguments); 507 | } 508 | 509 | Collision.prototype.apply = function(p, dt, index) { 510 | var dist, distSq, i, mt, o, overlap, r1, r2, radii, _ref, _results; 511 | _results = []; 512 | for (i = index, _ref = this.pool.length - 1; index <= _ref ? i <= _ref : i >= _ref; index <= _ref ? i++ : i--) { 513 | o = this.pool[i]; 514 | if (o !== p) { 515 | (this._delta.copy(o.pos)).sub(p.pos); 516 | distSq = this._delta.magSq(); 517 | radii = p.radius + o.radius; 518 | if (distSq <= radii * radii) { 519 | dist = Math.sqrt(distSq); 520 | overlap = (p.radius + o.radius) - dist; 521 | overlap += 0.5; 522 | mt = p.mass + o.mass; 523 | r1 = this.useMass ? o.mass / mt : 0.5; 524 | r2 = this.useMass ? p.mass / mt : 0.5; 525 | p.pos.add(this._delta.clone().norm().scale(overlap * -r1)); 526 | o.pos.add(this._delta.norm().scale(overlap * r2)); 527 | _results.push(typeof this.callback === "function" ? this.callback(p, o, overlap) : void 0); 528 | } else { 529 | _results.push(void 0); 530 | } 531 | } else { 532 | _results.push(void 0); 533 | } 534 | } 535 | return _results; 536 | }; 537 | 538 | return Collision; 539 | 540 | })(Behaviour); 541 | 542 | /* Constant Force Behaviour 543 | */ 544 | 545 | ConstantForce = (function(_super) { 546 | 547 | __extends(ConstantForce, _super); 548 | 549 | function ConstantForce(force) { 550 | this.force = force != null ? force : new Vector(); 551 | ConstantForce.__super__.constructor.apply(this, arguments); 552 | } 553 | 554 | ConstantForce.prototype.apply = function(p, dt, index) { 555 | return p.acc.add(this.force); 556 | }; 557 | 558 | return ConstantForce; 559 | 560 | })(Behaviour); 561 | 562 | /* Edge Bounce Behaviour 563 | */ 564 | 565 | EdgeBounce = (function(_super) { 566 | 567 | __extends(EdgeBounce, _super); 568 | 569 | function EdgeBounce(min, max) { 570 | this.min = min != null ? min : new Vector(); 571 | this.max = max != null ? max : new Vector(); 572 | EdgeBounce.__super__.constructor.apply(this, arguments); 573 | } 574 | 575 | EdgeBounce.prototype.apply = function(p, dt, index) { 576 | if (p.pos.x - p.radius < this.min.x) { 577 | p.pos.x = this.min.x + p.radius; 578 | } else if (p.pos.x + p.radius > this.max.x) { 579 | p.pos.x = this.max.x - p.radius; 580 | } 581 | if (p.pos.y - p.radius < this.min.y) { 582 | return p.pos.y = this.min.y + p.radius; 583 | } else if (p.pos.y + p.radius > this.max.y) { 584 | return p.pos.y = this.max.y - p.radius; 585 | } 586 | }; 587 | 588 | return EdgeBounce; 589 | 590 | })(Behaviour); 591 | 592 | /* Edge Wrap Behaviour 593 | */ 594 | 595 | EdgeWrap = (function(_super) { 596 | 597 | __extends(EdgeWrap, _super); 598 | 599 | function EdgeWrap(min, max) { 600 | this.min = min != null ? min : new Vector(); 601 | this.max = max != null ? max : new Vector(); 602 | EdgeWrap.__super__.constructor.apply(this, arguments); 603 | } 604 | 605 | EdgeWrap.prototype.apply = function(p, dt, index) { 606 | if (p.pos.x + p.radius < this.min.x) { 607 | p.pos.x = this.max.x + p.radius; 608 | p.old.pos.x = p.pos.x; 609 | } else if (p.pos.x - p.radius > this.max.x) { 610 | p.pos.x = this.min.x - p.radius; 611 | p.old.pos.x = p.pos.x; 612 | } 613 | if (p.pos.y + p.radius < this.min.y) { 614 | p.pos.y = this.max.y + p.radius; 615 | return p.old.pos.y = p.pos.y; 616 | } else if (p.pos.y - p.radius > this.max.y) { 617 | p.pos.y = this.min.y - p.radius; 618 | return p.old.pos.y = p.pos.y; 619 | } 620 | }; 621 | 622 | return EdgeWrap; 623 | 624 | })(Behaviour); 625 | 626 | /* Wander Behaviour 627 | */ 628 | 629 | Wander = (function(_super) { 630 | 631 | __extends(Wander, _super); 632 | 633 | function Wander(jitter, radius, strength) { 634 | this.jitter = jitter != null ? jitter : 0.5; 635 | this.radius = radius != null ? radius : 100; 636 | this.strength = strength != null ? strength : 1.0; 637 | this.theta = Math.random() * Math.PI * 2; 638 | Wander.__super__.constructor.apply(this, arguments); 639 | } 640 | 641 | Wander.prototype.apply = function(p, dt, index) { 642 | this.theta += (Math.random() - 0.5) * this.jitter * Math.PI * 2; 643 | p.acc.x += Math.cos(this.theta) * this.radius * this.strength; 644 | return p.acc.y += Math.sin(this.theta) * this.radius * this.strength; 645 | }; 646 | 647 | return Wander; 648 | 649 | })(Behaviour); 650 | -------------------------------------------------------------------------------- /spec/lib/jasmine-1.1.0/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | 82 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 83 | reporterView.addSpecs(specs, self.specFilter); 84 | }; 85 | 86 | self.reportRunnerResults = function(runner) { 87 | reporterView && reporterView.complete(); 88 | }; 89 | 90 | self.reportSuiteResults = function(suite) { 91 | reporterView.suiteComplete(suite); 92 | }; 93 | 94 | self.reportSpecStarting = function(spec) { 95 | if (self.logRunningSpecs) { 96 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 97 | } 98 | }; 99 | 100 | self.reportSpecResults = function(spec) { 101 | reporterView.specComplete(spec); 102 | }; 103 | 104 | self.log = function() { 105 | var console = jasmine.getGlobal().console; 106 | if (console && console.log) { 107 | if (console.log.apply) { 108 | console.log.apply(console, arguments); 109 | } else { 110 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 111 | } 112 | } 113 | }; 114 | 115 | self.specFilter = function(spec) { 116 | if (!focusedSpecName()) { 117 | return true; 118 | } 119 | 120 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 121 | }; 122 | 123 | return self; 124 | 125 | function focusedSpecName() { 126 | var specName; 127 | 128 | (function memoizeFocusedSpec() { 129 | if (specName) { 130 | return; 131 | } 132 | 133 | var paramMap = []; 134 | var params = doc.location.search.substring(1).split('&'); 135 | 136 | for (var i = 0; i < params.length; i++) { 137 | var p = params[i].split('='); 138 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 139 | } 140 | 141 | specName = paramMap.spec; 142 | })(); 143 | 144 | return specName; 145 | } 146 | 147 | function createReporterDom(version) { 148 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 149 | dom.banner = self.createDom('div', { className: 'banner' }, 150 | self.createDom('span', { className: 'title' }, "Jasmine "), 151 | self.createDom('span', { className: 'version' }, version)), 152 | 153 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 154 | dom.alert = self.createDom('div', {className: 'alert'}), 155 | dom.results = self.createDom('div', {className: 'results'}, 156 | dom.summary = self.createDom('div', { className: 'summary' }), 157 | dom.details = self.createDom('div', { id: 'details' })) 158 | ); 159 | } 160 | }; 161 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporterHelpers = {}; 162 | 163 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 164 | var el = document.createElement(type); 165 | 166 | for (var i = 2; i < arguments.length; i++) { 167 | var child = arguments[i]; 168 | 169 | if (typeof child === 'string') { 170 | el.appendChild(document.createTextNode(child)); 171 | } else { 172 | if (child) { 173 | el.appendChild(child); 174 | } 175 | } 176 | } 177 | 178 | for (var attr in attrs) { 179 | if (attr == "className") { 180 | el[attr] = attrs[attr]; 181 | } else { 182 | el.setAttribute(attr, attrs[attr]); 183 | } 184 | } 185 | 186 | return el; 187 | }; 188 | 189 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 190 | var results = child.results(); 191 | var status = results.passed() ? 'passed' : 'failed'; 192 | if (results.skipped) { 193 | status = 'skipped'; 194 | } 195 | 196 | return status; 197 | }; 198 | 199 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 200 | var parentDiv = this.dom.summary; 201 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 202 | var parent = child[parentSuite]; 203 | 204 | if (parent) { 205 | if (typeof this.views.suites[parent.id] == 'undefined') { 206 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 207 | } 208 | parentDiv = this.views.suites[parent.id].element; 209 | } 210 | 211 | parentDiv.appendChild(childElement); 212 | }; 213 | 214 | 215 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 216 | for(var fn in jasmine.HtmlReporterHelpers) { 217 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 218 | } 219 | }; 220 | 221 | jasmine.HtmlReporter.ReporterView = function(dom) { 222 | this.startedAt = new Date(); 223 | this.runningSpecCount = 0; 224 | this.completeSpecCount = 0; 225 | this.passedCount = 0; 226 | this.failedCount = 0; 227 | this.skippedCount = 0; 228 | 229 | this.createResultsMenu = function() { 230 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 231 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 232 | ' | ', 233 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 234 | 235 | this.summaryMenuItem.onclick = function() { 236 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 237 | }; 238 | 239 | this.detailsMenuItem.onclick = function() { 240 | showDetails(); 241 | }; 242 | }; 243 | 244 | this.addSpecs = function(specs, specFilter) { 245 | this.totalSpecCount = specs.length; 246 | 247 | this.views = { 248 | specs: {}, 249 | suites: {} 250 | }; 251 | 252 | for (var i = 0; i < specs.length; i++) { 253 | var spec = specs[i]; 254 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 255 | if (specFilter(spec)) { 256 | this.runningSpecCount++; 257 | } 258 | } 259 | }; 260 | 261 | this.specComplete = function(spec) { 262 | this.completeSpecCount++; 263 | 264 | if (isUndefined(this.views.specs[spec.id])) { 265 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 266 | } 267 | 268 | var specView = this.views.specs[spec.id]; 269 | 270 | switch (specView.status()) { 271 | case 'passed': 272 | this.passedCount++; 273 | break; 274 | 275 | case 'failed': 276 | this.failedCount++; 277 | break; 278 | 279 | case 'skipped': 280 | this.skippedCount++; 281 | break; 282 | } 283 | 284 | specView.refresh(); 285 | this.refresh(); 286 | }; 287 | 288 | this.suiteComplete = function(suite) { 289 | var suiteView = this.views.suites[suite.id]; 290 | if (isUndefined(suiteView)) { 291 | return; 292 | } 293 | suiteView.refresh(); 294 | }; 295 | 296 | this.refresh = function() { 297 | 298 | if (isUndefined(this.resultsMenu)) { 299 | this.createResultsMenu(); 300 | } 301 | 302 | // currently running UI 303 | if (isUndefined(this.runningAlert)) { 304 | this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); 305 | dom.alert.appendChild(this.runningAlert); 306 | } 307 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 308 | 309 | // skipped specs UI 310 | if (isUndefined(this.skippedAlert)) { 311 | this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); 312 | } 313 | 314 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 315 | 316 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 317 | dom.alert.appendChild(this.skippedAlert); 318 | } 319 | 320 | // passing specs UI 321 | if (isUndefined(this.passedAlert)) { 322 | this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); 323 | } 324 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 325 | 326 | // failing specs UI 327 | if (isUndefined(this.failedAlert)) { 328 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 329 | } 330 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 331 | 332 | if (this.failedCount === 1 && isDefined(dom.alert)) { 333 | dom.alert.appendChild(this.failedAlert); 334 | dom.alert.appendChild(this.resultsMenu); 335 | } 336 | 337 | // summary info 338 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 339 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 340 | }; 341 | 342 | this.complete = function() { 343 | dom.alert.removeChild(this.runningAlert); 344 | 345 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 346 | 347 | if (this.failedCount === 0) { 348 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 349 | } else { 350 | showDetails(); 351 | } 352 | 353 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 354 | }; 355 | 356 | return this; 357 | 358 | function showDetails() { 359 | if (dom.reporter.className.search(/showDetails/) === -1) { 360 | dom.reporter.className += " showDetails"; 361 | } 362 | } 363 | 364 | function isUndefined(obj) { 365 | return typeof obj === 'undefined'; 366 | } 367 | 368 | function isDefined(obj) { 369 | return !isUndefined(obj); 370 | } 371 | 372 | function specPluralizedFor(count) { 373 | var str = count + " spec"; 374 | if (count > 1) { 375 | str += "s" 376 | } 377 | return str; 378 | } 379 | 380 | }; 381 | 382 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 383 | 384 | 385 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 386 | this.spec = spec; 387 | this.dom = dom; 388 | this.views = views; 389 | 390 | this.symbol = this.createDom('li', { className: 'pending' }); 391 | this.dom.symbolSummary.appendChild(this.symbol); 392 | 393 | this.summary = this.createDom('div', { className: 'specSummary' }, 394 | this.createDom('a', { 395 | className: 'description', 396 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 397 | title: this.spec.getFullName() 398 | }, this.spec.description) 399 | ); 400 | 401 | this.detail = this.createDom('div', { className: 'specDetail' }, 402 | this.createDom('a', { 403 | className: 'description', 404 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 405 | title: this.spec.getFullName() 406 | }, this.spec.getFullName()) 407 | ); 408 | }; 409 | 410 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 411 | return this.getSpecStatus(this.spec); 412 | }; 413 | 414 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 415 | this.symbol.className = this.status(); 416 | 417 | switch (this.status()) { 418 | case 'skipped': 419 | break; 420 | 421 | case 'passed': 422 | this.appendSummaryToSuiteDiv(); 423 | break; 424 | 425 | case 'failed': 426 | this.appendSummaryToSuiteDiv(); 427 | this.appendFailureDetail(); 428 | break; 429 | } 430 | }; 431 | 432 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 433 | this.summary.className += ' ' + this.status(); 434 | this.appendToSummary(this.spec, this.summary); 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 438 | this.detail.className += ' ' + this.status(); 439 | 440 | var resultItems = this.spec.results().getItems(); 441 | var messagesDiv = this.createDom('div', { className: 'messages' }); 442 | 443 | for (var i = 0; i < resultItems.length; i++) { 444 | var result = resultItems[i]; 445 | 446 | if (result.type == 'log') { 447 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 448 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 449 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 450 | 451 | if (result.trace.stack) { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 453 | } 454 | } 455 | } 456 | 457 | if (messagesDiv.childNodes.length > 0) { 458 | this.detail.appendChild(messagesDiv); 459 | this.dom.details.appendChild(this.detail); 460 | } 461 | }; 462 | 463 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 464 | this.suite = suite; 465 | this.dom = dom; 466 | this.views = views; 467 | 468 | this.element = this.createDom('div', { className: 'suite' }, 469 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) 470 | ); 471 | 472 | this.appendToSummary(this.suite, this.element); 473 | }; 474 | 475 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 476 | return this.getSpecStatus(this.suite); 477 | }; 478 | 479 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 480 | this.element.className += " " + this.status(); 481 | }; 482 | 483 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 484 | 485 | /* @deprecated Use jasmine.HtmlReporter instead 486 | */ 487 | jasmine.TrivialReporter = function(doc) { 488 | this.document = doc || document; 489 | this.suiteDivs = {}; 490 | this.logRunningSpecs = false; 491 | }; 492 | 493 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 494 | var el = document.createElement(type); 495 | 496 | for (var i = 2; i < arguments.length; i++) { 497 | var child = arguments[i]; 498 | 499 | if (typeof child === 'string') { 500 | el.appendChild(document.createTextNode(child)); 501 | } else { 502 | if (child) { el.appendChild(child); } 503 | } 504 | } 505 | 506 | for (var attr in attrs) { 507 | if (attr == "className") { 508 | el[attr] = attrs[attr]; 509 | } else { 510 | el.setAttribute(attr, attrs[attr]); 511 | } 512 | } 513 | 514 | return el; 515 | }; 516 | 517 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 518 | var showPassed, showSkipped; 519 | 520 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 521 | this.createDom('div', { className: 'banner' }, 522 | this.createDom('div', { className: 'logo' }, 523 | this.createDom('span', { className: 'title' }, "Jasmine"), 524 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 525 | this.createDom('div', { className: 'options' }, 526 | "Show ", 527 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 528 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 529 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 530 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 531 | ) 532 | ), 533 | 534 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 535 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 536 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 537 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 538 | ); 539 | 540 | this.document.body.appendChild(this.outerDiv); 541 | 542 | var suites = runner.suites(); 543 | for (var i = 0; i < suites.length; i++) { 544 | var suite = suites[i]; 545 | var suiteDiv = this.createDom('div', { className: 'suite' }, 546 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 547 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 548 | this.suiteDivs[suite.id] = suiteDiv; 549 | var parentDiv = this.outerDiv; 550 | if (suite.parentSuite) { 551 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 552 | } 553 | parentDiv.appendChild(suiteDiv); 554 | } 555 | 556 | this.startedAt = new Date(); 557 | 558 | var self = this; 559 | showPassed.onclick = function(evt) { 560 | if (showPassed.checked) { 561 | self.outerDiv.className += ' show-passed'; 562 | } else { 563 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 564 | } 565 | }; 566 | 567 | showSkipped.onclick = function(evt) { 568 | if (showSkipped.checked) { 569 | self.outerDiv.className += ' show-skipped'; 570 | } else { 571 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 572 | } 573 | }; 574 | }; 575 | 576 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 577 | var results = runner.results(); 578 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 579 | this.runnerDiv.setAttribute("class", className); 580 | //do it twice for IE 581 | this.runnerDiv.setAttribute("className", className); 582 | var specs = runner.specs(); 583 | var specCount = 0; 584 | for (var i = 0; i < specs.length; i++) { 585 | if (this.specFilter(specs[i])) { 586 | specCount++; 587 | } 588 | } 589 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 590 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 591 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 592 | 593 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 594 | }; 595 | 596 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 597 | var results = suite.results(); 598 | var status = results.passed() ? 'passed' : 'failed'; 599 | if (results.totalCount === 0) { // todo: change this to check results.skipped 600 | status = 'skipped'; 601 | } 602 | this.suiteDivs[suite.id].className += " " + status; 603 | }; 604 | 605 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 606 | if (this.logRunningSpecs) { 607 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 608 | } 609 | }; 610 | 611 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 612 | var results = spec.results(); 613 | var status = results.passed() ? 'passed' : 'failed'; 614 | if (results.skipped) { 615 | status = 'skipped'; 616 | } 617 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 618 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 619 | this.createDom('a', { 620 | className: 'description', 621 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 622 | title: spec.getFullName() 623 | }, spec.description)); 624 | 625 | 626 | var resultItems = results.getItems(); 627 | var messagesDiv = this.createDom('div', { className: 'messages' }); 628 | for (var i = 0; i < resultItems.length; i++) { 629 | var result = resultItems[i]; 630 | 631 | if (result.type == 'log') { 632 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 633 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 634 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 635 | 636 | if (result.trace.stack) { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 638 | } 639 | } 640 | } 641 | 642 | if (messagesDiv.childNodes.length > 0) { 643 | specDiv.appendChild(messagesDiv); 644 | } 645 | 646 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 647 | }; 648 | 649 | jasmine.TrivialReporter.prototype.log = function() { 650 | var console = jasmine.getGlobal().console; 651 | if (console && console.log) { 652 | if (console.log.apply) { 653 | console.log.apply(console, arguments); 654 | } else { 655 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 656 | } 657 | } 658 | }; 659 | 660 | jasmine.TrivialReporter.prototype.getLocation = function() { 661 | return this.document.location; 662 | }; 663 | 664 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 665 | var paramMap = {}; 666 | var params = this.getLocation().search.substring(1).split('&'); 667 | for (var i = 0; i < params.length; i++) { 668 | var p = params[i].split('='); 669 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 670 | } 671 | 672 | if (!paramMap.spec) { 673 | return true; 674 | } 675 | return spec.getFullName().indexOf(paramMap.spec) === 0; 676 | }; 677 | --------------------------------------------------------------------------------