chrome skeleton

This commit is contained in:
Egor 2017-02-14 23:55:12 -08:00
parent 0f59207893
commit d11c71b5b7
86 changed files with 5715 additions and 3 deletions

4
chrome/.bowerrc Normal file
View file

@ -0,0 +1,4 @@
{
"directory": "bower_components",
"analytics": false
}

20
chrome/.editorconfig Normal file
View file

@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.hbs]
insert_final_newline = false
[*.{diff,md}]
trim_trailing_whitespace = false

10
chrome/.ember-cli Normal file
View file

@ -0,0 +1,10 @@
{
/**
Ember CLI sends analytics information by default. The data is completely
anonymous, but there are times when you might want to disable this behavior.
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"disableAnalytics": true,
"usePods": true
}

18
chrome/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/.idea/

37
chrome/.jshintrc Normal file
View file

@ -0,0 +1,37 @@
{
"predef": [
"document",
"window",
"-Promise",
"Dancer",
"ID3",
"FileAPIReader",
"SC",
"introJs"
],
"browser": true,
"boss": true,
"curly": true,
"debug": false,
"devel": true,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"esversion": 6,
"unused": true
}

24
chrome/.travis.yml Normal file
View file

@ -0,0 +1,24 @@
---
language: node_js
node_js:
- "4"
sudo: false
cache:
directories:
- $HOME/.npm
- $HOME/.cache # includes bowers cache
before_install:
- npm config set spin false
- npm install -g bower phantomjs-prebuilt
- bower --version
- phantomjs --version
install:
- npm install
- bower install
script:
- npm test

3
chrome/.watchmanconfig Normal file
View file

@ -0,0 +1,3 @@
{
"ignore_dirs": ["tmp", "dist"]
}

44
chrome/README.md Normal file
View file

@ -0,0 +1,44 @@
# Huegasm
This README outlines the details of collaborating on this Ember application.
Music awesomeness for hue lights.
## Prerequisites
You will need the following things properly installed on your computer.
* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/) (with NPM)
* [Bower](http://bower.io/)
* [Ember CLI](http://ember-cli.com/)
* [PhantomJS](http://phantomjs.org/)
## Installation
* `git clone <repository-url>` this repository
* `cd huegasm`
* `npm install`
* `bower install`
## Running / Development
* `ember serve`
* Visit your app at [http://localhost:4200](http://localhost:4200).
### Code Generators
Make use of the many generators for code, try `ember help generate` for more details
### Building
* `ember build` (development)
* `ember build --environment production` (production)
## Further Reading / Useful Links
* [ember.js](http://emberjs.com/)
* [ember-cli](http://ember-cli.com/)
* Development Browser Extensions
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
* [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)

18
chrome/app/app.js Normal file
View file

@ -0,0 +1,18 @@
import Ember from 'ember';
import Resolver from './resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
let App;
Ember.MODEL_FACTORY_INJECTIONS = true;
App = Ember.Application.extend({
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver
});
loadInitializers(App, config.modulePrefix);
export default App;

30
chrome/app/index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Huegasm</title>
<meta name="description" content="Huegasm is a free web application for managing and synchronizing your Philips Hue lights with the beat of your music.">
<meta name="keywords" content="huegasm,hue,philips hue,lights,ambience,music player">
<meta name="author" content="Egor Philippov">
<meta name="viewport" content="width=device-width, initial-scale=1"> {{content-for 'head'}}
<link href='//fonts.googleapis.com/css?family=Slabo+27px|Open+Sans' rel='stylesheet' type='text/css'>
<link rel="stylesheet" integrity="" href="{{rootURL}}assets/vendor.css">
<link rel="stylesheet" integrity="" href="{{rootURL}}assets/huegasm.css"> {{content-for 'head-footer'}}
<script src="https://connect.soundcloud.com/sdk/sdk-3.1.2.js"></script>
</head>
<body>
{{content-for 'body'}}
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/huegasm.js"></script>
{{content-for 'body-footer'}}
</body>
</html>

View file

@ -0,0 +1,54 @@
import Ember from 'ember';
const {
Controller,
isEmpty,
$
} = Ember;
export default Controller.extend({
dimmerOn: false,
lightsIconsOn: true,
init(){
this._super(...arguments);
let storage = new window.Locally.Store({compress: true}),
dimmerOn = storage.get('huegasm.dimmerOn'),
lightsIconsOn = storage.get('huegasm.lightsIconsOn');
this.set('storage', storage);
if (!isEmpty(dimmerOn) && dimmerOn) {
this.send('toggleDimmer');
}
if (!isEmpty(lightsIconsOn)) {
this.set('lightsIconsOn', lightsIconsOn);
}
},
actions: {
toggleLightsIcons() {
this.toggleProperty('lightsIconsOn');
let lightsIconsOn = this.get('lightsIconsOn');
this.get('storage').set('huegasm.lightsIconsOn', lightsIconsOn);
},
toggleDimmer(){
this.toggleProperty('dimmerOn');
let dimmerOn = this.get('dimmerOn');
if (dimmerOn) {
$('body').addClass('dimmerOn');
$('html').addClass('dimmerOn');
} else {
$('body').removeClass('dimmerOn');
$('html').removeClass('dimmerOn');
}
this.get('storage').set('huegasm.dimmerOn', dimmerOn);
}
}
});

View file

@ -0,0 +1,3 @@
{{huegasm-app toggleLightsIcons="toggleLightsIcons" toggleDimmer="toggleDimmer" dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn storage=storage}}
{{huegasm-footer action="toggleDimmer" dimmerOn=dimmerOn storage=storage}}

View file

@ -0,0 +1,152 @@
import Ember from 'ember';
const {
Component,
observer,
computed,
on,
isNone,
run: { later },
$
} = Ember;
export default Component.extend({
elementId: 'bridge-finder',
classNames: ['container'],
bridgeIp: null,
trial: false,
bridgeUsername: null,
bridgeFindStatus: null,
bridgeFindSuccess: computed.equal('bridgeFindStatus', 'success'),
bridgeFindMultiple: computed.equal('bridgeFindStatus', 'multiple'),
bridgeFindFail: computed.equal('bridgeFindStatus', 'fail'),
bridgeUsernamePingMaxTime: 30000, // 30 seconds
bridgeUsernamePingIntervalTime: 1500,
bridgeUserNamePingIntervalProgress: 0,
bridgePingIntervalHandle: null,
bridgeAuthenticateReachedStatus: null,
manualBridgeIp: null,
manualBridgeIpNotFound: false,
multipleBridgeIps: [],
isAuthenticating: computed.notEmpty('bridgePingIntervalHandle'),
// try to authenticate against the bridge here
onBridgeIpChange: on('init', observer('bridgeIp', function () {
if (!this.get('trial') && !this.get('isAuthenticating')) {
this.setProperties({
bridgePingIntervalHandle: setInterval(this.pingBridgeUser.bind(this), this.get('bridgeUsernamePingIntervalTime')),
bridgeUserNamePingIntervalProgress: 0
});
}
})),
didInsertElement() {
$(document).keypress((event) => {
if (!isNone(this.get('manualBridgeIp')) && event.which === 13) {
this.send('findBridgeByIp');
}
});
},
// find the bridge ip here
init() {
this._super(...arguments);
if (this.get('bridgeIp') === null) {
$.ajax('https://www.meethue.com/api/nupnp', {
timeout: 30000
})
.done((result, status) => {
let bridgeFindStatus = 'fail';
if (status === 'success' && result.length === 1) {
this.set('bridgeIp', result[0].internalipaddress);
this.get('storage').set('huegasm.bridgeIp', result[0].internalipaddress);
bridgeFindStatus = 'success';
} else if (result.length > 1) {
let multipleBridgeIps = this.get('multipleBridgeIps');
result.forEach(function (item) {
multipleBridgeIps.pushObject(item.internalipaddress);
});
bridgeFindStatus = 'multiple';
} else {
bridgeFindStatus = 'fail';
}
this.set('bridgeFindStatus', bridgeFindStatus);
})
.fail(() => {
this.set('bridgeFindStatus', 'fail');
});
}
},
pingBridgeUser() {
let bridgeIp = this.get('bridgeIp'),
bridgeUserNamePingIntervalProgress = this.get('bridgeUserNamePingIntervalProgress'),
bridgeUsernamePingMaxTime = this.get('bridgeUsernamePingMaxTime');
if (bridgeIp !== null && bridgeUserNamePingIntervalProgress < 100) {
$.ajax('http://' + bridgeIp + '/api', {
data: JSON.stringify({ "devicetype": "huegasm" }),
contentType: 'application/json',
type: 'POST'
}).done((result, status) => {
if (!this.isDestroyed) {
this.set('bridgeAuthenticateReachedStatus', status);
if (status === 'success' && !result[0].error) {
this.clearBridgePingIntervalHandle();
this.get('storage').set('huegasm.bridgeUsername', result[0].success.username);
this.set('bridgeUsername', result[0].success.username);
}
}
});
this.incrementProperty('bridgeUserNamePingIntervalProgress', this.get('bridgeUsernamePingIntervalTime') / bridgeUsernamePingMaxTime * 100);
} else {
this.clearBridgePingIntervalHandle();
}
},
clearBridgePingIntervalHandle() {
clearInterval(this.get('bridgePingIntervalHandle'));
this.set('bridgePingIntervalHandle', null);
},
actions: {
retry() {
this.onBridgeIpChange();
},
chooseBridge(bridge) {
this.set('bridgeIp', bridge);
this.get('storage').set('huegasm.bridgeIp', bridge);
},
findBridgeByIp() {
let manualBridgeIp = this.get('manualBridgeIp');
if (manualBridgeIp.toLowerCase() === 'trial' || manualBridgeIp.toLowerCase() === 'offline') {
this.setProperties({
trial: true,
bridgeIp: 'trial',
bridgeUsername: 'trial'
});
} else {
$.ajax('http://' + manualBridgeIp + '/api', {
data: JSON.stringify({ "devicetype": "huegasm" }),
contentType: 'application/json',
type: 'POST'
}).fail(() => {
this.set('manualBridgeIpNotFound', true);
later(this, function () {
this.set('manualBridgeIpNotFound', false);
}, 5000);
}).then(() => {
this.set('bridgeIp', manualBridgeIp);
});
}
}
},
});

View file

@ -0,0 +1,49 @@
<div class="title"><img src="assets/images/logo.png" alt="Huegasm"></div>
{{#unless bridgeUsername}}
{{#if bridgeIp}}
<img src="assets/images/pressButtonBridge.png" id="press-bridge-button-img"> {{paper-progress-linear warn=true value=bridgeUserNamePingIntervalProgress}}
{{#if isAuthenticating}}
<p>
Your bridge IP is <b>{{bridgeIp}}</b>
<br> Press the button on your bridge to authenticate this application.
</p>
{{else}}
<p>You failed to press the button in time. <a class="no-text-decoration" href="#" {{action 'retry'}}>RETRY</a></p>
{{/if}}
{{else}}
{{#unless bridgeFindStatus}}
{{paper-progress-circular diameter=100}}
<p>Trying to find your bridge's IP.</p>
{{/unless}}
{{#if bridgeFindMultiple}}
<p>Huegasm found multiple hue bridges. <br> Please select the one you want to use for this application.</p>
<div id="bridge-button-group">
{{#each multipleBridgeIps as |bridge|}}
{{paper-radio value=bridge label=bridge onChange=(action "chooseBridge")}}
{{/each}}
</div>
{{else}}
{{#if bridgeFindFail}}
<p>A hue bridge could not be automatically found on your network. <br> Enter one manually? <br><br> ( or type <b>offline</b> to look around )
</p>
<span id="bridge-input">
{{paper-input label="Hue bridge IP address" value=manualBridgeIp onChange=(action (mut manualBridgeIp))}}
<div>
{{paper-button onClick=(action "findBridgeByIp") raised=true primary=true label="Find"}}
</div>
</span>
{{#if manualBridgeIpNotFound}}
<p class="bg-danger">
Could not find a bridge with that IP address.
</p>
{{/if}}
{{/if}}
{{/if}}
{{/if}}
{{/unless}}

View file

@ -0,0 +1,245 @@
import Ember from 'ember';
const {
A,
Component,
computed,
isEmpty,
isNone,
run: { later, scheduleOnce },
inject,
$
} = Ember;
export default Component.extend({
classNames: ['container-fluid'],
elementId: 'hue-controls',
lightsData: null,
activeLights: A(),
tabList: ["Lights", "Music"],
selectedTab: 1,
pauseLightUpdates: false,
displayFailure: true,
notify: inject.service(),
dimmerOnClass: computed('dimmerOn', function () {
return this.get('dimmerOn') ? 'dimmerOn md-menu-origin' : 'md-menu-origin';
}),
ready: computed('lightsData', 'trial', function () {
return this.get('trial') || !isNone(this.get('lightsData'));
}),
apiURL: computed('bridgeIp', 'bridgeUsername', function () {
return 'http://' + this.get('bridgeIp') + '/api/' + this.get('bridgeUsername');
}),
tabData: computed('tabList', 'selectedTab', function () {
let tabData = [], selectedTab = this.get('selectedTab');
this.get('tabList').forEach(function (tab, i) {
let selected = false;
if (i === selectedTab) {
selected = true;
}
tabData.push({ "name": tab, "selected": selected });
});
return tabData;
}),
didInsertElement() {
// here's a weird way to automatically initialize bootstrap tooltips
let observer = new MutationObserver(function (mutations) {
let haveTooltip = !mutations.every(function (mutation) {
return isEmpty(mutation.addedNodes) || isNone(mutation.addedNodes[0].classList) || mutation.addedNodes[0].classList.contains('tooltip');
});
if (haveTooltip) {
scheduleOnce('afterRender', function () {
$('.bootstrap-tooltip').tooltip();
});
}
});
observer.observe($('#hue-controls')[0], { childList: true, subtree: true });
},
init() {
this._super(...arguments);
if (!this.get('trial')) {
this.updateLightData();
setInterval(this.updateLightData.bind(this), 2000);
}
if (!isNone(this.get('storage').get('huegasm.selectedTab'))) {
this.set('selectedTab', this.get('storage').get('huegasm.selectedTab'));
}
},
updateLightData() {
let fail = () => {
if (isNone(this.get('lightsData'))) {
this.send('clearBridge');
} else if (this.get('displayFailure')) {
this.get('notify').warning({ html: '<div class="alert alert-warning" role="alert">Error retrieving data from your lights. Yikes.</div>' });
this.set('displayFailure', false);
later(this, function () {
this.set('displayFailure', true);
}, 30000);
}
};
if (!this.get('pauseLightUpdates')) {
$.get(this.get('apiURL') + '/lights', (result, status) => {
if (!isNone(result[0]) && !isNone(result[0].error)) {
fail();
} else if (status === 'success' && JSON.stringify(this.get('lightsData')) !== JSON.stringify(result)) {
this.set('lightsData', result);
}
}).fail(fail);
}
},
actions: {
changeTab(tabName) {
let index = this.get('tabList').indexOf(tabName);
this.set('selectedTab', index);
this.get('storage').set('huegasm.selectedTab', index);
},
clearBridge() {
let storage = this.get('storage');
storage.remove('huegasm.bridgeUsername');
storage.remove('huegasm.bridgeIp');
location.reload();
},
toggleDimmer() {
this.sendAction('toggleDimmer');
},
toggleLightsIcons() {
this.sendAction('toggleLightsIcons');
},
clearAllSettings() {
this.get('storage').clear();
location.reload();
},
startIntro() {
let intro = introJs(),
playerBottom = $('#player-bottom');
if (this.get('dimmerOn')) {
this.send('toggleDimmer');
}
intro.setOptions({
steps: [
{
intro: 'Welcome! This short tutorial will introduce you to Huegasm.'
},
{
element: '#music-tab',
intro: 'This is the music player. You\'ll use this to play music and synchronize it with your active lights.<br><br>' +
'<i><b>TIP</b>: Control which lights are active through the <b>Lights</b> tab.</i>'
},
{
element: '#playlist',
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: $('#playlist md-menu')[0],
intro: '<img src="/assets/images/soundcloudUrl.png" id="soundcloud-tutorial">You can add songs from SoundCloud by copy and pasting the URL shown here'
},
{
element: '#player-area',
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: '#beat-option-row',
intro: 'These are the settings for the music tab:<br>' +
'<b>Sensitivity</b> - The sensitivity of the beat detector ( more sensitivity results in more registered beats )<br>' +
'<b>Hue Range</b> - The hue range that the lights may change to on beat.<br>' +
'<b>Brightness Range</b> - The minimum ( off-beat ) and maximum ( on-beat ) brightness of the lights.<br>' +
'<b>Flashing Transitions</b> - Quickly flash the lights on beat<br>' +
'<b>Colorloop</b> - Slowly cycle the lights through all the colors while the music is playing<br>' +
'<i><b>TIP</b>: Your sensitivity 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: '#beat-container',
intro: 'An interactive speaker that will bump when a beat is registered. <br><br>' +
'<i><b>TIP</b>: Click on the center of the speaker to simulate a beat.</i>',
position: 'top'
},
{
element: '#lights-tab',
intro: 'This is the lights tab. Here you\'ll be able to change various light properties:<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>' +
'<b>Colorloop</b> - Selected lights will slowly cycle through all the colors<br>'
},
{
element: '#active-lights',
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: $('#navigation .ember-basic-dropdown-trigger')[0],
intro: 'A few miscellaneous settings can be found here.<br><br>' +
'<b>WARNING</b>: clearing application settings will restore the application to its original state. This will even delete your playlist and any saved song beat preferences.'
},
{
intro: 'And that\'s it...Hope you enjoy the application. ;)'
}
]
});
intro.onexit(() => {
$('body').velocity('scroll', { duration: 200 });
});
intro.onchange((element) => {
if (element.id === '' || element.id === 'music-tab' || element.id === 'playlist' || element.id === 'player-area' || element.id === 'beat-option-row' || element.id === 'beat-option-button-group' || element.id === 'beat-container' || element.id === 'using-mic-audio-tooltip' || element.nodeName === 'MD-MENU') {
$('.navigation-item').eq(1).click();
} else {
$('.navigation-item').eq(0).click();
}
if (element.id === 'music-tab' || element.id === 'playlist' || element.id === 'player-area') {
playerBottom.hide();
} else if (element.id === 'beat-option-row' || element.id === 'beat-option-button-group' || element.id === 'beat-container') {
playerBottom.show();
} else if (element.id === 'dimmer') {
$(document).click();
}
});
// skip hidden/missing elements
intro.onafterchange((element) => {
let elem = $(element);
if (elem.html() === '<!---->') {
$('.introjs-nextbutton').click();
}
if (element.id === '') {
later(this, () => {
$('body').velocity('scroll');
}, 500);
} else {
later(this, () => {
$('.introjs-tooltip').velocity('scroll', { offset: -100 });
}, 500);
}
}).start();
}
}
});

View file

@ -0,0 +1,49 @@
{{#if ready}}
<div id="navigation" class="row">
<div class="col-sm-10 col-sm-offset-1 col-xs-12">
{{#each tabData as |tab|}}
<span class="navigation-item pointer text-uppercase {{if tab.selected "active"}}" {{action "changeTab" tab.name}}>{{tab.name}}</span>
{{/each}}
{{#paper-menu as |menu|}}
{{#menu.trigger}}
{{#paper-button iconButton=true}}
{{paper-icon "settings-icon" class=dimmerOnClass size=28}}
{{/paper-button}}
{{/menu.trigger}}
{{#menu.content width=3 as |content|}}
{{#content.menu-item onClick="toggleDimmer"}}
{{paper-icon "highlight" class=dimmerOnClass}} Dark Mode: <strong>{{if dimmerOn "On" "Off"}}</strong>
{{/content.menu-item}}
{{#content.menu-item onClick="toggleLightsIcons"}}
{{paper-icon "lightbulb outline" class=dimmerOnClass}} Active Lights: <strong>{{if lightsIconsOn "Icons" "Text"}}</strong>
{{/content.menu-item}}
{{#content.menu-item onClick="clearBridge"}}
{{paper-icon "compare arrows" class=dimmerOnClass}} Switch bridge
{{/content.menu-item}}
{{#content.menu-item onClick="startIntro"}}
{{paper-icon "cached" class=dimmerOnClass}} Restart tutorial
{{/content.menu-item}}
{{#content.menu-item onClick="clearAllSettings"}}
{{paper-icon "settings backup restore" class=dimmerOnClass}} Reset settings
{{/content.menu-item}}
{{/menu.content}}
{{/paper-menu}}
</div>
</div>
{{light-group lightsData=lightsData activeLights=activeLights syncLight=syncLight apiURL=apiURL dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn storage=storage}}
<div id="huegasm-content" class="row">
{{lights-tab active=(eq selectedTab 0) apiURL=apiURL lightsData=lightsData activeLights=activeLights syncLight=syncLight trial=trial colorLoopOn=colorLoopOn dimmerOn=dimmerOn playing=playing pauseLightUpdates=pauseLightUpdates}}
{{music-tab active=(eq selectedTab 1) apiURL=apiURL lightsData=lightsData activeLights=activeLights pauseLightUpdates=pauseLightUpdates dimmerOn=dimmerOn storage=storage colorLoopOn=colorLoopOn playing=playing action="startIntro"}}
</div>
{{else}}
{{paper-progress-circular diameter=100}}
{{/if}}
{{ember-notify messageStyle='bootstrap' closeAfter=5000}}

View file

@ -0,0 +1,37 @@
import Ember from 'ember';
const {
Component,
isEmpty,
$
} = Ember;
export default Component.extend({
bridgeIp: null,
bridgeUsername: null,
trial: false,
elementId: 'huegasm',
init() {
this._super(...arguments);
let storage = this.get('storage');
if (!isEmpty(storage.get('huegasm.bridgeIp')) && !isEmpty(storage.get('huegasm.bridgeUsername'))) {
this.setProperties({
bridgeIp: storage.get('huegasm.bridgeIp'),
bridgeUsername: storage.get('huegasm.bridgeUsername')
});
}
},
actions: {
toggleDimmer() {
this.sendAction('toggleDimmer');
},
toggleLightsIcons() {
this.sendAction('toggleLightsIcons');
}
}
});

View file

@ -0,0 +1,6 @@
{{#if bridgeUsername}}
{{hue-controls bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn
storage=storage toggleDimmer="toggleDimmer" toggleLightsIcons="toggleLightsIcons"}}
{{else}}
{{bridge-finder bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial storage=storage}}
{{/if}}

View file

@ -0,0 +1,21 @@
import Ember from 'ember';
const {
Component,
computed
} = Ember;
export default Component.extend({
tagName: 'footer',
classNames: ['footer'],
year: computed(function(){
return new Date().getFullYear();
}),
actions: {
toggleDimmer(){
this.sendAction();
}
}
});

View file

@ -0,0 +1,13 @@
<div class="logo" {{action "toggleDimmer"}}></div>
<div class="footer-text">
© {{year}}
<a href="http://www.egorphilippov.me" target="_blank" rel="noopener noreferrer">
Egor Philippov
</a>
</div>
<a href="https://play.google.com/store/apps/details?id=com.hoboman313.huegasm" target="_blank" rel="noopener noreferrer">
<img src="assets/images/google-play-badge.png" alt="Get it on the Google Play Store">
</a>

View file

@ -0,0 +1,161 @@
import Ember from 'ember';
const {
A,
Component,
computed,
isEmpty,
isNone,
observer,
$
} = Ember;
export default Component.extend({
elementId: 'active-lights',
classNames: ['light-group'],
isHovering: false,
activeLights: A(),
// list of all the lights in the hue system
lightsList: computed('lightsData', 'activeLights.[]', 'dimmerOn', function(){
let lightsData = this.get('lightsData'),
activeLights = this.get('activeLights'),
dimmerOn = this.get('dimmerOn'),
lightsList = A(),
type,
activeClass;
for (let key in lightsData) {
activeClass = 'light-active';
if (lightsData.hasOwnProperty(key) && lightsData[key].state.reachable) {
switch(lightsData[key].modelid){
case 'LCT001':
type = 'a19';
break;
case 'LCT002':
type = 'br30';
break;
case 'LCT003':
type = 'gu10';
break;
case 'LST001':
type = 'lightstrip';
break;
case 'LLC010':
type = 'lc_iris';
break;
case 'LLC011':
type = 'lc_bloom';
break;
case 'LLC012':
type = 'lc_bloom';
break;
case 'LLC006':
type = 'lc_iris';
break;
case 'LLC007':
type = 'lc_aura';
break;
case 'LLC013':
type = 'storylight';
break;
case 'LWB004':
type ='a19';
break;
case 'LLC020':
type = 'huego';
break;
default:
type = 'a19';
}
if(dimmerOn){
type += 'w';
}
if(!activeLights.includes(key)){
activeClass = 'light-inactive';
}
lightsList.push({type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: activeClass});
}
}
return lightsList;
}),
onActiveLightsChange: observer('activeLights.[]', function(){
this.get('storage').set('huegasm.activeLights', this.get('activeLights'));
}),
init(){
this._super(...arguments);
let lightsData = this.get('lightsData'),
activeLights = this.get('activeLights'),
activeLightsCache = this.get('storage').get('huegasm.activeLights');
if(!isNone(activeLightsCache)){
activeLightsCache.forEach(function(i){
if (!isNone(lightsData) && lightsData.hasOwnProperty(i) && lightsData[i].state.reachable) {
activeLights.pushObject(i);
}
});
} else {
for (let key in lightsData) {
if (lightsData.hasOwnProperty(key) && lightsData[key].state.reachable) {
activeLights.pushObject(key);
}
}
}
},
actions: {
clickLight(id){
let activeLights = this.get('activeLights'),
lightId = activeLights.indexOf(id);
if(lightId !== -1){
activeLights.removeObject(id);
} else {
activeLights.pushObject(id);
this.set('syncLight', id);
}
},
lightStartHover(id){
if(!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)){
let hoveredLight = this.get('lightsList').filter(function(light){
return light.activeClass !== 'unreachable' && light.id === id[0];
});
if(!isEmpty(hoveredLight) && this.get('noHover') !== true){
$.ajax(this.get('apiURL') + '/lights/' + id + '/state', {
data: JSON.stringify({"alert": "lselect"}),
contentType: 'application/json',
type: 'PUT'
});
}
this.set('isHovering', true);
}
},
lightStopHover(id){
if(!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)){
let hoveredLight = this.get('lightsList').filter(function(light){
return light.activeClass !== 'unreachable' && light.id === id[0];
});
if(!isEmpty(hoveredLight) && this.get('noHover') !== true){
$.ajax(this.get('apiURL') + '/lights/' + id + '/state', {
data: JSON.stringify({"alert": "none"}),
contentType: 'application/json',
type: 'PUT'
});
}
this.set('isHovering', false);
}
}
}
});

View file

@ -0,0 +1,11 @@
{{#each lightsList as |light|}}
{{#if lightsIconsOn}}
<div class="{{light.activeClass}} bootstrap-tooltip toggleable-light" data-toggle="tooltip" data-placement="top auto" data-title={{light.name}} {{action "clickLight" light.id}} {{action "lightStartHover" light.id on="mouseEnter"}} {{action "lightStopHover" light.id on="mouseLeave"}}>
<img class="hueLight" width="40" src="assets/images/lights/{{light.type}}.svg">
</div>
{{else}}
<div class="{{light.activeClass}} light-text toggleable-light" {{action "clickLight" light.id}} {{action "lightStartHover" light.id on="mouseEnter"}} {{action "lightStopHover" light.id on="mouseLeave"}}>
<div class="light-text-content">{{light.name}}</div>
</div>
{{/if}}
{{/each}}

View file

@ -0,0 +1,58 @@
import Ember from 'ember';
const {
Component,
$
} = Ember;
export default Component.extend({
elementId: 'color-picker',
rgb: null,
canvas: null,
canvasContext: null,
pressingDown: false,
mouseUp(){
this.set('pressingDown', false);
},
mouseMove(event){
if (this.get('pressingDown')) {
this.mouseDown(event);
}
},
mouseDown(event){
let canvasOffset = $(this.get('canvas')).offset(),
canvasX = Math.floor(event.pageX - canvasOffset.left),
canvasY = Math.floor(event.pageY - canvasOffset.top);
// get current pixel
let imageData = this.get('canvasContext').getImageData(canvasX, canvasY, 1, 1),
pixel = imageData.data;
this.set('pressingDown', true);
if (!(pixel[0] === 0 && pixel[1] === 0 && pixel[2] === 0)) {
this.set('rgb', [pixel[0], pixel[1], pixel[2]]);
}
},
// https://dzone.com/articles/creating-your-own-html5
didInsertElement(){
// handle color changes
let canvas = $('#picker')[0],
canvasContext = canvas.getContext('2d'),
image = new Image();
image.src = 'assets/images/colormap.png';
image.onload = function () {
canvasContext.drawImage(image, 0, 0, image.width, image.height); // draw the image on the canvas
};
this.setProperties({
canvas: canvas,
canvasContext: canvasContext
});
}
});

View file

@ -0,0 +1 @@
<canvas id="picker" width="256" height="256"></canvas>

View file

@ -0,0 +1,348 @@
import Ember from 'ember';
const {
Component,
observer,
computed,
on,
run: { later, once },
$
} = Ember;
export default Component.extend({
classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'],
classNameBindings: ['active::hidden'],
elementId: 'lights-tab',
rgb: [255, 255, 255],
lightsOn: false,
// COLOR LOOP related stuff
colorLoopOn: false,
lightsOnTxt: computed('lightsOn', function () {
return this.get('lightsOn') ? 'On' : 'Off';
}),
colorloopOnTxt: computed('colorLoopOn', function () {
return this.get('colorLoopOn') ? 'On' : 'Off';
}),
// determines the average brightness of the hue system for the brightness slider
lightsBrightness: computed('lightsData', function () {
let lightsData = this.get('lightsData'),
activeLights = this.get('activeLights'),
lightsBrightness = 0;
activeLights.forEach(function (light) {
lightsBrightness += lightsData[light].state.bri;
});
return lightsBrightness / activeLights.length;
}),
brightnessControlDisabled: computed.not('lightsOn'),
onColorLoopOnChange: observer('colorLoopOn', function () {
let lightsData = this.get('lightsData'),
activeLights = this.get('activeLights'),
colorLoopsOn = this.get('colorLoopOn'),
effect = colorLoopsOn ? 'colorloop' : 'none';
let colorLoopsOnSystem = activeLights.some(function (light) {
return lightsData[light].state.effect === 'colorloop';
});
// 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 (colorLoopsOn !== colorLoopsOnSystem) {
activeLights.forEach((light) => {
if (this.get('lightsData')[light].state.effect !== effect) {
$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
data: JSON.stringify({ 'effect': effect }),
contentType: 'application/json',
type: 'PUT'
});
}
});
}
}),
rgbPreview: observer('rgb', function () {
let rgb = this.get('rgb'),
xy = this.rgbToXy(rgb[0], rgb[1], rgb[2]);
this.set('colorLoopOn', false);
this.get('activeLights').forEach((light) => {
$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
data: JSON.stringify({ "xy": xy }),
contentType: 'application/json',
type: 'PUT'
});
});
this.set('colorLoopOn', false);
$('.color').css('background', 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')');
}),
// determines whether the lights are on/off for the lights switch
lightsOnChange: on('init', observer('lightsData.@each.state.on', 'activeLights.[]', function () {
if (!this.get('strobeOn')) {
let lightsData = this.get('lightsData'), lightsOn = this.get('activeLights').some(function (light) {
return lightsData[light].state.on === true;
});
this.set('lightsOn', lightsOn);
}
})),
onLightsOnChange: observer('lightsOn', function () {
let lightsData = this.get('lightsData'),
activeLights = this.get('activeLights'),
lightsOn = this.get('lightsOn');
let lightsOnSystem = activeLights.some(function (light) {
return lightsData[light].state.on === true;
});
// 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) {
activeLights.forEach((light) => {
$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
data: JSON.stringify({ "on": lightsOn }),
contentType: 'application/json',
type: 'PUT'
});
});
}
}),
onBrightnessChanged: observer('lightsBrightness', function () {
once(this, function () {
let lightsData = this.get('lightsData'),
lightsBrightnessSystem = false,
lightsBrightness = this.get('lightsBrightness'),
activeLights = this.get('activeLights');
activeLights.forEach(function (light) {
lightsBrightnessSystem += lightsData[light].state.bri;
});
lightsBrightnessSystem /= activeLights.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) {
activeLights.forEach((light) => {
$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
data: JSON.stringify({ "bri": lightsBrightness }),
contentType: 'application/json',
type: 'PUT'
});
});
}
});
}),
// sync the current light settings to the newly added light
onaActiveLightsChange: observer('syncLight', function () {
let options = {
on: this.get('lightsOn'),
bri: this.get('lightsBrightness'),
effect: this.get('colorLoopOn') ? 'colorloop' : 'none'
}, rgb = this.get('rgb'),
syncLight = this.get('syncLight');
if (rgb[0] !== 255 && rgb[1] !== 255 && rgb[2] !== 255) {
options['xy'] = this.rgbToXy(rgb[0], rgb[1], rgb[2]);
}
options['transitiontime'] = 0;
$.ajax(this.get('apiURL') + '/lights/' + syncLight + '/state', {
data: JSON.stringify(options),
contentType: 'application/json',
type: 'PUT'
});
}),
// **************** STROBE LIGHT START ****************
strobeOn: false,
strobeOnInervalHandle: null,
preStrobeOnLightsDataCache: null,
nextLightIdx: 0,
onStrobeOnChange: observer('strobeOn', function () {
let lightsData = this.get('lightsData'),
strobeOn = this.get('strobeOn');
if (strobeOn) {
this.set('preStrobeOnLightsDataCache', lightsData);
let stobeInitRequestData = { transitiontime: 0 };
for (let key in lightsData) {
if (lightsData.hasOwnProperty(key)) {
if (lightsData[key].state.on) {
stobeInitRequestData.on = false;
}
$.ajax(this.get('apiURL') + '/lights/' + key + '/state', {
data: JSON.stringify(stobeInitRequestData),
contentType: 'application/json',
type: 'PUT'
});
}
}
this.set('strobeOnInervalHandle', setInterval(this.strobeStep.bind(this), 500));
} else { // revert the light system to pre-strobe
let preStrobeOnLightsDataCache = this.get('preStrobeOnLightsDataCache'), updateLight = (lightIndex) => {
$.ajax(this.get('apiURL') + '/lights/' + lightIndex + '/state', {
data: JSON.stringify({
on: preStrobeOnLightsDataCache[lightIndex].state.on,
sat: preStrobeOnLightsDataCache[lightIndex].state.sat
}),
contentType: 'application/json',
type: 'PUT'
});
};
for (let key in lightsData) {
if (lightsData.hasOwnProperty(key)) {
later(this, updateLight, key, 2000);
}
}
later(this, this.onColorLoopOnChange, 2000);
clearInterval(this.get('strobeOnInervalHandle'));
}
this.set('pauseLightUpdates', strobeOn);
}),
strobeStep() {
let nextLightIdx = this.get('nextLightIdx') % this.get('activeLights').length,
nextStrobeLight = this.get('activeLights')[nextLightIdx],
turnOnOptions = { on: true, transitiontime: 0, alert: 'select' };
// random light if in cololoop mode
if (this.get('colorLoopOn')) {
turnOnOptions.hue = Math.floor(Math.random() * 65535);
}
$.ajax(this.get('apiURL') + '/lights/' + nextStrobeLight + '/state', {
data: JSON.stringify(turnOnOptions),
contentType: 'application/json',
type: 'PUT'
});
$.ajax(this.get('apiURL') + '/lights/' + nextStrobeLight + '/state', {
data: JSON.stringify({ 'on': false, 'transitiontime': 0 }),
contentType: 'application/json',
type: 'PUT'
});
this.set('nextLightIdx', ++nextLightIdx);
},
strobeOnTxt: computed('strobeOn', function () {
return this.get('strobeOn') ? 'On' : 'Off';
}),
dimmerOnClass: computed('dimmerOn', function () {
return this.get('dimmerOn') ? 'dimmerOn' : null;
}),
actions: {
toggleDimmer() {
this.sendAction('toggleDimmer');
}
},
// **************** STROBE LIGHT FINISH ****************
// http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
rgbToXy(red, green, blue) {
let X, Y, Z, x, y;
// normalize
red = Number((red / 255));
green = Number((green / 255));
blue = Number((blue / 255));
// gamma correction
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
// RGB to XYZ
X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
x = X / (X + Y + Z);
y = Y / (X + Y + Z);
return [x, y];
},
xyToRgb(x, y) {
let r, g, b, X, Y = 1.0, Z;
X = (Y / y) * x;
Z = (Y / y) * (1 - x - y);
r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
g = X * -0.707196 + Y * 1.655397 + Z * 0.036152;
b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
if (r > b && r > g && r > 1.0) {
// red is too big
g = g / r;
b = b / r;
r = 1.0;
} else if (g > b && g > r && g > 1.0) {
// green is too big
r = r / g;
b = b / g;
g = 1.0;
} else if (b > r && b > g && b > 1.0) {
// blue is too big
r = r / b;
g = g / b;
b = 1.0;
}
r = (r <= 0.0031308) ? 12.92 * r : 1.055 * Math.pow(r, (1.0 / 2.4)) - 0.055;
g = (g <= 0.0031308) ? 12.92 * g : 1.055 * Math.pow(g, (1.0 / 2.4)) - 0.055;
b = (b <= 0.0031308) ? 12.92 * b : 1.055 * Math.pow(b, (1.0 / 2.4)) - 0.055;
if (r > b && r > g) {
// red is biggest
if (r > 1.0) {
g = g / r;
b = b / r;
r = 1.0;
}
} else if (g > b && g > r) {
// green is biggest
if (g > 1.0) {
r = r / g;
b = b / g;
g = 1.0;
}
} else if (b > r && b > g) {
// blue is biggest
if (b > 1.0) {
r = r / b;
g = g / b;
b = 1.0;
}
}
r = r * 255;
g = g * 255;
b = b * 255;
return [r, g, b];
}
});

View file

@ -0,0 +1,43 @@
{{#paper-list}}
{{#paper-item}}
{{paper-icon "power-settings-new" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip lights-control-tooltip" data-title="Turn the selected lights on/off">Power</p>
{{paper-switch value=lightsOn onChange=(action (mut lightsOn)) disabled=(or trial playing) skipProxy=trial label=lightsOnTxt}}
{{/paper-item}}
{{#paper-item}}
{{paper-icon "brightness-4" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="top auto" class="layout flex-60 bootstrap-tooltip lights-control-tooltip" data-title="The brightness level of the selected lights">Brightness</p>
{{paper-slider class="flex" step=10 min=1 max=254 value=lightsBrightness onChange=(action (mut lightsBrightness)) disabled=brightnessControlDisabled}}
{{/paper-item}}
{{#paper-item elementId="color-row" }}
{{paper-icon "color-lens" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip lights-control-tooltip" data-title="The color of the selected lights">Color</p>
{{#paper-menu offset="0 -50" as |menu|}}
{{#menu.trigger}}
{{#paper-button iconButton=false}}
{{paper-button raised=true class="color" disabled=(or trial playing)}}
{{/paper-button}}
{{/menu.trigger}}
{{#menu.content class="color-content" width=0 as |content|}}
{{#content.menu-item}}
{{lights-tab/color-picker lightsData=lightsData activeLights=activeLights rgb=rgb}}
{{/content.menu-item}}
{{/menu.content}}
{{/paper-menu}}
{{/paper-item}}
{{#paper-item}}
{{paper-icon "flare" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip lights-control-tooltip" data-title="Selected lights will flash in sequential order">Strobe</p>
{{paper-switch value=strobeOn onChange=(action (mut strobeOn)) disabled=(or trial playing) skipProxy=trial label=strobeOnTxt}}
{{/paper-item}}
{{#paper-item}}
{{paper-icon "color-lens" class=dimmerOnClass}} {{paper-icon "loop" id="loop-addition" class=dimmerOnClass}}
<p data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip lights-control-tooltip" data-title="Selected lights will slowly cycle through all the colors">Colorloop</p>
{{paper-switch value=colorLoopOn onChange=(action (mut colorLoopOn)) disabled=(or trial playing) skipProxy=trial label=colorloopOnTxt}}
{{/paper-item}}
{{/paper-list}}

View file

@ -0,0 +1,46 @@
import Ember from 'ember';
const {
Component,
observer,
computed,
isEmpty,
isNone,
run: { later },
$
} = Ember;
export default Component.extend({
url: null,
onIsShowingModalChange: observer('isShowingModal', function(){
if(this.get('isShowingModal')){
this.set('url', null);
later(function(){
$('md-input-container input').focus();
}, 500);
}
}),
saveDisabled: computed('url', function(){
return isNone(this.get('url')) || isEmpty(this.get('url').trim());
}),
didInsertElement: function() {
$(document).keypress((event)=>{
if(!this.get('saveDisabled') && event.which === 13) {
this.send('add');
}
});
},
actions: {
close () {
this.sendAction();
},
add (){
this.sendAction('action', this.get('url'));
}
}
});

View file

@ -0,0 +1,14 @@
{{#if isShowingModal}}
{{#modal-dialog close="close" alignment="center" translucentOverlay=true attachment="center" targetAttachment="center"}}
<p>Enter a <a href="https://soundcloud.com" target="_blank" rel="noopener noreferrer">SoundCloud</a> track or playlist/set URL</p>
<p>( ex. https://soundcloud.com/mrsuicidesheep/tracks )</p>
{{paper-input label="SoundCloud URL" icon="search" value=url onChange=(action (mut url))}}
<div>
{{paper-button onClick=(action "close") label="Close"}}
{{paper-button class="pull-right" onClick=(action "add") disabled=saveDisabled primary=true label="Add Music"}}
</div>
{{/modal-dialog}}
{{/if}}

View file

@ -0,0 +1,737 @@
import Ember from 'ember';
import helperMixin from './mixins/helpers';
import visualizerMixin from './mixins/visualizer';
const {
Component,
observer,
isEmpty,
isNone,
$,
run: { later, next }
} = Ember;
export default Component.extend(helperMixin, visualizerMixin, {
updatePageTitle: observer('playQueuePointer', function () {
let title = 'Huegasm',
playQueuePointer = this.get('playQueuePointer'),
playQueue = this.get('playQueue');
if (playQueuePointer !== -1) {
let song = playQueue[playQueuePointer];
if (song.title) {
title = song.title;
if (song.artist) {
title += (' - ' + song.artist);
}
} else {
title = song.fileName;
}
title += '- Huegasm';
}
document.title = title;
}),
changePlayerControl(name, value, saveBeatPrefs) {
this.set(name, value);
if (name === 'threshold') {
this.get('kick').set({ threshold: value });
}
if (saveBeatPrefs && this.get('playQueuePointer') !== -1) {
this.saveSongBeatPreferences();
}
this.get('storage').set('huegasm.' + name, value);
},
saveSongBeatPreferences() {
let song = this.get('playQueue')[this.get('playQueuePointer')];
if (song) {
let title = isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title,
songBeatPreferences = this.get('songBeatPreferences');
songBeatPreferences[title] = { threshold: this.get('threshold') };
this.set('usingBeatPreferences', true);
this.get('storage').set('huegasm.songBeatPreferences', songBeatPreferences);
}
},
loadSongBeatPreferences() {
let song = this.get('playQueue')[this.get('playQueuePointer')],
title = isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title,
songBeatPreferences = this.get('songBeatPreferences'),
preference = songBeatPreferences[title],
oldBeatPrefCache = this.get('oldBeatPrefCache'),
newOldBeatPrefCache = null;
if (!isNone(preference)) { // load existing beat prefs
newOldBeatPrefCache = { threshold: this.get('threshold') };
this.changePlayerControl('threshold', preference.threshold);
this.set('usingBeatPreferences', true);
} else if (!isNone(oldBeatPrefCache)) { // revert to using beat prefs before the remembered song
this.changePlayerControl('threshold', oldBeatPrefCache.threshold);
this.set('usingBeatPreferences', false);
}
this.set('oldBeatPrefCache', newOldBeatPrefCache);
},
clearCurrentAudio(resetPointer) {
let dancer = this.get('dancer');
if (dancer.audio.pause) {
dancer.pause();
}
if (resetPointer) {
this.set('playQueuePointer', -1);
}
this.setProperties({
timeElapsed: 0,
timeTotal: 0,
playing: false
});
},
dragOver() {
let dragLeaveTimeoutHandle = this.get('dragLeaveTimeoutHandle');
this.set('dragging', true);
if (dragLeaveTimeoutHandle) {
clearTimeout(dragLeaveTimeoutHandle);
}
},
dragLeave() {
// need to delay the dragLeave notification to avoid flickering ( hovering over some page elements causes this event to be sent )
this.set('dragLeaveTimeoutHandle', setTimeout(() => { this.set('dragging', false); }, 500));
},
simulateKick(/*mag, ratioKickMag*/) {
let activeLights = this.get('activeLights'),
lightsData = this.get('lightsData'),
color = null,
transitiontime = this.get('flashingTransitions'),
stimulateLight = (light, brightness, hue) => {
let options = { 'bri': brightness };
if (transitiontime) {
options['transitiontime'] = 0;
} else {
options['transitiontime'] = 1;
}
if (!isNone(hue)) {
options.hue = hue;
}
if (lightsData[light].state.on === false) {
options.on = true;
}
$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
data: JSON.stringify(options),
contentType: 'application/json',
type: 'PUT'
});
},
timeToBriOff = 100;
if (activeLights.length > 0) {
let lastLightBopIndex = this.get('lastLightBopIndex'),
lightBopIndex,
brightnessRange = this.get('brightnessRange'),
light;
lightBopIndex = Math.floor(Math.random() * activeLights.length);
// let's try not to select the same light twice in a row
if (activeLights.length > 1) {
while (lightBopIndex === lastLightBopIndex) {
lightBopIndex = Math.floor(Math.random() * activeLights.length);
}
}
light = activeLights[lightBopIndex];
this.set('lastLightBopIndex', lightBopIndex);
if (!this.get('colorloopMode')) {
let hueRange = this.get('hueRange');
color = Math.floor(Math.random() * (hueRange[1] - hueRange[0] + 1) + hueRange[0]);
}
if (transitiontime) {
timeToBriOff = 80;
}
stimulateLight(light, brightnessRange[1], color);
later(this, stimulateLight, light, brightnessRange[0], timeToBriOff);
}
this.set('paused', true);
later(this, function () {
this.set('paused', false);
}, 150);
//work the music beat area - simulate the speaker vibration by running a CSS animation on it
$('#beat-speaker-center-outer').velocity({ blur: 3 }, 100).velocity({ blur: 0 }, 100);
$('#beat-speaker-center-inner').velocity({ scale: 1.05 }, 100).velocity({ scale: 1 }, 100);
},
init() {
this._super(...arguments);
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
let dancer = new Dancer(),
storage = this.get('storage'),
kick = dancer.createKick({
threshold: this.get('threshold'),
onKick: (mag, ratioKickMag) => {
if (this.get('paused') === false) {
this.simulateKick(mag, ratioKickMag);
}
}
});
kick.on();
this.setProperties({
dancer: dancer,
kick: kick
});
['volume', 'shuffle', 'repeat', 'volumeMuted', 'threshold', 'playerBottomDisplayed', 'songBeatPreferences', 'firstVisit', 'currentVisName', 'playQueue', 'playQueuePointer', 'flashingTransitions', 'colorloopMode', 'hueRange', 'brightnessRange'].forEach((item) => {
if (!isNone(storage.get('huegasm.' + item))) {
let itemVal = storage.get('huegasm.' + item);
if (isNone(this.actions[item + 'Changed'])) {
this.set(item, itemVal);
} else {
this.send(item + 'Changed', itemVal);
}
}
});
this.set('oldPlayQueueLength', this.get('playQueue.length'));
SC.initialize({
client_id: this.get('SC_CLIENT_ID')
});
},
didInsertElement() {
this._super();
let self = this;
// file input code
$('#file-input').on('change', function () {
let files = this.files;
self.send('handleNewFiles', files);
this.value = null; // reset in case upload the second file again
});
$(document).on('click', '.alert', (event) => {
$(event.target).addClass('removed');
});
// prevent space/text selection when the user repeatedly clicks on the center
$('#beat-container').on('mousedown', '#beat-speaker-center-inner', function (event) {
event.preventDefault();
});
$(document).keypress((event) => {
if (event.which === 32 && event.target.type !== 'text') {
this.send('play');
}
});
this.$().on('drop', '#play-list-area', (event) => {
this.send('dropFiles', event.dataTransfer.files);
});
// control the volume by scrolling up/down
$('#player-area').on('mousewheel', (event) => {
if (this.get('playQueueNotEmpty')) {
let scrollSize = 5;
if (event.deltaY < 0) {
scrollSize *= -1;
}
let newVolume = this.get('volume') + scrollSize;
this.send('volumeChanged', newVolume < 0 ? 0 : newVolume);
event.preventDefault();
}
});
// demo tracks
if (this.get('firstVisit')) {
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/candyland-speechless-feat-rkcb');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/dillistone/dillistone-lili-n-rude');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/vallis-alps-young-feki-remix');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/andrew-luce-when-to-love-you-feat-chelsea-cutler');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/ahh-ooh-carefree-with-me');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/crywolf-slow-burn');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/clozee-red-forest');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/elo-method-subranger-solace');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/90-pounds-of-pete-waited-too-long-feat-devon-baldwin');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/draper-eyes-open');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/itspapaya/sunny');
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/stonesthrow/nxworries-anderson-paak-knxwledge-suede');
this.get('storage').set('huegasm.firstVisit', false);
this.sendAction();
}
if (!this.get('playerBottomDisplayed')) {
$('#player-bottom').hide();
}
},
actions: {
clearPlaylist() {
this.get('playQueue').clear();
},
setVisName(name) {
this.set('currentVisName', name);
},
hideTooltip() {
$('.bootstrap-tooltip').tooltip('hide');
},
gotoSCURL(URL) {
// need to pause the music since soundcloud is going to start playing this song anyways
if (this.get('playing')) {
this.send('play');
}
this.send('gotoURL', URL);
},
gotoURL(URL) {
$('.tooltip').remove();
window.open(URL, '_blank');
},
handleNewSoundCloudURL(URL) {
if (URL) {
SC.resolve(URL).then((resultObj) => {
let processResult = (result) => {
if (result.kind === 'user') {
this.get('notify').alert({ html: this.get('scUserNotSupportedHtml') });
} else if (result.kind === 'track') {
if (result.streamable === true) {
let picture = null;
if (result.artwork_url) {
picture = result.artwork_url.replace('large', 't67x67');
} else if (result.user.avatar_url) {
picture = result.user.avatar_url;
}
$.get(picture)
.done(() => {
this.get('playQueue').pushObject({ url: result.stream_url + '?client_id=' + this.get('SC_CLIENT_ID'), fileName: result.title + ' - ' + result.user.username, artist: result.user.username, scUrl: result.permalink_url, title: result.title, picture: picture });
}).fail(() => { // no picture
this.get('playQueue').pushObject({ url: result.stream_url + '?client_id=' + this.get('SC_CLIENT_ID'), fileName: result.title + ' - ' + result.user.username, artist: result.user.username, scUrl: result.permalink_url, title: result.title });
});
} else {
failedSongs.push(result.title);
}
} else if (result.kind === 'playlist') {
if (result.streamable === true) {
result.tracks.forEach(processResult);
} else {
failedSongs.push(result.title);
}
}
},
failedSongs = [];
if (resultObj instanceof Array) {
resultObj.forEach(processResult);
} else {
processResult(resultObj);
}
if (failedSongs.length > 0) {
this.get('notify').alert({ html: this.get('notStreamableHtml')(failedSongs) });
}
if (this.get('playQueuePointer') === -1) {
if (this.get('firstVisit')) {
this.send('goToSong', 0);
} else {
this.send('next');
}
}
}, () => {
this.get('notify').alert({ html: this.get('urlNotFoundHtml')(URL) });
});
}
this.set('isShowingAddSoundCloudModal', false);
},
toggleIsShowingAddSoundCloudModal() {
this.toggleProperty('isShowingAddSoundCloudModal');
},
slideTogglePlayerBottom() {
let elem = this.$('#player-bottom');
elem.velocity(elem.is(':visible') ? 'slideUp' : 'slideDown', { duration: 300 });
this.changePlayerControl('playerBottomDisplayed', !this.get('playerBottomDisplayed'));
},
goToSong(index, playSong, scrollToSong) {
let dancer = this.get('dancer'), playQueue = this.get('playQueue');
if (dancer.audio) {
this.clearCurrentAudio(true);
}
if (!isNone(playQueue[index])) {
let audio = new Audio();
audio.src = this.get('playQueue')[index].url;
audio.crossOrigin = "anonymous";
audio.oncanplay = () => {
this.set('timeTotal', Math.floor(audio.duration));
this.set('soundCloudFuckUps', 0);
};
audio.onerror = (event) => {
let playQueuePointer = this.get('playQueuePointer'),
song = this.get('playQueue')[playQueuePointer];
if (this.get('soundCloudFuckUps') >= this.get('maxSoundCloudFuckUps')) {
this.get('notify').alert({ html: this.get('tooManySoundCloudFuckUps') });
this.send('play');
this.set('soundCloudFuckUps', 0);
} else {
if (song.local) {
this.send('removeAudio', playQueuePointer);
} else {
this.send('next', true);
}
if (event.target.error.code === 2) {
this.get('notify').alert({ html: this.get('failedToDecodeFileHtml')(song.fileName) });
} else {
this.get('notify').alert({ html: this.get('failedToPlayFileHtml')(song.fileName) });
}
this.set('usingBeatPreferences', false);
this.incrementProperty('soundCloudFuckUps');
}
};
audio.ontimeupdate = () => {
this.set('timeElapsed', Math.floor(audio.currentTime));
};
audio.onended = () => {
this.send('next');
};
dancer.load(audio, 1);
this.set('playQueuePointer', index);
this.loadSongBeatPreferences();
if (playSong) {
this.send('play');
}
if (scrollToSong) {
// this is just a bad workaround to make sure that the track has been rendered to the playlist
next(this, () => {
$('.track' + index).velocity('scroll', { container: $('#play-list-area'), duration: 200 });
});
}
}
},
removeAudio(index) {
this.get('playQueue').removeAt(index);
// need to manually remove the tooltip
$('body .tooltip').remove();
if (index === this.get('playQueuePointer')) {
this.send('goToSong', index, true, true);
}
},
playerAreaPlay() {
if (isEmpty($('#player-controls:hover')) && this.get('playQueuePointer') !== -1) {
this.send('play');
$('#play-notification').velocity({ opacity: 0.8, scale: 1 }, 0).velocity({ opacity: 0, scale: 3 }, 500);
}
},
play(replayPause) {
let dancer = this.get('dancer'),
playQueuePointer = this.get('playQueuePointer'),
playing = this.get('playing'),
lightsData = this.get('lightsData');
if (playQueuePointer !== -1) {
if (playing) {
dancer.pause();
let preMusicLightsDataCache = this.get('preMusicLightsDataCache'),
updateLight = (lightIndex) => {
$.ajax(this.get('apiURL') + '/lights/' + lightIndex + '/state', {
data: JSON.stringify({
'on': preMusicLightsDataCache[lightIndex].state.on,
'hue': preMusicLightsDataCache[lightIndex].state.hue,
'bri': preMusicLightsDataCache[lightIndex].state.bri
}),
contentType: 'application/json',
type: 'PUT'
});
};
for (let key in lightsData) {
if (lightsData.hasOwnProperty(key)) {
later(this, updateLight, key, 1000);
}
}
if (!replayPause) {
this.set('timeElapsed', Math.floor(dancer.getTime()));
}
} else {
let timeTotal = this.get('timeTotal');
if (this.get('volumeMuted')) {
dancer.setVolume(0);
} else {
dancer.setVolume(this.get('volume') / 100);
}
// replay song
if (this.get('timeElapsed') === timeTotal && timeTotal !== 0) {
this.send('next', true);
return;
}
$(window).trigger('resize'); // workaround to redraw the canvas for the vitualizer
this.set('preMusicLightsDataCache', lightsData);
dancer.play();
}
this.set('pauseLightUpdates', !playing);
this.onColorloopModeChange();
this.toggleProperty('playing');
}
},
volumeChanged(value) {
this.changePlayerControl('volume', value);
if (this.get('playing')) {
this.get('dancer').setVolume(value / 100);
}
if (this.get('volume') > 0 && this.get('volumeMuted')) {
this.changePlayerControl('volumeMuted', false);
}
},
next(repeatAll) {
let playQueuePointer = this.get('playQueuePointer'),
playQueue = this.get('playQueue'),
nextSong = (playQueuePointer + 1),
repeat = this.get('repeat'),
shuffle = this.get('shuffle');
if (repeat === 2) { // repeating one song takes precedence over shuffling
if (playQueuePointer === -1 && playQueue.length > 0) {
nextSong = 0;
} else {
nextSong = playQueuePointer;
}
} else if (shuffle) { // next shuffle song
let shufflePlayed = this.get('shufflePlayed');
// played all the song in shuffle mode
if (shufflePlayed.length === playQueue.length) {
shufflePlayed.clear();
this.send('play', true);
return;
}
// we're going to assume that the song URL is the id
do {
nextSong = Math.floor(Math.random() * playQueue.length);
} while (shufflePlayed.includes(playQueue[nextSong].url));
shufflePlayed.pushObject(playQueue[nextSong].url);
} else if (nextSong > playQueue.length - 1) {
if (repeat === 1 || repeatAll) {
nextSong = nextSong % playQueue.length;
} else {
this.send('play', true);
return;
}
}
this.send('goToSong', nextSong, true, true);
},
previous() {
if (this.get('timeElapsed') > 5) {
this.send('seekChanged', 0);
} else {
let nextSong = this.get('playQueuePointer'),
playQueue = this.get('playQueue');
if (this.get('shuffle') && !isNone(playQueue[nextSong])) { // go to the previously shuffled song
let shufflePlayed = this.get('shufflePlayed'),
shuffledSongIndx = this.get('shufflePlayed').indexOf(playQueue[nextSong].url),
i = 0;
if (shufflePlayed.length > 0 && shuffledSongIndx !== -1) { // only if there was one
nextSong = shuffledSongIndx - 1;
if (nextSong < 0) {
nextSong = shufflePlayed.length - 1;
}
playQueue.some(function (item) { // try to find the previous song id
if (item.url === shufflePlayed[nextSong]) {
nextSong = i;
return true;
}
i++;
return false;
});
}
} else {
nextSong--;
if (nextSong < 0) {
nextSong = playQueue.length - 1;
}
}
this.send('goToSong', nextSong, true, true);
}
},
seekChanged(position) {
let dancer = this.get('dancer');
if (dancer.audio) {
dancer.audio.currentTime = Math.floor(this.get('timeTotal') * position / 100);
}
},
volumeMutedChanged(value) {
let dancer = this.get('dancer'),
volumeMuted = isNone(value) ? !this.get('volumeMuted') : value;
this.changePlayerControl('volumeMuted', volumeMuted);
if (this.get('playing')) {
if (volumeMuted) {
dancer.setVolume(0);
} else {
dancer.setVolume(this.get('volume') / 100);
}
}
},
addLocalAudio: function () {
$('#file-input').click();
},
shuffleChanged(value) {
this.changePlayerControl('shuffle', isNone(value) ? !this.get('shuffle') : value);
},
repeatChanged(value) {
this.changePlayerControl('repeat', isNone(value) ? (this.get('repeat') + 1) % 3 : value);
},
playerBottomDisplayedChanged(value) {
this.changePlayerControl('playerBottomDisplayed', value);
},
thresholdChanged(value) {
this.changePlayerControl('threshold', value, true);
},
brightnessRangeChanged(value) {
this.changePlayerControl('brightnessRange', value);
},
hueRangeChanged(value) {
this.changePlayerControl('hueRange', value);
},
playQueuePointerChanged(value) {
this.send('goToSong', value, false, true);
},
clickSpeaker() {
this.simulateKick(1);
},
dropFiles(files) {
this.setProperties({
dragging: false,
draggingOverPlayListArea: false
});
this.send('handleNewFiles', files);
},
playerListAreaDragOver() {
this.set('draggingOverPlayListArea', true);
},
playerListAreaDragLeave() {
this.set('draggingOverPlayListArea', false);
},
handleNewFiles(files) {
let self = this,
playQueue = this.get('playQueue'),
updatePlayQueue = function () {
let tags = ID3.getAllTags("local"),
picture = null;
if (tags.picture) {
let base64String = "";
for (let i = 0; i < tags.picture.data.length; i++) {
base64String += String.fromCharCode(tags.picture.data[i]);
}
picture = "data:" + tags.picture.format + ";base64," + window.btoa(base64String);
}
playQueue.pushObject({
fileName: this.name.replace(/\.[^/.]+$/, ""),
url: URL.createObjectURL(this),
artist: tags.artist,
title: tags.title,
picture: picture,
local: true
});
ID3.clearAll();
if (self.get('playQueuePointer') === -1) {
self.send('next');
}
};
for (let key in files) {
if (files.hasOwnProperty(key)) {
let file = files[key];
if (file.type.startsWith('audio')) {
ID3.loadTags("local", updatePlayQueue.bind(file), {
dataReader: new FileAPIReader(file),
tags: ['title', 'artist', 'album', 'track', 'picture']
});
}
}
}
},
toggleDimmer() {
this.sendAction('toggleDimmer');
}
}
});

