commit d403e80f994b8a43a863dc3783ed65172e585741
parent ffeadc2d9b4cee98aa35b6e351240226600f829b
Author: Massimo Siboldi <mdsiboldi@gmail.com>
Date: Mon, 12 Mar 2018 00:35:24 -0700
move rest of state to redux, finally
Diffstat:
5 files changed, 154 insertions(+), 155 deletions(-)
diff --git a/src/App/index.js b/src/App/index.js
@@ -11,46 +11,6 @@ import '../iconfont/style.css';
import consts from '../consts.js';
import helpers from '../helpers.js';
-const initialWave = new Array(consts.BUF_SIZE)
- .fill(0)
- .map((val, i) => Math.sin(i / consts.BUF_SIZE * Math.PI * 2));
-
-const immObjArray = {
- update: (arr, idx, opts) => {
- const newArr = arr.slice();
- newArr[idx] = Object.assign({}, arr[idx], opts)
- return newArr;
- },
- add: (arr, idx, opts) => {
- const newArr = arr.slice();
- newArr.splice(idx, 0, opts);
- return newArr;
- },
- remove: (arr, idx) => {
- const newArr = arr.slice();
- newArr.splice(idx, 1);
- return newArr;
- }
-}
-
-const boolArray = {
- setLength: (ba, newLength) => {
- let newBa = ba.slice(0, newLength);
- if (ba.length < newLength) {
- newBa = newBa.concat(new Array(newLength - ba.length).fill(false));
- }
- return newBa;
- }, create: (length) => {
- return new Array(length).fill(false);
- },
- update: (ba, idx, val) => {
- const newBa = ba.slice();
- newBa[idx] = val;
- return newBa;
- }
-}
-
-
const Adsr = (props) => (
<div style="display: inline-block;">
{consts.adsrProperties.map((aspect) => (
@@ -70,27 +30,13 @@ const Adsr = (props) => (
</div>
)
-
class App extends Component {
- constructor() {
- super();
- this.state = {
- tones: [{
- active: true,
- waveform: initialWave.slice(),
- mix: 0.7,
- mute: false,
- solo: false,
- beats: boolArray.update(boolArray.create(4), 0, true)
- }],
- }
- }
-
- editingWaveform = () => this.state.tones[this.props.editingToneIdx].waveform
+ editingWaveform = () => this.props.tones[this.props.editingToneIdx].waveform
+ //TODO these can be in redux too, using reselect
activeTones = () => {
let hasSolo = false;
- const waves = this.state.tones.reduce((accum, val) => {
+ const waves = this.props.tones.reduce((accum, val) => {
let group = 'rest';
if (val.solo) {
hasSolo = true;
@@ -131,106 +77,46 @@ class App extends Component {
);
}
- updateTone = (idx = this.props.editingToneIdx, opts) => {
- this.setState({
- tones: immObjArray.update(this.state.tones, idx, opts)
- });
- }
-
- removeTone = (idx) => {
- const tones = immObjArray.remove(this.state.tones, idx);
- this.setState({ tones });
- this.props.setEditingToneIdx(Math.min(
- this.props.editingToneIdx,
- tones.length - 1
- ));
- }
-
- changeEditingTone = (i) => this.props.set({editingToneIdx: i})
-
- addTone = (
- waveform = initialWave.slice(),
- at = this.state.tones.length,
- isEditing = false
- ) => {
- const tones = immObjArray.add(this.state.tones, at, {
- waveform,
- beats: boolArray.create(this.props.numBeats),
- mix: 0.7,
- mute: false,
- solo: false
- });
-
- const state = {
- tones
- };
-
- if (isEditing) {
- state.editingToneIdx = at;
- }
-
- this.setState(state);
- }
-
- setBeats = (newNumBeats) => {
- newNumBeats = Math.max(newNumBeats, 1);
- this.props.setNumBeats(newNumBeats);
- this.setState({
- tones: this.state.tones.map((val, idx) => {
- let ret = Object.assign({}, val, {
- beats: boolArray.setLength(val.beats, newNumBeats)
- });
- return ret;
- })
- })
- }
-
keyHandler(e) {
//TODO handle global commands, maybe some modal stuff even wow
console.log('wow i got through', e.key);
}
render() {
- const tones = this.state.tones.map((form, idx) => {
+ const tones = this.props.tones.map((form, idx) => {
return (
<WaveManager
activate={this.props.setEditingToneIdx.bind(null, idx)}
- remove={this.removeTone.bind(this, idx)}
+ remove={this.props.deleteTone.bind(null, idx)}
duplicate={() => {
let pleaseActivate = false;
if (this.props.editingToneIdx === idx) {
pleaseActivate = true;
}
- this.addTone(this.state.tones[idx].waveform.slice(), idx + 1, pleaseActivate);
+ this.props.addTone(this.props.tones[idx].waveform.slice(), idx + 1, pleaseActivate);
}}
activated={idx === this.props.editingToneIdx}
- tone={this.state.tones[idx]}
+ tone={this.props.tones[idx]}
beat={this.props.beat}
toggleMute={() => {
- this.updateTone(idx, {
- mute: !this.state.tones[idx].mute
- })
+ this.props.setToneProperty(idx, 'mute', !this.props.tones[idx].mute);
}}
toggleSolo={() => {
- this.updateTone(idx, {
- solo: !this.state.tones[idx].solo
- })
+ this.props.setToneProperty(idx, 'solo', !this.props.tones[idx].solo);
}}
updateBeat={(i, val) => {
- this.updateTone(idx, {
- beats: boolArray.update(
- this.state.tones[idx].beats,
+ this.props.setToneProperty(
+ idx,
+ 'beats',
+ helpers.boolArray.update(
+ this.props.tones[idx].beats,
i,
val
)
- });
- }}
- mix={this.state.tones[idx].mix}
- updateMix={(mix) => {
- this.updateTone(idx, {
- mix
- });
+ );
}}
+ mix={this.props.tones[idx].mix}
+ updateMix={(mix) => {this.props.setToneProperty(idx, 'mix', mix)}}
></WaveManager>
);
})
@@ -247,7 +133,7 @@ class App extends Component {
mouseData={this.state.mouseData}
waveform={this.editingWaveform()}
updateWaveform={(waveform) => {
- this.updateTone(this.props.editingToneIdx, {waveform});
+ this.props.setToneProperty(this.props.editingToneIdx, 'waveform', waveform);
}}
></WaveEditor>
<div class="global-controls">
@@ -275,11 +161,11 @@ class App extends Component {
/>
<Param
name="beats"
- minVal="1"
+ minVal={3}
maxVal={16}
step="1"
val={this.props.numBeats}
- update={this.setBeats}
+ update={this.props.setNumBeats}
/>
<Adsr adsr={this.props.adsr} update={this.props.setAdsrProperty} />
<HSlider value={this.props.volume} update={this.props.setVolume} />
@@ -287,7 +173,7 @@ class App extends Component {
<div class="wave-manager-container">
{tones}
</div>
- <button onClick={() => this.addTone()}>+</button>
+ <button onClick={() => this.props.addTone()}>+</button>
<Synth
waveform={this.totalWaveform()}
volume={this.props.volume}
diff --git a/src/consts.js b/src/consts.js
@@ -1,5 +1,6 @@
+const BUF_SIZE = 256;
export default {
- BUF_SIZE: 256,
+ BUF_SIZE,
adsrProperties: [
{
name: 'attack',
@@ -20,5 +21,8 @@ export default {
suffix: 's',
maxVal: 30,
}
- ]
+ ],
+ initialWave: new Array(BUF_SIZE)
+ .fill(0)
+ .map((val, i) => Math.sin(i / BUF_SIZE * Math.PI * 2))
}
diff --git a/src/helpers.js b/src/helpers.js
@@ -42,5 +42,38 @@ export default {
}
});
})
+ },
+ boolArray: {
+ setLength: (ba, newLength) => {
+ let newBa = ba.slice(0, newLength);
+ if (ba.length < newLength) {
+ newBa = newBa.concat(new Array(newLength - ba.length).fill(false));
+ }
+ return newBa;
+ }, create: (length) => {
+ return new Array(length).fill(false);
+ },
+ update: (ba, idx, val) => {
+ const newBa = ba.slice();
+ newBa[idx] = val;
+ return newBa;
+ }
+ },
+ immObjArray: {
+ update: (arr, idx, opts) => {
+ const newArr = arr.slice();
+ newArr[idx] = Object.assign({}, arr[idx], opts)
+ return newArr;
+ },
+ add: (arr, idx, opts) => {
+ const newArr = arr.slice();
+ newArr.splice(idx, 0, opts);
+ return newArr;
+ },
+ remove: (arr, idx) => {
+ const newArr = arr.slice();
+ newArr.splice(idx, 1);
+ return newArr;
+ }
}
};
diff --git a/src/index.js b/src/index.js
@@ -6,7 +6,7 @@ import 'preact/devtools';
import { Provider, connect } from 'preact-redux';
import { store } from './store.js';
-const ConnectedApp = connect(state => Object.assign({}, state.global, {adsr: state.adsr}), {
+const ConnectedApp = connect(state => state, {
setVolume: (value) => ({type: 'SET_GLOBAL_VOLUME', value}),
setBpm: (value) => ({type: 'SET_GLOBAL_BPM', value}),
setBeat: (value) => ({type: 'SET_GLOBAL_BEAT', value}),
@@ -28,7 +28,11 @@ const ConnectedApp = connect(state => Object.assign({}, state.global, {adsr: sta
loop();
},
stopMetro: () => ({type: 'STOP_METRO'}),
- setAdsrProperty: (property, value) => ({type: 'SET_ADSR_PROPERTY', property, value})
+ setAdsrProperty: (property, value) => ({type: 'SET_ADSR_PROPERTY', property, value}),
+ addTone: (waveform, idx, activate) => ({type: 'ADD_TONE', waveform, idx, activate}),
+ setToneProperty: (idx, property, value) => ({type: 'SET_TONE_PROPERTY', idx, property, value}),
+ deleteTone: (idx) => ({type: 'DELETE_TONE', idx})
+
})(App);
const InformedApp = <Provider store={store}><ConnectedApp /></Provider>;
diff --git a/src/store.js b/src/store.js
@@ -1,22 +1,39 @@
-import { createStore, combineReducers, applyMiddleware } from 'redux';
+import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import consts from './consts.js';
+import helpers from './helpers.js';
+const numBeats = 4;
const initialState = {
- global: {
- volume: 0.7,
- bpm: 120,
- beat: 0,
- playing: false,
- numBeats: 4,
- editingToneIdx: 0
- },
+ volume: 0.7,
+ bpm: 120,
+ beat: 0,
+ playing: false,
+ numBeats,
+ editingToneIdx: 0,
adsr: {
attack: 0.3,
decay: 1,
sustain: 0.4,
release: 1
- }
+ },
+ tones: [{
+ active: true,
+ waveform: consts.initialWave.slice(),
+ mix: 0.7,
+ mute: false,
+ solo: false,
+ beats: helpers.boolArray.update(helpers.boolArray.create(numBeats), 0, true)
+ }],
+};
+
+const newTone = {
+ active: false,
+ waveform: consts.initialWave.slice(),
+ mix: 0.7,
+ mute: false,
+ solo: false,
+ beats: helpers.boolArray.create(numBeats)
};
const adsrReducer = (state, action) => {
@@ -24,7 +41,6 @@ const adsrReducer = (state, action) => {
const propertiesAllowed = consts.adsrProperties.map(val => val.name);
switch (action.type) {
case 'SET_ADSR_PROPERTY':
- console.log(action);
if (propertiesAllowed.indexOf(action.property) > -1) {
updates[action.property] = action.value
}
@@ -33,7 +49,53 @@ const adsrReducer = (state, action) => {
break;
}
return Object.assign({}, state, updates);
-}
+};
+
+const tonesReducer = (state = [], action) => {
+ switch (action.type) {
+ case 'ADD_TONE':
+ // idx: optional, defaults to end
+ // waveform: optional, defaults to sine wave
+ // activate: optional, defaults active to false
+ const idx = action.idx === undefined ? state.length : action.idx;
+
+ const newToneProps = {};
+ if (action.waveform)
+ newToneProps.waveform = action.waveform;
+ if (action.activate)
+ newToneProps.active = true;
+
+ return helpers.immObjArray.add(
+ state,
+ idx,
+ Object.assign({}, newTone, newToneProps)
+ );
+ case 'SET_TONE_PROPERTY':
+ // idx: required
+ // property: required
+ // value: required
+ const propertiesAllowed = Object.keys(initialState.tones[0]);
+ if (propertiesAllowed.indexOf(action.property) > -1) {
+ return helpers.immObjArray.update(state, action.idx, {
+ [action.property]: action.value
+ });
+ }
+ else return state;
+ case 'DELETE_TONE':
+ // idx: required
+ return helpers.immObjArray.remove(state, action.idx);
+ case 'SET_GLOBAL_NUM_BEATS':
+ // TODO save beats and just change the view, instead of deleting them
+ return state.map((val, idx) => {
+ let ret = Object.assign({}, val, {
+ beats: helpers.boolArray.setLength(val.beats, action.value)
+ });
+ return ret;
+ });
+ default:
+ return state;
+ }
+};
const globalReducer = (state, action) => {
const updates = {};
@@ -63,18 +125,28 @@ const globalReducer = (state, action) => {
case 'SET_EDITING_TONE_IDX':
updates.editingToneIdx = action.value;
break;
+ case 'DELETE_TONE':
+ updates.editingToneIdx = Math.min(
+ state.editingToneIdx,
+ // 1 for length, 1 for deleted tone.
+ // min length of tones array should be 2 before a delete...
+ state.tones.length - 2
+ );
+ break;
default:
break;
}
return Object.assign({}, state, updates);
}
-const reducers = {
- global: globalReducer,
- adsr: adsrReducer
+const reducer = (state = initialState, action) => {
+ return Object.assign({}, state, globalReducer(state, action), {
+ tones: tonesReducer(state.tones, action),
+ adsr: adsrReducer(state.adsr, action)
+ });
};
-const store = createStore(combineReducers(reducers), initialState, applyMiddleware(thunk));
+const store = createStore(reducer, initialState, applyMiddleware(thunk));
export default store;
export { store };