diff --git a/app/components/bridge-controls.js b/app/components/bridge-controls.js index f4007e9..9cb501b 100644 --- a/app/components/bridge-controls.js +++ b/app/components/bridge-controls.js @@ -7,7 +7,17 @@ export default Em.Component.extend({ lightsData: null, - lightsDataIntervalHandle: null, + numLights: function(){ + var lightsData = this.get('lightsData'), numLights = 0; + + for (let key in this.get('lightsData')) { + if(lightsData.hasOwnProperty(key)){ + numLights++; + } + } + + return numLights; + }.property('lightsData'), lightsApiURL: function(){ return 'http://' + this.get('bridgeIp') + '/api/' + this.get('bridgeUsername') + '/lights'; @@ -17,144 +27,21 @@ export default Em.Component.extend({ this.set('lightsDataIntervalHandle', setInterval(this.updateLightData.bind(this), 1000)); }, - // determines whether the lights are on/off for the lights switch - lightsOn: function(){ - var lightsData = this.get('lightsData'); - - for (var key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - if(lightsData[key].state.on){ - return true; - } - } - } - - return false; - }.property('lightsData'), - - // determines the average brightness of the hue system for the brightness slider - lightsBrightness: function(){ - var lightsData = this.get('lightsData'), lightsBrightness = 0; - - for (var key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - lightsBrightness += lightsData[key].state.bri; - } - } - - return lightsBrightness/this.get('lightsList').length; - }.property('lightsData', 'lightsList'), - - // list of all the lights in the hue system - lightsList: function(){ - var lightsData = this.get('lightsData'), lightsList = []; - for (var key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - switch(lightsData[key].modelid){ - case 'LCT001': - lightsList.push('a19'); - break; - case 'LCT002': - lightsList.push('br30'); - break; - case 'LCT003': - lightsList.push('gu10'); - break; - case 'LST001': - lightsList.push('lightstrip'); - break; - case 'LLC010': - lightsList.push('lc_iris'); - break; - case 'LLC011': - lightsList.push('lc_bloom'); - break; - case 'LLC012': - lightsList.push('lc_bloom'); - break; - case 'LLC006': - lightsList.push('lc_iris'); - break; - case 'LLC007': - lightsList.push('lc_aura'); - break; - case 'LLC013': - lightsList.push('storylight'); - break; - case 'LWB004': - lightsList.push('a19'); - break; - case 'LLC020': - lightsList.push('huego'); - break; - default: - lightsList.push('a19'); - } - } - } - - return lightsList; - }.property('lightsData'), - - brightnessControlDisabled: function(){ - return !this.get('lightsOn'); - }.property('lightsOn'), - - onLightsOnChange: function(){ - var lightsData = this.get('lightsData'), lightsOnSystem = false, lightsOn = this.get('lightsOn'); - - for (let key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - if(lightsData[key].state.on){ - lightsOnSystem = true; - break; - } - } - } - - // if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state - if(lightsOn !== lightsOnSystem){ - for (let key in lightsData) { - Em.$.ajax(this.get('lightsApiURL') + '/' + key + '/state', { - data: JSON.stringify({"on": lightsOn}), - contentType: 'application/json', - type: 'PUT' - }) - } - } - }.observes('lightsOn'), - - onBrightnessChange: function(){ - var lightsData = this.get('lightsData'), lightsBrightnessSystem = false, lightsBrightness = this.get('lightsBrightness'); - - for (let key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - lightsBrightnessSystem += lightsData[key].state.bri; - } - } - lightsBrightnessSystem /= this.get('lightsList').length; - - // if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state - if(lightsBrightness !== lightsBrightnessSystem){ - for (let key in lightsData) { - Em.$.ajax(this.get('lightsApiURL') + '/' + key + '/state', { - data: JSON.stringify({"bri": lightsBrightness}), - contentType: 'application/json', - type: 'PUT' - }) - } - } - }.observes('lightsBrightness'), - - lightsOnTxt: function(){ - return this.get('lightsOn') ? 'On' : 'Off'; - }.property('lightsOn'), - updateLightData: function(){ var self = this; + Em.$.get(this.get('lightsApiURL'), function (result, status) { - if (status === 'success') { + if (status === 'success' && JSON.stringify(self.get('lightsData')) !== JSON.stringify(result) ) { self.set('lightsData', result); + } else if(status !== 'success' ) { + // something went terribly wrong ( user got unauthenticated? ) and we'll need to start all over + clearInterval(self.get('lightsDataIntervalHandle')); + this.setProperties({ + bridgeIp: null, + bridgeUsername: null + }); + + console.error(status + ': ' + result); } }); } diff --git a/app/components/controls/lights-control.js b/app/components/controls/lights-control.js new file mode 100644 index 0000000..e7babc1 --- /dev/null +++ b/app/components/controls/lights-control.js @@ -0,0 +1,141 @@ +import Em from 'ember'; + +export default Em.Component.extend({ + + lightsDataIntervalHandle: null, + + lightsApiURL: null, + + lightsData: null, + + // determines whether the lights are on/off for the lights switch + lightsOn: function(){ + var lightsData = this.get('lightsData'); + + for (var key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + if(lightsData[key].state.on){ + return true; + } + } + } + + return false; + }.property('lightsData'), + + // determines the average brightness of the hue system for the brightness slider + lightsBrightness: function(){ + var lightsData = this.get('lightsData'), lightsBrightness = 0; + + for (var key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + lightsBrightness += lightsData[key].state.bri; + } + } + + return lightsBrightness/this.get('lightsList').length; + }.property('lightsData', 'lightsList'), + + // list of all the lights in the hue system + lightsList: function(){ + var lightsData = this.get('lightsData'), lightsList = []; + for (var key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + switch(lightsData[key].modelid){ + case 'LCT001': + lightsList.push('a19'); + break; + case 'LCT002': + lightsList.push('br30'); + break; + case 'LCT003': + lightsList.push('gu10'); + break; + case 'LST001': + lightsList.push('lightstrip'); + break; + case 'LLC010': + lightsList.push('lc_iris'); + break; + case 'LLC011': + lightsList.push('lc_bloom'); + break; + case 'LLC012': + lightsList.push('lc_bloom'); + break; + case 'LLC006': + lightsList.push('lc_iris'); + break; + case 'LLC007': + lightsList.push('lc_aura'); + break; + case 'LLC013': + lightsList.push('storylight'); + break; + case 'LWB004': + lightsList.push('a19'); + break; + case 'LLC020': + lightsList.push('huego'); + break; + default: + lightsList.push('a19'); + } + } + } + + return lightsList; + }.property('lightsData'), + + brightnessControlDisabled: Em.computed.not('lightsOn'), + + onLightsOnChange: function(){ + var lightsData = this.get('lightsData'), lightsOnSystem = false, lightsOn = this.get('lightsOn'); + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + if(lightsData[key].state.on){ + lightsOnSystem = true; + break; + } + } + } + + // if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state + if(lightsOn !== lightsOnSystem){ + for (let key in lightsData) { + Em.$.ajax(this.get('lightsApiURL') + '/' + key + '/state', { + data: JSON.stringify({"on": lightsOn}), + contentType: 'application/json', + type: 'PUT' + }); + } + } + }.observes('lightsOn'), + + onBrightnessChange: function(){ + var lightsData = this.get('lightsData'), lightsBrightnessSystem = false, lightsBrightness = this.get('lightsBrightness'); + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + lightsBrightnessSystem += lightsData[key].state.bri; + } + } + lightsBrightnessSystem /= this.get('lightsList').length; + + // if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state + if(lightsBrightness !== lightsBrightnessSystem){ + for (let key in lightsData) { + Em.$.ajax(this.get('lightsApiURL') + '/' + key + '/state', { + data: JSON.stringify({"bri": lightsBrightness}), + contentType: 'application/json', + type: 'PUT' + }); + } + } + }.observes('lightsBrightness'), + + lightsOnTxt: function(){ + return this.get('lightsOn') ? 'On' : 'Off'; + }.property('lightsOn') +}); diff --git a/app/components/controls/party-control.js b/app/components/controls/party-control.js new file mode 100644 index 0000000..a322721 --- /dev/null +++ b/app/components/controls/party-control.js @@ -0,0 +1,82 @@ +import Em from 'ember'; + +export default Em.Component.extend({ + + lightsApiURL: null, + + strobeOn: false, + + numLights: 0, + lightsData: null, + + strobeOnInervalHandle: null, + strobeSat: 0, + preStrobeOnLightsDataCache: null, + lastStrobeLight: 0, + + onStrobeOnChange: function () { + var lightsData = this.get('lightsData'), self = this; + + if (this.get('strobeOn')) { + this.set('preStrobeOnLightsDataCache', lightsData); + var stobeInitRequestData = {'sat': this.get('strobeSat'), 'bri': 254, 'transitiontime': 0}; + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + if (lightsData[key].state.on) { + stobeInitRequestData.on = false; + } + + Em.$.ajax(this.get('lightsApiURL') + '/' + key + '/state', { + data: JSON.stringify(stobeInitRequestData), + contentType: 'application/json', + type: 'PUT' + }); + } + } + + this.set('strobeOnInervalHandle', setInterval(this.strobeStep.bind(this), 200)); + } else { // revert the light system to pre-strobe + var preStrobeOnLightsDataCache = this.get('preStrobeOnLightsDataCache'); + + for (let key in lightsData) { + if (lightsData.hasOwnProperty(key)) { + setTimeout(function () { + Em.$.ajax(self.get('lightsApiURL') + '/' + key + '/state', { + data: JSON.stringify({ + 'on': preStrobeOnLightsDataCache[key].state.on, + 'sat': preStrobeOnLightsDataCache[key].state.sat, + 'bri': preStrobeOnLightsDataCache[key].state.bri + }), + contentType: 'application/json', + type: 'PUT' + }); + }, 2000); + } + } + + clearInterval(this.get('strobeOnInervalHandle')); + } + }.observes('strobeOn'), + + strobeStep: function () { + var lightsData = this.get('lightsData'), lastStrobeLight = (this.get('lastStrobeLight') + 1) % (this.get('numLights') + 1), self = this; + + Em.$.ajax(this.get('lightsApiURL') + '/' + lastStrobeLight + '/state', { + data: JSON.stringify({'on': true, 'transitiontime': 0, 'alert': 'select'}), + contentType: 'application/json', + type: 'PUT' + }); + Em.$.ajax(self.get('lightsApiURL') + '/' + lastStrobeLight + '/state', { + data: JSON.stringify({'on': false, 'transitiontime': 0}), + contentType: 'application/json', + type: 'PUT' + }); + + this.set('lastStrobeLight', lastStrobeLight); + }, + + strobeOnTxt: function () { + return this.get('strobeOn') ? 'On' : 'Off'; + }.property('strobeOn') +}); diff --git a/app/styles/app.scss b/app/styles/app.scss index 25cc68d..eeb714c 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -10,11 +10,20 @@ text-decoration: none; } -md-switch { - display: inline-flex; - margin: 0; +.md-subheader-content { + max-width: 500px; + margin-right: auto; + margin-left: auto; } -.controlTxt { - margin-top: 20px; +md-list { + max-width: 600px; + margin-right: auto; + margin-left: auto; +} + +.item { + max-width: 400px; + margin-right: auto; + margin-left: auto; } diff --git a/app/templates/components/bridge-controls.hbs b/app/templates/components/bridge-controls.hbs index 1625fa6..8af38d3 100644 --- a/app/templates/components/bridge-controls.hbs +++ b/app/templates/components/bridge-controls.hbs @@ -1,10 +1,9 @@ +{{#paper-list}} -{{#each lightsList as |light|}} - -{{/each}} + {{controls/lights-control lightsApiURL=lightsApiURL lightsData=lightsData}} -
Lights:
-{{#paper-switch checked=lightsOn}} {{lightsOnTxt}} {{/paper-switch}} + {{paper-divider}} -
Brightness:
-{{paper-slider flex=true min='1' max='254' value=lightsBrightness disabled=brightnessControlDisabled}} + {{controls/party-control lightsApiURL=lightsApiURL lightsData=lightsData numLights=numLights}} + +{{/paper-list}} \ No newline at end of file diff --git a/app/templates/components/controls/lights-control.hbs b/app/templates/components/controls/lights-control.hbs new file mode 100644 index 0000000..ffff981 --- /dev/null +++ b/app/templates/components/controls/lights-control.hbs @@ -0,0 +1,19 @@ +{{#paper-subheader class="md-no-sticky"}}Lights{{/paper-subheader}} + +{{#paper-item class="item"}} + {{#each lightsList as |light|}} + + {{/each}} +{{/paper-item}} + +{{#paper-item class="item"}} + {{paper-icon icon="power-settings-new"}} +

Light:

+ {{#paper-switch checked=lightsOn}} {{lightsOnTxt}} {{/paper-switch}} +{{/paper-item}} + +{{#paper-item class="item"}} + {{paper-icon icon="brightness-4"}} +

Brightness

+ {{paper-slider flex=true min='1' max='254' value=lightsBrightness disabled=brightnessControlDisabled}} +{{/paper-item}} \ No newline at end of file diff --git a/app/templates/components/controls/party-control.hbs b/app/templates/components/controls/party-control.hbs new file mode 100644 index 0000000..df28d92 --- /dev/null +++ b/app/templates/components/controls/party-control.hbs @@ -0,0 +1,13 @@ +{{#paper-subheader class="md-no-sticky"}}Party{{/paper-subheader}} + +{{#paper-item class="item"}} + {{paper-icon icon="flare"}} +

Strobe

+ {{#paper-switch checked=strobeOn}} {{strobeOnTxt}} {{/paper-switch}} +{{/paper-item}} + +{{#paper-item class="item"}} + {{paper-icon icon="music-note"}} +

Music

+ {{#paper-button raised=true primary=true}}UPLOAD{{/paper-button}} +{{/paper-item}} \ No newline at end of file diff --git a/vendor/.gitkeep b/vendor/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/dancer.js b/vendor/dancer.js new file mode 100644 index 0000000..f8a7eac --- /dev/null +++ b/vendor/dancer.js @@ -0,0 +1,1107 @@ +/* + * dancer - v0.4.0 - 2014-02-01 + * https://github.com/jsantell/dancer.js + * Copyright (c) 2014 Jordan Santell + * Licensed MIT + */ +(function() { + + var Dancer = function () { + this.audioAdapter = Dancer._getAdapter( this ); + this.events = {}; + this.sections = []; + this.bind( 'update', update ); + }; + + Dancer.version = '0.3.2'; + Dancer.adapters = {}; + + Dancer.prototype = { + + load : function ( source ) { + var path; + + // Loading an Audio element + if ( source instanceof HTMLElement ) { + this.source = source; + if ( Dancer.isSupported() === 'flash' ) { + this.source = { src: Dancer._getMP3SrcFromAudio( source ) }; + } + + // Loading an object with src, [codecs] + } else { + this.source = window.Audio ? new Audio() : {}; + this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); + } + + this.audio = this.audioAdapter.load( this.source ); + return this; + }, + + /* Controls */ + + play : function () { + this.audioAdapter.play(); + return this; + }, + + pause : function () { + this.audioAdapter.pause(); + return this; + }, + + setVolume : function ( volume ) { + this.audioAdapter.setVolume( volume ); + return this; + }, + + + /* Actions */ + + createKick : function ( options ) { + return new Dancer.Kick( this, options ); + }, + + bind : function ( name, callback ) { + if ( !this.events[ name ] ) { + this.events[ name ] = []; + } + this.events[ name ].push( callback ); + return this; + }, + + unbind : function ( name ) { + if ( this.events[ name ] ) { + delete this.events[ name ]; + } + return this; + }, + + trigger : function ( name ) { + var _this = this; + if ( this.events[ name ] ) { + this.events[ name ].forEach(function( callback ) { + callback.call( _this ); + }); + } + return this; + }, + + + /* Getters */ + + getVolume : function () { + return this.audioAdapter.getVolume(); + }, + + getProgress : function () { + return this.audioAdapter.getProgress(); + }, + + getTime : function () { + return this.audioAdapter.getTime(); + }, + + // Returns the magnitude of a frequency or average over a range of frequencies + getFrequency : function ( freq, endFreq ) { + var sum = 0; + if ( endFreq !== undefined ) { + for ( var i = freq; i <= endFreq; i++ ) { + sum += this.getSpectrum()[ i ]; + } + return sum / ( endFreq - freq + 1 ); + } else { + return this.getSpectrum()[ freq ]; + } + }, + + getWaveform : function () { + return this.audioAdapter.getWaveform(); + }, + + getSpectrum : function () { + return this.audioAdapter.getSpectrum(); + }, + + isLoaded : function () { + return this.audioAdapter.isLoaded; + }, + + isPlaying : function () { + return this.audioAdapter.isPlaying; + }, + + + /* Sections */ + + after : function ( time, callback ) { + var _this = this; + this.sections.push({ + condition : function () { + return _this.getTime() > time; + }, + callback : callback + }); + return this; + }, + + before : function ( time, callback ) { + var _this = this; + this.sections.push({ + condition : function () { + return _this.getTime() < time; + }, + callback : callback + }); + return this; + }, + + between : function ( startTime, endTime, callback ) { + var _this = this; + this.sections.push({ + condition : function () { + return _this.getTime() > startTime && _this.getTime() < endTime; + }, + callback : callback + }); + return this; + }, + + onceAt : function ( time, callback ) { + var + _this = this, + thisSection = null; + this.sections.push({ + condition : function () { + return _this.getTime() > time && !this.called; + }, + callback : function () { + callback.call( this ); + thisSection.called = true; + }, + called : false + }); + // Baking the section in the closure due to callback's this being the dancer instance + thisSection = this.sections[ this.sections.length - 1 ]; + return this; + } + }; + + function update () { + for ( var i in this.sections ) { + if ( this.sections[ i ].condition() ) + this.sections[ i ].callback.call( this ); + } + } + + window.Dancer = Dancer; +})(); + +(function ( Dancer ) { + + var CODECS = { + 'mp3' : 'audio/mpeg;', + 'ogg' : 'audio/ogg; codecs="vorbis"', + 'wav' : 'audio/wav; codecs="1"', + 'aac' : 'audio/mp4; codecs="mp4a.40.2"' + }, + audioEl = document.createElement( 'audio' ); + + Dancer.options = {}; + + Dancer.setOptions = function ( o ) { + for ( var option in o ) { + if ( o.hasOwnProperty( option ) ) { + Dancer.options[ option ] = o[ option ]; + } + } + }; + + Dancer.isSupported = function () { + if ( !window.Float32Array || !window.Uint32Array ) { + return null; + } else if ( !isUnsupportedSafari() && ( window.AudioContext || window.webkitAudioContext )) { + return 'webaudio'; + } else if ( audioEl && audioEl.mozSetup ) { + return 'audiodata'; + } else if ( FlashDetect.versionAtLeast( 9 ) ) { + return 'flash'; + } else { + return ''; + } + }; + + Dancer.canPlay = function ( type ) { + var canPlay = audioEl.canPlayType; + return !!( + Dancer.isSupported() === 'flash' ? + type.toLowerCase() === 'mp3' : + audioEl.canPlayType && + audioEl.canPlayType( CODECS[ type.toLowerCase() ] ).replace( /no/, '')); + }; + + Dancer.addPlugin = function ( name, fn ) { + if ( Dancer.prototype[ name ] === undefined ) { + Dancer.prototype[ name ] = fn; + } + }; + + Dancer._makeSupportedPath = function ( source, codecs ) { + if ( !codecs ) { return source; } + + for ( var i = 0; i < codecs.length; i++ ) { + if ( Dancer.canPlay( codecs[ i ] ) ) { + return source + '.' + codecs[ i ]; + } + } + return source; + }; + + Dancer._getAdapter = function ( instance ) { + switch ( Dancer.isSupported() ) { + case 'webaudio': + return new Dancer.adapters.webaudio( instance ); + case 'audiodata': + return new Dancer.adapters.moz( instance ); + case 'flash': + return new Dancer.adapters.flash( instance ); + default: + return null; + } + }; + + Dancer._getMP3SrcFromAudio = function ( audioEl ) { + var sources = audioEl.children; + if ( audioEl.src ) { return audioEl.src; } + for ( var i = sources.length; i--; ) { + if (( sources[ i ].type || '' ).match( /audio\/mpeg/ )) return sources[ i ].src; + } + return null; + }; + + // Browser detection is lame, but Safari 6 has Web Audio API, + // but does not support processing audio from a Media Element Source + // https://gist.github.com/3265344 + function isUnsupportedSafari () { + var + isApple = !!( navigator.vendor || '' ).match( /Apple/ ), + version = navigator.userAgent.match( /Version\/([^ ]*)/ ); + version = version ? parseFloat( version[ 1 ] ) : 0; + return isApple && version <= 6; + } + +})( window.Dancer ); + +(function ( undefined ) { + var Kick = function ( dancer, o ) { + o = o || {}; + this.dancer = dancer; + this.frequency = o.frequency !== undefined ? o.frequency : [ 0, 10 ]; + this.threshold = o.threshold !== undefined ? o.threshold : 0.3; + this.decay = o.decay !== undefined ? o.decay : 0.02; + this.onKick = o.onKick; + this.offKick = o.offKick; + this.isOn = false; + this.currentThreshold = this.threshold; + + var _this = this; + this.dancer.bind( 'update', function () { + _this.onUpdate(); + }); + }; + + Kick.prototype = { + on : function () { + this.isOn = true; + return this; + }, + off : function () { + this.isOn = false; + return this; + }, + + set : function ( o ) { + o = o || {}; + this.frequency = o.frequency !== undefined ? o.frequency : this.frequency; + this.threshold = o.threshold !== undefined ? o.threshold : this.threshold; + this.decay = o.decay !== undefined ? o.decay : this.decay; + this.onKick = o.onKick || this.onKick; + this.offKick = o.offKick || this.offKick; + }, + + onUpdate : function () { + if ( !this.isOn ) { return; } + var magnitude = this.maxAmplitude( this.frequency ); + if ( magnitude >= this.currentThreshold && + magnitude >= this.threshold ) { + this.currentThreshold = magnitude; + this.onKick && this.onKick.call( this.dancer, magnitude ); + } else { + this.offKick && this.offKick.call( this.dancer, magnitude ); + this.currentThreshold -= this.decay; + } + }, + maxAmplitude : function ( frequency ) { + var + max = 0, + fft = this.dancer.getSpectrum(); + + // Sloppy array check + if ( !frequency.length ) { + return frequency < fft.length ? + fft[ ~~frequency ] : + null; + } + + for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) { + if ( fft[ i ] > max ) { max = fft[ i ]; } + } + return max; + } + }; + + window.Dancer.Kick = Kick; +})(); + +(function() { + var + SAMPLE_SIZE = 2048, + SAMPLE_RATE = 44100; + + var adapter = function ( dancer ) { + this.dancer = dancer; + this.audio = new Audio(); + this.context = window.AudioContext ? + new window.AudioContext() : + new window.webkitAudioContext(); + }; + + adapter.prototype = { + + load : function ( _source ) { + var _this = this; + this.audio = _source; + + this.isLoaded = false; + this.progress = 0; + + this.proc = this.context.createScriptProcessor( SAMPLE_SIZE / 2, 1, 1 ); + + this.proc.onaudioprocess = function ( e ) { + _this.update.call( _this, e ); + }; + + this.gain = this.context.createGain(); + + this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE ); + this.signal = new Float32Array( SAMPLE_SIZE / 2 ); + + if ( this.audio.readyState < 3 ) { + this.audio.addEventListener( 'canplay', function () { + connectContext.call( _this ); + }); + } else { + connectContext.call( _this ); + } + + this.audio.addEventListener( 'progress', function ( e ) { + if ( e.currentTarget.duration ) { + _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; + } + }); + + return this.audio; + }, + + play : function () { + this.audio.play(); + this.isPlaying = true; + }, + + pause : function () { + this.audio.pause(); + this.isPlaying = false; + }, + + setVolume : function ( volume ) { + this.gain.gain.value = volume; + }, + + 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 ( e ) { + if ( !this.isPlaying || !this.isLoaded ) return; + + var + buffers = [], + channels = e.inputBuffer.numberOfChannels, + resolution = SAMPLE_SIZE / channels, + sum = function ( prev, curr ) { + return prev[ i ] + curr[ i ]; + }, i; + + for ( i = channels; i--; ) { + buffers.push( e.inputBuffer.getChannelData( i ) ); + } + + for ( i = 0; i < resolution; i++ ) { + this.signal[ i ] = channels > 1 ? + buffers.reduce( sum ) / channels : + buffers[ 0 ][ i ]; + } + + this.fft.forward( this.signal ); + this.dancer.trigger( 'update' ); + } + }; + + function connectContext () { + 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 = true; + this.progress = 1; + this.dancer.trigger( 'loaded' ); + } + + Dancer.adapters.webaudio = adapter; + +})(); + +(function() { + + var adapter = function ( dancer ) { + this.dancer = dancer; + this.audio = new Audio(); + }; + + adapter.prototype = { + + load : function ( _source ) { + var _this = this; + this.audio = _source; + + this.isLoaded = false; + this.progress = 0; + + if ( this.audio.readyState < 3 ) { + this.audio.addEventListener( 'loadedmetadata', function () { + getMetadata.call( _this ); + }, false); + } else { + getMetadata.call( _this ); + } + + this.audio.addEventListener( 'MozAudioAvailable', function ( e ) { + _this.update( e ); + }, false); + + this.audio.addEventListener( 'progress', function ( e ) { + if ( e.currentTarget.duration ) { + _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; + } + }, false); + + return this.audio; + }, + + play : function () { + this.audio.play(); + this.isPlaying = true; + }, + + pause : function () { + this.audio.pause(); + this.isPlaying = false; + }, + + setVolume : function ( volume ) { + this.audio.volume = volume; + }, + + 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 ( e ) { + if ( !this.isPlaying || !this.isLoaded ) return; + + for ( var i = 0, j = this.fbLength / 2; i < j; i++ ) { + this.signal[ i ] = ( e.frameBuffer[ 2 * i ] + e.frameBuffer[ 2 * i + 1 ] ) / 2; + } + + this.fft.forward( this.signal ); + this.dancer.trigger( 'update' ); + } + }; + + function getMetadata () { + 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 = true; + this.progress = 1; + this.dancer.trigger( 'loaded' ); + } + + Dancer.adapters.moz = adapter; + +})(); + +(function() { + var + SAMPLE_SIZE = 1024, + SAMPLE_RATE = 44100, + smLoaded = false, + smLoading = false, + CONVERSION_COEFFICIENT = 0.93; + + var adapter = function ( dancer ) { + this.dancer = dancer; + this.wave_L = []; + this.wave_R = []; + this.spectrum = []; + window.SM2_DEFER = true; + }; + + adapter.prototype = { + // `source` can be either an Audio element, if supported, or an object + // either way, the path is stored in the `src` property + load : function ( source ) { + var _this = this; + this.path = source ? source.src : this.path; + + this.isLoaded = false; + this.progress = 0; + + !window.soundManager && !smLoading && loadSM.call( this ); + + if ( window.soundManager ) { + this.audio = soundManager.createSound({ + id : 'dancer' + Math.random() + '', + url : this.path, + stream : true, + autoPlay : false, + autoLoad : true, + whileplaying : function () { + _this.update(); + }, + whileloading : function () { + _this.progress = this.bytesLoaded / this.bytesTotal; + }, + onload : function () { + _this.fft = new FFT( SAMPLE_SIZE, SAMPLE_RATE ); + _this.signal = new Float32Array( SAMPLE_SIZE ); + _this.waveform = new Float32Array( SAMPLE_SIZE ); + _this.isLoaded = true; + _this.progress = 1; + _this.dancer.trigger( 'loaded' ); + } + }); + this.dancer.audio = this.audio; + } + + // Returns audio if SM already loaded -- otherwise, + // sets dancer instance's audio property after load + return this.audio; + }, + + play : function () { + this.audio.play(); + this.isPlaying = true; + }, + + pause : function () { + this.audio.pause(); + this.isPlaying = false; + }, + + setVolume : function ( volume ) { + this.audio.setVolume( volume * 100 ); + }, + + 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 / 1000; + }, + + update : function () { + if ( !this.isPlaying && !this.isLoaded ) return; + this.wave_L = this.audio.waveformData.left; + this.wave_R = this.audio.waveformData.right; + var avg; + for ( var i = 0, j = this.wave_L.length; i < j; i++ ) { + avg = parseFloat(this.wave_L[ i ]) + parseFloat(this.wave_R[ i ]); + this.waveform[ 2 * i ] = avg / 2; + this.waveform[ i * 2 + 1 ] = avg / 2; + this.signal[ 2 * i ] = avg * CONVERSION_COEFFICIENT; + this.signal[ i * 2 + 1 ] = avg * CONVERSION_COEFFICIENT; + } + + this.fft.forward( this.signal ); + this.dancer.trigger( 'update' ); + } + }; + + function loadSM () { + var adapter = this; + smLoading = true; + loadScript( Dancer.options.flashJS, function () { + soundManager = new SoundManager(); + soundManager.flashVersion = 9; + soundManager.flash9Options.useWaveformData = true; + soundManager.useWaveformData = true; + soundManager.useHighPerformance = true; + soundManager.useFastPolling = true; + soundManager.multiShot = false; + soundManager.debugMode = false; + soundManager.debugFlash = false; + soundManager.url = Dancer.options.flashSWF; + soundManager.onready(function () { + smLoaded = true; + adapter.load(); + }); + soundManager.ontimeout(function(){ + console.error( 'Error loading SoundManager2.swf' ); + }); + soundManager.beginDelayedInit(); + }); + } + + function loadScript ( url, callback ) { + var + script = document.createElement( 'script' ), + appender = document.getElementsByTagName( 'script' )[0]; + script.type = 'text/javascript'; + script.src = url; + script.onload = callback; + appender.parentNode.insertBefore( script, appender ); + } + + Dancer.adapters.flash = adapter; + +})(); + +/* + * DSP.js - a comprehensive digital signal processing library for javascript + * + * Created by Corban Brook on 2010-01-01. + * Copyright 2010 Corban Brook. All rights reserved. + * + */ + +// Fourier Transform Module used by DFT, FFT, RFFT +function FourierTransform(bufferSize, sampleRate) { + this.bufferSize = bufferSize; + this.sampleRate = sampleRate; + this.bandwidth = 2 / bufferSize * sampleRate / 2; + + this.spectrum = new Float32Array(bufferSize/2); + this.real = new Float32Array(bufferSize); + this.imag = new Float32Array(bufferSize); + + this.peakBand = 0; + this.peak = 0; + + /** + * Calculates the *middle* frequency of an FFT band. + * + * @param {Number} index The index of the FFT band. + * + * @returns The middle frequency in Hz. + */ + this.getBandFrequency = function(index) { + return this.bandwidth * index + this.bandwidth / 2; + }; + + this.calculateSpectrum = function() { + var spectrum = this.spectrum, + real = this.real, + imag = this.imag, + bSi = 2 / this.bufferSize, + sqrt = Math.sqrt, + rval, + ival, + mag; + + for (var i = 0, N = bufferSize/2; i < N; i++) { + rval = real[i]; + ival = imag[i]; + mag = bSi * sqrt(rval * rval + ival * ival); + + if (mag > this.peak) { + this.peakBand = i; + this.peak = mag; + } + + spectrum[i] = mag; + } + }; +} + +/** + * FFT is a class for calculating the Discrete Fourier Transform of a signal + * with the Fast Fourier Transform algorithm. + * + * @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2 + * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) + * + * @constructor + */ +function FFT(bufferSize, sampleRate) { + FourierTransform.call(this, bufferSize, sampleRate); + + this.reverseTable = new Uint32Array(bufferSize); + + var limit = 1; + var bit = bufferSize >> 1; + + var i; + + while (limit < bufferSize) { + for (i = 0; i < limit; i++) { + this.reverseTable[i + limit] = this.reverseTable[i] + bit; + } + + limit = limit << 1; + bit = bit >> 1; + } + + this.sinTable = new Float32Array(bufferSize); + this.cosTable = new Float32Array(bufferSize); + + for (i = 0; i < bufferSize; i++) { + this.sinTable[i] = Math.sin(-Math.PI/i); + this.cosTable[i] = Math.cos(-Math.PI/i); + } +} + +/** + * Performs a forward transform on the sample buffer. + * Converts a time domain signal to frequency domain spectra. + * + * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 + * + * @returns The frequency spectrum array + */ +FFT.prototype.forward = function(buffer) { + // Locally scope variables for speed up + var bufferSize = this.bufferSize, + cosTable = this.cosTable, + sinTable = this.sinTable, + reverseTable = this.reverseTable, + real = this.real, + imag = this.imag, + spectrum = this.spectrum; + + var k = Math.floor(Math.log(bufferSize) / Math.LN2); + + if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; } + if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; } + + var halfSize = 1, + phaseShiftStepReal, + phaseShiftStepImag, + currentPhaseShiftReal, + currentPhaseShiftImag, + off, + tr, + ti, + tmpReal, + i; + + for (i = 0; i < bufferSize; i++) { + real[i] = buffer[reverseTable[i]]; + imag[i] = 0; + } + + while (halfSize < bufferSize) { + //phaseShiftStepReal = Math.cos(-Math.PI/halfSize); + //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); + phaseShiftStepReal = cosTable[halfSize]; + phaseShiftStepImag = sinTable[halfSize]; + + currentPhaseShiftReal = 1; + currentPhaseShiftImag = 0; + + for (var fftStep = 0; fftStep < halfSize; fftStep++) { + i = fftStep; + + while (i < bufferSize) { + off = i + halfSize; + tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); + ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); + + real[off] = real[i] - tr; + imag[off] = imag[i] - ti; + real[i] += tr; + imag[i] += ti; + + i += halfSize << 1; + } + + tmpReal = currentPhaseShiftReal; + currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); + currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); + } + + halfSize = halfSize << 1; + } + + return this.calculateSpectrum(); +}; + +/* + Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved. + Code licensed under the BSD License: http://www.featureblend.com/license.txt + Version: 1.0.4 + */ +var FlashDetect = new function(){ + var self = this; + self.installed = false; + self.raw = ""; + self.major = -1; + self.minor = -1; + self.revision = -1; + self.revisionStr = ""; + var activeXDetectRules = [ + { + "name":"ShockwaveFlash.ShockwaveFlash.7", + "version":function(obj){ + return getActiveXVersion(obj); + } + }, + { + "name":"ShockwaveFlash.ShockwaveFlash.6", + "version":function(obj){ + var version = "6,0,21"; + try{ + obj.AllowScriptAccess = "always"; + version = getActiveXVersion(obj); + }catch(err){} + return version; + } + }, + { + "name":"ShockwaveFlash.ShockwaveFlash", + "version":function(obj){ + return getActiveXVersion(obj); + } + } + ]; + /** + * Extract the ActiveX version of the plugin. + * + * @param {Object} The flash ActiveX object. + * @type String + */ + var getActiveXVersion = function(activeXObj){ + var version = -1; + try{ + version = activeXObj.GetVariable("$version"); + }catch(err){} + return version; + }; + /** + * Try and retrieve an ActiveX object having a specified name. + * + * @param {String} name The ActiveX object name lookup. + * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true. + * @type Object + */ + var getActiveXObject = function(name){ + var obj = -1; + try{ + obj = new ActiveXObject(name); + }catch(err){ + obj = {activeXError:true}; + } + return obj; + }; + /** + * Parse an ActiveX $version string into an object. + * + * @param {String} str The ActiveX Object GetVariable($version) return value. + * @return An object having raw, major, minor, revision and revisionStr attributes. + * @type Object + */ + var parseActiveXVersion = function(str){ + var versionArray = str.split(",");//replace with regex + return { + "raw":str, + "major":parseInt(versionArray[0].split(" ")[1], 10), + "minor":parseInt(versionArray[1], 10), + "revision":parseInt(versionArray[2], 10), + "revisionStr":versionArray[2] + }; + }; + /** + * Parse a standard enabledPlugin.description into an object. + * + * @param {String} str The enabledPlugin.description value. + * @return An object having raw, major, minor, revision and revisionStr attributes. + * @type Object + */ + var parseStandardVersion = function(str){ + var descParts = str.split(/ +/); + var majorMinor = descParts[2].split(/\./); + var revisionStr = descParts[3]; + return { + "raw":str, + "major":parseInt(majorMinor[0], 10), + "minor":parseInt(majorMinor[1], 10), + "revisionStr":revisionStr, + "revision":parseRevisionStrToInt(revisionStr) + }; + }; + /** + * Parse the plugin revision string into an integer. + * + * @param {String} The revision in string format. + * @type Number + */ + var parseRevisionStrToInt = function(str){ + return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision; + }; + /** + * Is the major version greater than or equal to a specified version. + * + * @param {Number} version The minimum required major version. + * @type Boolean + */ + self.majorAtLeast = function(version){ + return self.major >= version; + }; + /** + * Is the minor version greater than or equal to a specified version. + * + * @param {Number} version The minimum required minor version. + * @type Boolean + */ + self.minorAtLeast = function(version){ + return self.minor >= version; + }; + /** + * Is the revision version greater than or equal to a specified version. + * + * @param {Number} version The minimum required revision version. + * @type Boolean + */ + self.revisionAtLeast = function(version){ + return self.revision >= version; + }; + /** + * Is the version greater than or equal to a specified major, minor and revision. + * + * @param {Number} major The minimum required major version. + * @param {Number} (Optional) minor The minimum required minor version. + * @param {Number} (Optional) revision The minimum required revision version. + * @type Boolean + */ + self.versionAtLeast = function(major){ + var properties = [self.major, self.minor, self.revision]; + var len = Math.min(properties.length, arguments.length); + for(i=0; i=arguments[i]){ + if(i+10){ + var type = 'application/x-shockwave-flash'; + var mimeTypes = navigator.mimeTypes; + if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){ + var version = mimeTypes[type].enabledPlugin.description; + var versionObj = parseStandardVersion(version); + self.raw = versionObj.raw; + self.major = versionObj.major; + self.minor = versionObj.minor; + self.revisionStr = versionObj.revisionStr; + self.revision = versionObj.revision; + self.installed = true; + } + }else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){ + var version = -1; + for(var i=0; i