View file

@ -0,0 +1,412 @@
import Ember from 'ember';
const {
Mixin,
observer,
computed,
isNone,
run,
$,
inject,
on,
A
} = Ember;
export default Mixin.create({
classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'],
classNameBindings: ['active::hidden'],
elementId: 'music-tab',
dancer: null,
notify: inject.service(),
beatOptions: {
threshold: {
range: {min: 0, max: 0.5},
step: 0.01,
defaultValue: 0.3,
pips: {
mode: 'values',
values: [0, 0.25, 0.5],
density: 10,
format: {
to: function ( value ) {
if(value === 0) {
value = 'More';
} else if(value === 0.25) {
value = '';
} else {
value = 'Less';
}
return value;
},
from: function ( value ) { return value; }
}
}
},
hueRange: {
range: {min: 0, max: 65535},
step: 1,
defaultValue: 0.3,
pips: {
mode: 'values',
values: [0, 25500, 46920, 65535],
density: 10,
format: {
to: function ( value ) {
if(value === 0 || value === 65535) {
value = 'Red';
} else if(value === 25500 ) {
value = 'Green';
} else {
value = 'Blue';
}
return value;
},
from: function ( value ) { return value; }
}
}
},
brightnessRange: {
range: {min: 1, max: 254},
step: 1,
defaultValue: 0,
pips: {
mode: 'values',
values: [1, 50, 100, 150, 200, 254],
density: 10,
format: {
to: function ( value ) { return value; },
from: function ( value ) { return value; }
}
}
}
},
threshold: 0.3,
hueRange: [0, 65535],
brightnessRange: [1, 254],
oldThreshold: null,
playQueuePointer: -1,
playQueue: A(),
timeElapsed: 0,
timeTotal: 0,
lastLightBopIndex: 0,
playerBottomDisplayed: true,
dragging: false,
draggingOverPlayListArea: false,
dragLeaveTimeoutHandle: null,
audioStream: null,
dimmerOn: false,
isShowingAddSoundCloudModal: false,
colorloopMode: false,
flashingTransitions: false,
// 0 - no repeat, 1 - repeat all, 2 - repeat one
repeat: 0,
shuffle: false,
volumeMuted: false,
volume: 100,
// beat detection related pausing
paused: false,
// audio: playing or paused
playing: false,
songBeatPreferences: {},
usingBeatPreferences: false,
oldBeatPrefCache: null,
storage: null,
firstVisit: true,
soundCloudFuckUps: 0,
maxSoundCloudFuckUps: 3,
// used to insure that we don't replay the same thing multiple times in shuffle mode
shufflePlayed: [],
// noUiSlider connection specification
filledConnect: [true, false],
hueRangeConnect: [false, true, false],
SC_CLIENT_ID: 'aeec0034f58ecd85c2bd1deaecc41594',
scUserNotSupportedHtml: '<div class="alert alert-danger" role="alert">SoundCloud user URLs are not supported.</div>',
tooManySoundCloudFuckUps: '<div class="alert alert-danger" role="alert">The SoundCloud API is not seving the audio properly. More details <a href="https://www.soundcloudcommunity.com/soundcloud/topics/some-soundcloud-cdn-hosted-tracks-dont-have-access-control-allow-origin-header" target="_blank" rel="noopener noreferrer">HERE</a>.</div>',
notStreamableHtml(fileNames){
let html = '<div class="alert alert-danger" role="alert">The following file(s) could not be added because they are not allowed to be streamed:<br>' + fileNames.toString().replace(/,/g, '<br>') + '</div>';
return html;
},
urlNotFoundHtml(url){
return '<div class="alert alert-danger" role="alert">The URL ( ' + url + ' ) could not be resolved.</div>';
},
failedToPlayFileHtml(fileName){
return '<div class="alert alert-danger" role="alert">Failed to play file ( ' + fileName + ' ).</div>';
},
failedToDecodeFileHtml(fileName){
return '<div class="alert alert-danger" role="alert">Failed to decode file ( ' + fileName + ' ).</div>';
},
scUrl: computed('playQueuePointer', 'playQueue.[]', function(){
let rtn = null,
currentSong = this.get('playQueue')[this.get('playQueuePointer')];
if(currentSong && currentSong.scUrl){
rtn = currentSong.scUrl;
}
return rtn;
}),
playQueueEmpty: computed.empty('playQueue'),
playQueueNotEmpty: computed.notEmpty('playQueue'),
playQueueMultiple: computed('playQueue.[]', function(){
return this.get('playQueue').length > 1;
}),
seekPosition: computed('timeElapsed', 'timeTotal', function(){
let timeTotal = this.get('timeTotal'),
timeElapsed = this.get('timeElapsed');
if (timeTotal === 0) {
return 0;
}
return timeElapsed/timeTotal*100;
}),
largeArtworkPic: computed('playQueuePointer', 'currentVisName', function(){
let pic = '',
currentVisName = this.get('currentVisName'),
playQueuePointer = this.get('playQueuePointer'),
playQueue = this.get('playQueue');
if(playQueuePointer !== -1 && currentVisName === 'None'){
let song = playQueue[playQueuePointer];
if(!isNone(song.picture)){
pic = song.picture;
if(song.scUrl){
pic = pic.replace('67x67', '500x500');
}
}
}
return pic;
}),
repeatIcon: computed('repeat', function() {
if(this.get('repeat') === 2) {
return 'repeat-one';
}
return 'repeat';
}),
playingIcon: computed('playing', function() {
if(this.get('playing')){
return 'pause';
} else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){
return 'replay';
} else {
return 'play-arrow';
}
}),
playerAreaClickIcon: computed('playing', function() {
if(this.get('playing')){
return 'play-arrow';
} else {
return 'pause';
}
}),
playListAreaClass: computed('dragging', 'draggingOverPlayListArea', 'dimmerOn', function(){
let classes = 'pointer';
if(this.get('dragging')){
classes += ' drag-here-highlight';
}
if(this.get('draggingOverPlayListArea')){
classes += ' dragging-over';
}
if(this.get('dimmerOn')){
classes += ' dimmerOn';
}
return classes;
}),
dimmerOnClass: computed('dimmerOn', function(){
return this.get('dimmerOn') ? 'dimmerOn' : null;
}),
volumeMutedClass: computed('volumeMuted', function(){
let classes = 'player-control-icon volumeButton';
if(this.get('volumeMuted')){
classes += ' active';
}
return classes;
}),
repeatClass: computed('repeat', function(){
return this.get('repeat') !== 0 ? 'player-control-icon active' : 'player-control-icon';
}),
shuffleClass: computed('shuffle', function(){
return this.get('shuffle') ? 'player-control-icon active' : 'player-control-icon';
}),
volumeIcon: computed('volumeMuted', 'volume', function() {
let volume = this.get('volume');
if (this.get('volumeMuted')) {
return "volume-off";
} else if (volume >= 70) {
return "volume-up";
} else if (volume > 10) {
return "volume-down";
} else {
return 'volume-mute';
}
}),
beatDetectionAreaArrowIcon: computed('playerBottomDisplayed', function(){
if(!this.get('playerBottomDisplayed')){
return 'keyboard-arrow-down';
} else {
return 'keyboard-arrow-up';
}
}),
timeElapsedTxt: computed('timeElapsed', function(){
return this.formatTime(this.get('timeElapsed'));
}),
timeTotalTxt: computed('timeTotal', function() {
return this.formatTime(this.get('timeTotal'));
}),
onPlayQueueChange: observer('playQueue.length', function(){
let playQueueLength = this.get('playQueue.length');
if(playQueueLength > this.get('oldPlayQueueLength')){
run.once(this, ()=>{
run.next(this, function() {
$(`.track${playQueueLength-1}`).velocity('scroll', { container: $('#play-list-area'), duration: 200 });
});
});
}
this.set('oldPlayQueueLength', playQueueLength);
}),
onColorloopModeChange: observer('colorloopMode', 'playing', function(){
this.set('colorLoopOn', this.get('playing') && this.get('colorloopMode'));
}),
onOptionChange: observer('flashingTransitions', 'playQueue.[]', 'playQueuePointer', 'colorloopMode', function(self, option){
option = option.replace('.[]', '');
let value = this.get(option);
// can't really save local music
if(option === 'playQueue'){
value = value.filter((song)=>{
return !song.url.startsWith('blob:');
});
}
this.get('storage').set('huegasm.' + option, value);
}),
onRepeatChange: on('init', observer('repeat', function () {
let tooltipTxt = 'Repeat all', type = 'repeat';
if (this.get(type) === 1) {
tooltipTxt = 'Repeat one';
} else if (this.get(type) === 2) {
tooltipTxt = 'Repeat off';
}
this.changeTooltipText(type, tooltipTxt);
})),
onShuffleChange: on('init', observer('shuffle', function () {
let tooltipTxt = 'Shuffle', type = 'shuffle';
if (this.get(type)) {
this.get('shufflePlayed').clear();
tooltipTxt = 'Unshuffle';
}
this.changeTooltipText(type, tooltipTxt);
})),
onVolumeMutedChange: on('init', observer('volumeMuted', function() {
let tooltipTxt = 'Mute', type = 'volumeMuted',
volumeMuted = this.get(type), dancer = this.get('dancer'),
volume=0;
if (volumeMuted) {
tooltipTxt = 'Unmute';
volume = 0;
} else {
volume = this.get('volume')/100;
}
if(this.get('playing')){
dancer.setVolume(volume);
}
this.changeTooltipText(type, tooltipTxt);
})),
onPrevChange: on('init', observer('timeElapsed', 'playQueueNotEmpty', 'playQueue.[]', function() {
if(this.get('playQueueNotEmpty')){
let tooltipTxt = 'Previous', type = 'prev';
if(this.get('timeElapsed') > 5 || this.get('playQueue').length === 1) {
tooltipTxt = 'Replay';
}
this.changeTooltipText(type, tooltipTxt);
}
})),
onPlayingChange: on('init', observer('playing', function () {
let tooltipTxt = 'Play', type = 'playing';
if (this.get(type)) {
tooltipTxt = 'Pause';
} else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){
tooltipTxt = 'Replay';
}
this.changeTooltipText(type, tooltipTxt);
})),
changeTooltipText(type, text) {
// change the tooltip text if it's already visible
$('#' + type + 'Tooltip + .tooltip .tooltip-inner').html(text);
//change the tooltip text for hover
$('#' + type + 'Tooltip').attr('data-original-title', text);
if(isNone(this.get(type + 'TooltipTxt'))) {
this.set(type + 'TooltipTxt', text);
}
},
formatTime(time){
return this.pad(Math.floor(time/60), 2) + ':' + this.pad(time%60, 2);
},
pad(num, size){ return ('000000000' + num).substr(-size); }
});

