Skip to content

Instantly share code, notes, and snippets.

@chrmcg
Created October 20, 2016 18:52
[RxJS] Music monitor
<input type="text" id="name" placeholder="Click here, then play notes using QWERTY keyboard!" size="50" />
<br/><br/>
<canvas id="canv" width="500" height="300"></canvas>
<pre id="target"></pre>
let keyListeners = {};
let notes = {};
let unfinishedNote = {};
let startTime = null;
let keyOrder = [
"Space",
"KeyZ", "KeyX", "KeyC", "KeyV", "KeyB", "KeyN", "KeyM", "Comma", "Period", "Slash",
"KeyA", "KeyS", "KeyD", "KeyF", "KeyG", "KeyH", "KeyJ", "KeyK", "KeyL", "Semicolon", "Quote",
'KeyQ', "KeyW", "KeyE", "KeyR", "KeyT", "KeyY", "KeyU", "KeyI", "KeyO", "KeyP", "BracketLeft", "BracketRight", "Backslash",
'Backquote', 'Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5', 'Digit6', 'Digit7', 'Digit8', 'Digit9', 'Digit0', 'Minus', 'Equal',
];
let margin = 10;
let timeWindowMs = 7500;
let printState = () => {
document.querySelector('#target').innerHTML =
JSON.stringify(unfinishedNote, null, 4) +
JSON.stringify(notes, null, 4);
}
let drawDot = (x, y) => {
let canv = document.querySelector('#canv');
let ctx = canv.getContext('2d');
let radius = 2;
ctx.beginPath();
ctx.arc(
margin + x,
canv.height - margin - y,
radius,
0,
2 * Math.PI
);
ctx.fillStyle = "black";
ctx.fill();
}
let drawSegment = (x1, x2, y1, y2, complete=true) => {
let canv = document.querySelector('#canv');
let ctx = canv.getContext('2d');
let radius = 2;
drawDot(x1, y1);
if (complete) {
drawDot(x2, y2);
}
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(
margin + x1,
canv.height - margin - y1
);
ctx.lineTo(
margin + x2,
canv.height - margin - y2
);
ctx.stroke();
}
let addKeyListener = key => {
notes[key] = [];
unfinishedNote[key] = null;
let listener = new Rx.Subject();
keyListeners[key] = listener;
let distinct = listener.distinctUntilChanged(
keyEvent => keyEvent.dir
);
distinct.filter(
keyEvent => keyEvent.dir == 'down'
).subscribe(keyEvent => {
unfinishedNote[key] = keyEvent.time;
printState();
});
distinct.bufferWithCount(
2
).map(
pair => pair.map(keyEvent => keyEvent.time)
).subscribe(note => {
notes[key].push(note);
unfinishedNote[key] = null;
printState();
});
}
let routeToKeyListener = (key, time, dir) => {
if (!(key in keyListeners)) {
addKeyListener(key);
}
keyListeners[key].onNext( {time: time, dir: dir} );
}
const KeyUp = Rx.Observable.fromEvent(document.querySelector('#name'), 'keyup')
.subscribe(keyEvent => {
document.querySelector('#name').value = '';
let now = Date.now();
if (startTime == null) { startTime = now; }
routeToKeyListener(keyEvent.code, now, 'up');
});
const KeyDown = Rx.Observable.fromEvent(document.querySelector('#name'), 'keydown')
.subscribe(keyEvent => {
document.querySelector('#name').value = '';
let now = Date.now();
if (startTime == null) { startTime = now; }
routeToKeyListener(keyEvent.code, now, 'down');
});
let draw = () => {
let canv = document.querySelector('#canv');
let ctx = canv.getContext('2d');
ctx.clearRect(0, 0, canv.width, canv.height);
let w = canv.width - margin - margin;
let h = canv.height - margin - margin;
let now = Date.now();
//let x = t => (t - startTime) * w / timeWindowMs; // notes appear from left to right and never move
let x = t => w - (w/timeWindowMs)*(1 + now - t); // notes begin at right border and move leftward
for (let key in notes) {
let hValue = keyOrder.indexOf(key) * h / keyOrder.length;
for (let i = 0; i < notes[key].length; i++) {
let [start, end] = notes[key][i];
drawSegment(
x(start),
x(end),
hValue,
hValue
);
}
if (unfinishedNote[key]) {
let start = unfinishedNote[key];
drawSegment(
x(start),
x(now),
hValue,
hValue,
false
);
}
}
}
setInterval(draw, 1000 / 60);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>
html {
background-color: gray;
color: white;
}
#canv {
background-color: white;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment