better mic mode

This commit is contained in:
Egor 2015-10-29 15:19:19 -07:00
parent e46a82e433
commit 2f2678ac1c
11 changed files with 172 additions and 80 deletions

View file

@ -3,8 +3,6 @@ Music awesomeness for hue lights.
# TODO
## FEATURES
- microphone mode
- youtube video
## BUGS

View file

@ -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.<br><br>' +
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.<br><br>' +
'<i><b>TIP</b>: Songs added through soundcloud will be saved for when you visit this page again.</i>'
},
{
element: '#usingMicAudioTooltip',
intro: 'This icon will trigger the mode in which the application will listen to your microphone.<br>' +
'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:<br>' +
intro: 'These are the beat detection settings:<br>' +
'<b>Beat Threshold</b> - The minimum sound intensity for the beat to register<br>' +
'<b>Beat Interval</b> - The minimum amount of time between each registered beat <br>' +
'<b>Frequency Range</b> - The frequency range of the sound to listen on for the beat<br>' +
'<b>Transition Time</b> - The time it takes for a light to change color or brightness<br><br>' +
'<i><b>TIP</b>: 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.</i>',
'<i><b>TIP</b>: 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.</i>',
position: 'top'
},
{
element: '#beatOptionButtonGroup',
intro: 'Some additional options:<br>' +
intro: 'Some additional settings:<br>' +
'<b>Default</b> - Revert to the default beat detection settings<br>' +
'<b>Random/Sequential</b> - The transition order of lights on beat<br>' +
'<b>Brightness/Brightness & Color</b> - The properties of the lights to change on beat<br><br>' +
@ -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 <b>Debug View</b> to see the intesity of all the registered and unregistered beats.<br><br>' +
intro: 'An interactive speaker that will bump when a beat is registered. Switch over to the <b>Debug View</b> to see the intesity of all the registered and unregistered beats.<br><br>' +
'<i><b>TIP</b>: Click on the center of the speaker to simulate a beat.</i>',
position: 'top'
},
{
element: '#lightsTab',
intro: 'This is the lights tab. Here you\'ll be able to change various light properties:<br>' +
'<b>Power</b> - The selected lights to be on/off<br>' +
'<b>Power</b> - Turn the selected lights on/off<br>' +
'<b>Brightness</b> - The brightness level of the selected lights<br>' +
'<b>Color</b> - The color of the selected lights<br>' +
'<b>Strobe</b> - Selected lights will flash in sequential order<br>' +
@ -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.<br>' +
'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. ;) <br><br>' +
'<i><b>TIP</b>: click on the lightbulb to turn off the lights.</i>',
intro: 'And that\'s it...Feel free to reach out to me through the link at the bottom of the page.<br>' +
'Hope you enjoy the application. ;)<br><br>' +
'<i><b>TIP</b>: click on the lightbulb to switch to a darker theme.</i>',
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');

View file

@ -8,7 +8,11 @@
<div class="title">Huegasm</div>
<p>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.</p>
{{#paper-button raised=true primary=true action="isReady" class="goButton"}}Start!{{/paper-button}}
<a href="#" {{action "isReady"}}>
<img src="assets/images/intro.jpg" id="introPic" />
</a>
{{#paper-button raised=true primary=true action="isReady" class="goButton"}}Go!{{/paper-button}}
</div>
{{/if}}
{{/if}}

View file

@ -5,7 +5,7 @@
{{#paper-item}}
{{paper-icon icon="power-settings-new" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="The selected lights to be on/off">Power</p>
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="Turn the selected lights on/off">Power</p>
{{#paper-switch checked=lightsOn disabled=trial skipProxy=trial}} {{lightsOnTxt}} {{/paper-switch}}
{{/paper-item}}

View file

@ -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();
}
});

View file

@ -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 {

View file

@ -43,9 +43,7 @@
{{/each}}
</ul>
</span>
{{!--<span data-toggle="tooltip" data-placement="top" class="bootstrapTooltip" data-title="Full screen" {{action "fullscreen"}}>{{paper-icon icon="fullscreen" class="playerControllIcon"}}</span>--}}
</span>
</span>
</div>
</div>
@ -80,7 +78,11 @@
{{#if usingMicAudio}}
<div id="playAreaMic" class="{{if dimmerOn "dimmerOn"}}">
{{paper-icon icon="mic" class=dimmerOnClass}}
<div id="micBoost" class="beatOption">
<span data-toggle="tooltip" data-placement="bottom auto" data-title="The coefficient to boost the microphone signal by" class="optionDescription bootstrapTooltip">Microphone Boost</span>
{{range-slider start=micBoost orientation="vertical" step=beatOptions.micBoost.step range=beatOptions.micBoost.range slide="micBoostChanged" pips=beatOptions.micBoost.pips}}
<div class="text-center">{{micBoost}}</div>
</div>
</div>
{{else}}
{{#if usingLocalAudio}}
@ -130,7 +132,7 @@
<div id="slideToggle" class="text-center cursorPointer row" {{action "slideTogglePlayerBottom"}}>
<div class="col-xs-offset-5 col-xs-2">
{{paper-icon icon=beatDetectionArrowIcon}}
{{paper-icon icon=beatDetectionAreaArrowIcon id="beatDetectionAreaArrowIcon"}}
</div>
</div>

View file

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

BIN
assets/intro1.psd Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

39
vendor/dancer.js vendored
View file

@ -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);