View file

@ -0,0 +1,94 @@
import Ember from 'ember';
const {
Mixin,
observer,
$
} = Ember;
export default Mixin.create({
currentVisName: 'None',
visNames: ['None', 'Bars', 'Wave'],
onCurrentVisNameChange: observer('currentVisName', function () {
let currentVisName = this.get('currentVisName');
if(currentVisName === 'None'){
let canvasEl = $('#visualization')[0],
ctx = canvasEl.getContext('2d');
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
}
this.get('storage').set('huegasm.currentVisName', currentVisName);
}),
didInsertElement(){
let dancer = this.get('dancer'),
canvas = $('#visualization')[0],
playerArea = $('#player-area'),
ctx = canvas.getContext('2d'),
spacing = 2,
h = playerArea.height(), w;
canvas.height = h;
// must be done to preserver resolution so that things don't appear blurry
// note that the height is set to 400px via css so it doesn't need to be recalculated
let syncCanvasHeight = ()=>{
w = playerArea.width();
canvas.width = w;
};
syncCanvasHeight();
$(window).on('resize', syncCanvasHeight);
dancer.bind('update', () => {
let currentVisName = this.get('currentVisName'),
gradient = ctx.createLinearGradient(0, 0, 0, h),
pageHidden = document.hidden || document.msHidden || document.webkitHidden || document.mozHidden;
// dont do anything if the page is hidden or no visualization
if(currentVisName === 'None' || pageHidden || !this.get('active')){
return;
}
ctx.clearRect(0, 0, w, h);
if (currentVisName === 'Wave') {
let width = 3,
count = 1024;
gradient.addColorStop(0.6, 'white');
gradient.addColorStop(0, '#0036FA');
ctx.lineWidth = 1;
ctx.strokeStyle = gradient;
let waveform = dancer.getWaveform();
ctx.beginPath();
ctx.moveTo(0, h / 2);
for (let i = 0, l = waveform.length; i < l && i < count; i++) {
ctx.lineTo(i * ( spacing + width ), ( h / 2 ) + waveform[i] * ( h / 2 ));
}
ctx.stroke();
ctx.closePath();
} else if (currentVisName === 'Bars') {
let width = 4,
count = 128;
gradient.addColorStop(1, '#0f0');
gradient.addColorStop(0.6, '#ff0');
gradient.addColorStop(0.2, '#F12B24');
ctx.fillStyle = gradient;
let spectrum = dancer.getSpectrum();
for (let i = 0, l = spectrum.length; i < l && i < count; i++) {
ctx.fillRect(i * ( spacing + width ), h, width, -spectrum[i] * h - 60);
}
}
});
}
});

