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}}
-
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