synthing

a waveform sequencing synth on the web
Log | Files | Refs | Submodules

commit 4e546d4d1eb8b9a36722ec4fb65884e2666382fd
parent 07e308b61ee86a9bc39e7f3c2617005597d39d66
Author: Massimo Siboldi <mdsiboldi@gmail.com>
Date:   Sun, 11 Mar 2018 22:13:41 -0700

Introducing thunk to handle the metro until it goes into a web worker

Diffstat:
Mpackage-lock.json | 5+++++
Mpackage.json | 3++-
Msrc/App/index.js | 24++----------------------
Msrc/Synth/index.js | 8++++++--
Msrc/index.js | 19+++++++++++++++++--
Msrc/store.js | 44+++++++++++++++++++++++++++-----------------
6 files changed, 59 insertions(+), 44 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -8855,6 +8855,11 @@ "symbol-observable": "1.2.0" } }, + "redux-thunk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", + "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" + }, "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", diff --git a/package.json b/package.json @@ -11,7 +11,8 @@ "envelope-generator": "^3.0.0", "preact": "^8.2.6", "preact-redux": "^2.0.3", - "redux": "^3.7.2" + "redux": "^3.7.2", + "redux-thunk": "^2.2.0" }, "scripts": { "start": "preact-scripts start", diff --git a/src/App/index.js b/src/App/index.js @@ -222,26 +222,6 @@ class App extends Component { }) } - //TODO this could be part of the reducer, and sending PLAY action can start it. Wonder how that can be done without side effects? Or if it should. - metro = () => { - this.props.setPlaying(true); - const loop = () => { - if (this.props.playing) { - this.props.setBeat((this.props.beat + 1) % this.props.numBeats); - window.setTimeout(loop, (1 / this.props.bpm) * 60000); - } - } - // give redux a chance to convey it's playing, a good sign of a bad design. - // i like this https://medium.com/@machadogj/timers-in-react-with-redux-apps-9a5a722162e8#Timers in Actions - window.setTimeout(loop, 0); - } - - stopMetro = () => { - //TODO this could be one action handler called STOP, rather than composing everything manually like this. - this.props.setBeat(0); - this.props.setPlaying(false); - } - keyHandler(e) { //TODO handle global commands, maybe some modal stuff even wow console.log('wow i got through', e.key); @@ -310,14 +290,14 @@ class App extends Component { <div class="global-controls"> <CircleButton active={this.props.playing} - action={this.metro} + action={this.props.startMetro} disabled={this.props.playing} > <div class="triangle"></div> </CircleButton> <CircleButton active={!this.props.playing} - action={this.stopMetro} + action={this.props.stopMetro} > <div class="rectangle"></div> </CircleButton> diff --git a/src/Synth/index.js b/src/Synth/index.js @@ -35,11 +35,15 @@ export default class Synth extends Component { } } +const taperOff = (length, val, i) => { + return val * (1 - Math.pow(i / length, 0.5)); +} + function updateAudio(waveform) { FFT.forward(waveform); const periodicWave = ac.createPeriodicWave( - new Float32Array(FFT.real), - new Float32Array(FFT.imag) + new Float32Array(FFT.real.map(taperOff.bind(null, FFT.real.length))), + new Float32Array(FFT.imag.map(taperOff.bind(null, FFT.imag.length))) ); P.changeWave(periodicWave); } diff --git a/src/index.js b/src/index.js @@ -10,8 +10,23 @@ const ConnectedApp = connect(state => state, { setVolume: (newVal) => ({type: 'SET_GLOBAL_VOLUME', value: newVal}), setBpm: (newVal) => ({type: 'SET_GLOBAL_BPM', value: newVal}), setBeat: (newVal) => ({type: 'SET_GLOBAL_BEAT', value: newVal}), - setPlaying: (newVal) => ({type: 'SET_GLOBAL_PLAYING', value: newVal}), - setNumBeats: (newVal) => ({type: 'SET_GLOBAL_NUM_BEATS', value: newVal}) + setNumBeats: (newVal) => ({type: 'SET_GLOBAL_NUM_BEATS', value: newVal}), + startMetro: () => (dispatch, getState) => { + const tickMetro = {type: 'TICK_METRO'}; + const startMetro = {type: 'START_METRO'}; + + dispatch(startMetro); + + const loop = () => { + if (getState().playing) { + dispatch(tickMetro); + window.setTimeout(loop, (1 / getState().bpm) * 60000); + } + } + + loop(); + }, + stopMetro: () => ({type: 'STOP_METRO'}) })(App); const InformedApp = <Provider store={store}><ConnectedApp /></Provider>; diff --git a/src/store.js b/src/store.js @@ -1,4 +1,5 @@ -import { createStore } from 'redux'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; const initialState = { volume: 0.7, @@ -11,26 +12,35 @@ const initialState = { const reducer = (state, action) => { let updates = {}; switch (action.type) { - case 'SET_GLOBAL_VOLUME': - updates.volume = action.value; - break; - case 'SET_GLOBAL_BPM': - updates.bpm = action.value; - break; - case 'SET_GLOBAL_BEAT': - updates.beat = action.value; - break; - case 'SET_GLOBAL_PLAYING': - updates.playing = action.value; - break; - case 'SET_GLOBAL_NUM_BEATS': - updates.numBeats = action.value; - break; + case 'SET_GLOBAL_VOLUME': + updates.volume = action.value; + break; + case 'SET_GLOBAL_BPM': + updates.bpm = action.value; + break; + case 'SET_GLOBAL_BEAT': + updates.beat = action.value; + break; + case 'SET_GLOBAL_NUM_BEATS': + updates.numBeats = action.value; + break; + case 'START_METRO': + updates.playing = true; + break; + case 'TICK_METRO': + updates.beat = (state.beat + 1) % state.numBeats; + break; + case 'STOP_METRO': + updates.playing = false; + updates.beat = 0; + break; + default: + break; } return Object.assign({}, state, updates); } -const store = createStore(reducer, initialState); +const store = createStore(reducer, initialState, applyMiddleware(thunk)); export default store; export { store };