View file

@ -0,0 +1,194 @@
<div class="row" id="step1">
<div id="player-area" class="col-sm-8 col-xs-12 {{if (eq "None" currentVisName) "display-icon"}}" {{action "playerAreaPlay"}}>
<canvas id="visualization"></canvas>
<div id="artwork">
<img src={{largeArtworkPic}}>
</div>
{{paper-icon playerAreaClickIcon id="play-notification"}}
<div id="player-controls">
{{range-slider start=seekPosition min=0 max=100 connect=filledConnect id="seek-slider" on-change="seekChanged"}}
{{#if playQueueNotEmpty}}
<span data-toggle="tooltip" data-placement="top" class="bootstrap-tooltip" id="prevTooltip"
data-title={{prevTooltipTxt}} {{action "previous"}}>{{paper-icon "skip-previous" class="player-control-icon"}}</span><!--
-->{{/if}}<!--
--><span data-toggle="tooltip" data-placement="top" id="playingTooltip" class="bootstrap-tooltip"
data-title={{playingTooltipTxt}} {{action "play"}}>{{paper-icon playingIcon class="player-control-icon"}}</span><!--
-->{{#if playQueueMultiple}}<!--
--><span data-toggle="tooltip" data-placement="top" class="bootstrap-tooltip"
data-title="Next" {{action "next" true}}>{{paper-icon "skip-next" action="" class="player-control-icon"}}</span><!--
-->{{/if}}<!--
--><span data-toggle="tooltip" data-placement="top" class="bootstrap-tooltip" id="volumeMutedTooltip"
data-title={{volumeMutedTooltipTxt}} {{action "volumeMutedChanged"}}>{{paper-icon icon=volumeIcon class=volumeMutedClass}}</span><!--
-->{{range-slider start=volume min=0 max=100 connect=filledConnect on-change="volumeChanged" id="volume-bar" class="hidden-xs"}}
<div id="player-time-controls">{{timeElapsedTxt}} / {{timeTotalTxt}}</div>
{{#paper-menu as |menu|}}
{{#menu.trigger}}
{{#paper-button iconButton=true}}
{{paper-icon "remove-red-eye" class="player-control-icon"}}
{{/paper-button}}
{{/menu.trigger}}
{{#menu.content width=2 as |content|}}
{{#each visNames as |name|}}
{{#content.menu-item onClick=(action "setVisName" name)}}
{{name}}
{{#if (eq currentVisName name)}}
{{paper-icon "check" classNames=dimmerOnClass}}
{{/if}}
{{/content.menu-item}}
{{/each}}
{{/menu.content}}
{{/paper-menu}}
{{#if scUrl}}
<a href={{scUrl}} class="sound-cloud-link"{{action "gotoSCURL" scUrl}}>
<img src="assets/images/sc-white.png" id="soundcloud-logo" />
<img src="assets/images/sc-white-sm.png" id="soundcloud-logo-small" />
</a>
{{/if}}
</div>
</div>
<div id="playlist" class="col-sm-4 col-xs-12">
<input id="file-input" type="file" accept="audio/*" multiple="true"/>
<div id="play-list-controls">
{{#paper-menu as |menu|}}
{{#menu.trigger}}
{{#paper-button iconButton=false}}
{{paper-icon "playlist add" class="player-control-icon"}} <span id="add-new-music-label">Add new music</span>
{{/paper-button}}
{{/menu.trigger}}
{{#menu.content width=3 as |content|}}
{{#content.menu-item onClick="addLocalAudio"}}
{{paper-icon "attachment" class=shuffleClass}} Local file
{{/content.menu-item}}
{{#content.menu-item onClick="toggleIsShowingAddSoundCloudModal"}}
{{paper-icon "cloud" class=shuffleClass}} SoundCloud
{{/content.menu-item}}
{{/menu.content}}
{{/paper-menu}}
<span data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip" id="shuffleTooltip" data-title={{shuffleTooltipTxt}} {{action "shuffleChanged"}}>{{paper-icon "shuffle" class=shuffleClass}}</span>
<span data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip" id="repeatTooltip" data-title={{repeatTooltipTxt}} {{action "repeatChanged"}}>{{paper-icon repeatIcon class=repeatClass}}</span>
<span data-toggle="tooltip" data-placement="top auto" class="bootstrap-tooltip hidden-xs" data-title="Clear playlist" {{action "clearPlaylist"}}>{{paper-icon "clear-all" class="player-control-icon"}}</span>
</div>
<div id="play-list-area" class={{playListAreaClass}} {{action "addLocalAudio"}} {{action "playerListAreaDragOver" on="dragOver"}} {{action "playerListAreaDragLeave" on="dragLeave"}} {{action "dropFiles" on="drop"}}>
{{#if (or playQueueEmpty dragging)}}
<div id="dragHere">
{{#if dragging}}
Drag your music files here
{{else}}
Add your music files here
{{/if}}
</div>
{{paper-icon "library-music" class=dimmerOnClass}}
{{/if}}
{{#each playQueue as |item index|}}
<div class="playlist-item pointer track{{index}} {{if (eq index playQueuePointer) "active"}} {{if dragging "hidden"}}" {{action "goToSong" index true bubbles=false}}>
{{#if item.picture}}
<img class="album-art" src={{item.picture}}>
{{else}}
<img class="album-art" src="assets/images/missingArtwork.png">
{{/if}}
<div class="song-info">
{{#if item.title}}
<div class="song-title">{{item.title}}</div>
<div class="song-artist">
{{#if item.artistUrl}}
<a href="#" {{action "gotoURL" item.artistUrl bubbles=false}}>{{item.artist}}</a>
{{else}}
{{item.artist}}
{{/if}}
</div>
{{else}}
{{item.fileName}}
{{/if}}
</div>
<span data-toggle="tooltip" data-placement="top auto" data-title="Remove" data-container="body" class="audio-remove-button pointer bootstrap-tooltip" {{action "removeAudio" index bubbles=false}}>{{paper-icon "close" classNames="close"}}</span>
</div>
{{/each}}
</div>
</div>
</div>
<div id="slide-toggle" class="text-center pointer row" {{action "slideTogglePlayerBottom"}}>
<div class="col-xs-offset-5 col-xs-2">
{{paper-icon beatDetectionAreaArrowIcon id="beat-detection-area-arrow-icon"}}
</div>
</div>
<div id="player-bottom" class="row {{if dimmerOn "dimmerOn"}} {{if playerBottomDisplayed "display-flex"}}">
<div id="beat-area" class="col-sm-7 col-xs-12">
{{#if usingBeatPreferences}}
<span data-toggle="tooltip" data-placement="top" data-title="Using the saved sensitivity preference from the last time you listened to this song" class="bootstrap-tooltip" id="save-beat-preferences-star">
{{paper-icon "star" class=dimmerOnClass}}
</span>
{{/if}}
<div class="row" id="beat-option-row">
<div class="beat-option col-xs-4">
<span data-toggle="tooltip" data-placement="top" data-title="The range of hues ( colors ) that the lights may change to on beat." class="option-description bootstrap-tooltip">
Hue Range
</span>
{{range-slider start=hueRange orientation="vertical" step=beatOptions.hueRange.step range=beatOptions.hueRange.range connect=hueRangeConnect on-change="hueRangeChanged" pips=beatOptions.hueRange.pips}}
</div>
<div class="beat-option col-xs-4">
<span data-toggle="tooltip" data-placement="top" data-title="The minimum ( off-beat ) and maximum ( on-beat ) brightness of the lights" class="option-description bootstrap-tooltip">
Brightness Range
</span>
{{range-slider start=brightnessRange orientation="vertical" step=beatOptions.brightnessRange.step range=beatOptions.brightnessRange.range on-change="brightnessRangeChanged" pips=beatOptions.brightnessRange.pips}}
</div>
<div id="sensitivity-settings" class="beat-option col-xs-4">
<span data-toggle="tooltip" data-placement="top" data-title="The sensitivity of the beat detector ( more sensitivity results in more registered beats )" class="option-description bootstrap-tooltip">
Sensitivity
</span>
{{range-slider start=threshold orientation="vertical" step=beatOptions.threshold.step range=beatOptions.threshold.range on-change="thresholdChanged" pips=beatOptions.threshold.pips}}
</div>
<div id="light-option" class="col-xs-12">
<span data-toggle="tooltip" data-placement="top auto" data-title="Quickly flash the lights on beat" class="bootstrap-tooltip" {{action "hideTooltip" on="mouseLeave"}}>
{{paper-checkbox value=flashingTransitions onChange=(action (mut flashingTransitions)) label="Flashing Transitions"}}
</span>
<span data-toggle="tooltip" data-placement="top auto" data-title="Slowly cycle the lights through all the colors" class="bootstrap-tooltip" {{action "hideTooltip" on="mouseLeave"}}>
{{paper-checkbox value=colorloopMode onChange=(action (mut colorloopMode)) label="Colorloop"}}
</span>
</div>
</div>
</div>
<div id="beat-container" class="col-sm-5 col-xs-12">
<div class="bezel">
<div class="rivet1"></div>
<div class="rivet2"></div>
<div class="rivet3"></div>
<div class="rivet4"></div>
<div class="rivet5"></div>
<div class="rivet6"></div>
<div class="rivet7"></div>
<div class="rivet8"></div>
<div id="beat-speaker-center-outer">
<div id="beat-speaker-center-inner" class="pointer" {{action "clickSpeaker"}}></div>
</div>
</div>
</div>
</div>
{{music-tab/add-soundcloud-sound-modal action="handleNewSoundCloudURL" isShowingModal=isShowingAddSoundCloudModal}}

3
chrome/app/resolver.js Normal file
View file

@ -0,0 +1,3 @@
import Resolver from 'ember-resolver';
export default Resolver;

12
chrome/app/router.js Normal file
View file

@ -0,0 +1,12 @@
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
});
export default Router;

118
chrome/app/styles/app.scss Normal file
View file

@ -0,0 +1,118 @@
@import 'ember-modal-dialog/ember-modal-structure';
@import 'ember-modal-dialog/ember-modal-appearance';
@import 'huegasm-variables';
@import 'bootstrap'; // used to take out bootstrap scss modules that we don't need
@import 'paper';
@import 'bridge-finder';
@import 'common';
@import 'dimmer';
@import 'fancy-speaker';
@import 'introjs';
@import 'hue-controls';
@import 'light-group';
@import 'music-tab';
@import 'noui-slider';
body {
min-width: 500px;
}
body > .ember-view {
display: flex;
min-height: 100vh;
flex-direction: column;
}
body, button {
font-family: 'Slabo 27px', serif;
}
#huegasm {
flex: 1;
}
.ember-app {
padding-bottom: 50px;
}
.footer {
margin: 0 auto 10px auto;
width: 100%;
max-width: 800px;
text-align: center;
display: flex;
align-items: center;
justify-content: space-around;
}
.footer-text {
display: inline-block;
font-size: 18px;
a {
margin-left: 5px;
}
}
.alert {
margin-bottom: 0;
border: none;
}
.title {
margin-bottom: 20px;
img {
width: 200px;
}
}
button.md-warn {
background: $secondaryThemeColor;
}
div.ember-modal-dialog {
padding: 20px;
color: $blackish;
md-input-container {
width: 100%;
input.md-input[type="text"] {
color: $blackish !important;
}
}
md-input-container label {
color: rgba(0, 0, 0, 0.26);
}
}
.display-flex {
display: flex !important;
}
// fancy webkit scrollbars
::-webkit-scrollbar {
-webkit-appearance: none;
}
::-webkit-scrollbar:vertical {
width: 12px;
}
::-webkit-scrollbar:horizontal {
height: 12px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .5);
border-radius: 10px;
border: 2px solid #ffffff;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
}
::-webkit-scrollbar-track {
background-color: #ffffff;
}

56
chrome/app/styles/bootstrap.scss vendored Normal file
View file

@ -0,0 +1,56 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
// Core variables and mixins
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/mixins";
// Reset and dependencies
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/normalize";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/print";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";
// Core CSS
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/type";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/code";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/grid";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/tables";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/forms";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/buttons";
// Components
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/navs";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/navbar";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/pagination";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/pager";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/labels";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/badges";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/jumbotron";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/thumbnails";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/alerts";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/progress-bars";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/media";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/list-group";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/panels";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/wells";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/close";
// Components w/ JavaScript
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/modals";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/popovers";
//@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/carousel";
// Utility classes
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/utilities";
@import "bower_components/bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";

View file

@ -0,0 +1,73 @@
#press-bridge-button-img {
width: 200px;
margin: 0 auto 30px auto;
display: inherit;
}
#bridge-button-group {
width: 150px;
margin: 30px auto;
text-align: left;
}
#bridge-input md-input-container{
max-width: 200px;
margin: 30px auto 20px;
}
#intro {
font-size: 22px;
}
#intro-paragraph {
margin-bottom: 20px;
font-size: 16px;
}
#bridge-finder, .ready-block {
text-align: center;
padding: 10px 15px 0;
font-size: 16px;
}
#bridge-finder {
min-height: 500px;
.md-bar {
background-color: $secondaryThemeColor !important;
}
}
// preloading image
.ready-block:after {
display: none;
content: url(images/pressButtonBridge.png);
}
.embed-container {
position:relative;
padding-bottom:56.25%;
padding-top:30px;
height:0;
overflow:hidden;
}
.embed-container-wrapper {
max-width: 550px;
margin: auto;
}
.embed-container iframe, .embed-container object, .embed-container embed {
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
}
.go-button {
margin: 20px 0;
border-radius: 100% !important;
width: 100px;
height: 100px;
font-size: 28px;
}

View file

@ -0,0 +1,15 @@
.text-left {
text-align: left !important;
}
.relative {
position: relative !important;
}
.no-text-decoration {
text-decoration: none !important;
}
.pointer {
cursor: pointer;
}

View file

@ -0,0 +1,76 @@
div.dimmerOn {
color: $whitish !important;
background: $blackish !important;
}
html.dimmerOn {
color: white;
background: $blackish;
}
body.dimmerOn {
color: $whitish;
background: $blackish;
md-input-container {
label {
color: #3f51b5 !important;
}
.md-input {
color: $whitish !important;
border-color: #3f51b5 !important;
}
}
.md-track {
background: $whitish;
}
.color {
border: 1px solid white;
}
.playlist-item, .ember-basic-dropdown-content md-menu-content {
color: $whitish;
background-color: $dimmerOnButtonColor;
}
.ember-basic-dropdown-content a {
color: $whitish;
}
.playlist-item {
&.active {
background: darken($dimmerOnButtonColor, 15%) !important;
}
&:hover {
background: darken($dimmerOnButtonColor, 10%);
}
.audio-remove-button .paper-icon {
color: $whitish !important;
&:hover {
color: white !important;
}
}
}
svg {
-webkit-filter: drop-shadow(0 0 5px #228DFF);
}
.md-container {
color: $whitish;
}
.add-new-music:hover {
background: darken($dimmerOnButtonColor, 5%);
}
.md-bar {
background-color: darken(white, 60%) !important;
}
}
.paper-icon.dimmerOn {
text-shadow: $glowingText;
opacity: 0.9 !important;
}
.logo {
display: inline-block;
cursor: pointer;
width: 40px;
height: 40px;
background: url(images/huegasm.png) center center no-repeat;
background-size: 40px 40px;
}

View file

@ -0,0 +1,101 @@
$centersize: 80px;
$center1size: 205px;
$bezelsize: 240px;
%base {
border-radius: 100%;
}
%rivet {
position: absolute;
height: 8px;
width: 8px;
background-color: #555;
border-radius: 100%;
box-shadow: inset 0 0 3px #000, 0 0 2px #000;
}
#beat-speaker-center-inner {
@extend %base;
height: $centersize;
width: $centersize;
position: absolute;
bottom: 47px;
right: 47px;
-webkit-filter: blur(1px);
filter: blur(1px);
background: rgb(0,0,0);
background: -moz-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%, rgba(79,79,79,1) 0%, rgba(0,0,0,1) 100%);
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(0,0,0,1)), color-stop(0%,rgba(79,79,79,1)), color-stop(100%,rgba(0,0,0,1)));
background: -webkit-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
background: -o-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
background: -ms-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
background: radial-gradient(ellipse at center, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
box-shadow: 0 0 10px rgba(0, 0, 0, 1);
}
#beat-speaker-center-outer {
@extend %base;
position: absolute;
top: 16px;
left: 16px;
height: $center1size;
width: $center1size;
border: 15px solid #333;
box-shadow: -3px -3px 15px rgba(0, 0, 0, 0.4), inset -3px -3px 15px rgba(0, 0, 0, 0.5);
background: -moz-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
background: -webkit-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
background: -o-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
background: -ms-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
background: linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
}
.bezel {
@extend %base;
margin: 0 auto;
height: $bezelsize;
width: $bezelsize;
position: relative;
background-color: #A8A8A8;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8), inset 3px 3px 10px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.8), inset 0 0 30px -5px rgba(0, 0, 0, 0.8);
}
.rivet1 {
@extend %rivet;
top: 6px;
left: 50%;
}
.rivet2 {
@extend %rivet;
bottom: 6px;
left: 50%;
}
.rivet3 {
@extend %rivet;
top: 50%;
left: 6px;
}
.rivet4 {
@extend %rivet;
top: 50%;
right: 6px;
}
.rivet5 {
@extend %rivet;
top: 18%;
left: 13.7%;
}
.rivet6 {
@extend %rivet;
top: 18%;
right: 13.5%;
}
.rivet7 {
@extend %rivet;
bottom: 17%;
left: 13.5%;
}
.rivet8 {
@extend %rivet;
bottom: 17%;
right: 13.5%;
}

View file

@ -0,0 +1,112 @@
#lights-tab {
padding: 0;
margin-top: 5vh;
.paper-icon {
line-height: 0.8 !important;
}
}
.lights-control-tooltip + .tooltip {
left: 0 !important;
}
#color-row {
cursor: pointer;
.md-list-item-inner {
padding-right: 0;
}
}
#hue-controls {
max-width: 1200px;
height: 90vh;
md-progress-circular {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
// preload images
#hue-controls:after, md-progress-circular:after {
display: none;
content: url(images/colormap.png) url(images/missingArtwork.png) url(images/sc-white.png) url(images/huegasm.png) url(images/lights/a19.svg) url(images/lights/a19w.svg) url(images/lights/br30.svg) url(images/lights/br30w.svg) url(images/lights/gu10.svg) url(images/lights/gu10w.svg) url(images/lights/huego.svg) url(images/lights/huegow.svg) url(images/lights/lc_aura.svg) url(images/lights/lc_auraw.svg) url(images/lights/lc_bloom.svg) url(images/lights/lc_bloomw.svg) url(images/lights/lc_iris.svg) url(images/lights/lc_irisw.svg) url(images/lights/lightstrip.svg) url(images/lights/lightstripw.svg) url(images/lights/storylight.svg) url(images/lights/storylightw.svg);
}
#navigation {
padding: 15px 0 4vh;
text-align: center;
margin: auto;
position: relative;
.ember-basic-dropdown-trigger {
z-index: 3;
text-align: right;
position: absolute;
top: -10px;
right: 10px;
transform: scale(1.1);
}
}
.navigation-item {
font-size: 18px;
padding: 0 10px 0 10px;
&.active {
font-weight: bold;
cursor: default;
text-decoration: none !important;
}
&:hover {
text-decoration: underline;
}
}
.color {
border: 1px solid rgba(0, 0, 0, 0.5);
}
#color-picker {
padding: 5px;
background: rgba(0, 0, 0, 0.7);
box-shadow: 5px 10px 15px 5px rgba(0, 0, 0, 0.3);
color: #FFFFFF;
position: absolute;
width: 266px;
height: 266px;
right: 6px;
top: -9px;
z-index: 3;
}
.color-content {
box-shadow: none !important;
md-menu-content, md-menu-item {
background-color: transparent !important;
}
}
#picker {
cursor: crosshair;
}
#loop-addition {
position: absolute;
left: 33px;
top: 15px;
font-size: 16px !important;
}
#huegasm-content {
height: 80%;
max-height: 500px;
}
@media(min-width:767px) {
#lights-tab {
font-size: 20px;
.paper-icon {
font-size: 24px;
}
}
}

View file

@ -0,0 +1,8 @@
$playerHeight: 400px;
$playerDefaultIconColor: #BBBBBB;
$secondaryThemeColor: #F12B24;
$glowingText: 0 0 2px #fff, 0 0 4px #fff, 0 0 20px #228DFF;
$dimmerOnButtonColor: #404040;
$blackish: #242424;
$whitish: #e0e0e0;
$paperThemeColor: #3f51b5;

View file

@ -0,0 +1,18 @@
#settings.introjs-fixParent {
position: inherit !important;
}
.introjs-tooltip {
width: 300px;
}
.introjs-skipbutton {
color: $secondaryThemeColor;
}
.introjs-bullets ul li a.active {
position: relative;
height: 10px;
width: 10px;
top: -2px;
}

View file

@ -0,0 +1,71 @@
.light-group {
max-width: 800px;
margin: 0 auto;
display: flex;
justify-content: center;
.tooltip.top {
margin-top: 1px;
margin-left: 2px;
}
}
.toggleable-light {
cursor: pointer;
position: relative;
border-radius: 30%;
border: 2px solid $whitish;
margin: 0 2px;
display: flex;
height: 50px;
align-items: center;
justify-content: center;
}
.light-inactive {
border-color: rgba($secondaryThemeColor, 0.4);
}
.light-inactive::before {
font-weight: bold;
position: absolute;
top: -5px;
content: "\e014";
font-family: 'Glyphicons Halflings';
font-size: 40px;
color: rgba($secondaryThemeColor, 0.6);
}
.light-active {
border-color: rgba(green, 0.4);
img {
transition-duration: 0.3s;
transition-property: transform;
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
}
img:hover {
transform: scale(1.2);
}
}
.ember-modal-overlay.translucent {
background-color: rgba(0, 0, 0, 0.50);
}
.remove-button {
margin: 10px 0 10px 60px;
}
.light-text {
width: 60px;
word-wrap: break-word;
padding: 0 10px;
}
.light-text-content {
display: block; /* Fallback for non-webkit */
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}

View file

@ -0,0 +1,496 @@
.row {
margin: 0;
}
#music-tab {
padding: 0;
margin-top: 10px;
margin-bottom: 20px;
}
#slide-toggle {
font-size: 22px;
color: $playerDefaultIconColor;
background: #730B07;
div .paper-icon {
color: inherit !important;
font-size: 24px;
font-weight: bold;
}
}
#slide-toggle:hover {
color: lighten($playerDefaultIconColor, 30%) !important;
}
#player-controls {
transition: all 0.2s ease-in-out;
position: absolute;
bottom: 0;
left: 0;
padding: 15px 10px;
width: 100%;
color: white !important;
z-index: 20;
cursor: default;
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
.ember-basic-dropdown-trigger {
position: absolute;
right: 0;
bottom: 13px;
}
.tooltip.top {
margin-top: -17px;
}
.tooltip-arrow {
display: none;
}
md-menu-item>.md-button md-icon {
margin: auto 0 5px 10px;
}
.play-arrow,
.pause,
.replay {
font-size: 30px;
}
}
#player-time-controls {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
display: inline-block;
margin-left: 1em;
}
.player-control-icon {
color: $playerDefaultIconColor !important;
transition-duration: 0.1s;
margin-right: 10px;
margin-top: 4px;
font-size: 22px;
}
.player-control-icon.active {
color: $secondaryThemeColor !important;
}
.player-control-icon:hover {
color: white !important;
}
#play-notification {
position: relative;
color: white !important;
background: black;
top: 50%;
left: 50%;
opacity: 0;
border-radius: 100%;
}
#player-area {
height: $playerHeight;
background-color: black;
display: inline-block;
padding: 0;
cursor: pointer;
}
#playlist {
height: $playerHeight;
background-color: #1E1E1E;
padding: 0 5px 0 5px;
}
#player-area * .noUi-origin {
background-color: $blackish;
border-radius: 5px;
}
#player-area * .noUi-base {
background-color: $blackish;
border-radius: 5px;
}
#volume-bar {
width: 5em;
height: 0.5em;
display: inline-block;
}
#player-area * .noUi-handle::after,
#player-area * .noUi-handle::before {
content: none;
}
#seek-slider {
margin-bottom: 15px;
transition-duration: 0.2s;
height: 8px;
.noUi-handle {
opacity: 1 !important;
}
}
#seek-slider:hover {
height: 8px;
}
#seek-slider:hover * .noUi-handle {
opacity: 1;
}
#seek-slider * .noUi-handle {
border: none;
height: 13px;
width: 13px;
border-radius: 50%;
top: -4px;
left: -6px;
opacity: 0;
transition-duration: 0.1s;
background-color: $secondaryThemeColor !important;
box-shadow: none;
}
#play-list-controls {
min-height: 40px;
margin-top: 5px;
border-bottom: 1px solid #3a3a3a;
position: relative;
button .player-control-icon {
margin: 0 5px 1px 3px;
}
.ember-basic-dropdown-trigger {
position: absolute;
bottom: 0;
right: 0;
color: $whitish;
.paper-button {
margin: 0;
}
}
}
#play-list-area {
background-color: white;
width: 100%;
height: 350px;
margin: 0 auto;
border-radius: 5px;
transition: 0.1s all ease-in-out;
position: relative;
overflow: auto;
#dragHere {
position: absolute;
top: 27%;
font-size: 20px;
text-align: center;
width: 100%;
}
[md-font-icon="library-music"] {
position: absolute;
top: 40%;
font-size: 100px;
opacity: 0.5;
width: 100%;
text-align: center;
}
}
.song-artist {
font-weight: bold;
}
#play-list-area.drag-here-highlight {
background-color: white;
border: 5px dotted #5383ff;
}
#play-list-area.dragging-over {
background-color: darken(white, 5%);
box-shadow: inset 0 0 20px 0 rgba(0, 0, 0, 1);
}
#file-input {
width: 1px;
height: 1px;
visibility: hidden;
}
.playlist-item {
border-bottom: 1px solid rgba(128, 128, 128, 0.3);
border-top: 1px solid rgba(128, 128, 128, 0.3);
height: 62px;
font-family: 'Open Sans', sans-serif;
padding: 0 20px 0 5px;
position: relative;
color: $blackish;
background: darken(white, 5%);
.close {
font-size: 18px;
}
.album-art {
height: 60px;
float: left;
margin-right: 5px;
border: 1px solid rgba(0, 0, 0, 0.5);
}
.song-info {
.song-title {
max-height: 40px;
overflow: hidden;
}
.song-artist {
max-height: 20px;
overflow: hidden;
}
}
.audio-remove-button {
position: absolute;
top: 10px;
right: 0;
padding: 10px;
}
}
.playlist-item.active {
background: darken(white, 15%) !important;
border-top: 1px solid $secondaryThemeColor;
border-bottom: 1px solid $secondaryThemeColor;
}
.playlist-item:hover {
background: darken(white, 10%);
.close {
display: block;
}
}
#beat-area {
position: relative;
padding: 0;
}
.star {
cursor: auto !important;
}
#beat-option-button-group {
margin: 20px 0 10px 0;
}
#light-option {
margin-top: 20px;
display: flex;
justify-content: space-around;
.md-label {
width: auto;
}
}
.beat-option {
padding: 5px 0;
text-align: center;
md-checkbox {
padding: 10px 0;
}
md-switch {
margin: 0;
}
.option-description {
display: inline-flex;
font-size: 20px;
justify-content: center;
flex-direction: column;
}
button {
margin-top: 0;
}
.tooltip {
margin: 0;
}
}
#player-bottom {
color: $blackish;
border: 1px solid black;
width: 100%;
background: white;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
display: flex;
align-items: center;
}
#beat-container {
display: flex;
margin-bottom: 10px;
}
#beat-area .light-group {
margin: 10px 20px 0 40px;
float: right;
div {
display: block;
padding: 10px;
}
}
#add-music-choices {
min-width: initial;
right: 0;
left: initial;
width: 100px;
top: 25px;
}
.add-new-music {
padding: 0 5px 0 10px;
font-size: 16px;
border-radius: 5px;
background: #f8f8f8;
border: none;
}
.add-new-music:hover {
background: darken(#f8f8f8, 5%);
}
.sound-cloud-link {
position: absolute;
right: 55px;
bottom: 22px;
}
#visualization {
position: absolute;
top: 0;
left: 0;
}
#save-beat-preferences-star {
position: absolute;
top: 5px;
left: 5px;
z-index: 1000;
md-icon {
color: $secondaryThemeColor !important;
font-size: 25px;
cursor: default;
}
}
.visualizers-menu {
left: -135px;
}
.display-icon {
background: url(images/huegasm.png) center center no-repeat;
background-size: 80px 80px;
}
#artwork {
position: absolute;
width: 100%;
overflow: hidden;
img {
display: block;
margin: 0 auto;
max-height: 400px;
}
}
.keyboard-arrow-down {
font-size: 20px;
}
.visualizers-menu .paper-icon {
margin-left: 10px;
position: relative;
top: -4px;
}
.close {
font-size: 18px !important;
color: rgb(51, 51, 51);
display: none;
text-shadow: none;
&:hover {
color: darken(#333333, 5%) !important;
}
}
.ember-notify-default.ember-notify-cn {
top: 0;
bottom: initial;
}
#soundcloud-logo {
display: block;
}
#soundcloud-logo-small {
display: none;
}
#soundcloud-tutorial {
width: 100%;
}
@media(max-width:1100px) {
#soundcloud-logo {
display: none;
}
#soundcloud-logo-small {
display: block;
}
}
@media(min-width:767px) and (max-width:1200px) {
#add-new-music-label {
display: none;
}
#play-list-controls .paper-button {
border: 1px solid $whitish;
border-radius: 5px;
}
}
@media(max-width: 500px) {
#sensitivity-settings .noUi-value-vertical {
display: none;
}
.option-description {
height: 55px;
}
}
// mobile overrides
@media(max-width:767px) {
div#player-bottom {
display: block !important;
}
#beat-area {
height: initial;
}
#seek-slider {
height: 8px;
.noUi-handle {
opacity: 1 !important;
}
}
#seek-slider {
margin-bottom: 15px;
}
.close {
display: block;
}
#save-beat-preferences-star {
right: 5px;
left: initial;
}
md-checkbox {
padding-right: 20px !important;
}
}

