diff --git a/README.md b/README.md index ed50549..75f66e3 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Music awesomeness for hue lights. # TODO ## FEATURES -- microphone mode -- youtube video ## BUGS diff --git a/app/pods/components/hue-controls/component.js b/app/pods/components/hue-controls/component.js index e68a702..bb7e24f 100644 --- a/app/pods/components/hue-controls/component.js +++ b/app/pods/components/hue-controls/component.js @@ -32,7 +32,8 @@ export default Em.Component.extend({ }, startIntro(){ var intro = introJs(), - playerBottom = Em.$('#playerBottom'); + playerBottom = Em.$('#playerBottom'), + beatDetectionAreaArrowIcon = Em.$('#beatDetectionAreaArrowIcon'); intro.setOptions({ steps: [ @@ -43,26 +44,31 @@ export default Em.Component.extend({ }, { element: '#playlist', - intro: 'You can add and select music to play from your playlist here. You may add local audio files, stream music from soundcloud or stream music into the application fromn your mic.

' + + intro: 'You can add and select music to play from your playlist here. You may listen to local audio files, stream music from soundcloud or stream directly from a connected microphone.

' + 'TIP: Songs added through soundcloud will be saved for when you visit this page again.' }, + { + element: '#usingMicAudioTooltip', + intro: 'This icon will trigger the mode in which the application will listen to your microphone.
' + + 'Note that this is a highly experimental feature that will require your authorization to be able to listen to the microphone. Also note that the beat detection will not be nearly as accurate in this mode.' + }, { element: '#playerArea', - intro: 'The audio playback may be controlled with the controls here. Basic music visualization effects may be shown here by selecting them from the menu ( eyeball in the bottom right ).' + intro: 'The audio playback may be controlled with the controls here. Basic music visualization effects may be shown here by selecting them from the menu ( eyeball icon in the bottom right ).' }, { element: '#beatOptionRow', - intro: 'Beat detection settings:
' + + intro: 'These are the beat detection settings:
' + 'Beat Threshold - The minimum sound intensity for the beat to register
' + 'Beat Interval - The minimum amount of time between each registered beat
' + 'Frequency Range - The frequency range of the sound to listen on for the beat
' + 'Transition Time - The time it takes for a light to change color or brightness

' + - 'TIP: Beat settings are saved per song as indicated by the red start icon in the top left corner. These settings they will be restored if you ever listen to the same song again.', + 'TIP: Beat settings are saved per song as indicated by the red star icon in the top left corner. These settings they will be restored if you ever listen to the same song again.', position: 'top' }, { element: '#beatOptionButtonGroup', - intro: 'Some additional options:
' + + intro: 'Some additional settings:
' + 'Default - Revert to the default beat detection settings
' + 'Random/Sequential - The transition order of lights on beat
' + 'Brightness/Brightness & Color - The properties of the lights to change on beat

' + @@ -71,14 +77,14 @@ export default Em.Component.extend({ }, { element: '#beatContainer', - intro: 'An interactive speaker that will bump on a registered beat. Switch over to the Debug View to see the intesity of all the registered and unregistered beats.

' + + intro: 'An interactive speaker that will bump when a beat is registered. Switch over to the Debug View to see the intesity of all the registered and unregistered beats.

