Skip to content

Commit

Permalink
add v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mpobrien committed Jun 25, 2016
1 parent 65f2a71 commit 29123ba
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"extract-text-webpack-plugin": "^1.0.1",
"less-loader": "^2.2.2",
"node-sass": "^3.4.2",
"react-bootstrap": "^0.28.5",
"sass-loader": "^3.2.0",
"style-loader": "^0.13.0",
"teoria": "^2.2.0",
Expand Down
2 changes: 1 addition & 1 deletion scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ exports.accidental = function(noteName){

var notes = [ 'c', 'c♯', 'd', 'e♭', 'e', 'f', 'f♯', 'g', 'a♭', 'a', 'b♭', 'b' ]
var enharmonics = [["c♯","d♭"], ["d♯", "e♭",], ["g♭", "f♯"], ["a♭", "g♯"], ["b♭", "a♯"]]
var keySignatureType = [null, sharp, sharp, flat, sharp, flat, sharp,
//var keySignatureType = [null, sharp, sharp, flat, sharp, flat, sharp,
exports.notes = notes
exports.enharmonics = enharmonics

Expand Down
2 changes: 1 addition & 1 deletion sightread2.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var Nav = require('react-bootstrap').Nav;
var NavItem = require('react-bootstrap').NavItem;
var MenuItem = require('react-bootstrap').MenuItem;
var NavDropdown = require('react-bootstrap').NavDropdown;
var css = require("./sightread.scss");
//var css = require("./sightread.scss");
var teoria = require("teoria")

var scaleTypes = _.map(Scale.Types, function(obj){
Expand Down
10 changes: 10 additions & 0 deletions v2/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>SoundCloud React Redux App</title>
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
43 changes: 43 additions & 0 deletions v2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "keysight2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --progress --colors --hot --config ./webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"babel": {
"presets": [
"es2015",
"react",
"stage-2"
]
},
"devDependencies": {
"babel-core": "^6.9.1",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.5.0",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"react-bootstrap": "^0.29.5",
"react-hot-loader": "^1.3.0",
"style-loader": "^0.13.1",
"teoria": "^2.2.0",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"midi": "github:mudcube/MIDI.js",
"react": "^15.1.0",
"react-dom": "^15.1.0",
"react-redux": "^4.4.5",
"redux": "^3.5.2",
"redux-logger": "^2.6.1"
}
}
7 changes: 7 additions & 0 deletions v2/src/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

import { gotMidiEvent } from './midi';

export {
gotMidiEvent
};

9 changes: 9 additions & 0 deletions v2/src/actions/midi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as actionTypes from '../constants/actionTypes';

export function gotMidiEvent(midiEvent) {
return {
type: actionTypes.MIDI_EVENT,
midiEvent
};
};

9 changes: 9 additions & 0 deletions v2/src/components/Container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

function Container(){
return (
<div>HELLO4!</div>
);
}

export default Container;
21 changes: 21 additions & 0 deletions v2/src/components/MidiDebugger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { connect } from 'react-redux';


class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
}

render() {
return <div onClick={this._handleClick}>Hello, world.</div>;
}
_handleClick() {
console.log(this); // this is undefined
}
}


export default ExampleComponent;

2 changes: 2 additions & 0 deletions v2/src/constants/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const MIDI_EVENT = 'MIDI_EVENT';

49 changes: 49 additions & 0 deletions v2/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import ReactDOM from 'react-dom';
import Container from './components/Container';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { Provider } from 'react-redux';
import ThemeManager from 'material-ui/styles/ThemeManager';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import {MidiEventDebuggerDisplay, getMidiData, midiSetup} from './midihandler';
import configureStore from './stores/configureStore';
import * as actions from './actions';
import {Synth} from './synth';


const store = configureStore();
function init() {
// Create a list of callbacks that we can push to. Each time a midi event is
// received, we broadcast the event to all the callbacks in the list.
let midiCallbacks = [ (x) => {store.dispatch(actions.gotMidiEvent(x))} ]

try {
// Fix up for prefixing
window.AudioContext = window.AudioContext||window.webkitAudioContext;
let soundGen = new Synth(new AudioContext());
midiCallbacks.push((e) => {soundGen.handleMidiEvent(e)})
}
catch(e) {
console.log('Web Audio API is not supported in this browser', e);
}

midiSetup((event) => {
for(let cb of midiCallbacks){
console.log("calling!", cb)
cb(event)
}
})
}

window.addEventListener('load', init, false);

ReactDOM.render(
<div>
<Provider store={store}>
<MidiEventDebuggerDisplay/>
</Provider>
</div>,
document.getElementById('app')
);
72 changes: 72 additions & 0 deletions v2/src/midihandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { connect } from 'react-redux'
import React from 'react';
export const EVENT_NOTEOFF = "Note Off";
export const EVENT_NOTEON = "Note On";
export const EVENT_SUSTAIN = "Sustain";
export const EVENT_Expr = "Expression";

export function midiSetup(callback) {
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess({sysex:false}).then(function(midiAccess){
let midi = midiAccess;
let inputs = midi.inputs.values();
for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
input.value.onmidimessage = (message) => callback(message)
}
}, function(){
throw Exception("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim:"+e);
});
} else {
throw Exception("No MIDI support in your browser.");
}
}