View file

@ -0,0 +1,56 @@
.noUi-value-vertical {
margin-top: -10px;
transform: none;
}
.noUi-value-vertical, .noUi-pips {
color: inherit !important;
}
.noUi-vertical .noUi-handle {
border: 1px solid #A3A0A0;
width: 26px;
}
.noUi-vertical .noUi-handle:after, .noUi-vertical .noUi-handle:before{
background: grey;
}
.noUi-base {
cursor: pointer;
}
.noUi-connect {
background-color: $secondaryThemeColor;
}
.noUi-handle {
cursor: pointer;
}
.noUi-horizontal .noUi-handle {
width: 0.4em;
height: 1.3em;
left: -0.071em;
top: -0.550em;
transition-duration: 0.1s;
background: $playerDefaultIconColor !important;
}
.noUi-horizontal .noUi-handle:hover {
background: white !important;
}
.noUi-target {
margin: 0 auto;
}
.noUi-base {
background-color: #ADADAD;
border: 1px solid #797979;
}
.noUi-vertical {
height: 200px;
margin: 15px auto 10px;
}

View file

@ -0,0 +1,72 @@
@import 'ember-paper';
.paper-icon {
cursor: pointer;
}
md-checkbox .md-icon, .md-off, .md-on {
border-color: inherit !important;
}
md-checkbox.md-default-theme.md-checked .md-icon {
background: $secondaryThemeColor;
}
md-checkbox .md-label {
width: 125px;
text-align: left;
}
.md-button {
flex-direction: unset;
span {
width: 100%;
}
}
md-switch[disabled=disabled], md-switch[disabled=disabled] .md-container, md-slider[disabled=disabled] {
cursor: not-allowed;
}
md-progress-circular {
margin: 0 auto 20px auto !important;
}
md-progress-linear {
margin-bottom: 50px !important;
}
md-slider {
cursor: pointer;
}
.md-thumb-text {
user-select: none;
}
md-slider.md-default-theme .md-thumb:after {
border-color: $secondaryThemeColor;
background-color: $secondaryThemeColor;
}
md-icon {
color: rgba(0, 0, 0, 0.54) !important;
}
md-switch.md-default-theme.md-checked .md-thumb {
background-color: $secondaryThemeColor;
}
.ember-basic-dropdown-trigger {
outline: none !important;
}
md-list-item {
margin-bottom: 2vh;
}
@media(max-width:500px) {
#save-beat-preferences-star {
right: 5px;
}
}