' + 'TIP: Click on the center of the speaker to simulate a beat.', position: 'top' }, { element: '#lightsTab', intro: 'This is the lights tab. Here you\'ll be able to change various light properties:
' + - 'Power - The selected lights to be on/off
' + + 'Power - Turn the selected lights on/off
' + 'Brightness - The brightness level of the selected lights
' + 'Color - The color of the selected lights
' + 'Strobe - Selected lights will flash in sequential order
' + @@ -86,11 +92,12 @@ export default Em.Component.extend({ }, { element: '#activeLights', - intro: 'These icons represent the hue lights in your system. Active lights will be controlled by the application while the inactive lights will have a red X over them and will not be controlled. You may toggle a light\'s active state by clicking on it' + intro: 'These icons represent the hue lights in your system. Active lights will be controlled by the application while the inactive lights will have a red X over them and will not be controlled.
' + + 'You may toggle a light\'s state by clicking on it.' }, { element: Em.$('.settingsItem')[0], - intro: 'Groups allow for saving and selecting sets of lights.', + intro: 'The Groups menu allows for saving and quickly selecting groups of lights.', position: 'left' }, { @@ -101,18 +108,19 @@ export default Em.Component.extend({ }, { element: '#dimmerWrapper', - intro: 'And that\'s it...Enjoy the application. ;)

' + - 'TIP: click on the lightbulb to turn off the lights.', + intro: 'And that\'s it...Feel free to reach out to me through the link at the bottom of the page.
' + + 'Hope you enjoy the application. ;)

' + + 'TIP: click on the lightbulb to switch to a darker theme.', position: 'top' } ] }); - // it's not pretty but it works + // it's VERY ugly but it works intro.onchange((element) => { this.set('dimmerOn', false); - if(element.id === 'musicTab' || element.id === 'playlist' || element.id === 'playerArea' || element.id === 'beatOptionRow' || element.id === 'beatOptionButtonGroup' || element.id === 'beatContainer'){ + if(element.id === 'musicTab' || element.id === 'playlist' || element.id === 'playerArea' || element.id === 'beatOptionRow' || element.id === 'beatOptionButtonGroup' || element.id === 'beatContainer' || element.id === 'usingMicAudioTooltip'){ Em.$('#musicTab').removeClass('hidden'); Em.$('#lightsTab').addClass('hidden'); Em.$('.navigationItem').eq(0).removeClass('active'); @@ -126,11 +134,16 @@ export default Em.Component.extend({ if(element.id === 'musicTab' || element.id === 'playlist' || element.id === 'playerArea'){ playerBottom.hide(); + + if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-up')){ + beatDetectionAreaArrowIcon.removeClass('keyboard-arrow-up').addClass('keyboard-arrow-down'); + } } else if(element.id === 'beatOptionRow' || element.id === 'beatOptionButtonGroup' || element.id === 'beatContainer'){ playerBottom.show(); - } else if(element.id === 'lightsTab'){ - Em.$('#musicTab').addClass('hidden'); - Em.$('#lightsTab').removeClass('hidden'); + + if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-down')){ + beatDetectionAreaArrowIcon.removeClass('keyboard-arrow-down').addClass('keyboard-arrow-up'); + } } else if(element.id === 'dimmerWrapper'){ Em.$(document).click(); } @@ -142,12 +155,20 @@ export default Em.Component.extend({ Em.$('#lightsTab').addClass('hidden'); Em.$('.navigationItem').eq(0).removeClass('active'); Em.$('.navigationItem').eq(1).addClass('active'); - playerBottom.hide(); + + if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-up')){ + playerBottom.show(); + } else { + playerBottom.hide(); + } }; - intro.onexit(onFinish); - intro.oncomplete(onFinish); - intro.start(); + // skip hidden/missing elements + intro.onafterchange((element)=>{ + if(Em.$(element).hasClass('introjsFloatingElement')){ + Em.$('.introjs-nextbutton').click(); + } + }).onexit(onFinish).oncomplete(onFinish).start(); } }, @@ -229,8 +250,8 @@ export default Em.Component.extend({ updateLightData(){ var fail = ()=>{ - clearInterval(self.get('lightsDataIntervalHandle')); - + clearInterval(this.get('lightsDataIntervalHandle')); + this.get('storage').remove('huegasm.bridgeIp'); this.get('storage').remove('huegasm.bridgeUsername'); diff --git a/app/pods/components/huegasm-app/template.hbs b/app/pods/components/huegasm-app/template.hbs index 39fc004..569c1d4 100644 --- a/app/pods/components/huegasm-app/template.hbs +++ b/app/pods/components/huegasm-app/template.hbs @@ -8,7 +8,11 @@
Huegasm

Huegasm is a free web application for controlling your Philips Hue lights...oh and it's kind of awesome at syncing music with your lights.

- {{#paper-button raised=true primary=true action="isReady" class="goButton"}}Start!{{/paper-button}} + + + + + {{#paper-button raised=true primary=true action="isReady" class="goButton"}}Go!{{/paper-button}} {{/if}} {{/if}} diff --git a/app/pods/components/lights-tab/template.hbs b/app/pods/components/lights-tab/template.hbs index 9a4c723..0aa9a6b 100644 --- a/app/pods/components/lights-tab/template.hbs +++ b/app/pods/components/lights-tab/template.hbs @@ -5,7 +5,7 @@ {{#paper-item}} {{paper-icon icon="power-settings-new" class=dimmerOnClass}} -

Power

+

Power

{{#paper-switch checked=lightsOn disabled=trial skipProxy=trial}} {{lightsOnTxt}} {{/paper-switch}} {{/paper-item}} diff --git a/app/pods/components/music-tab/component.js b/app/pods/components/music-tab/component.js index a8bf7a2..2bff80f 100644 --- a/app/pods/components/music-tab/component.js +++ b/app/pods/components/music-tab/component.js @@ -3,10 +3,6 @@ import helperMixin from './mixins/helpers'; import visualizerMixin from './mixins/visualizer'; export default Em.Component.extend(helperMixin, visualizerMixin, { - classNames: ['col-lg-10', 'col-lg-offset-1', 'col-xs-12'], - classNameBindings: ['active::hidden'], - elementId: 'musicTab', - onActiveChange: function(){ if(this.get('active')){ Em.$('#playNotification').removeClass('fadeOut'); @@ -112,6 +108,12 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { this.send('volumeChanged', this.get('volume')); } + // restore the old beat preferences ( before the user went into mic mode ) + if(!Em.isNone(this.get('oldThreshold'))){ + this.set('threshold', this.get('oldThreshold')); + this.set('frequency', this.get('oldFrequency')); + } + document.title = 'Huegasm'; }, useMicAudio() { @@ -163,7 +165,7 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { this.send('next'); }; - dancer.load(audio); + dancer.load(audio, 1); this.set('playQueuePointer', index); @@ -375,6 +377,11 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { thresholdChanged(value) { this.changePlayerControl('threshold', value, true); }, + micBoostChanged(value) { + this.set('micBoost', value); + this.get('storage').set('huegasm.micBoost', value); + this.get('dancer').setBoost(value); + }, intervalChanged(value){ this.changePlayerControl('interval', value, true); }, @@ -470,7 +477,7 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { title = Em.isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title, songBeatPreferences = this.get('songBeatPreferences'); - songBeatPreferences[title] = {threshold: this.get('threshold'), interval: this.get('interval'), frequency: this.get('frequency') }; + songBeatPreferences[title] = {threshold: this.get('threshold'), interval: this.get('interval'), frequency: this.get('frequency')}; this.set('usingBeatPreferences', true); this.get('storage').set('huegasm.songBeatPreferences', songBeatPreferences); @@ -519,7 +526,18 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { }); document.title = 'Listening to Mic - Huegasm'; - dancer.load(stream, true); + + dancer.load(stream, this.get('micBoost'), true); + this.set('usingBeatPreferences', false); + + // much more sensitive beath preference settings are needed for mic mode + this.setProperties({ + oldFrequency: this.get('frequency'), + oldThreshold: this.get('threshold'), + threshold: 0.1, + frequency: [0,10] + }); + dancer.setVolume(0); }, (err) => { @@ -711,7 +729,7 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { this.set('usingMicSupported', false); } - ['volume', 'shuffle', 'repeat', 'volumeMuted', 'threshold', 'interval', 'frequency', 'speakerViewed', 'transitionTime', 'randomTransition', 'playerBottomDisplayed', 'onBeatBriAndColor', 'audioMode', 'songBeatPreferences', 'debugFiltered', 'firstVisit', 'currentVisName', 'playQueue', 'playQueuePointer'].forEach(function (item) { + ['volume', 'shuffle', 'repeat', 'volumeMuted', 'threshold', 'interval', 'frequency', 'speakerViewed', 'transitionTime', 'randomTransition', 'playerBottomDisplayed', 'onBeatBriAndColor', 'audioMode', 'songBeatPreferences', 'debugFiltered', 'firstVisit', 'currentVisName', 'playQueue', 'playQueuePointer', 'micBoost'].forEach(function (item) { if (!Em.isNone(storage.get('huegasm.' + item))) { var itemVal = storage.get('huegasm.' + item); @@ -751,16 +769,16 @@ export default Em.Component.extend(helperMixin, visualizerMixin, { }); // control the volume by scrolling up/down - Em.$('#playerArea').on('mousewheel', function(event) { - if(self.get('playQueueNotEmpty')) { + Em.$('#playerArea').on('mousewheel', (event)=>{ + if(this.get('playQueueNotEmpty') && !this.get('usingMicAudio')) { var scrollSize = 5; if(event.deltaY < 0) { scrollSize *= -1; } - var newVolume = self.get('volume') + scrollSize; + var newVolume = this.get('volume') + scrollSize; - self.send('volumeChanged', newVolume < 0 ? 0 : newVolume); + this.send('volumeChanged', newVolume < 0 ? 0 : newVolume); event.preventDefault(); } }); diff --git a/app/pods/components/music-tab/mixins/helpers.js b/app/pods/components/music-tab/mixins/helpers.js index ff9c99d..09a2825 100644 --- a/app/pods/components/music-tab/mixins/helpers.js +++ b/app/pods/components/music-tab/mixins/helpers.js @@ -1,6 +1,10 @@ import Em from 'ember'; export default Em.Mixin.create({ + classNames: ['col-lg-10', 'col-lg-offset-2', 'col-xs-12'], + classNameBindings: ['active::hidden'], + elementId: 'musicTab', + dancer: null, notify: Em.inject.service('notify'), @@ -23,7 +27,7 @@ export default Em.Mixin.create({ interval: { range: {min: 0, max: 0.5}, step: 0.01, - defaultValue: 0.15, + defaultValue: 0.1, pips: { mode: 'positions', values: [0,20,40,60,80,100], @@ -61,13 +65,30 @@ export default Em.Mixin.create({ from: function ( value ) { return value; } } } + }, + micBoost: { + range: {min: 1, max: 11}, + step: 0.5, + defaultValue: 5, + pips: { + mode: 'positions', + values: [0,20,40,60,80,100], + density: 10, + format: { + to: function ( value ) {return value;}, + from: function ( value ) { return value; } + } + } } }, transitionTime: 0.1, threshold: 0.3, - interval: 0.15, + interval: 0.1, frequency: [0,4], + micBoost: 5, + oldThreshold: null, + oldFrequency: null, playQueuePointer: -1, playQueue: Em.A(), @@ -380,7 +401,7 @@ export default Em.Mixin.create({ } }, - beatDetectionArrowIcon: function(){ + beatDetectionAreaArrowIcon: function(){ if(!this.get('playerBottomDisplayed')){ return 'keyboard-arrow-down'; } else { diff --git a/app/pods/components/music-tab/template.hbs b/app/pods/components/music-tab/template.hbs index 6f1be2d..93e7c65 100644 --- a/app/pods/components/music-tab/template.hbs +++ b/app/pods/components/music-tab/template.hbs @@ -43,9 +43,7 @@ {{/each}} - -{{!--{{paper-icon icon="fullscreen" class="playerControllIcon"}}--}} - + @@ -80,7 +78,11 @@ {{#if usingMicAudio}}
- {{paper-icon icon="mic" class=dimmerOnClass}} +
+ Microphone Boost + {{range-slider start=micBoost orientation="vertical" step=beatOptions.micBoost.step range=beatOptions.micBoost.range slide="micBoostChanged" pips=beatOptions.micBoost.pips}} +
{{micBoost}}
+
{{else}} {{#if usingLocalAudio}} @@ -130,7 +132,7 @@
-{{paper-icon icon=beatDetectionArrowIcon}} + {{paper-icon icon=beatDetectionAreaArrowIcon id="beatDetectionAreaArrowIcon"}}
diff --git a/app/styles/app.scss b/app/styles/app.scss index 4a87b52..e5f95f7 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -25,13 +25,13 @@ body { } .goButton { - margin-top: 60px; + margin-top: 20px; border-radius: 100% !important; width: 100px; height: 100px; span { font-size: 20px; - margin-top: 30px; + width: 100%; } } @@ -39,10 +39,13 @@ body { background: darken(#3f51b5, 10%) !important; } -// temporary HAX...not working in FF +// Ember paper hax for FF md-progress-circular[md-mode=indeterminate] .md-spinner-wrapper { transform: none !important; } +.md-button { + flex-direction: unset; +} .alert { margin-bottom: 0; @@ -196,6 +199,7 @@ md-list-item .md-no-style { #hueControls { max-width: 1200px; + min-width: 400px; position: relative; } @@ -629,7 +633,7 @@ md-switch.md-default-theme.md-checked .md-thumb { position: relative; } -#playListArea, #playAreaMic, #playAreaYoutube { +#playListArea, #playAreaMic { background-color: white; width: 100%; height: 350px; @@ -645,7 +649,7 @@ md-switch.md-default-theme.md-checked .md-thumb { text-align: center; width: 100%; } - .library-music, .mic { + .library-music { position: absolute; top: 40%; font-size: 100px; @@ -653,10 +657,6 @@ md-switch.md-default-theme.md-checked .md-thumb { width: 100%; text-align: center; } - .mic { - top: 30%; - font-size: 120px; - } } .ember-notify-cn { @@ -736,20 +736,19 @@ md-switch.md-default-theme.md-checked .md-thumb { height: $playerBottomHeight; position: relative; padding: 20px 0 0 0; - * { - .noUi-target { - margin: 0 auto; - } - .noUi-base, .noUi-background { - background-color: #ADADAD; - border: 1px solid #797979; - } - .noUi-vertical { - height: 170px; - margin-top: 15px; - margin-bottom: 15px; - } - } +} + +.noUi-target { + margin: 0 auto; +} +.noUi-base, .noUi-background { + background-color: #ADADAD; + border: 1px solid #797979; +} +.noUi-vertical { + height: 170px; + margin-top: 15px; + margin-bottom: 15px; } .star { @@ -913,7 +912,6 @@ body.dimmerOn { .loop.dimmerOn, .group.dimmerOn, .settings.dimmerOn, -.mic.dimmerOn, .library-music.dimmerOn { color: inherit !important; text-shadow: $glowingText; @@ -1066,3 +1064,12 @@ div.ember-modal-dialog { width: 10px; top: -2px; } + +#micBoost { + margin-top: 50px; +} + +#introPic { + display: block; + margin: 0 auto; +} diff --git a/assets/intro1.psd b/assets/intro1.psd new file mode 100644 index 0000000..a3f6cdb Binary files /dev/null and b/assets/intro1.psd differ diff --git a/public/assets/images/intro.jpg b/public/assets/images/intro.jpg new file mode 100644 index 0000000..d4ee9d0 Binary files /dev/null and b/public/assets/images/intro.jpg differ diff --git a/vendor/dancer.js b/vendor/dancer.js index 2bb7e7b..ad78488 100644 --- a/vendor/dancer.js +++ b/vendor/dancer.js @@ -18,7 +18,7 @@ Dancer.prototype = { - load : function ( source, useMic ) { + load : function ( source, micBoost, useMic ) { // Loading an Audio element if ( source instanceof HTMLElement ) { this.source = source; @@ -35,7 +35,8 @@ } this.useMic = useMic === true; - this.audio = this.audioAdapter.load(this.source, this.useMic); + this.boost = micBoost ? micBoost : 1; + this.audio = this.audioAdapter.load(this.source, this.useMic, this.boost); return this; }, @@ -56,6 +57,10 @@ return this; }, + setBoost : function ( boost ) { + this.audioAdapter.setBoost( boost ); + return this; + }, /* Actions */ createKick : function ( options ) { @@ -380,10 +385,11 @@ adapter.prototype = { - load : function (_source, useMic) { + load : function (_source, useMic, boost) { var _this = this; this.audio = _source; this.useMic = useMic; + this.boost = boost; this.isLoaded = false; this.progress = 0; @@ -401,7 +407,7 @@ this.gain = this.context.createGain(); - this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE ); + this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE, this.boost ); this.signal = new Float32Array( SAMPLE_SIZE / 2 ); if ( this.audio.readyState < 3 ) { @@ -435,6 +441,14 @@ this.gain.gain.value = volume; }, + setBoost : function( boost ){ + if(this.fft){ + this.fft.setBoost(boost); + } + + this.boost = boost; + }, + getVolume : function () { return this.gain.gain.value; }, @@ -495,7 +509,7 @@ this.source.connect(this.proc); this.source.connect(this.gain); - //this.source.connect( this.filter ); + this.source.connect( this.filter ); this.gain.connect(this.context.destination); this.proc.connect(this.context.destination); //this.filter.connect( this.context.destination ); @@ -765,10 +779,11 @@ */ // Fourier Transform Module used by DFT, FFT, RFFT -function FourierTransform(bufferSize, sampleRate) { +function FourierTransform(bufferSize, sampleRate, boost) { this.bufferSize = bufferSize; this.sampleRate = sampleRate; this.bandwidth = 2 / bufferSize * sampleRate / 2; + this.boost = boost ? boost : 1; this.spectrum = new Float32Array(bufferSize/2); this.real = new Float32Array(bufferSize); @@ -788,10 +803,15 @@ function FourierTransform(bufferSize, sampleRate) { return this.bandwidth * index + this.bandwidth / 2; }; + this.setBoost = function(boost){ + this.boost = boost; + }; + this.calculateSpectrum = function() { var spectrum = this.spectrum, real = this.real, imag = this.imag, + boost = this.boost, bSi = 2 / this.bufferSize, sqrt = Math.sqrt, rval, @@ -808,7 +828,7 @@ function FourierTransform(bufferSize, sampleRate) { this.peak = mag; } - spectrum[i] = mag; + spectrum[i] = mag * boost; } }; } @@ -819,11 +839,12 @@ function FourierTransform(bufferSize, sampleRate) { * * @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) + * @param {Number} boost The coefficient * * @constructor */ -function FFT(bufferSize, sampleRate) { - FourierTransform.call(this, bufferSize, sampleRate); +function FFT(bufferSize, sampleRate, boost) { + FourierTransform.call(this, bufferSize, sampleRate, boost); this.reverseTable = new Uint32Array(bufferSize);