├── .babelrc ├── .gitignore ├── .travis.yml ├── actions └── index.js ├── audio-listener.js ├── bright.mp3 ├── components ├── main-section.js ├── menu-actions.js ├── pattern-selector.js ├── sound-properties.js ├── sound-selector.js └── tick-container.js ├── containers └── App.js ├── index.html ├── lib └── throttle.js ├── main.js ├── package.json ├── reducers ├── index.js └── machine.js ├── scripts ├── after_deploy.sh ├── after_failure.sh ├── after_script.sh ├── after_success.sh ├── before_deploy.sh ├── before_install.sh ├── before_script.sh ├── deploy.sh ├── install.sh └── script.sh ├── server.js ├── store └── configureStore.js ├── webpack.config.js └── webpack.production.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .* 3 | dist/* 4 | !.travis.yml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: ./scripts/before_install.sh 2 | install: 3 | - ./scripts/install.sh 4 | - sudo apt-get install s3cmd 5 | after_install: ./scripts/after_install.sh 6 | before_deploy: ./scripts/before_deploy.sh 7 | deploy: 8 | provider: script 9 | script: ./scripts/deploy.sh 10 | on: 11 | branch: master 12 | after_deploy: ./scripts/after_deploy.sh 13 | script: ./scripts/script.sh 14 | language: node_js 15 | node_js: '5' 16 | sudo: true 17 | env: 18 | global: 19 | - secure: U3fwrz84UcXGIZ4QvNchTzSHgnMhJweVVu5Tpw/zBQMbjfxcoWIELIc8Lx+AgHi4rvNLOgX45OQhWDJCa3fWbpM/VAwQlbtEJFYQaN3Z+4ZG0lGqj+LdqMHvhmqAbOwdExmRt4wkyA5gIdplhJy98IxIq43egdj4+c0cN7XAMExc46AVRfi+TAkhfiHj6PlDYraMwalIcLu2WwzD8FfeqD4kMpEi8xNunCxuXTwfMQCBKu0WhCkISnyUIlZOxaiWr7Nnohi+sSD8nqMjktkiVtdnU9kwKZ2RLmZHrFFWbBtf9rR7wc/EYCkgaIvhVttO0WfrxV9Pc36qjcToyMGcXpB6A0c7H9SwH240OKE0bVz9rH0BO0PrQyG2ZLDKkb7kdSu7py90xml8BhyBCZHxK3TGVnCilmOyCoOG+ZyrtGjjvlVUpimcQ0g+2yABbP5CNkoKMwLQ4L8+kP8Ea/XD27J+x1SOojl3Gm6JTEJ/i+T6oTX6KqNOWeCvo7DSz8T3UX+N2y3mLLY7dVivNpGaUWXYP5hV4u91NRhN3lScMUjyTjDIVLaCfT173CFZhSDbDuB/BLAfCHXCBNhwSoKEda/soEY7Lif0KV0g0LufcCYVvBieyu62mdNg3cKCyxq6RgQela2RnjVTADx3XsZESRiSIDeSTVXa90ffPUMGI3w= 20 | - secure: DMlhtXhu9BqnFKmKeMc8OdXL8tnsfNbyinYxEPOQC4gwHPw+xTqSYdIO4yFoDt4gkqPtvmPqdRgl1Nb6mbt0G3eBjq3Fm9JqiI3a6uiloDMz/LZpZYR2NceNSzoOrEoWINNBl7giSB5M65WryBjrTAO1OcATJ+8FnfJTPnEdyx3Ip/KbLUJlLuLGLsiU/8o5EmCwjhvi3NbJ6vgVK1Tdq5I2EGCYZxMt+436N4RzER63/Gb2gKVCxEri+SV1QlKLxZG3UE80DxvQuzTg+2aVuNSqRDUzJhOHoRai1fXP2ULvyolyoM6ybuKaaUlHb8NAVgedgH6+12MlpdYHIrqhktNzGJQyi5zFKGHFqkduqfb64vaKiTC546VFNo3nVXPAvtjx2AEw0e9CoT5ESYOYf0kcE1iw6OIU3P3W1DYSonTc1po5LwPcjJcag80skxSB9QAtK92pnaEw0mfgV3K7SzYf8GlkC5J9ySuqgKkthOG6161U8zbUp91KS+Vi1r0AsSYL3bnWgoUyRkel+6dG261Rq6ZDCy+pBM9H4mJ1HCU7viSC9RkyRkdXZGVBcSfeOyGi/7+LViY+ZIWgwfjsGCd+HTW7fP0kswcTs+/W2TojBlwswrozgZlQSVYEzgPxI4o1rWvq28MMpQlth/12F3rliIl6BnqPJ7HA+6/hUcg= 21 | -------------------------------------------------------------------------------- /actions/index.js: -------------------------------------------------------------------------------- 1 | export function changeTempo(tempo) { 2 | return { type: "CHANGE_TEMPO", tempo } 3 | } 4 | 5 | export function changeSwing(swing) { 6 | return { type: "CHANGE_SWING", swing } 7 | } 8 | 9 | export function patternChange(index) { 10 | return { type: "PATTERN_CHANGE", index } 11 | } 12 | 13 | export function togglePlay(index) { 14 | return { type: "TOGGLE_PLAY" } 15 | } 16 | 17 | export function changeActiveSound(index) { 18 | return { type: "CHANGE_ACTIVE_SOUND", index } 19 | } 20 | 21 | export function changeSoundProperty(propertyIndex, value) { 22 | return { type: "CHANGE_SOUND_PROPERTY", propertyIndex, value } 23 | } 24 | export function changePatternMode(mode) { 25 | return { type: "CHANGE_PATTERN_MODE", mode } 26 | } 27 | 28 | export function setCursor(index) { 29 | return { type: "SET_CURSOR", index } 30 | } 31 | 32 | export function setActivePatternSectionIndex(index) { 33 | return { type: "SET_ACTIVE_PATTERN_SECTION_INDEX", index } 34 | } 35 | 36 | export function changeSoundMode(index) { 37 | return { type: "CHANGE_SOUND_MODE", index } 38 | } 39 | 40 | export function reset() { 41 | return { type: "RESET" } 42 | } 43 | 44 | export function copyPattern(index) { 45 | return { type: "COPY_PATTERN", index } 46 | } 47 | 48 | export function pastePattern(index) { 49 | return { type: "PASTE_PATTERN_TO_TARGET", index } 50 | } 51 | 52 | export function clearPattern(index) { 53 | return { type: "CLEAR_PATTERN", index } 54 | } 55 | 56 | export function copyInstrumentPattern(index) { 57 | return { type: "COPY_INSTRUMENT_PATTERN", index } 58 | } 59 | 60 | export function pasteInstrumentPattern(index) { 61 | return { type: "PASTE_INSTRUMENT_PATTERN_TO_TARGET", index } 62 | } 63 | 64 | export function clearInstrumentPattern(index) { 65 | return { type: "CLEAR_INSTRUMENT_PATTERN", index } 66 | } 67 | 68 | export function handleGeneralKeyDown(e) { 69 | var target = e.target; 70 | if (/(input|button|select|option)/i.test(target.tagName)) { 71 | return { type: ''}; 72 | } else if (e.metaKey /*|| e.shiftKey*/ || e.ctrlKey) { 73 | return { type: ''}; 74 | } else { 75 | return { type: 'GENERAL_KEYDOWN', which: e.which, shift: e.shiftKey }; 76 | } 77 | }; 78 | 79 | export function handleGeneralKeyUp(e) { 80 | return { type: 'GENERAL_KEY_UP', which: e.which }; 81 | } 82 | -------------------------------------------------------------------------------- /audio-listener.js: -------------------------------------------------------------------------------- 1 | var Snare = require('snare'); 2 | var Kick8 = require('kick-eight'); 3 | var HiHat = require('hi-hat'); 4 | var Conga = require('tom-tom'); 5 | var RimShot = require('rim-shot'); 6 | var Clap = require('clappy'); 7 | var CowBell = require('cow-bell'); 8 | var Maracas = require('maracas'); 9 | var Claves = require('claves'); 10 | 11 | 12 | var throttle = require('./lib/throttle'); 13 | var lastCursorTickAt = false; 14 | var lastCursor = 0; 15 | var context = new (window.AudioContext || window.webkitAudioContext)(); 16 | 17 | 18 | 19 | var compressor = context.createDynamicsCompressor(); 20 | compressor.connect(context.destination); 21 | compressor.ratio.value = 6; 22 | compressor.threshold.value = -20; 23 | compressor.attack.value = 0.003; 24 | compressor.release.value = 0.1; 25 | 26 | 27 | var filter = context.createBiquadFilter(); 28 | filter.type = 'highpass'; 29 | filter.frequency.value = 300; 30 | 31 | var isFirefox = /Firefox/.test(navigator.userAgent); 32 | 33 | if (!window.AudioContext || /iphone|ipad/i.test(navigator.userAgent)) { 34 | var wai = require('web-audio-ios'); 35 | wai(document.body, context, function (unlocked) { }); 36 | 37 | 38 | // context state at this time is `undefined` in iOS8 Safari 39 | if (context.state === 'suspended') { 40 | var resume = function () { 41 | context.resume(); 42 | 43 | setTimeout(function () { 44 | if (context.state === 'running') { 45 | document.body.removeEventListener('touchend', resume, false); 46 | document.body.removeEventListener('click', resume, false); 47 | } 48 | }, 0); 49 | }; 50 | 51 | document.body.addEventListener('touchend', resume, false); 52 | document.body.addEventListener('click', resume, false); 53 | } 54 | 55 | } 56 | 57 | var animationFrameRequests = []; 58 | 59 | var audioListener = module.exports = throttle(function(component, 60 | setCursor, 61 | setActivePatternSection, getActivePatternSection) { 62 | 63 | animationFrameRequests.forEach(function(req) { 64 | cancelAnimationFrame(req); 65 | }); 66 | animationFrameRequests = []; 67 | 68 | var state = component.props.machine; 69 | if (state.playing) { 70 | go(component.props.machine, setCursor, setActivePatternSection, getActivePatternSection); 71 | animationFrameRequests.push(requestAnimationFrame( 72 | audioListener.bind(null, component, setCursor, setActivePatternSection, getActivePatternSection) 73 | )); 74 | } else { 75 | if (state.cursor !== 0) { 76 | setCursor(0); 77 | } 78 | lastCursorTickAt = false; 79 | } 80 | }, 10); 81 | 82 | 83 | //var startedAt = false; 84 | 85 | function isTimeForCursorTick(tempo, lastCursorTickAt, currentTime) { 86 | var sinceLastTick = currentTime - lastCursorTickAt; 87 | if (60 / (4 * tempo) < sinceLastTick) { 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | } 93 | 94 | function go(state, setCursor, setActivePatternSection, getActivePatternSection) { 95 | if (!lastCursorTickAt) { 96 | lastCursor = 0; 97 | lastCursorTickAt = context.currentTime; 98 | setCursor(lastCursor); 99 | scheduleTick(state); 100 | } else { 101 | var newState = Object.assign({}, state); 102 | if (isTimeForCursorTick(state.tempo, lastCursorTickAt, context.currentTime)) { 103 | var tickLength = 60 / (4 * state.tempo); 104 | lastCursorTickAt += tickLength; 105 | if (state.patternMode === "AB") { 106 | if (lastCursor === 15) { 107 | if (state.activePatternSection === 0) { 108 | var newSection = 1; 109 | } else { 110 | var newSection = 0; 111 | } 112 | setActivePatternSection(newSection); 113 | newState.activePatternSection = newSection; 114 | } 115 | } 116 | lastCursor += 1; 117 | lastCursor = lastCursor % 16; 118 | setCursor(lastCursor); 119 | newState.cursor = lastCursor; 120 | newState.activePatternSection = getActivePatternSection(); 121 | scheduleTick(newState); 122 | } 123 | } 124 | } 125 | 126 | 127 | var hiHat = HiHat(context); 128 | 129 | var sources = [ 130 | [null], // Accent 131 | [Kick8(context)], // Bass Drum 132 | [Snare(context)], // Snare 133 | [Conga(context).bind(null, { frequency: 196 }), Conga(context).bind(null, { frequency: 98 })], // LC/LT 134 | [Conga(context).bind(null, { frequency: 294 }), Conga(context).bind(null, { frequency: 147 })], // MC/MT 135 | [Conga(context).bind(null, { frequency: 440 }), Conga(context).bind(null, { frequency: 220 })], // HC/HT 136 | [Claves(context), RimShot(context)], // CL/RS 137 | [Maracas(context), Clap(context)], // MA/CP 138 | [CowBell(context)], // CB 139 | [null], // CY 140 | [hiHat.bind(null, true)], // OH 141 | [hiHat.bind(null, false)] // CH 142 | ]; 143 | 144 | window.oneShot = function oneShot(index, state) { 145 | 146 | var sound = state.sounds[index]; 147 | var currentModeIndex = sound.currentModeIndex; 148 | var shortName = sound.modes[sound.currentModeIndex].shortName; 149 | var properties = sound.properties; 150 | var accent = false; 151 | var accentValue = 0; 152 | var playing = true; 153 | var when = context.currentTime; 154 | 155 | 156 | scheduleHit({ 157 | index, 158 | currentModeIndex, 159 | shortName, 160 | properties, 161 | accent, 162 | accentValue, 163 | when 164 | }); 165 | 166 | } 167 | 168 | 169 | 170 | function scheduleTick(state) { 171 | state.sounds.forEach(function(sound, i) { 172 | 173 | if (isFirefox) { 174 | return; 175 | } 176 | 177 | var index = i; 178 | var currentModeIndex = sound.currentModeIndex; 179 | var shortName = sound.modes[sound.currentModeIndex].shortName; 180 | var properties = sound.properties; 181 | var accent = state.pattern[0][state.activePatternSection][state.cursor] === 1; 182 | var playing = state.pattern[i][state.activePatternSection][state.cursor] === 1; 183 | if (accent) { 184 | var accentValue = state.sounds[0].properties[0].value; 185 | } else { 186 | accentValue = 0; 187 | } 188 | var when = lastCursorTickAt + 0.05; 189 | 190 | if (index > 0 && playing) { 191 | scheduleHit({ 192 | index, 193 | currentModeIndex, 194 | shortName, 195 | properties, 196 | accent, 197 | accentValue, 198 | when}); 199 | } 200 | }); 201 | } 202 | 203 | function scheduleHit(settings) { 204 | let { 205 | index, 206 | currentModeIndex, 207 | shortName, 208 | properties, 209 | accent, 210 | accentValue, 211 | when 212 | } = settings; 213 | 214 | var factory = sources[index][currentModeIndex]; 215 | 216 | if (typeof factory !== 'function') { 217 | return; 218 | } 219 | 220 | var node = factory(); 221 | 222 | properties.forEach(function(property) { 223 | if (/^sd$/i.test(shortName)) { 224 | if (property.name !== "level" && node[property.name] instanceof window.AudioParam) { 225 | node[property.name].value = property.value / 127;; 226 | } 227 | } else if (/^(l|m|h)(c|t)$/i.test(shortName)) { 228 | // toms 229 | node.frequency *= 1 + (properties.filter(function(prop) { return prop.name === "tuning" })[0].value - 64) / 127; 230 | } else if (/^oh$/i.test(shortName)) { 231 | var decay = properties.filter(function(prop) { return prop.name === "decay" })[0].value; 232 | node.duration *= 1 + (decay - 100) / 127; 233 | } else if (/^bd$/i.test(shortName)) { 234 | var decay = properties.filter(function(prop) { return prop.name === "decay" })[0].value; 235 | var tone = properties.filter(function(prop) { return prop.name === "tone" })[0].value; 236 | node.decay = decay; 237 | node.tone = tone; 238 | } 239 | }); 240 | 241 | var level = properties.filter(function(property) { 242 | return property.name === 'level'; 243 | })[0].value / 127; 244 | if (accent) { 245 | level *= 1 + (accentValue / 127); 246 | } 247 | var gainNode = context.createGain(); 248 | gainNode.gain.value = level; 249 | 250 | node.connect(gainNode); 251 | gainNode.connect(compressor); 252 | node.start(when); 253 | } 254 | -------------------------------------------------------------------------------- /bright.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsjoesullivan/tiny-808/dd95382095db4b88de4b855d7b732c158d7277e6/bright.mp3 -------------------------------------------------------------------------------- /components/main-section.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import audioListener from './../audio-listener'; 3 | 4 | import PatternSelector from './pattern-selector'; 5 | import SoundSelector from './sound-selector'; 6 | import SoundProperties from './sound-properties'; 7 | import MenuActions from './menu-actions'; 8 | import TickContainer from './tick-container'; 9 | 10 | class MainSection extends Component { 11 | constructor(props, context) { 12 | super(props, context) 13 | this.listener = audioListener; 14 | this.listener(this, 15 | this.setCursor.bind(this), 16 | this.setActivePatternSection.bind(this), 17 | this.getActivePatternSection.bind(this)); 18 | document.addEventListener("visibilitychange", 19 | this.handleVisibilityChange.bind(this), 20 | false); 21 | document.addEventListener('keydown', function(e) { 22 | this.props.actions.handleGeneralKeyDown(e); 23 | }.bind(this)); 24 | } 25 | 26 | 27 | componentDidUpdate() { 28 | this.listener(this, 29 | this.setCursor.bind(this), 30 | this.setActivePatternSection.bind(this), 31 | this.getActivePatternSection.bind(this)); 32 | } 33 | 34 | render() { 35 | 36 | 37 | const { machine, actions } = this.props 38 | 39 | var warning = ''; 40 | if (/Firefox/.test(navigator.userAgent)) { 41 | warning =

For Firefox users: some of the drum synths, including the kick, aren't working very well. Hopefully I can improve this situation soon. Until then the sound is muted to avoid damaging your speakers or ears. - Joe

42 | } 43 | 44 | return ( 45 |
46 |

tiny-808

47 | {warning} 48 | 65 | 72 |
73 | 77 | 83 |
84 |
85 | 86 | 94 | {machine.tempo} BPM 95 |
96 |
97 |
98 |
99 | 104 |
105 | 116 |
117 |
118 | ) 119 | } 120 | 121 | handleTempoChange(e) { 122 | this.props.actions.changeTempo(parseInt(e.target.value)) 123 | } 124 | handleSwingChange(e) { 125 | this.props.actions.changeSwing(parseInt(e.target.value)) 126 | } 127 | handlePatternChange(i) { 128 | this.props.actions.patternChange(i); 129 | } 130 | handlePlayClick() { 131 | this.props.actions.togglePlay(); 132 | } 133 | handleResetClick() { 134 | this.props.actions.reset(); 135 | } 136 | handleActiveSoundChange(e) { 137 | this.props.actions.changeActiveSound(parseInt(e.target.value)); 138 | } 139 | handleSoundPropertyChange(propertyIndex, e) { 140 | this.props.actions.changeSoundProperty(propertyIndex, parseInt(e.target.value)); 141 | } 142 | handleSoundModeChange(index) { 143 | this.props.actions.changeSoundMode(index); 144 | } 145 | handlePatternModeChange(mode) { 146 | this.props.actions.changePatternMode(mode); 147 | } 148 | setCursor(cursor) { 149 | this.props.actions.setCursor(cursor); 150 | } 151 | setActivePatternSection(index) { 152 | this.props.actions.setActivePatternSectionIndex(index); 153 | } 154 | getActivePatternSection() { 155 | return this.props.machine.activePatternSection; 156 | } 157 | handleVisibilityChange() { 158 | if (this.props.machine.playing && document.hidden) { 159 | this.props.actions.togglePlay(); 160 | } 161 | } 162 | 163 | } 164 | 165 | MainSection.propTypes = { 166 | machine: PropTypes.object.isRequired, 167 | actions: PropTypes.object.isRequired 168 | } 169 | 170 | export default MainSection 171 | -------------------------------------------------------------------------------- /components/menu-actions.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class MenuActions extends Component { 4 | render() { 5 | let { 6 | handleSaveClick, 7 | handleGetLinkClick, 8 | handleClearClick 9 | } = this.props; 10 | 11 | return
12 | 17 | 22 | 27 |
28 | } 29 | } 30 | 31 | export default MenuActions 32 | -------------------------------------------------------------------------------- /components/pattern-selector.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class PatternSelector extends Component { 4 | render() { 5 | let { 6 | activePatternSection, 7 | patternMode, 8 | handlePatternModeChange 9 | } = this.props 10 | 11 | return
12 | 25 | 36 | 49 |
50 | } 51 | handleKeyDown(index, e) { 52 | if (e.metaKey || e.ctrlKey) { 53 | if (e.shiftKey) { 54 | switch (e.which) { 55 | case 67: 56 | e.preventDefault() 57 | this.props.copyPattern(index); 58 | break; 59 | case 86: 60 | e.preventDefault() 61 | this.props.pastePattern(index); 62 | break; 63 | case 88: 64 | e.preventDefault() 65 | this.props.copyPattern(index); 66 | this.props.clearPattern(index); 67 | break; 68 | } 69 | } else { 70 | switch (e.which) { 71 | case 67: 72 | this.props.copyInstrumentPattern(index); 73 | break; 74 | case 86: 75 | this.props.pasteInstrumentPattern(index); 76 | break; 77 | case 88: 78 | this.props.copyInstrumentPattern(index); 79 | this.props.clearInstrumentPattern(index); 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | export default PatternSelector 88 | -------------------------------------------------------------------------------- /components/sound-properties.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class SoundProperties extends Component { 4 | render() { 5 | let { 6 | sound, 7 | handleSoundPropertyChange 8 | } = this.props; 9 | 10 | return
11 | {sound.name} 12 | {sound.properties.map(function(property, i) { 13 | return
14 | 15 | 23 | 24 | {property.value} 25 | 26 |
27 | })} 28 |
29 | } 30 | } 31 | export default SoundProperties 32 | -------------------------------------------------------------------------------- /components/sound-selector.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class SoundSelector extends Component { 4 | render() { 5 | let { 6 | sounds, 7 | currentSound, 8 | handleSoundModeChange, 9 | activeSoundIndex, 10 | handleActiveSoundChange 11 | } = this.props 12 | 13 | let modeSelector = ''; 14 | if (currentSound.modes.length > 1) { 15 | modeSelector =
16 | {currentSound.modes.map(function(mode, i) { 17 | let checked = i === currentSound.currentModeIndex; 18 | return 25 | {mode.shortName} 26 | 27 | })} 28 |
29 | } 30 | 31 | return
32 |
33 | {sounds.map(function(sound, i) { 34 | return 37 | {sound.modes[sound.currentModeIndex].shortName} 38 | 39 | })} 40 |
41 | 50 |
51 |
52 | 53 | 66 | {modeSelector} 67 |
68 |
69 | } 70 | } 71 | export default SoundSelector 72 | -------------------------------------------------------------------------------- /components/tick-container.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class TickContainer extends Component { 4 | render() { 5 | let { 6 | pattern, 7 | playing, 8 | cursor, 9 | handlePatternChange 10 | } = this.props; 11 | 12 | return
13 |
14 | {pattern.map(function(val, i) { 15 | if (playing && cursor === i) { 16 | var className = "highlighted"; 17 | } else { 18 | var className = ""; 19 | } 20 | return 24 | 29 | 30 | }.bind(this))} 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | } 40 | } 41 | 42 | export default TickContainer 43 | -------------------------------------------------------------------------------- /containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import MainSection from '../components/main-section' 5 | import * as MachineActions from '../actions' 6 | 7 | class App extends Component { 8 | render() { 9 | const { machine, actions } = this.props 10 | return ( 11 |
12 | 13 |
14 | ) 15 | } 16 | } 17 | 18 | function mapStateToProps(state) { 19 | return { 20 | machine: state.machine 21 | } 22 | } 23 | 24 | function mapDispatchToProps(dispatch) { 25 | return { 26 | actions: bindActionCreators(MachineActions, dispatch) 27 | } 28 | } 29 | 30 | export default connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(App) 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tiny-808 drum machine 5 | 6 | 7 | 8 | 140 | 141 | 142 |
143 |
144 | 145 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /lib/throttle.js: -------------------------------------------------------------------------------- 1 | // https://remysharp.com/2010/07/21/throttling-function-calls 2 | module.exports = function throttle(fn, threshhold, scope) { 3 | threshhold || (threshhold = 250); 4 | var last, 5 | deferTimer; 6 | return function () { 7 | var context = scope || this; 8 | 9 | var now = +new Date, 10 | args = arguments; 11 | if (last && now < last + threshhold) { 12 | // hold on to it 13 | clearTimeout(deferTimer); 14 | deferTimer = setTimeout(function () { 15 | last = now; 16 | fn.apply(context, args); 17 | }, threshhold); 18 | } else { 19 | last = now; 20 | fn.apply(context, args); 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import App from './containers/App' 5 | import configureStore from './store/configureStore' 6 | 7 | 8 | render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-808", 3 | "version": "0.0.0", 4 | "description": "Small drum machine", 5 | "scripts": { 6 | "start": "node server.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/itsjoesullivan/tiny-808" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/itsjoesullivan/tiny-808/issues" 14 | }, 15 | "homepage": "http://tiny-808.com/", 16 | "dependencies": { 17 | "clappy": "*", 18 | "classnames": "^2.1.2", 19 | "claves": "*", 20 | "cow-bell": "*", 21 | "hi-hat": "*", 22 | "html-minifier": "^2.1.3", 23 | "kick-eight": "*", 24 | "maracas": "^1.0.1", 25 | "react-lite": "^0.15.14", 26 | "react-redux": "^4.2.1", 27 | "redux": "^3.2.1", 28 | "rim-shot": "*", 29 | "snare": "*", 30 | "tom-tom": "*", 31 | "webpack": "^1.13.1", 32 | "web-audio-ios": "^1.0.2" 33 | }, 34 | "devDependencies": { 35 | "babel-core": "^6.3.15", 36 | "babel-loader": "^6.2.0", 37 | "babel-preset-es2015": "^6.3.13", 38 | "babel-preset-react": "^6.3.13", 39 | "babel-preset-react-hmre": "^1.1.1", 40 | "babel-register": "^6.3.13", 41 | "cross-env": "^1.0.7", 42 | "express": "^4.13.3", 43 | "jsdom": "^5.6.1", 44 | "node-libs-browser": "^0.5.2", 45 | "raw-loader": "^0.5.1", 46 | "react": "^0.14.7", 47 | "react-addons-test-utils": "^0.14.7", 48 | "react-dom": "^0.14.7", 49 | "style-loader": "^0.12.3", 50 | "webpack": "^1.13.1", 51 | "webpack-dev-middleware": "^1.2.0", 52 | "webpack-hot-middleware": "^2.9.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import machine from './machine' 3 | 4 | const rootReducer = combineReducers({ 5 | machine 6 | }) 7 | 8 | export default rootReducer 9 | -------------------------------------------------------------------------------- /reducers/machine.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | tempo: 120, 3 | swing: 0, 4 | pattern: [ 5 | [ 6 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 7 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 8 | ], 9 | [ 10 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 11 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 12 | ], 13 | [ 14 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 15 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 16 | ], 17 | [ 18 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 19 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 20 | ], 21 | [ 22 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 23 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 24 | ], 25 | [ 26 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 28 | ], 29 | [ 30 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 32 | ], 33 | [ 34 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 35 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 36 | ], 37 | [ 38 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 39 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 40 | ], 41 | [ 42 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 43 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 44 | ], 45 | [ 46 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 47 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 48 | ], 49 | [ 50 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 51 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 52 | ] 53 | ], 54 | sounds: [ 55 | { 56 | modes: [ 57 | { 58 | name: "Accent", 59 | shortName: "AC" 60 | } 61 | ], 62 | currentModeIndex: 0, 63 | properties: [ 64 | { 65 | name: "level", 66 | value: 100 67 | } 68 | ] 69 | }, 70 | { 71 | modes: [ 72 | { 73 | name: "Bass Drum", 74 | shortName: "BD" 75 | } 76 | ], 77 | currentModeIndex: 0, 78 | properties: [ 79 | { 80 | name: "level", 81 | value: 100 82 | }, 83 | { 84 | name: "tone", 85 | value: 64 86 | }, 87 | { 88 | name: "decay", 89 | value: 20 90 | } 91 | ] 92 | }, 93 | { 94 | modes: [ 95 | { 96 | name: "Snare Drum", 97 | shortName: "SD" 98 | } 99 | ], 100 | currentModeIndex: 0, 101 | properties: [ 102 | { 103 | name: "level", 104 | value: 64 105 | }, 106 | { 107 | name: "tone", 108 | value: 100 109 | }, 110 | { 111 | name: "snappy", 112 | value: 100 113 | } 114 | ] 115 | }, 116 | { 117 | modes: [ 118 | { 119 | name: "Low Conga", 120 | shortName: "LC" 121 | }, 122 | { 123 | name: "Low Tom", 124 | shortName: "LT" 125 | } 126 | ], 127 | currentModeIndex: 1, 128 | properties: [ 129 | { 130 | name: "level", 131 | value: 100 132 | }, 133 | { 134 | name: "tuning", 135 | value: 64 136 | } 137 | ] 138 | }, 139 | { 140 | modes: [ 141 | { 142 | name: "Mid Conga", 143 | shortName: "MC" 144 | }, 145 | { 146 | name: "Mid Tom", 147 | shortName: "MT" 148 | } 149 | ], 150 | currentModeIndex: 1, 151 | properties: [ 152 | { 153 | name: "level", 154 | value: 100 155 | }, 156 | { 157 | name: "tuning", 158 | value: 64 159 | } 160 | ] 161 | }, 162 | { 163 | modes: [ 164 | { 165 | name: "Hi Conga", 166 | shortName: "HC" 167 | }, 168 | { 169 | name: "Hi Tom", 170 | shortName: "HT" 171 | } 172 | ], 173 | currentModeIndex: 1, 174 | properties: [ 175 | { 176 | name: "level", 177 | value: 100 178 | }, 179 | { 180 | name: "tuning", 181 | value: 64 182 | } 183 | ] 184 | }, 185 | { 186 | modes: [ 187 | { 188 | name: "Claves", 189 | shortName: "CL" 190 | }, 191 | { 192 | name: "Rim Shot", 193 | shortName: "RS" 194 | } 195 | ], 196 | currentModeIndex: 1, 197 | properties: [ 198 | { 199 | name: "level", 200 | value: 100 201 | } 202 | ] 203 | }, 204 | { 205 | modes: [ 206 | { 207 | name: "Maracas", 208 | shortName: "MA" 209 | }, 210 | { 211 | name: "Hand Clap", 212 | shortName: "CP" 213 | } 214 | ], 215 | currentModeIndex: 1, 216 | properties: [ 217 | { 218 | name: "level", 219 | value: 64 220 | } 221 | ] 222 | }, 223 | { 224 | modes: [ 225 | { 226 | name: "Cow Bell", 227 | shortName: "CB" 228 | } 229 | ], 230 | currentModeIndex: 0, 231 | properties: [ 232 | { 233 | name: "level", 234 | value: 100 235 | } 236 | ] 237 | }, 238 | { 239 | modes: [ 240 | { 241 | name: "Cymbal", 242 | shortName: "CY" 243 | } 244 | ], 245 | currentModeIndex: 0, 246 | properties: [ 247 | { 248 | name: "level", 249 | value: 100 250 | }, 251 | { 252 | name: "tone", 253 | value: 100 254 | }, 255 | { 256 | name: "decay", 257 | value: 100 258 | } 259 | ] 260 | }, 261 | { 262 | modes: [ 263 | { 264 | name: "Open HiHat", 265 | shortName: "OH" 266 | } 267 | ], 268 | currentModeIndex: 0, 269 | properties: [ 270 | { 271 | name: "level", 272 | value: 100 273 | }, 274 | { 275 | name: "decay", 276 | value: 100 277 | } 278 | ] 279 | }, 280 | { 281 | modes: [ 282 | { 283 | name: "Closed HiHat", 284 | shortName: "CH" 285 | } 286 | ], 287 | currentModeIndex: 0, 288 | properties: [ 289 | { 290 | name: "level", 291 | value: 100 292 | } 293 | ] 294 | } 295 | ], 296 | activeSoundIndex: 1, 297 | activePatternSection: 0, 298 | playing: false, 299 | patternMode: "A" 300 | } 301 | 302 | var resetState = JSON.parse(JSON.stringify(initialState)); 303 | 304 | var savedState; 305 | try { 306 | savedState = JSON.parse(decodeURIComponent(document.location.hash.slice(1))); 307 | savedState.cursor = 0; 308 | savedState.playing = false; 309 | } catch (e) { 310 | // may not fit 311 | savedState = false; 312 | } 313 | 314 | export default function todos(state = (savedState || initialState), action) { 315 | switch (action.type) { 316 | case "CHANGE_TEMPO": 317 | return Object.assign({}, state, {tempo: action.tempo}); 318 | case "CHANGE_SWING": 319 | return Object.assign({}, state, {swing: action.swing}); 320 | case "TOGGLE_PLAY": 321 | var nowPlaying = !state.playing; 322 | var obj = { 323 | playing: nowPlaying 324 | } 325 | if (!nowPlaying && state.patternMode === "AB") { 326 | obj.activePatternSection = 0; 327 | } 328 | return Object.assign({}, state, obj); 329 | case "CHANGE_ACTIVE_SOUND": 330 | return Object.assign({}, state, {activeSoundIndex: action.index}); 331 | case "CHANGE_SOUND_PROPERTY": 332 | var newState = Object.assign({}, state); 333 | newState.sounds[newState.activeSoundIndex].properties[action.propertyIndex].value = action.value; 334 | return newState; 335 | case "PATTERN_CHANGE": 336 | var newState = Object.assign({}, state); 337 | 338 | var pattern = newState.pattern[newState.activeSoundIndex][newState.activePatternSection]; 339 | if (pattern[action.index] === 0) { 340 | pattern[action.index] = 1; 341 | } else { 342 | pattern[action.index] = 0; 343 | } 344 | return newState; 345 | case "CHANGE_PATTERN_MODE": 346 | var newState = Object.assign({}, state, { patternMode: action.mode }); 347 | if (newState.playing) { 348 | if (action.mode === "B" && newState.activePatternSection !== 1) { 349 | newState.targetPatternSection = 1; 350 | } else if (action.mode === "A" && newState.activePatternSection !== 0) { 351 | newState.targetPatternSection = 0; 352 | } 353 | } else { 354 | if (action.mode === "B") { 355 | newState.activePatternSection = 1; 356 | } else if (action.mode === "A") { 357 | newState.activePatternSection = 0; 358 | } 359 | } 360 | return newState; 361 | case "SET_CURSOR": 362 | var newState = Object.assign({}, state, { cursor: action.index }); 363 | if (action.index === 0) { 364 | if (typeof state.targetPatternSection === 'number') { 365 | if (state.patternMode !== "AB") { 366 | newState.activePatternSection = state.targetPatternSection; 367 | } 368 | delete newState.targetPatternSection; 369 | } 370 | } 371 | return newState; 372 | case "SET_ACTIVE_PATTERN_SECTION_INDEX": 373 | var newState = Object.assign({}, state, { activePatternSection: action.index }); 374 | return newState; 375 | case "CHANGE_SOUND_MODE": 376 | var newState = Object.assign({}, state); 377 | var currentSound = newState.sounds[newState.activeSoundIndex]; 378 | currentSound.currentModeIndex = action.index; 379 | return newState; 380 | case "RESET": 381 | return JSON.parse(JSON.stringify(resetState)); 382 | case "COPY_PATTERN": 383 | var newState = Object.assign({}, state); 384 | newState.copiedPattern = state.pattern.map(function(soundPatternSections) { 385 | return soundPatternSections[action.index]; 386 | }); 387 | newState.copiedPattern = JSON.parse(JSON.stringify(newState.copiedPattern)); 388 | return newState; 389 | case "PASTE_PATTERN_TO_TARGET": 390 | var newState = Object.assign({}, state); 391 | if (newState.copiedPattern) { 392 | newState.pattern.forEach(function(instrumentsPatterns, i) { 393 | instrumentsPatterns[action.index] = newState.copiedPattern[i]; 394 | }); 395 | 396 | } 397 | return newState; 398 | case "CLEAR_PATTERN": 399 | var newState = Object.assign({}, state); 400 | newState.pattern.forEach(function(instrumentsPatterns, i) { 401 | instrumentsPatterns[action.index] = instrumentsPatterns[action.index].map(function() { return 0; }); 402 | }); 403 | 404 | case "COPY_INSTRUMENT_PATTERN": 405 | var newState = Object.assign({}, state); 406 | newState.copiedInstrumentPattern = state.pattern[state.activeSoundIndex][action.index]; 407 | newState.copiedInstrumentPattern = JSON.parse(JSON.stringify(newState.copiedInstrumentPattern)); 408 | return newState; 409 | case "PASTE_INSTRUMENT_PATTERN_TO_TARGET": 410 | var newState = Object.assign({}, state); 411 | if (newState.copiedInstrumentPattern) { 412 | newState.pattern[state.activeSoundIndex][action.index] = newState.copiedInstrumentPattern; 413 | } 414 | return newState; 415 | case "CLEAR_INSTRUMENT_PATTERN": 416 | var newState = Object.assign({}, state); 417 | var toChange = newState.pattern[state.activeSoundIndex][action.index]; 418 | newState.pattern[state.activeSoundIndex][action.index] = toChange.map(function() { 419 | return 0; 420 | }); 421 | return newState; 422 | case 'GENERAL_KEYDOWN': 423 | var newState = Object.assign({}, state); 424 | var index = false; 425 | switch (action.which) { 426 | case 81: // q 427 | index = 0; 428 | break; 429 | case 65: // a 430 | index = 1; 431 | break; 432 | case 87: // w 433 | index = 6; 434 | break; 435 | case 83: // s 436 | index = 2; 437 | break; 438 | case 69: // e 439 | index = 7; 440 | break; 441 | case 68: // d 442 | index = 2; 443 | break; 444 | case 70: // f 445 | index = 3; 446 | break; 447 | case 71: // d 448 | index = 4; 449 | break; 450 | case 72: // d 451 | index = 5; 452 | break; 453 | case 84: // t 454 | index = 11; 455 | break; 456 | case 85: // u 457 | index = 10; 458 | break; 459 | case 79: // o 460 | index = 9; 461 | break; 462 | case 80: // p 463 | index = 9; 464 | break; 465 | case 82: // r 466 | index = 8; 467 | break; 468 | } 469 | if (typeof index === 'number') { 470 | if (state.playing) { 471 | // Seems like a bad practice to call this here. 472 | window.oneShot(index, state); 473 | if (action.shift) { 474 | // Add 475 | var currentVal = newState.pattern[index][state.activePatternSection][state.cursor]; 476 | var newVal; 477 | if (currentVal === 1) { 478 | newVal = 0; 479 | } else { 480 | newVal = 1; 481 | } 482 | newState.pattern[index][state.activePatternSection][state.cursor] = newVal; 483 | } 484 | newState.activeSoundIndex = index; 485 | } else { 486 | if (action.shift) { 487 | newState.activeSoundIndex = index; 488 | } else { 489 | // Seems like a bad practice to call this here. 490 | window.oneShot(index, state); 491 | } 492 | } 493 | } 494 | return newState; 495 | break; 496 | default: 497 | return state; 498 | } 499 | } 500 | -------------------------------------------------------------------------------- /scripts/after_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "AFTER DEPLOY" 3 | -------------------------------------------------------------------------------- /scripts/after_failure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "AFTER_FAILURE" 3 | -------------------------------------------------------------------------------- /scripts/after_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "AFTER SCRIPT" 3 | -------------------------------------------------------------------------------- /scripts/after_success.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "AFTER_SUCCESS" 3 | -------------------------------------------------------------------------------- /scripts/before_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "BEFORE DEPLOY" 3 | -------------------------------------------------------------------------------- /scripts/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "BEFORE_INSTALL" 3 | -------------------------------------------------------------------------------- /scripts/before_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "BEFORE_SCRIPT" 3 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "DEPLOY" 3 | npm install 4 | NODE_ENV=production ./node_modules/webpack/bin/webpack.js -p --config webpack.production.config.js 5 | 6 | ls 7 | 8 | HASH=`cat dist/bundle.js | shasum | awk '{print substr($0,0,5)}'` 9 | mv dist/bundle.js dist/bundle-$HASH.js 10 | cp index.html dist/index.html 11 | ./node_modules/html-minifier/cli.js dist/index.html > dist/index2.html --collapse-whitespace --minify-css --minify-js --remove-attribute-quotes --use-short-doctype 12 | mv dist/index2.html dist/index.html 13 | sed -i -e "s/\/static\/bundle\.js/http\:\/\/dyclrq6t27il\.cloudfront\.net\/bundle-$HASH\.js/g" dist/index.html 14 | gzip dist/index.html 15 | mv dist/index.html.gz dist/index.html 16 | 17 | s3cmd --access_key=$s3_access_key --secret_key=$s3_secret_key \ 18 | sync dist/ s3://tiny-808.com 19 | s3cmd --access_key=$s3_access_key --secret_key=$s3_secret_key \ 20 | modify s3://tiny-808.com/bundle-$HASH.js --add-header=Cache-Control:max-age=315360000 21 | s3cmd --access_key=$s3_access_key --secret_key=$s3_secret_key \ 22 | modify s3://tiny-808.com/index.html --add-header=Content-Encoding:gzip 23 | s3cmd --access_key=$s3_access_key --secret_key=$s3_secret_key \ 24 | modify s3://tiny-808.com/index.html --add-header=Content-Type:text/html 25 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "INSTALL" 3 | npm install 4 | 5 | wget http://ufpr.dl.sourceforge.net/project/s3tools/s3cmd/1.6.1/s3cmd-1.6.1.tar.gz 6 | tar xzf s3cmd-1.6.1.tar.gz 7 | cd s3cmd-1.6.1 8 | sudo python setup.py install 9 | -------------------------------------------------------------------------------- /scripts/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "SCRIPT" 3 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var webpackDevMiddleware = require('webpack-dev-middleware') 3 | var webpackHotMiddleware = require('webpack-hot-middleware') 4 | var config = require('./webpack.config') 5 | 6 | var app = new (require('express'))() 7 | var port = 3000 8 | 9 | var compiler = webpack(config) 10 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) 11 | app.use(webpackHotMiddleware(compiler)) 12 | 13 | app.get("/", function(req, res) { 14 | res.sendFile(__dirname + '/index.html') 15 | }) 16 | app.get("/bright.mp3", function(req, res) { 17 | res.sendFile(__dirname + '/bright.mp3') 18 | }) 19 | 20 | app.listen(port, function(error) { 21 | if (error) { 22 | console.error(error) 23 | } else { 24 | console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux' 2 | import rootReducer from '../reducers' 3 | 4 | export default function configureStore(initialState) { 5 | const store = createStore(rootReducer, initialState) 6 | 7 | return store 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: [ 6 | './main' 7 | ], 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: 'bundle.js', 11 | publicPath: '/static/' 12 | }, 13 | plugins: [ 14 | new webpack.optimize.OccurenceOrderPlugin(), 15 | ], 16 | module: { 17 | loaders: [ 18 | { 19 | test: /\.js$/, 20 | loaders: [ 'babel' ], 21 | exclude: /node_modules/, 22 | include: __dirname 23 | } 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: [ 6 | './main' 7 | ], 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: 'bundle.js', 11 | publicPath: '/static/' 12 | }, 13 | plugins: [ 14 | new webpack.optimize.OccurenceOrderPlugin(), 15 | new webpack.DefinePlugin({ 16 | 'process.env': { 17 | 'NODE_ENV': JSON.stringify('production') 18 | } 19 | }), 20 | new webpack.optimize.UglifyJsPlugin({ 21 | compress: { warnings: false } 22 | }) 23 | ], 24 | module: { 25 | loaders: [ 26 | { 27 | test: /\.js$/, 28 | loaders: [ 'babel' ], 29 | exclude: /node_modules/, 30 | include: __dirname 31 | } 32 | ] 33 | }, 34 | resolve: { 35 | alias: { 36 | 'react': 'react-lite', 37 | 'react-dom': 'react-lite' 38 | } 39 | } 40 | } 41 | --------------------------------------------------------------------------------