14
chrome/bower.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "huegasm",
"dependencies": {
"JavaScript-ID3-Reader": "https://github.com/aadsm/JavaScript-ID3-Reader.git",
"bootstrap-sass": "^3.3.5",
"hammer.js": "^2.0.8",
"intro.js": "^2.1.0",
"jquery-mousewheel": "^3.1.13",
"locallyjs": "^0.3.2",
"matchMedia": "^0.3.0",
"nouislider": "^9.0.0",
"velocity": "^1.3.1"
}
}

View file

@ -0,0 +1,47 @@
/* jshint node: true */
module.exports = function (environment) {
var ENV = {
modulePrefix: 'huegasm',
podModulePrefix: 'huegasm/pods',
environment: environment,
rootURL: '',
locationType: 'hash',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
// ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.locationType = 'none';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
}
if (environment === 'production') {
}
return ENV;
};

29
chrome/ember-cli-build.js Normal file
View file

@ -0,0 +1,29 @@
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var Funnel = require('broccoli-funnel');
module.exports = function (defaults) {
var app = new EmberApp(defaults, {
fingerprint: {
enabled: false
}
});
var extraAssets = new Funnel('bower_components/bootstrap-sass/assets/fonts/bootstrap/', {
srcDir: '/',
include: ['**'],
destDir: '/fonts/bootstrap'
});
app.import('vendor/dancer.js');
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js');
app.import('bower_components/intro.js/intro.js');
app.import('bower_components/intro.js/introjs.css');
app.import('bower_components/intro.js/themes/introjs-nassim.css');
app.import('bower_components/JavaScript-ID3-Reader/dist/id3-minimized.js');
app.import('bower_components/jquery-mousewheel/jquery.mousewheel.js');
app.import('bower_components/locallyjs/dist/locally.min.js');
app.import('bower_components/velocity/velocity.js');
return app.toTree(extraAssets);
};

46
chrome/package.json Normal file
View file

@ -0,0 +1,46 @@
{
"name": "huegasm",
"version": "1.0.0",
"description": "Huegasm is a free web application for managing and synchronizing your Philips Hue lights with the beat of your music.",
"private": true,
"directories": {
"doc": "doc",
"test": "tests"
},
"scripts": {
"start": "ember server --live-reload=false",
"build": "ember build --env=production"
},
"engines": {
"node": ">= 0.12.0"
},
"author": "Egor Philippov",
"license": "MIT",
"devDependencies": {
"broccoli-asset-rev": "^2.2.0",
"ember-ajax": "^2.0.1",
"ember-cli": "^2.8.0",
"ember-cli-app-version": "^2.0.0",
"ember-cli-babel": "^5.1.5",
"ember-cli-dependency-checker": "^1.2.0",
"ember-cli-htmlbars": "^1.0.1",
"ember-cli-htmlbars-inline-precompile": "^0.3.1",
"ember-cli-inject-live-reload": "^1.3.1",
"ember-cli-nouislider": "^0.11.0",
"ember-cli-release": "0.2.8",
"ember-cli-shims": "^1.0.2",
"ember-cli-sass": "^6.0.0",
"ember-cli-sri": "^2.1.0",
"ember-cli-test-loader": "^1.1.0",
"ember-cli-uglify": "^1.2.0",
"ember-export-application-global": "^1.0.4",
"ember-load-initializers": "^0.6.3",
"ember-modal-dialog": "^0.9.0",
"ember-notify": "^5.0.4",
"ember-paper": "^1.0.0-alpha.14",
"ember-resolver": "^2.0.3",
"ember-truth-helpers": "^1.2.0",
"ember-source": "^2.11.0",
"loader.js": "^4.0.7"
}
}

