Last active
January 6, 2018 23:21
-
-
Save Joeltbond/256900c217080d4786ac850ade4a56cf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { eventChannel } from "redux-saga"; | |
import { takeEvery, take, call, put } from "redux-saga/effects"; | |
const APP_MOUNTED = "app/APP_MOUNTED"; | |
const MIDI_UNSUPPORTED = "app/MIDI_UNSUPPORTED"; | |
const MIDI_ACCESS_FAILURE = "app/MIDI_ACCESS_FAILURE"; | |
const MIDI_NOTE_ON = "app/MIDI_NOTE_ON"; | |
const MIDI_NOTE_OFF = "app/MIDI_NOTE_OFF"; | |
const initialState = { | |
errors: [], | |
notes: {} | |
}; | |
export default (state = initialState, action = {}) => { | |
switch (action.type) { | |
case MIDI_UNSUPPORTED: | |
return { | |
...state, | |
errors: state.errors.concat( | |
"Midi input is not supported in your browser" | |
) | |
}; | |
case MIDI_ACCESS_FAILURE: | |
return { | |
...state, | |
errors: state.errors.concat( | |
"Failed to access midi" | |
) | |
}; | |
case MIDI_NOTE_ON: { | |
return { | |
...state, | |
notes: { | |
...state.notes, | |
[action.payload.note]: action.payload.velocity | |
} | |
}; | |
} | |
case MIDI_NOTE_OFF: { | |
const newNotes = { ...state.notes }; | |
delete newNotes[action.payload.note]; | |
return { | |
...state, | |
notes: newNotes | |
}; | |
} | |
default: | |
return state; | |
} | |
}; | |
export const appMounted = () => ({ | |
type: APP_MOUNTED | |
}); | |
function* onMidiMessage({ data }) { | |
const [type, note, velocity] = data; | |
switch (type) { | |
case 144: | |
yield put({ type: MIDI_NOTE_ON, payload: { note, velocity } }); | |
break; | |
case 128: | |
yield put({ type: MIDI_NOTE_OFF, payload: { note } }); | |
break; | |
default: | |
} | |
} | |
function createMidiEventChannel(midiAccess) { | |
return eventChannel(emitter => { | |
const inputs = midiAccess.inputs.values(); | |
for ( | |
var input = inputs.next(); | |
input && !input.done; | |
input = inputs.next() | |
) { | |
// each time there is a midi message call the onMIDIMessage function | |
input.value.onmidimessage = emitter; | |
// the emitter can also be called with the 'DONE' constant from redux-saga | |
// which causes the channel to close. For this example we will never close. | |
} | |
// The subscriber must return an unsubscribe function. We'll just return no op for this example | |
return () => { | |
// Cleanup event listeners. Clear timers etc... | |
}; | |
}); | |
} | |
function* onMidiSuccess(midiAccess) { | |
const channel = yield call(createMidiEventChannel, midiAccess); | |
while (true) { | |
const message = yield take(channel); | |
yield call(onMidiMessage, message); | |
} | |
} | |
function* appMountedSaga() { | |
if (window.navigator.requestMIDIAccess) { | |
try { | |
const midiAccess = yield call( | |
[window.navigator, window.navigator.requestMIDIAccess], | |
{ | |
sysex: false | |
} | |
); | |
yield call(onMidiSuccess, midiAccess); | |
} catch (error) { | |
yield put({ type: MIDI_ACCESS_FAILURE }); | |
console.error(error); | |
} | |
} else { | |
yield put({ type: MIDI_UNSUPPORTED }) | |
} | |
} | |
export const appSagas = [takeEvery(APP_MOUNTED, appMountedSaga)]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment