├── .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 | Tempo:
86 |
94 | {machine.tempo} BPM
95 |
96 |
97 |
98 |
99 |
102 | {machine.playing ? "Pause" : "Play"}
103 |
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 |
15 | Save to url
16 |
17 |
20 | Get shortlink
21 |
22 |
25 | Clear
26 |
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
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 | {property.name}:
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 =
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 | Sound:
53 |
57 | {sounds.map(function(sound, i) {
58 | return
62 | {sound.modes[sound.currentModeIndex].name}
63 |
64 | })}
65 |
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 |
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 |
--------------------------------------------------------------------------------