BIN
chrome/public/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
chrome/public/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
chrome/public/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
chrome/public/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="341.939,163.139 247.72,163.139
246.876,163.139 158.423,163.139 "/>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="300.502,364.005 248.822,364.005
248.376,364.005 199.884,364.005 "/>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
304.533,379.239 248.705,384.044 248.212,384.067 195.853,388.567 "/>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
305.307,395.106 249.525,399.888 249.033,399.958 196.673,404.435 "/>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
304.533,410.974 248.705,415.755 248.212,415.802 195.853,420.302 "/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M342.314,163.139
c0,0,12.234-21.234,26.039-61.078c4.781-14.344,6.398-32.953-19.664-45.703c-22.734-11.133-69.75-16.969-97.922-16.477
c-28.219-0.492-75.234,5.344-97.969,16.477c-26.062,12.75-24.469,31.359-19.688,45.703c13.828,39.844,26.062,61.078,26.062,61.078
c29.766,93.003,27.633,183.851,27.633,183.851l14.883,17.016c0,0,0,55.781,0,63.211s11.672,12.773,20.695,14.883
c1.594,3.188,6.398,8.508,11.695,14.344c7.125,2.391,26.204,2.391,33.329,0c5.297-5.836,10.078-11.156,11.672-14.344
c9.047-2.109,20.742-7.453,20.742-14.883s0-63.211,0-63.211l14.883-17.016C314.705,346.989,312.549,256.142,342.314,163.139z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="341.939,163.139 247.72,163.139
246.876,163.139 158.423,163.139 "/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="300.502,364.005 248.822,364.005
248.376,364.005 199.884,364.005 "/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
304.533,379.239 248.705,384.044 248.212,384.067 195.853,388.567 "/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
305.307,395.106 249.525,399.888 249.033,399.958 196.673,404.435 "/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
304.533,410.974 248.705,415.755 248.212,415.802 195.853,420.302 "/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M342.314,163.139
c0,0,12.234-21.234,26.039-61.078c4.781-14.344,6.398-32.953-19.664-45.703c-22.734-11.133-69.75-16.969-97.922-16.477
c-28.219-0.492-75.234,5.344-97.969,16.477c-26.062,12.75-24.469,31.359-19.688,45.703c13.828,39.844,26.062,61.078,26.062,61.078
c29.766,93.003,27.633,183.851,27.633,183.851l14.883,17.016c0,0,0,55.781,0,63.211s11.672,12.773,20.695,14.883
c1.594,3.188,6.398,8.508,11.695,14.344c7.125,2.391,26.204,2.391,33.329,0c5.297-5.836,10.078-11.156,11.672-14.344
c9.047-2.109,20.742-7.453,20.742-14.883s0-63.211,0-63.211l14.883-17.016C314.705,346.989,312.549,256.142,342.314,163.139z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M276.748,445.245c0,0-4.406,6.281-6,8.625
c-1.57,2.344-3.047,9.586-20.602,9h0.023c-17.555,0.586-18.984-6.656-20.578-9c-1.547-2.344-7.078-8.273-7.078-8.273"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M250.17,377.042c0,0,36.844,0.422,40.969-3.188
c4.383-3.844,5.25-15.539,5.25-15.539s3.516-82.336,17.391-114.746c12.656-29.695,36.188-58.852,36.188-58.852
s24.234-4.711,35.25-34.945c11.016-30.211,12.094-47.367,12.094-47.367H247.709H101.436c0,0,1.125,17.156,12.117,47.367
c11.016,30.234,35.297,34.945,35.297,34.945s23.484,29.156,36.164,58.852c13.875,32.41,17.391,114.746,17.391,114.746
s0.891,11.695,5.297,15.539C211.779,377.464,250.17,377.042,250.17,377.042z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M98.881,87.405h302.273
c0,0,8.531-54.352-151.734-54.352S98.881,87.405,98.881,87.405z"/>
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="113.951,60.265 246.654,60.265
385.381,60.265 "/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M148.029,184.858
c0,0,52.711,8.648,101.367,8.648c48.633,0,101.391-8.648,101.391-8.648"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M207.396,386.815"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M206.646,386.136"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M209.154,424.784
c1.125,0.984,2.203,5.367,0.094,7.195c-2.133,1.758,0,3.164,0,3.164c0.117,1.992,18.914,2.414,41.953,0.938
c21.609-1.383,39.258-4.008,41.273-5.977c0,0,1.875-2.578,0-3.797c-1.852-1.242-4.172-4.289-2.812-6.844"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,412.573
c0.281,2.438,1.547,5.062-3.188,8.227c-2.344,1.523,0,3.164,0,3.164c4.734,2.344,18.891,2.438,41.953,0.938
c21.562-1.383,39.258-3.984,41.297-5.953c0,0,1.828-2.578,0-3.797c-1.875-1.266-3.094-4.219-2.859-6.867"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,400.737
c0.281,2.461,1.547,5.086-3.188,8.227c-2.344,1.523,0,3.188,0,3.188c4.734,2.32,18.891,2.391,41.953,0.938
c21.562-1.406,39.258-4.008,41.297-6c0,0,1.828-2.531,0-3.773c-1.875-1.242-3.094-4.219-2.859-6.891"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,388.597
c0.281,2.461,1.547,5.086-3.188,8.203c-2.344,1.523,0,3.211,0,3.211c4.734,2.297,18.891,2.438,41.953,0.938
c21.562-1.406,39.258-3.984,41.297-5.977c0,0,1.828-2.555,0-3.82c-1.875-1.219-3.094-4.195-2.859-6.844"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M212.576,436.995c0,0,1.594,7.312,12.141,9.047
c10.547,1.781,17.672,1.359,24.375,1.359s13.828,0.188,24.422-1.359c12.047-1.734,14.812-14.391,14.812-14.391"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M290.998,375.964c0,0,0,6.727-1.008,7.711
c-1.008,1.031-3.352,1.242-3.352,1.242s-74.859,5.484-78.328,2.836c-2.062-1.523-1.758-11.789-1.758-11.789"/>
<line fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" x1="137.436" y1="87.405" x2="143.365" y2="100.483"/>
<line fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" x1="361.732" y1="87.405" x2="355.826" y2="100.483"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M276.748,445.245c0,0-4.406,6.281-6,8.625
c-1.57,2.344-3.047,9.586-20.602,9h0.023c-17.555,0.586-18.984-6.656-20.578-9c-1.547-2.344-7.078-8.273-7.078-8.273"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M250.17,377.042c0,0,36.844,0.422,40.969-3.188
c4.383-3.844,5.25-15.539,5.25-15.539s3.516-82.336,17.391-114.746c12.656-29.695,36.188-58.852,36.188-58.852
s24.234-4.711,35.25-34.945c11.016-30.211,12.094-47.367,12.094-47.367H247.709H101.436c0,0,1.125,17.156,12.117,47.367
c11.016,30.234,35.297,34.945,35.297,34.945s23.484,29.156,36.164,58.852c13.875,32.41,17.391,114.746,17.391,114.746
s0.891,11.695,5.297,15.539C211.779,377.464,250.17,377.042,250.17,377.042z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M98.881,87.405h302.273
c0,0,8.531-54.352-151.734-54.352S98.881,87.405,98.881,87.405z"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="113.951,60.265 246.654,60.265
385.381,60.265 "/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M148.029,184.858
c0,0,52.711,8.648,101.367,8.648c48.633,0,101.391-8.648,101.391-8.648"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M207.396,386.815"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M206.646,386.136"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M209.154,424.784
c1.125,0.984,2.203,5.367,0.094,7.195c-2.133,1.758,0,3.164,0,3.164c0.117,1.992,18.914,2.414,41.953,0.938
c21.609-1.383,39.258-4.008,41.273-5.977c0,0,1.875-2.578,0-3.797c-1.852-1.242-4.172-4.289-2.812-6.844"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,412.573
c0.281,2.438,1.547,5.062-3.188,8.227c-2.344,1.523,0,3.164,0,3.164c4.734,2.344,18.891,2.438,41.953,0.938
c21.562-1.383,39.258-3.984,41.297-5.953c0,0,1.828-2.578,0-3.797c-1.875-1.266-3.094-4.219-2.859-6.867"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,400.737
c0.281,2.461,1.547,5.086-3.188,8.227c-2.344,1.523,0,3.188,0,3.188c4.734,2.32,18.891,2.391,41.953,0.938
c21.562-1.406,39.258-4.008,41.297-6c0,0,1.828-2.531,0-3.773c-1.875-1.242-3.094-4.219-2.859-6.891"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,388.597
c0.281,2.461,1.547,5.086-3.188,8.203c-2.344,1.523,0,3.211,0,3.211c4.734,2.297,18.891,2.438,41.953,0.938
c21.562-1.406,39.258-3.984,41.297-5.977c0,0,1.828-2.555,0-3.82c-1.875-1.219-3.094-4.195-2.859-6.844"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M212.576,436.995c0,0,1.594,7.312,12.141,9.047
c10.547,1.781,17.672,1.359,24.375,1.359s13.828,0.188,24.422-1.359c12.047-1.734,14.812-14.391,14.812-14.391"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M290.998,375.964c0,0,0,6.727-1.008,7.711
c-1.008,1.031-3.352,1.242-3.352,1.242s-74.859,5.484-78.328,2.836c-2.062-1.523-1.758-11.789-1.758-11.789"/>
<line fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" x1="137.436" y1="87.405" x2="143.365" y2="100.483"/>
<line fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" x1="361.732" y1="87.405" x2="355.826" y2="100.483"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.452,55.93
c-4.196,0-88.923-0.258-129.892,8.508v12c0,0,3.984,3.492,10.992,4.992c0,0,0.516,95.555,13.008,134.555
c12.516,39,18,40.523,27.516,50.015c9.516,9.516,20.508,20.016,20.508,40.5c0,20.531,0,84.047,0,84.047l23.508,19.5h3v14.508
h-6.984v15.516h26.484v-15.516h-6v-15.023h17.672h0.353h17.649v15.023h-6v15.516h26.531v-15.516h-7.031v-14.508h3l23.531-19.5
c0,0,0-63.516,0-84.047c0-20.484,10.992-30.984,20.484-40.5c9.516-9.492,15.023-11.016,27.516-50.015
c12.516-39,13.031-134.555,13.031-134.555c6.984-1.5,10.992-4.992,10.992-4.992v-12C340.352,55.672,255.625,55.93,251.452,55.93z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.452,55.93
c-4.196,0-88.923-0.258-129.892,8.508v12c0,0,3.984,3.492,10.992,4.992c0,0,0.516,95.555,13.008,134.555
c12.516,39,18,40.523,27.516,50.015c9.516,9.516,20.508,20.016,20.508,40.5c0,20.531,0,84.047,0,84.047l23.508,19.5h3v14.508
h-6.984v15.516h26.484v-15.516h-6v-15.023h17.672h0.353h17.649v15.023h-6v15.516h26.531v-15.516h-7.031v-14.508h3l23.531-19.5
c0,0,0-63.516,0-84.047c0-20.484,10.992-30.984,20.484-40.5c9.516-9.492,15.023-11.016,27.516-50.015
c12.516-39,13.031-134.555,13.031-134.555c6.984-1.5,10.992-4.992,10.992-4.992v-12C340.352,55.672,255.625,55.93,251.452,55.93z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M185.782,359.98
c-4.172,5.203-10.594,8.531-17.766,8.531c-12.562,0-22.734-10.172-22.734-22.734c0-1.922,0.234-3.797,0.703-5.578"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M444.813,174.026
c0,106.829-86.625,197.345-193.453,197.345c-38.86,0-75.047-11.438-105.375-31.172c-53.016-34.5-88.078-99.423-88.078-167.392"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.501,126.073
c106.828,0.328,193.359,21.797,193.312,47.953c-0.094,26.062-86.766,47.016-193.594,46.688
c-106.829-0.281-193.36-21.75-193.313-47.906C58,146.698,144.625,125.745,251.501,126.073z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.454,131.417
c98.203,0.281,177.75,16.969,177.703,37.266c-0.141,20.344-79.734,36.609-177.891,36.328
c-98.204-0.281-177.75-16.969-177.704-37.312C73.61,147.401,153.297,131.089,251.454,131.417z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M185.782,359.98
c-4.172,5.203-10.594,8.531-17.766,8.531c-12.562,0-22.734-10.172-22.734-22.734c0-1.922,0.234-3.797,0.703-5.578"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M444.813,174.026
c0,106.829-86.625,197.345-193.453,197.345c-38.86,0-75.047-11.438-105.375-31.172c-53.016-34.5-88.078-99.423-88.078-167.392"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.501,126.073
c106.828,0.328,193.359,21.797,193.312,47.953c-0.094,26.062-86.766,47.016-193.594,46.688
c-106.829-0.281-193.36-21.75-193.313-47.906C58,146.698,144.625,125.745,251.501,126.073z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.454,131.417
c98.203,0.281,177.75,16.969,177.703,37.266c-0.141,20.344-79.734,36.609-177.891,36.328
c-98.204-0.281-177.75-16.969-177.704-37.312C73.61,147.401,153.297,131.089,251.454,131.417z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M114.314,82.195
c26.32-1.031,66.891,15.984,120.938,54.375c18,12.844,85.828,65.906,132.093,124.313c36.234,44.25,72.211,104.344,53.719,129.469
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
C94.58,134.742,72.97,83.695,114.314,82.195z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M421.063,390.352
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
c-11.695-20.625-27.422-56.344-14.062-71.625c-8.18,9.328-8.531,21.75-2.789,44.812c5.789,23.109,18.586,78.75,20.766,89.578
c2.203,10.734,18.211,59.907,13.945,99.329c-3.141,22.312-0.281,35.625,9.938,44.859c10.219,9.281,13.125,12.094,57,7.734
c21.938-2.25,29.438-2.906,91.593,7.312c53.766,9.188,94.312,16.734,106.148,16.453S413.376,399.117,421.063,390.352z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M145.392,119.929
c20.203-0.797,51.328,12.328,92.766,41.672c13.781,9.891,65.789,50.578,101.273,95.391
c27.773,33.891,56.789,83.016,42.633,102.328c-3.422,4.312-19.031,16.734-56.484,0.516c-37.477-16.125-84.281-54-110.46-78.422
c-21.68-19.406-54.258-53.954-80.789-97.079C122.892,164.132,113.705,121.054,145.392,119.929z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M192.267,185.601
c10.406-0.422,26.484,6.422,47.953,21.75c7.125,5.156,34.007,26.391,52.382,49.735c14.367,17.672,29.391,43.266,22.055,53.297
c-1.781,2.25-9.844,8.719-29.203,0.328c-19.383-8.438-43.593-28.219-57.14-40.969c-11.203-10.125-28.055-28.079-41.789-50.579
C180.595,208.617,175.861,186.164,192.267,185.601z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M114.08,226.57
c10.477,21.047,24,50.344,68.906,92.344c44.906,42.047,81.609,57.703,99.07,63.562"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M114.314,82.195
c26.32-1.031,66.891,15.984,120.938,54.375c18,12.844,85.828,65.906,132.093,124.313c36.234,44.25,72.211,104.344,53.719,129.469
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
C94.58,134.742,72.97,83.695,114.314,82.195z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M421.063,390.352
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
c-11.695-20.625-27.422-56.344-14.062-71.625c-8.18,9.328-8.531,21.75-2.789,44.812c5.789,23.109,18.586,78.75,20.766,89.578
c2.203,10.734,18.211,59.907,13.945,99.329c-3.141,22.312-0.281,35.625,9.938,44.859c10.219,9.281,13.125,12.094,57,7.734
c21.938-2.25,29.438-2.906,91.593,7.312c53.766,9.188,94.312,16.734,106.148,16.453S413.376,399.117,421.063,390.352z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M145.392,119.929
c20.203-0.797,51.328,12.328,92.766,41.672c13.781,9.891,65.789,50.578,101.273,95.391
c27.773,33.891,56.789,83.016,42.633,102.328c-3.422,4.312-19.031,16.734-56.484,0.516c-37.477-16.125-84.281-54-110.46-78.422
c-21.68-19.406-54.258-53.954-80.789-97.079C122.892,164.132,113.705,121.054,145.392,119.929z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M192.267,185.601
c10.406-0.422,26.484,6.422,47.953,21.75c7.125,5.156,34.007,26.391,52.382,49.735c14.367,17.672,29.391,43.266,22.055,53.297
c-1.781,2.25-9.844,8.719-29.203,0.328c-19.383-8.438-43.593-28.219-57.14-40.969c-11.203-10.125-28.055-28.079-41.789-50.579
C180.595,208.617,175.861,186.164,192.267,185.601z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M114.08,226.57
c10.477,21.047,24,50.344,68.906,92.344c44.906,42.047,81.609,57.703,99.07,63.562"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M287.609,219.602
c32.25,29.578,49.617,63.141,38.719,75c-10.852,11.859-45.844-2.484-78.091-32.109c-32.273-29.578-49.594-63.188-38.719-75.094
C220.393,175.539,255.338,189.93,287.609,219.602z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M299.422,203.102
c54.656,50.203,84.516,106.594,66.656,126.094c-17.859,19.453-76.641-5.344-131.341-55.5
c-54.656-50.109-84.516-106.641-66.656-126.094C185.94,128.102,244.721,152.945,299.422,203.102z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M313.953,187.445
c75.984,69.703,117.422,148.125,92.625,175.219c-24.844,27.047-106.547-7.453-182.529-77.156
c-75.961-69.656-117.445-148.125-92.625-175.219C156.245,83.242,237.948,117.789,313.953,187.445z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
c0,0-39.656-40.125-45.422-108.609c-3.211-33.938,12.844-87.844,37.078-92.156c24.305-4.312,38.391,2.203,61.406,11.203
c0,0,44.016,19.078,100.263,66.047c56.203,46.969,94.5,102.891,111.844,146.156c1.969,4.828,12.469,28.781,0.188,45.656
c-3.492,4.781-17.906,15-22.594,16.969c-5.719,2.391-20.578,11.625-57.234,15.656c0,0-45.422,6.422-104.623-29.719
c0,0-3.844,7.031-6.047,10.219c-2.25,3.188-48.633,11.859-87,8.016c-38.391-3.844-55.031-13.781-55.031-13.781s-1.266,0,0-2.578
C83.846,369.32,149.752,298.773,149.752,298.773z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
c0,0,39.656,48.609,80.906,71.203"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M287.609,219.602
c32.25,29.578,49.617,63.141,38.719,75c-10.852,11.859-45.844-2.484-78.091-32.109c-32.273-29.578-49.594-63.188-38.719-75.094
C220.393,175.539,255.338,189.93,287.609,219.602z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M299.422,203.102
c54.656,50.203,84.516,106.594,66.656,126.094c-17.859,19.453-76.641-5.344-131.341-55.5
c-54.656-50.109-84.516-106.641-66.656-126.094C185.94,128.102,244.721,152.945,299.422,203.102z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M313.953,187.445
c75.984,69.703,117.422,148.125,92.625,175.219c-24.844,27.047-106.547-7.453-182.529-77.156
c-75.961-69.656-117.445-148.125-92.625-175.219C156.245,83.242,237.948,117.789,313.953,187.445z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
c0,0-39.656-40.125-45.422-108.609c-3.211-33.938,12.844-87.844,37.078-92.156c24.305-4.312,38.391,2.203,61.406,11.203
c0,0,44.016,19.078,100.263,66.047c56.203,46.969,94.5,102.891,111.844,146.156c1.969,4.828,12.469,28.781,0.188,45.656
c-3.492,4.781-17.906,15-22.594,16.969c-5.719,2.391-20.578,11.625-57.234,15.656c0,0-45.422,6.422-104.623-29.719
c0,0-3.844,7.031-6.047,10.219c-2.25,3.188-48.633,11.859-87,8.016c-38.391-3.844-55.031-13.781-55.031-13.781s-1.266,0,0-2.578
C83.846,369.32,149.752,298.773,149.752,298.773z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
c0,0,39.656,48.609,80.906,71.203"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M441.079,276.902
c-11.391,22.406-65.766,30-182.438-56.388c-59.576-46.547-97.919-101.297-98.904-133.031c0.047-5.812,0.188-14.156,7.5-20.156
c-26.297,23.531-115.406,123-103.828,222.56c11.625,99.656,102.234,142.125,144.281,147.234
c26.625,3.188,57.326,0.047,86.294-11.625C355.438,400.793,414.267,339.105,441.079,276.902z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M365.61,232.936
c-5.672,11.203-32.859,13.875-91.219-29.297c-29.857-23.344-46.873-45.281-47.435-63.328c0.047-4.969-1.828-17.578,15.938-17.812
c18.513,0.234,49.357,13.125,82.732,42.047C342.735,177.999,375.501,211.092,365.61,232.936z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M195.597,59.827
c36.984,0.562,98.763,26.25,165.513,84.234c56.953,48.562,92.344,105.937,79.969,132.935
c-11.391,22.406-65.766,30.047-182.484-56.388c-59.576-46.594-97.966-101.344-98.951-133.125
C159.738,77.639,160.113,60.342,195.597,59.827z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M136.535,348.434
c-2.766-0.094-5.344,0.516-7.734,2.062c-4.969,3.422-9.094,17.203,6.469,29.812c15.609,12.562,32.672,18.938,41.859,11.578
c3.656-2.953,3.516-7.172,1.219-11.812 M172.16,267.434c-7.406,24.047-14.766,62.625-40.594,85.875
c-1.359,6.375,0,16.969,11.203,24.75c11.203,7.828,19.594,11.625,30.75,8.812c3.422-6.609,40.406-51.891,76.64-60.234
M102.785,147.483c1.031,15.703,9.094,77.812,106.5,151.169c96.091,72.422,154.169,71.203,178.732,58.641"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M441.079,276.902
c-11.391,22.406-65.766,30-182.438-56.388c-59.576-46.547-97.919-101.297-98.904-133.031c0.047-5.812,0.188-14.156,7.5-20.156
c-26.297,23.531-115.406,123-103.828,222.56c11.625,99.656,102.234,142.125,144.281,147.234
c26.625,3.188,57.326,0.047,86.294-11.625C355.438,400.793,414.267,339.105,441.079,276.902z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M365.61,232.936
c-5.672,11.203-32.859,13.875-91.219-29.297c-29.857-23.344-46.873-45.281-47.435-63.328c0.047-4.969-1.828-17.578,15.938-17.812
c18.513,0.234,49.357,13.125,82.732,42.047C342.735,177.999,375.501,211.092,365.61,232.936z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M195.597,59.827
c36.984,0.562,98.763,26.25,165.513,84.234c56.953,48.562,92.344,105.937,79.969,132.935
c-11.391,22.406-65.766,30.047-182.484-56.388c-59.576-46.594-97.966-101.344-98.951-133.125
C159.738,77.639,160.113,60.342,195.597,59.827z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M136.535,348.434
c-2.766-0.094-5.344,0.516-7.734,2.062c-4.969,3.422-9.094,17.203,6.469,29.812c15.609,12.562,32.672,18.938,41.859,11.578
c3.656-2.953,3.516-7.172,1.219-11.812 M172.16,267.434c-7.406,24.047-14.766,62.625-40.594,85.875
c-1.359,6.375,0,16.969,11.203,24.75c11.203,7.828,19.594,11.625,30.75,8.812c3.422-6.609,40.406-51.891,76.64-60.234
M102.785,147.483c1.031,15.703,9.094,77.812,106.5,151.169c96.091,72.422,154.169,71.203,178.732,58.641"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M404.109,402.393v-64.711H186.448
c0,0-92.288,2.23-92.288-61.205c0,57.201,0,59.703,0,59.703s-2.002,66.213,95.292,66.213
C266.178,402.393,404.109,402.393,404.109,402.393z"/>
<rect x="127.516" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
<rect x="187.722" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
<rect x="247.926" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.499" height="15.518"/>
<rect x="308.09" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.541" height="15.518"/>
<polygon fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" points="380.354,149.172 366.339,142.506
366.111,126.192 380.127,132.836 "/>
<rect x="361.311" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
<rect x="301.104" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
<rect x="240.919" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.566" height="15.562"/>
<rect x="180.737" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.562"/>
<polygon fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" points="130.884,350.697 144.945,357.342
144.809,373.678 130.748,367.035 "/>
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M94.114,91.924v64.666h217.707
c0,0,83.278-5.506,92.288,61.207c0-57.18,0-59.705,0-59.705s2.002-66.168-95.291-66.168
C232.045,91.924,94.114,91.924,94.114,91.924z"/>
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M129.382,326.67
c0.819-11.195,7.281-26.826,34.494-35.654c20.068-6.508,139.185-13.289,139.185-13.289s97.498-1.25,100.911-60.75
c-3.709-25.414-18.293-40.182-34.812-48.76c-0.5,11.832-6.735,28.146-34.768,35.041c-38.612,9.51-139.208,13.287-139.208,13.287
s-98.659-1.684-100.957,61.728C94.934,303.779,110.906,318.342,129.382,326.67z"/>
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M94.16,279.957
c0,0.182-0.045,0.342-0.045,0.547c0,0.158,0.045,0.295,0.045,0.477C94.16,280.617,94.16,280.299,94.16,279.957z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M404.109,402.393v-64.711H186.448
c0,0-92.288,2.23-92.288-61.205c0,57.201,0,59.703,0,59.703s-2.002,66.213,95.292,66.213
C266.178,402.393,404.109,402.393,404.109,402.393z"/>
<rect x="127.516" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
<rect x="187.722" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
<rect x="247.926" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.499" height="15.518"/>
<rect x="308.09" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.541" height="15.518"/>
<polygon fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" points="380.354,149.172 366.339,142.506
366.111,126.192 380.127,132.836 "/>
<rect x="361.311" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
<rect x="301.104" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
<rect x="240.919" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.566" height="15.562"/>
<rect x="180.737" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.562"/>
<polygon fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" points="130.884,350.697 144.945,357.342
144.809,373.678 130.748,367.035 "/>
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M94.114,91.924v64.666h217.707
c0,0,83.278-5.506,92.288,61.207c0-57.18,0-59.705,0-59.705s2.002-66.168-95.291-66.168
C232.045,91.924,94.114,91.924,94.114,91.924z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M129.382,326.67
c0.819-11.195,7.281-26.826,34.494-35.654c20.068-6.508,139.185-13.289,139.185-13.289s97.498-1.25,100.911-60.75
c-3.709-25.414-18.293-40.182-34.812-48.76c-0.5,11.832-6.735,28.146-34.768,35.041c-38.612,9.51-139.208,13.287-139.208,13.287
s-98.659-1.684-100.957,61.728C94.934,303.779,110.906,318.342,129.382,326.67z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M94.16,279.957
c0,0.182-0.045,0.342-0.045,0.547c0,0.158,0.045,0.295,0.045,0.477C94.16,280.617,94.16,280.299,94.16,279.957z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M249.167,347.182
c46.219,49.922,139.031,114.234,209.227,74.719c49.617-28.172,21.727-114.234-6.75-152.016
c-38.648-52.129-67.758-73.879-84.258-84.004c-2.602-2.016-7.875-5.203-1.852-5.203c6,0,35.156,3.188,34.969-29.672
c-0.211-32.906-39.305-77.25-78.961-95.906c-39.68-18.609-72.375-6.562-66.188,25.359c6.188,32.016,34.219,59.438,46.828,69.797
c2.625,2.438,5.812,6-1.312,3.938c-7.172-2.062-41.766-9.984-65.812-2.25c-24.094,7.688-32.156,21.234-35.156,25.172
c-0.188,1.547-2.836,4.5-5.086-1.5s-16.359-48.094-68.062-78.188C96.026,79.521,50.229,66.208,23.534,91.943
s9.398,84.234,32.344,102.281s76.898,57.895,124.828,32.907c2.836-1.312,8.086-6.563,9.023,2.813
C190.667,239.367,196.128,288.588,249.167,347.182z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M176.792,229.006
c-1.734,22.175-12.984,95.16,64.828,169.597c77.859,74.438,188.531,62.203,227.016,15.656"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.917,363.447
c-8.859,7.969-31.406,30.562-33.703,32.766c-2.25,2.297-15.469,11.859-3.656,19.172c11.859,7.359,30.984,15.75,70.805,14.953
c8.461,0,24.375-1.734,33.633-4.219"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M176.464,396.916
c5.859,6.656,19.172,24.188,85.734,18.891"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M235.62,161.927
c-27.094,9.562-83.109,78.284,40.594,201.52c36.867,32.156,126.727,88.922,180.891,44.578
c54.117-44.391-14.297-139.172-25.594-153.047c-5.766-7.125-31.781-40.551-71.438-66.192
C322.245,164.318,269.37,148.896,235.62,161.927z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.183,193.99
c-21.797,7.734-66.891,63.051,32.672,162.239c29.672,25.828,101.977,71.484,145.547,35.812
c43.594-35.719-11.484-111.984-20.578-123.188c-4.641-5.719-25.547-32.627-57.492-53.254
C320.909,195.912,278.37,183.49,251.183,193.99z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M52.011,92.13
c-17.812,5.391-49.875,36.516,20.953,100.5c20.016,15.984,68.977,44.393,98.414,22.219c29.461-22.172-4.734-69.188-10.898-76.172
c-3.117-3.562-20.297-20.578-41.883-33.375C97.995,93.115,70.386,85.662,52.011,92.13z"/>
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M282.894,58.943
c-16.57,4.172-30.914,30.516,20.508,80.297c14.531,12.516,55.969,40.5,77.344,23.25c21.375-17.297-8.062-55.969-12.867-61.078
c-4.055-4.312-17.039-18.562-32.695-28.547C320.229,63.349,296.229,53.88,282.894,58.943z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="Layer_1" display="none">
<rect display="inline" width="500" height="500"/>
</g>
<g id="Layer_2">
<g>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M249.167,347.182
c46.219,49.922,139.031,114.234,209.227,74.719c49.617-28.172,21.727-114.234-6.75-152.016
c-38.648-52.129-67.758-73.879-84.258-84.004c-2.602-2.016-7.875-5.203-1.852-5.203c6,0,35.156,3.188,34.969-29.672
c-0.211-32.906-39.305-77.25-78.961-95.906c-39.68-18.609-72.375-6.562-66.188,25.359c6.188,32.016,34.219,59.438,46.828,69.797
c2.625,2.438,5.812,6-1.312,3.938c-7.172-2.062-41.766-9.984-65.812-2.25c-24.094,7.688-32.156,21.234-35.156,25.172
c-0.188,1.547-2.836,4.5-5.086-1.5s-16.359-48.094-68.062-78.188C96.026,79.521,50.229,66.208,23.534,91.943
s9.398,84.234,32.344,102.281s76.898,57.895,124.828,32.907c2.836-1.312,8.086-6.563,9.023,2.813
C190.667,239.367,196.128,288.588,249.167,347.182z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M176.792,229.006
c-1.734,22.175-12.984,95.16,64.828,169.597c77.859,74.438,188.531,62.203,227.016,15.656"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.917,363.447
c-8.859,7.969-31.406,30.562-33.703,32.766c-2.25,2.297-15.469,11.859-3.656,19.172c11.859,7.359,30.984,15.75,70.805,14.953
c8.461,0,24.375-1.734,33.633-4.219"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M176.464,396.916
c5.859,6.656,19.172,24.188,85.734,18.891"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M235.62,161.927
c-27.094,9.562-83.109,78.284,40.594,201.52c36.867,32.156,126.727,88.922,180.891,44.578
c54.117-44.391-14.297-139.172-25.594-153.047c-5.766-7.125-31.781-40.551-71.438-66.192
C322.245,164.318,269.37,148.896,235.62,161.927z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.183,193.99
c-21.797,7.734-66.891,63.051,32.672,162.239c29.672,25.828,101.977,71.484,145.547,35.812
c43.594-35.719-11.484-111.984-20.578-123.188c-4.641-5.719-25.547-32.627-57.492-53.254
C320.909,195.912,278.37,183.49,251.183,193.99z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M52.011,92.13
c-17.812,5.391-49.875,36.516,20.953,100.5c20.016,15.984,68.977,44.393,98.414,22.219c29.461-22.172-4.734-69.188-10.898-76.172
c-3.117-3.562-20.297-20.578-41.883-33.375C97.995,93.115,70.386,85.662,52.011,92.13z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M282.894,58.943
c-16.57,4.172-30.914,30.516,20.508,80.297c14.531,12.516,55.969,40.5,77.344,23.25c21.375-17.297-8.062-55.969-12.867-61.078
c-4.055-4.312-17.039-18.562-32.695-28.547C320.229,63.349,296.229,53.88,282.894,58.943z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -0,0 +1,28 @@
{
"manifest_version": 2,
"name": "Huegasm",
"description": "Huegasm is a free web application for managing and synchronizing your Philips Hue lights with the beat of your music.",
"version": "1.0",
"content_security_policy": "script-src https://connect.soundcloud.com 'self'; object-src 'self'",
"icons": {
"16": "16x16.png",
"48": "48x48.png",
"128": "128x128.png"
},
"background": {
"page": "index.html"
},
"browser_action": {
"default_icon": {
"16": "16x16.png",
"32": "32x32.png"
},
"default_popup": "index.html",
"default_title": "Huegasm"
},
"permissions": [
"activeTab",
"background",
"https://ajax.googleapis.com/"
]
}