export function getMidiData(data){
let scaleMap = [ 'C', 'C♯', 'D', 'E♭', 'E', 'F', 'F♯', 'G', 'A♭', 'A', 'B♭', 'B' ]
let retVal = {}

if (data[0] >= 128 && data[0] <= 159) {
retVal.velocity = data[2];
retVal.note = data[1];
retVal.letter = scaleMap[data[1] % 12];
retVal.octave = Math.floor(data[1] / 12);
}

if (data[0] >= 128 && data[0] <= 143) {
retVal.event = EVENT_NOTEOFF;
}
if (data[0] >= 144 && data[0] <= 159) {
retVal.event = EVENT_NOTEON;
}
if (data[0] === 185) {
if (data[1] === 11) {
retVal.event = EVENT_EXPR;
}
if (data[1] === 64) {
retVal.event = EVENT_SUSTAIN;
}
}
if (data[0] === 185 && data[1] === 11) {
retVal.event = EVENT_EXPR;
}
return retVal;
}

let midiStateDisplay = function({midi = {}}){
return (
<div>hi {midi.notes}</div>
)
}

function mapStateToProps(state) {
const midi = state.midi;
return {
midi
}
}


const MidiEventDebuggerDisplay = connect(mapStateToProps)(midiStateDisplay)

export {MidiEventDebuggerDisplay}

7 changes: 7 additions & 0 deletions v2/src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { combineReducers } from 'redux';
import midi from './midi';

export default combineReducers({
midi
});

25 changes: 25 additions & 0 deletions v2/src/reducers/midi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as actionTypes from '../constants/actionTypes';
import * as midihandler from '../midihandler'

const initialState = {notes:new Set()};

export default function(state = initialState, action) {
switch (action.type) {
case actionTypes.MIDI_EVENT:
return updateMidiState(state, action);
}
return state;
}

function updateMidiState(state, action) {
let noteSet = new Set(state.notes.values())
let parsedMidi = midihandler.getMidiData(action.midiEvent.data)
let noteKey = `${parsedMidi.letter}${parsedMidi.octave}`
if(parsedMidi.event == midihandler.EVENT_NOTEOFF){
noteSet.delete(noteKey)
}else if (parsedMidi.event == midihandler.EVENT_NOTEON){
noteSet.add(noteKey)
}
return {notes:noteSet};
}

12 changes: 12 additions & 0 deletions v2/src/stores/configureStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import rootReducer from '../reducers/index';

const logger = createLogger();

const createStoreWithMiddleware = applyMiddleware(logger)(createStore);

export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}

75 changes: 75 additions & 0 deletions v2/src/synth/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as midihandler from '../midihandler'

let noteNumToFreq = (note) => 440 * Math.pow(2,(note-69)/12);

const waveforms = ["sine","square","sawtooth","triangle"];

// TODO: make these non-constants!
const PORTAMENTO = 0.05;
const ATTACK = 0.05;
const RELEASE = 0.05;
const PITCHWHEEL = 0.0;

class Synth {
constructor(context){
this.context = context;
this.activeNotes = {};
}

noteOn(noteNum){
let voice = new Voice(this.context, noteNum)
voice.on()
this.activeNotes[noteNum] = voice
}

noteOff(noteNum){
let voice = this.activeNotes[noteNum]
if(voice){
voice.off()
}
}

handleMidiEvent(event){
console.log("synth handling midi event!", event)
let parsedMidi = midihandler.getMidiData(event.data)
if(parsedMidi.event == midihandler.EVENT_NOTEOFF){
this.noteOff(parsedMidi.note)
}else if (parsedMidi.event == midihandler.EVENT_NOTEON){
this.noteOn(parsedMidi.note)
}else{
console.log("doing whatever ", parsedMidi.event)
}
}
}



class Voice{
constructor(context, note){
this.note = note
let baseFreq = noteNumToFreq(note)
// set up the basic oscillator chain, muted to begin with.
this.oscillator = context.createOscillator();
this.oscillator.frequency.setValueAtTime(baseFreq, 0);
this.envelope = context.createGain();
this.oscillator.connect(this.envelope);
this.envelope.connect(context.destination);
this.envelope.gain.value = 0.0; // Mute the sound
this.oscillator.start(0); // Go ahead and start up the oscillator
}

on() {
this.oscillator.frequency.cancelScheduledValues(0);
this.oscillator.frequency.setTargetAtTime(noteNumToFreq(this.note), 0, PORTAMENTO);
this.envelope.gain.cancelScheduledValues(0);
this.envelope.gain.setTargetAtTime(1.0, 0, ATTACK);
}
off(){
this.envelope.gain.cancelScheduledValues(0);
this.envelope.gain.setTargetAtTime(0.0, 0, RELEASE );
this.oscillator.frequency.cancelScheduledValues(0);
this.oscillator.frequency.setTargetAtTime( noteNumToFreq(this.note), 0, PORTAMENTO );
}
}

export {Synth}
Loading

0 comments on commit 29123ba

Please sign in to comment.