Skip to content

Instantly share code, notes, and snippets.

@Joeltbond
Last active January 6, 2018 23:21
Show Gist options
  • Save Joeltbond/256900c217080d4786ac850ade4a56cf to your computer and use it in GitHub Desktop.
Save Joeltbond/256900c217080d4786ac850ade4a56cf to your computer and use it in GitHub Desktop.
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