709
chrome/vendor/dancer.js vendored Normal file
View file

@ -0,0 +1,709 @@
/*
* 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 = 'X.X.X';
Dancer.adapters = {};
Dancer.prototype = {
load : function ( source, micBoost, useMic ) {
// Loading an Audio element
if ( source instanceof HTMLElement ) {
this.source = source;
// Loading an object with src, [codecs]
} else if(source instanceof EventTarget){
this.source = source;
} else {
this.source = window.Audio ? new Audio() : {};
this.source.src = Dancer._makeSupportedPath( source.src, source.codecs );
}
this.useMic = useMic === true;
this.boost = micBoost ? micBoost : 1;
this.audio = this.audioAdapter.load(this.source, this.useMic, this.boost);
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;
},
setBoost : function ( boost ) {
this.audioAdapter.setBoost( boost );
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].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 {
return '';
}
};
Dancer.canPlay = function ( type ) {
var canPlay = audioEl.canPlayType;
return !!(
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 );
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, 5 ];
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;
this.previousMag = 0;
this.canUseRatio = true;
this.canUseRatioHandle = null;
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);
this.canUseRatio = false;
if(this.canUseRatioHandle) {
clearTimeout(this.canUseRatioHandle);
this.canUseRatioHandle = null;
}
var self = this;
this.canUseRatioHandle = setTimeout(function(){
self.canUseRatio = true;
}, 5000);
} else {
if(magnitude/this.previousMag > this.threshold*5 && magnitude>0.1 && this.canUseRatio) {
this.onKick && this.onKick.call(this.dancer, magnitude, magnitude/this.previousMag);
} else {
this.offKick && this.offKick.call(this.dancer, magnitude);
}
this.currentThreshold -= this.decay;
this.previousMag = (magnitude > 0) ? magnitude : 0.0001;
}
},
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 ) {
var context;
if('AudioContext' in window) {
context = new AudioContext();
} else {
context = new webkitAudioContext();
}
this.dancer = dancer;
this.audio = new Audio();
this.context = context;
};
adapter.prototype = {
load : function (_source, useMic, boost) {
var _this = this;
this.audio = _source;
this.useMic = useMic;
this.boost = boost;
this.isLoaded = false;
this.progress = 0;
if(this.proc){
this.proc.onaudioprocess = null;
delete this.proc;
}
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.boost );
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 && e.currentTarget.duration !== Infinity ) {
_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;
},
setBoost : function( boost ){
if(this.fft){
this.fft.setBoost(boost);
}
this.boost = boost;
},
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) && this.useMic !== true ) 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 () {
try {
if(this.useMic){
this.source = this.context.createMediaStreamSource(this.audio);
} else {
this.source = this.context.createMediaElementSource(this.audio);
}
} catch (err) {
console.info('Dancer: '+ err);
return;
}
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;
})();
/*
* DSP.js - a comprehensive digital signal processing library for javascript
*
* Created by Corban Brook <corbanbrook@gmail.com> on 2010-01-01.
* Copyright 2010 Corban Brook. All rights reserved.
*
*/
// Fourier Transform Module used by DFT, FFT, RFFT
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);
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.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,
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 * boost;
}
};
}
/**
* 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)
* @param {Number} boost The coefficient
*
* @constructor
*/
function FFT(bufferSize, sampleRate, boost) {
FourierTransform.call(this, bufferSize, sampleRate, boost);
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();
};

View file

@ -11,7 +11,6 @@
"start": "ember server",
"build": "ember cordova:build --platform=android --environment=production --release",
"build-test": "ember cordova:build --platform=android",
"test": "ember test",
"cordova": "ember cdv:serve --platform=android"
},
"engines": {

View file

@ -9,8 +9,7 @@
},
"scripts": {
"start": "ember server",
"build": "ember build --env=production",
"test": "ember test"
"build": "ember build --env=production"
},
"engines": {
"node": ">= 0.12.0"