UNPKG

shredbeat

Version:

Music beat reacts to your keyboard shredding speed 🤘

1,816 lines (1,524 loc) • 5.59 MB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const { ipcRenderer } = window.require('electron'); const { HashRouter: Router, Route, Link } = require('react-router-dom'); const Main = require('./Main.jsx'); class App extends React.Component { render() { return React.createElement( Router, null, React.createElement( 'div', { className: 'ui grid stackable padded' }, React.createElement( 'div', { className: 'DragRegion' }, React.createElement( 'a', { onClick: this.onMinimizeWindowClick.bind(this), className: 'MinimizeHandle' }, React.createElement('i', { className: 'fa fa-window-minimize', 'aria-hidden': 'true' }) ), React.createElement( 'span', null, 'hold and drag' ), React.createElement( 'span', { className: 'ResizeHandle' }, 'resize \u2197' ) ), React.createElement(Route, { exact: true, path: '/', component: Main }) ) ); } onMinimizeWindowClick(event) { event.preventDefault(); ipcRenderer.send('minimize-window', true); } } module.exports = App; },{"./Main.jsx":3,"react":601,"react-dom":424,"react-router-dom":562}],2:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const Slider = require('react-slider'); const store = require('../lib/storeService'); class ConfigSettings extends React.Component { constructor() { super(); this.state = { sensitivity: (store.get('sensitivity') || 0.10) * 100 | 0, startingVolume: store.get('startingVolume') || -50, volumeSensitivity: (store.get('volumeSensitivity') || 0.10) * 100 | 0 }; } render() { const { sensitivity, startingVolume, volumeSensitivity } = this.state; return React.createElement( 'div', { className: 'ui grid ConfigSettings', id: 'ConfigSettings' }, React.createElement( 'form', { className: 'ui form' }, React.createElement( 'div', { className: 'ui field' }, React.createElement( 'label', null, 'Shredometer Sensitivity' ), React.createElement( Slider, { className: 'SettingRangeSlider', min: 0, step: 1, max: 100, defaultValue: sensitivity, orientation: 'horizontal', onChange: this.onSensitivityChange.bind(this), withBars: true }, React.createElement( 'div', { className: 'handle-label' }, sensitivity, '%' ) ) ), React.createElement( 'div', { className: 'ui field' }, React.createElement( 'label', null, 'Starting volume' ), React.createElement( Slider, { className: 'SettingRangeSlider', min: -100, step: 1, max: 0, defaultValue: startingVolume, orientation: 'horizontal', onChange: this.onStartingVolumeChange.bind(this), withBars: true }, React.createElement( 'div', { className: 'handle-label' }, startingVolume, '%' ) ) ), React.createElement( 'div', { className: 'ui field' }, React.createElement( 'label', null, 'Volume Sensitivity' ), React.createElement( Slider, { className: 'SettingRangeSlider', min: 0, step: 1, max: 100, defaultValue: volumeSensitivity, orientation: 'horizontal', onChange: this.onVolumeSensitivityChange.bind(this), withBars: true }, React.createElement( 'div', { className: 'handle-label' }, volumeSensitivity, '%' ) ) ) ) ); } onSensitivityChange(value) { const sensitivity = value / 100; store.set('sensitivity', sensitivity); this.setState({ sensitivity: value }); } onStartingVolumeChange(startingVolume) { store.set('startingVolume', startingVolume); this.setState({ startingVolume }); } onVolumeSensitivityChange(value) { const sensitivity = value / 100; store.set('volumeSensitivity', sensitivity); this.setState({ volumeSensitivity: value }); } } module.exports = ConfigSettings; },{"../lib/storeService":15,"react":601,"react-dom":424,"react-slider":575}],3:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const _ = require('lodash'); // https://github.com/electron/electron/issues/7300 const { remote, shell } = window.require('electron'); const musicService = require('../lib/musicService'); const shredometerService = require('../lib/shredometerService'); const store = require('../lib/storeService'); const player = require('../lib/playerService'); const Shredometer = require('./Shredometer.jsx'); const ConfigSettings = require('./ConfigSettings.jsx'); const Player = require('./Player.jsx'); class Main extends React.Component { constructor(props) { super(props); this.state = { playlistUrlInput: '', playlistUrlSubmitDisabled: false, playButtonDisabled: true, stopButtonDisabled: true, nextButtonDisabled: true, prevButtonDisabled: true, randomButtonDisabled: false, repeatButtonDisabled: false, randomActive: false, repeatActive: false, isMuted: false, maxVolume: 1, isPlaying: true, shredProgress: 0, errorMessage: null, isFetchingPlaylist: false }; player.on('EmptyQueue', () => { this.setState({ playButtonDisabled: true, stopButtonDisabled: true, nextButtonDisabled: true, prevButtonDisabled: true, randomButtonDisabled: false, repeatButtonDisabled: false }); }); player.on('Enqueue', () => { this.setState({ playButtonDisabled: false }); player.hasPrevious().then(hasPrevious => { this.setState({ previousButtonDisabled: !hasPrevious }); }); player.hasNext().then(hasNext => { this.setState({ nextButtonDisabled: !hasNext }); }); }); player.on('Play', () => { this.setState({ playButtonDisabled: true, stopButtonDisabled: false }); }); player.on('Stop', () => { this.setState({ playButtonDisabled: false, stopButtonDisabled: true }); }); player.on('Previous', () => { player.hasPrevious().then(hasPrevious => { this.setState({ previousButtonDisabled: !hasPrevious }); }); player.hasNext().then(hasNext => { this.setState({ nextButtonDisabled: !hasNext }); }); }); player.on('Next', () => { player.hasPrevious().then(hasPrevious => { this.setState({ previousButtonDisabled: !hasPrevious }); }); player.hasNext().then(hasNext => { this.setState({ nextButtonDisabled: !hasNext }); }); }); shredometerService.setSensitivity(store.get('sensitivity')); shredometerService.setStartingAmplitude(store.get('startingVolume')); shredometerService.setAmplitudeSensitivity(store.get('volumeSensitivity')); shredometerService.on('ShredProgress', _.throttle(shredProgress => { this.setState({ shredProgress }); }, 100)); shredometerService.on('ShredRate', _.throttle(shredRate => { player.setPlaybackRate(shredRate); }, 100)); shredometerService.on('AmplitudeRate', _.throttle(volume => { player.setVolume(volume); }, 100)); store.on('sensitivity', _.throttle(value => { shredometerService.setSensitivity(value); }, 100)); store.on('startingVolume', _.throttle(value => { shredometerService.setStartingAmplitude(value); }, 100)); store.on('volumeSensitivity', _.throttle(value => { shredometerService.setAmplitudeSensitivity(value); }, 100)); this.handleError = this.handleError.bind(this); this.onExternalClick = this.onExternalClick.bind(this); this.setPlaylistUrl = this.setPlaylistUrl.bind(this); const playlistUrl = store.get('playlistUrl'); if (playlistUrl) { this.state.playlistUrlInput = playlistUrl; this.setPlaylistUrl(playlistUrl); } } render() { const { playlistUrlInput, playlistUrlSubmitDisabled, playButtonDisabled, stopButtonDisabled, nextButtonDisabled, previousButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive, isMuted, maxVolume, shredProgress, errorMessage, isFetchingPlaylist } = this.state; return React.createElement( 'div', { className: 'ui grid stackable padded MainView' }, React.createElement( 'div', { className: 'ui large header MainTitle' }, 'Shredbeat' ), errorMessage ? React.createElement( 'div', { className: 'ui message error' }, errorMessage ) : null, React.createElement( 'div', { className: 'PlaylistFormContainer' }, React.createElement( 'form', { className: 'ui form PlaylistForm', onSubmit: this.onPlaylistFormSubmit.bind(this) }, React.createElement( 'div', { className: 'PlaylistUrlInputContainer' }, React.createElement('input', { type: 'text', className: 'ui input PlaylistUrlInput', defaultValue: playlistUrlInput, onInput: this.onPlaylistUrlInput.bind(this), placeholder: 'Playlist URL' }), React.createElement( 'button', { className: `ui button PlaylistUrlSubmit ${isFetchingPlaylist ? 'loading' : ''}`, type: 'submit', disabled: playlistUrlSubmitDisabled }, isFetchingPlaylist ? React.createElement('i', { className: 'fa fa-circle-o-notch fa-spin' }) : React.createElement( 'span', null, 'Set' ) ) ), React.createElement( 'div', { className: 'PlaylistUrlInputHelp' }, React.createElement( 'p', null, React.createElement( 'small', null, 'Use\xA0', React.createElement( 'a', { onClick: this.onExternalClick, href: 'https://www.youtube.com/' }, 'YouTube' ), ',\xA0', React.createElement( 'a', { onClick: this.onExternalClick, href: 'https://soundcloud.com/' }, 'SoundCloud' ), ',\xA0', React.createElement( 'a', { onClick: this.onExternalClick, href: 'http://hypem.com/' }, 'Hype Machine' ), ',\xA0', React.createElement( 'a', { onClick: this.onExternalClick, href: 'https://www.mixcloud.com/' }, 'Mixcloud' ), ',\xA0 or\xA0', React.createElement( 'a', { onClick: this.onExternalClick, href: 'https://fanburst.com/' }, 'Fanburst' ), '\xA0 playlist urls' ) ) ) ), React.createElement( 'div', { className: 'PlayerContainer' }, React.createElement(Player, { playButtonDisabled: playButtonDisabled, pauseButtonDisabled: stopButtonDisabled, stopButtonDisabled: stopButtonDisabled, previousButtonDisabled: previousButtonDisabled, nextButtonDisabled: nextButtonDisabled, randomButtonDisabled: randomButtonDisabled, repeatButtonDisabled: repeatButtonDisabled, onPlayClick: this.onPlay.bind(this), onPauseClick: this.onStop.bind(this), onStopClick: this.onStop.bind(this), onPreviousClick: this.onPrev.bind(this), onNextClick: this.onNext.bind(this), onRandomClick: this.onRandom.bind(this), onRepeatClick: this.onRepeat.bind(this), onVolumeClick: this.onVolumeClick.bind(this), onVolumeChange: this.onVolumeChange.bind(this), repeatActive: repeatActive, randomActive: randomActive }) ) ), React.createElement(Shredometer, { shredProgress: shredProgress }), React.createElement( 'div', { id: 'bottom' }, React.createElement( 'a', { id: 'settings', onClick: this.onSettingsClick }, React.createElement('i', { className: 'fa fa-cog', 'aria-hidden': 'true' }), '\xA0 settings' ), React.createElement( 'a', { id: 'quit', onClick: this.onQuitClick.bind(this) }, 'quit' ) ), React.createElement(ConfigSettings, null) ); } onExternalClick(event) { event.preventDefault(); shell.openExternal(event.target.href); } onPlaylistUrlInput(event) { const value = event.target.value; this.setState({ playlistUrlInput: value }); } setPlaylistUrl(url) { this.setState({ errorMessage: null, isFetchingPlaylist: true }); player.stop(); // timeout used because of player stop/play events listener setTimeout(() => { this.setState({ //playlistUrlSubmitDisabled: true, playButtonDisabled: true, stopButtonDisabled: true }); }, 0); if (/youtube\.com/gi.test(url)) { setTimeout(() => { this.setState({ playlistUrlSubmitDisabled: false, playButtonDisabled: false, isFetchingPlaylist: false }); }, 1e3); return player.setPlaylist(url).catch(this.handleError); } else { musicService.getPlaylist(url).then(tracks => { console.log(tracks); this.setState({ playButtonDisabled: false }); setTimeout(() => { this.setState({ playlistUrlSubmitDisabled: false, isFetchingPlaylist: false }); }, 10); return player.setPlaylist(tracks); }).catch(error => { setTimeout(() => { this.setState({ isFetchingPlaylist: false }); }, 10); this.handleError(error); }); } } onPlaylistFormSubmit(event) { event.preventDefault(); const url = this.state.playlistUrlInput; store.set('playlistUrl', url); this.setPlaylistUrl(url); } onPlay() { player.play(); this.setState({ isPlaying: true, stopButtonDisabled: false, playButtonDisabled: true }); } onRandom() { const enabled = !this.state.randomActive; player.setRandom(enabled); this.setState({ randomActive: enabled }); } onRepeat() { const enabled = !this.state.repeatActive; player.setRepeat(enabled); this.setState({ repeatActive: enabled }); } onVolumeChange(volume) { player.setMaxVolume(volume); } onVolumeClick() { const enabled = !this.state.isMuted; player.setMute(enabled); } onStop() { player.pause(); this.setState({ stopButtonDisabled: true, playButtonDisabled: false }); } onPrev() { player.previous(); } onNext() { player.next(); } onSettingsClick(event) { event.preventDefault(); document.body.scrollTop = window.outerHeight + 200; } onQuitClick(event) { event.preventDefault(); remote.app.quit(); } handleError(error) { console.error(error); this.setState({ errorMessage: error }); } } module.exports = Main; },{"../lib/musicService":12,"../lib/playerService":13,"../lib/shredometerService":14,"../lib/storeService":15,"./ConfigSettings.jsx":2,"./Player.jsx":4,"./Shredometer.jsx":5,"lodash":355,"react":601,"react-dom":424}],4:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const VolumeSlider = require('./VolumeSlider.jsx'); class Player extends React.Component { constructor(props) { super(props); const { playButtonDisabled, pauseButtonDisabled, stopButtonDisabled, previousButtonDisabled, nextButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive } = props; this.state = { playButtonDisabled, pauseButtonDisabled, stopButtonDisabled, previousButtonDisabled, nextButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive }; this.onPlayClick = props.onPlayClick.bind(this); this.onPauseClick = props.onPauseClick.bind(this); this.onStopClick = props.onStopClick.bind(this); this.onPreviousClick = props.onPreviousClick.bind(this); this.onNextClick = props.onNextClick.bind(this); this.onRandomClick = props.onRandomClick.bind(this); this.onRepeatClick = props.onRepeatClick.bind(this); this.onVolumeChange = props.onVolumeChange.bind(this); this.onVolumeClick = props.onVolumeClick.bind(this); } componentWillReceiveProps(nextProps) { const { playButtonDisabled, pauseButtonDisabled, stopButtonDisabled, previousButtonDisabled, nextButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive } = nextProps; this.setState({ playButtonDisabled, pauseButtonDisabled, stopButtonDisabled, previousButtonDisabled, nextButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive }); } render() { const { playButtonDisabled, pauseButtonDisabled, stopButtonDisabled, previousButtonDisabled, nextButtonDisabled, randomButtonDisabled, repeatButtonDisabled, randomActive, repeatActive } = this.state; return React.createElement( 'div', { className: 'ui grid Player' }, React.createElement( 'button', { className: 'PlayerControlButton PlayerControlPreviousButton', onClick: this.onPreviousClick, disabled: previousButtonDisabled }, React.createElement('i', { className: 'fa fa-step-backward', 'aria-hidden': 'true' }) ), React.createElement( 'button', { className: 'PlayerControlButton PlayerControlPlayButton', onClick: this.onPlayClick, disabled: playButtonDisabled }, React.createElement('i', { className: 'fa fa-play-circle-o', 'aria-hidden': 'true' }) ), React.createElement( 'button', { className: 'PlayerControlButton PlayerControlPauseButton', onClick: this.onPauseClick, disabled: pauseButtonDisabled }, React.createElement('i', { className: 'fa fa-pause-circle-o', 'aria-hidden': 'true' }) ), React.createElement( 'button', { className: 'PlayerControlButton PlayerControlNextButton', onClick: this.onNextClick, disabled: nextButtonDisabled }, React.createElement('i', { className: 'fa fa-step-forward', 'aria-hidden': 'true' }) ), React.createElement( 'button', { className: `PlayerControlButton PlayerControlRandomButton ${randomActive ? 'active' : ''}`, onClick: this.onRandomClick, disabled: randomButtonDisabled }, React.createElement('i', { className: 'fa fa-random', 'aria-hidden': 'true' }) ), React.createElement( 'button', { className: `PlayerControlButton PlayerControlRepeatButton ${repeatActive ? 'active' : ''}`, onClick: this.onRepeatClick, disabled: repeatButtonDisabled }, React.createElement('i', { className: 'fa fa-repeat', 'aria-hidden': 'true' }) ), React.createElement( 'div', { className: 'PlayerVolumeContainer' }, React.createElement(VolumeSlider, { onVolumeChange: this.onVolumeChange, onClick: this.onVolumeClick }) ) ); } } module.exports = Player; },{"./VolumeSlider.jsx":6,"react":601,"react-dom":424}],5:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); function Shredometer(props) { const shredProgress = props.shredProgress; var level = null; if (shredProgress >= 0 && shredProgress < 30) { level = 'low'; } else if (shredProgress >= 30 && shredProgress < 70) { level = 'medium'; } else if (shredProgress >= 70) { level = 'high'; } return React.createElement( 'div', { className: 'Shredometer' }, React.createElement( 'div', { className: 'ui header ShredometerTitle' }, 'Shredometer' ), React.createElement( 'div', { className: 'ShredometerProgress' }, '|', React.createElement('div', { className: `ShredometerProgressFill ${level}`, style: { width: `${shredProgress}%` } }) ) ); } module.exports = Shredometer; },{"react":601,"react-dom":424}],6:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const Slider = require('react-slider'); const store = require('../lib/storeService'); class VolumeSlider extends React.Component { constructor(props) { super(props); let volume = 1; if (typeof props.volume === 'number') { volume = props.volume; } let muted = false; if (typeof props.muted === 'boolean') { muted = props.muted; } this.state = { volume, showSlider: false, muted }; this.onVolumeChange = (props.onVolumeChange || (() => {})).bind(this); this.onMuteToggle = (props.onMutetoggle || (() => {})).bind(this); } render() { const { showSlider, muted, volume } = this.state; let volumeValue = 1 - volume; const volumeLow = volume <= 0.5; let volumeIcon = 'fa-volume-up'; if (muted) { volumeValue = 1; } if (muted || volume <= 0) { volumeIcon = 'fa-volume-off'; } else if (volumeLow) { volumeIcon = 'fa-volume-down'; } return React.createElement( 'div', { onMouseOver: this.onVolumeMouseOver.bind(this), onMouseOut: this.onVolumeMouseOut.bind(this), className: 'VolumeSliderContainer' }, React.createElement( 'div', { className: 'VolumeSliderWrapper' }, React.createElement(Slider, { className: `VolumeSlider ${showSlider ? 'visible' : 'hidden'}`, min: 0, step: .1, max: 1, defaultValue: volumeValue, value: volumeValue, orientation: 'vertical', onChange: this.onSliderChange.bind(this), withBars: true }), React.createElement( 'button', { className: 'PlayerControlButton PlayerControlVolumeButton', onClick: this.onVolumeClick.bind(this) }, React.createElement('i', { className: `fa ${volumeIcon}`, 'aria-hidden': 'true' }) ) ) ); } onSliderChange(value) { const volume = 1 - value; let muted = this.state.muted; if (volume > 0 && muted) { muted = false; } this.setState({ volume, muted }); this.onVolumeChange(volume); } onVolumeMouseOver(event) { this.setState({ showSlider: true }); } onVolumeMouseOut(event) { this.setState({ showSlider: false }); } onVolumeClick(event) { event.preventDefault(); const muted = !this.state.muted; const volume = this.state.volume; this.setState({ muted }); this.onMuteToggle(muted); this.onVolumeChange(muted ? 0 : volume); } } module.exports = VolumeSlider; },{"../lib/storeService":15,"react":601,"react-dom":424,"react-slider":575}],7:[function(require,module,exports){ const React = require('react'); const ReactDOM = require('react-dom'); const App = require('./App.jsx'); ReactDOM.render(React.createElement(App, null), document.getElementById('root')); },{"./App.jsx":1,"react":601,"react-dom":424}],8:[function(require,module,exports){ var getStreamUrls = require('fanburst-audio').getStreamUrls; class FanburstService { constructor() {} getPlaylist(playlistUrl) { return getStreamUrls(playlistUrl); } } module.exports = new FanburstService(); },{"fanburst-audio":238}],9:[function(require,module,exports){ var getStreamUrls = require('hypem-audio').getStreamUrls; class HypemService { constructor() {} getPlaylist(playlistUrl) { return getStreamUrls(playlistUrl); } } module.exports = new HypemService(); },{"hypem-audio":322}],10:[function(require,module,exports){ var getStreamUrls = require('mixcloud-audio').getStreamUrls; class MixcloudService { constructor() {} getPlaylist(playlistUrl) { return getStreamUrls(playlistUrl); } } module.exports = new MixcloudService(); },{"mixcloud-audio":363}],11:[function(require,module,exports){ var config = { clientId: 'a0cbcf3a37c999ef9eae76469ea8db92' }; class SoundCloudService { constructor(config) { this.clientId = config.clientId; this.handleTrackResponse = this.handleTrackResponse.bind(this); this.getPlaylist = this.getPlaylist.bind(this); } clientIdParam() { return 'client_id=' + this.clientId; } handleTrackResponse(response) { if (response.kind === 'track') { if (response.stream_url) { return `${response.stream_url}?${this.clientIdParam()}`; } } return null; } getPlaylist(url) { return fetch(`https://api.soundcloud.com/resolve?url=${url}&${this.clientIdParam()}`).then(response => response.json()).then(json => { var { kind } = json; if (kind === 'track') { return [this.handleTrackResponse(json)]; } else if (kind === 'user') { return fetch(`${json.uri}/tracks?${this.clientIdParam()}`).then(response => response.json()).then(json => { return json.map(this.handleTrackResponse); }); } else if (kind === 'playlist') { return json.tracks.map(this.handleTrackResponse); } else if (Array.isArray(json)) { return json.map(this.handleTrackResponse); } return []; }).then(tracks => { return tracks.filter(x => x); }); } } module.exports = new SoundCloudService(config); },{}],12:[function(require,module,exports){ var url = require('url'); var soundcloud = require('./apis/soundcloud'); var mixcloud = require('./apis/mixcloud'); var hypem = require('./apis/hypem'); var fanburst = require('./apis/fanburst'); class MusicService { constructor() {} getPlaylist(playlistUrl) { if (!playlistUrl) { return Promise.reject('Playlist URL is required'); } var hostname = url.parse(playlistUrl).hostname; if (/soundcloud/gi.test(hostname)) { return soundcloud.getPlaylist(playlistUrl); } else if (/mixcloud/gi.test(hostname)) { return mixcloud.getPlaylist(playlistUrl); } else if (/fanburst/gi.test(hostname)) { return fanburst.getPlaylist(playlistUrl); } else if (/hypem/gi.test(hostname)) { return hypem.getPlaylist(playlistUrl); } else { return Promise.reject(hostname + ' music service is not supported.'); } } } module.exports = new MusicService(); },{"./apis/fanburst":8,"./apis/hypem":9,"./apis/mixcloud":10,"./apis/soundcloud":11,"url":1037}],13:[function(require,module,exports){ const EventEmitter = require('events'); const { Player, YoutubePlayer } = require('audio-director'); const PlayerEventTypes = Player.EventTypes; const playerST = new Player(); const playerYT = new YoutubePlayer(); let player = playerYT; // Facade class PlayerService extends EventEmitter { constructor() { super(); playerST.on(PlayerEventTypes.ENQUEUE, () => { this.emit('Enqueue'); }); playerYT.on(PlayerEventTypes.ENQUEUE, () => { this.emit('Enqueue'); }); playerST.on(PlayerEventTypes.EMPTY_QUEUE, () => { this.emit('EmptyQueue'); }); playerYT.on(PlayerEventTypes.EMPTY_QUEUE, () => { this.emit('EmptyQueue'); }); playerST.on(PlayerEventTypes.PLAY, () => { this.emit('Play'); }); playerYT.on(PlayerEventTypes.PLAY, () => { this.emit('Play'); }); playerST.on(PlayerEventTypes.STOP, () => { this.emit('Stop'); }); playerYT.on(PlayerEventTypes.STOP, () => { this.emit('Stop'); }); playerST.on(PlayerEventTypes.PREVIOUS, () => { this.emit('Next'); }); playerYT.on(PlayerEventTypes.PREVIOUS, () => { this.emit('Previous'); }); playerST.on(PlayerEventTypes.NEXT, () => { this.emit('Next'); }); playerYT.on(PlayerEventTypes.NEXT, () => { this.emit('Next'); }); } play() { return player.play(); } pause() { return player.pause(); } stop() { return player.stop(); } next() { return player.next(); } previous() { return player.previous(); } setRandom(enabled) { return player.setRandom(enabled); } setRepeat(enabled) { return player.setRepeat(enabled); } hasPrevious() { return player.hasPrevious(); } hasNext() { return player.hasNext(); } setPlaybackRate(rate) { if (playerST.isReady) { playerST.setPlaybackRate(rate); } if (playerYT.isReady) { playerYT.setPlaybackRate(rate); } return Promise.resolve(); } setVolume(volume) { if (playerST.isReady) { playerST.setVolume(volume); } if (playerYT.isReady) { playerYT.setVolume(volume); } return Promise.resolve(); } setMaxVolume(maxVolume) { if (playerST.isReady) { playerST.setMaxVolume(maxVolume); } if (playerYT.isReady) { playerYT.setMaxVolume(maxVolume); } return Promise.resolve(); } setMuted(enabled) { if (playerST.isReady) { playerST.setMuted(enabled); } if (playerYT.isReady) { playerYT.setMuted(enabled); } return Promise.resolve(); } enqueue(data) { return player.enqueue(data); } emptyQueue() { return player.emptyQueue(); } setPlaylist(url) { if (/youtube\.com/gi.test(url)) { player = playerYT; } else { player = playerST; } player.emptyQueue(); return player.enqueue(url); } } module.exports = new PlayerService(); },{"audio-director":80,"events":234}],14:[function(require,module,exports){ const EventEmitter = require('events'); const Rx = require('rxjs'); const _ = require('lodash'); const d3 = require('d3'); const keyup$ = new Rx.Subject(); // from index.html const shredbeat = window.shredbeat || {}; if (typeof shredbeat.osGlobalKeyPress === 'function') { shredbeat.osGlobalKeyPress(event => { keyup$.next(event); }); } class ShredometerService extends EventEmitter { constructor() { super(); this.sensitivity = 0.001; this.sensitivityScale = d3.scaleLinear().domain([0, 1]).range([0.0001, 0.003]).clamp(true); this.playbackRange = [1, 3]; this.startingAmplitude = 0.5; this.amplitudeRange = [this.startingAmplitude, 1]; this.amplitudeScale = d3.scaleLinear().domain([-100, 0]).range([0, 1]).clamp(true); this.amplitude = this.amplitudeRange[0]; this.amplitudeSensitivity = 0.005; this.amplitudeSensitivityScale = d3.scaleLinear().domain([0, 1]).range([0.0001, 0.01]).clamp(true); this.deltaScale = d3.scaleLinear().domain([0, 1000]) // milliseconds .range([3, 1]).clamp(true); this.progressScale = d3.scaleLinear().domain([1, 2.5]).range([0, 100]).clamp(true); this.currentRate = this.playbackRange[0]; this.direction = -1; this.directionTimeout = null; this.rate = 1; keyup$.timestamp().map(obj => obj.timestamp).pairwise().map(([a, b]) => b - a).subscribe(delta => { this.rate = this.deltaScale(delta); this.direction = 1; // reverse direction after a second of no activity clearTimeout(this.directionTimeout); this.directionTimeout = setTimeout(() => { this.direction = -1; }, 1e3); }); setInterval(() => { this.currentRate += this.rate * this.sensitivity * this.direction; const shredProgress = this.progressScale(this.currentRate); // clamping if (this.currentRate >= this.playbackRange[1]) { this.currentRate = this.playbackRange[1]; } else if (this.currentRate <= this.playbackRange[0]) { this.currentRate = this.playbackRange[0]; } this.amplitude += this.amplitudeSensitivity * this.direction; // clamping if (this.amplitude >= this.amplitudeRange[1]) { this.amplitude = this.amplitudeRange[1]; } else if (this.amplitude <= this.amplitudeRange[0]) { this.amplitude = this.amplitudeRange[0]; } this.emit('ShredProgress', shredProgress); this.emit('ShredRate', this.currentRate); this.emit('AmplitudeRate', this.amplitude); }, 50); } setSensitivity(value) { if (typeof value === 'number') { this.sensitivity = this.sensitivityScale(value); } } setAmplitudeSensitivity(value) { if (typeof value === 'number') { this.amplitudeSensitivity = this.amplitudeSensitivityScale(value); } } setStartingAmplitude(value) { if (typeof value === 'number') { this.startingAmplitude = this.amplitudeScale(value); this.amplitudeRange = [this.startingAmplitude, 1]; this.amplitude = this.amplitudeRange[0]; } } } module.exports = new ShredometerService(); },{"d3":178,"events":234,"lodash":355,"rxjs":624}],15:[function(require,module,exports){ let Store = null; if (typeof window !== 'undefined' && window.require) { // https://github.com/electron/electron/issues/7300 Store = window.require('electron-store'); } else { Store = require('electron-store'); } const EventEmitter = require('events'); class StoreService extends EventEmitter { constructor() { super(); this._store = new Store(); } get(key) { return this._store.get(key); } set(key, value) { this._store.set(key, value); this.emit(key, value); return this._store.get(key); } delete(key) { return this._store.delete(key); } } const storeService = new StoreService(); module.exports = storeService; },{"electron-store":207,"events":234}],16:[function(require,module,exports){ 'use strict'; var compileSchema = require('./compile') , resolve = require('./compile/resolve') , Cache = require('./cache') , SchemaObject = require('./compile/schema_obj') , stableStringify = require('json-stable-stringify') , formats = require('./compile/formats') , rules = require('./compile/rules') , v5 = require('./v5') , util = require('./compile/util') , async = require('./async') , co = require('co'); module.exports = Ajv; Ajv.prototype.compileAsync = async.compile; var customKeyword = require('./keyword'); Ajv.prototype.addKeyword = customKeyword.add; Ajv.prototype.getKeyword = customKeyword.get; Ajv.prototype.removeKeyword = customKeyword.remove; Ajv.ValidationError = require('./compile/validation_error'); var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema'; var SCHEMA_URI_FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i; function SCHEMA_URI_FORMAT_FUNC(str) { return SCHEMA_URI_FORMAT.test(str); } var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ]; /** * Creates validator instance. * Usage: `Ajv(opts)` * @param {Object} opts optional options * @return {Object} ajv instance */ function Ajv(opts) { if (!(this instanceof Ajv)) return new Ajv(opts); var self = this; opts = this._opts = util.copy(opts) || {}; this._schemas = {}; this._refs = {}; this._fragments = {}; this._formats = formats(opts.format); this._cache = opts.cache || new Cache; this._loadingSchemas = {}; this._compilations = []; this.RULES = rules(); // this is done on purpose, so that methods are bound to the instance // (without using bind) so that they can be used without the instance this.validate = validate; this.compile = compile; this.addSchema = addSchema; this.addMetaSchema = addMetaSchema; this.validateSchema = validateSchema; this.getSchema = getSchema; this.removeSchema = removeSchema; this.addFormat = addFormat; this.errorsText = errorsText; this._addSchema = _addSchema; this._compile = _compile; opts.loopRequired = opts.loopRequired || Infinity; if (opts.async || opts.transpile) async.setup(opts); if (opts.beautify === true) opts.beautify = { indent_size: 2 }; if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true; this._metaOpts = getMetaSchemaOptions(); if (opts.formats) addInitialFormats(); addDraft4MetaSchema(); if (opts.v5) v5.enable(this); if (typeof opts.meta == 'object') addMetaSchema(opts.meta); addInitialSchemas(); /** * Validate data using schema * Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize. * @param {String|Object} schemaKeyRef key, ref or schema object * @param {Any} data to be validated * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`). */ function validate(schemaKeyRef, data) { var v; if (typeof schemaKeyRef == 'string') { v = getSchema(schemaKeyRef); if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"'); } else { var schemaObj = _addSchema(schemaKeyRef); v = schemaObj.validate || _compile(schemaObj); } var valid = v(data); if (v.$async === true) return self._opts.async == '*' ? co(valid) : valid; self.errors = v.errors; return valid; } /** * Create validating function for passed schema. * @param {Object} schema schema object * @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords. * @return {Function} validating function */ function compile(schema, _meta) { var schemaObj = _addSchema(schema, undefined, _meta); return schemaObj.validate || _compile(schemaObj); } /** * Adds schema to the instance. * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored. * @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`. * @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead. * @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead. */ function addSchema(schema, key, _skipValidation, _meta) { if (Array.isArray(schema)){ for (var i=0; i<schema.length; i++) addSchema(schema[i], undefined, _skipValidation, _meta); return; } // can key/id have # inside? key = resolve.normalizeId(key || schema.id); checkUnique(key); self._schemas[key] = _addSchema(schema, _skipValidation, _meta, true); } /** * Add schema that will be used to validate other schemas * options in META_IGNORE_OPTIONS are alway set to false * @param {Object} schema schema object * @param {String} key optional schema key * @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema */ function addMetaSchema(schema, key, skipValidation) { addSchema(schema, key, skipValidation, true); } /** * Validate schema * @param {Object} schema schema to validate * @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid * @return {Boolean} true if schema is valid */ function validateSchema(schema, throwOrLogError) { var $schema = schema.$schema || self._opts.defaultMeta || defaultMeta(); var currentUriFormat = self._formats.uri; self._formats.uri = typeof currentUriFormat == 'function' ? SCHEMA_URI_FORMAT_FUNC : SCHEMA_URI_FORMAT; var valid; try { valid = validate($schema, schema); } finally { self._formats.uri = currentUriFormat; } if (!valid && throwOrLogError) { var message = 'schema is invalid: ' + errorsText(); if (self._opts.validateSchema == 'log') console.error(message); else throw new Error(message); } return valid; } function defaultMeta() { var meta = self._opts.meta; self._opts.defaultMeta = typeof meta == 'object' ? meta.id || meta : self._opts.v5 ? v5.META_SCHEMA_ID : META_SCHEMA_ID; return self._opts.defaultMeta; } /** * Get compiled schema from the instance by `key` or `ref`. * @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id). * @return {Function} schema validating function (with property `schema`). */ function getSchema(keyRef) { var schemaObj = _getSchemaObj(keyRef); switch (typeof schemaObj) { case 'object': return schemaObj.validate || _compile(schemaObj); case 'string': return getSchema(schemaObj); case 'undefined': return _getSchemaFragment(keyRef); } } function _getSchemaFragment(ref) { var res = resolve.schema.call(self, { schema: {} }, ref); if (res) { var schema = res.schema , root = res.root , baseId = res.baseId; var v = compileSchema.call(self, schema, root, undefined, baseId); self._fragments[ref] = new SchemaObject({ ref: ref, fragment: true, schema: schema, root: root, baseId: baseId, validate: v }); return v; } } function _getSchemaObj(keyRef) { keyRef = resolve.normalizeId(keyRef); return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef]; } /** * Remove cached schema(s). * If no parameter is passed all schemas but meta-schemas are removed. * If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed. * Even if schema is referenced by other schemas it still can be removed as other schemas have local references. * @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object */ function removeSchema(schemaKeyRef) { if (schemaKeyRef instanceof RegExp) { _removeAllSchemas(self._schemas, schemaKeyRef); _removeAllSchemas(self._refs, schemaKeyRef); return; } switch (typeof schemaKeyRef) { case 'undefined': _removeAllSchemas(self._schemas); _removeAllSchemas(self._refs); self._cache.clear(); return; case 'string': var schemaObj = _getSchemaObj(schemaKeyRef); if (schemaObj) self._cache.del(schemaObj.jsonStr); delete self._schemas[schemaKeyRef]; delete self._refs[schemaKeyRef]; return; case 'object': var jsonStr = stableStringify(schemaKeyRef); self._cache.del(jsonStr); var id = schemaKeyRef.id; if (id) { id = resolve.normalizeId(id); delete self._schemas[id]; delete self._refs[id]; } } } function _removeAllSchemas(schemas, regex) { for (var keyRef in schemas) { var schemaObj = schemas[keyRef]; if (!schemaObj.meta && (!regex || regex.test(keyRef))) { self._cache.del(schemaObj.jsonStr); delete schemas[keyRef]; } } } function _addSchema(schema, skipValidation, meta, shouldAddSchema) { if (typeof schema != 'object') throw new Error('schema should be object'); var jsonStr = stableStringify(schema); var cached = self._cache.get(jsonStr); if (cached) return cached; shouldAddSchema = shouldAddSchema || self._opts.addUsedSchema !== false; var id = resolve.normalizeId(schema.id); if (id && shouldAddSchema) checkUnique(id); var willValidate = self._opts.validateSchema !== false && !skipValidation; var recursiveMeta; if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema)) validateSchema(schema, true); var localRefs = resolve.ids.call(self, schema); var schemaObj = new SchemaObject({ id: id, schema: schema, localRefs: localRefs, jsonStr: jsonStr, meta: meta }); if (id[0] != '#' && shouldAddSchema) self._refs[id] = schemaObj; self._cache.put(jsonStr, schemaObj); if (willValidate && recursiveMeta) validateSchema(schema, true); return schemaObj; } function _compile(schemaObj, root) { if (schemaObj.compiling) { schemaObj.validate = callValidate; callValidate.schema = schemaObj.schema; callValidate.errors = null; callValidate.root = root ? root : callValidate; if (schemaObj.schema.$async === true) callValidate.$async = true; return callValidate; } schemaObj.compiling = true; var currentOpts; if (schemaObj.meta) { currentOpts = self._opts; self._opts = self._metaOpts; } var v; try { v = compileSchema.call(self, schemaObj.schema, root, schemaObj.localRefs); } finally { schemaObj.compiling = false; if (schemaObj.meta) self._opts = currentOpts; } schemaObj.validate = v; schemaObj.refs = v.refs; schemaObj.refVal = v.refVal; schemaObj.root = v.root; return v; function callValidate() { var _validate = schemaObj.validate; var result = _validate.apply(null, arguments); callValidate.errors = _validate.errors; return result; } } /** * Convert array of error message objects to string * @param {Array<Object>} errors optional array of validation errors, if not passed errors from the instance are used. * @param {Object} options optional options with properties `separator` and `dataVar`. * @return {String} human readable string with all errors descriptions */ function errorsText(errors, options) { errors = errors || self.errors; if (!errors) return 'No errors'; options = options || {}; var separator = options.separator === undefined ? ', ' : options.separator; var dataVar = options.dataVar === undefined ? 'data' : options.dataVar; var text = ''; for (var i=0; i<errors.length; i++) { var e = errors[i]; if (e) text += dataVar + e.dataPath + ' ' + e.message + separator; } return text.slice(0, -separator.length); } /** * Add custom format * @param {String} name format name * @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid) */ function addFormat(name, format) { if (typeof format == 'string') format = new RegExp(format); self._formats[name] = format; } function addDraft4MetaSchema() { if (self._opts.meta !== false) { var metaSchema = require('./refs/json-schema-draft-04.json'); addMetaSchema(metaSchema, META_SCHEMA_ID, true); self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID; } } function addInitialSchemas() { var optsSchemas = self._opts.schemas; if (!optsSchemas) return; if (Array.isArray(optsSchemas)) addSchema(optsSchemas); else for (var key in optsSchemas) addSchema(optsSchemas[key], key); } function addInitialFormats() { for (var name in self._opts.formats) { var format = sel