Compare commits
2 commits
ed45d2a72f
...
3e04ee0dd7
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e04ee0dd7 | |||
| 7f9345b6f8 |
1 changed files with 46 additions and 28 deletions
|
|
@ -3,25 +3,48 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>piano</title>
|
<title>piano</title>
|
||||||
<h1>piano</h1>
|
<h1>piano</h1>
|
||||||
<div>
|
<p>play music with your computer keyboard! currently only works on desktop and with a qwerty keyboard.</p>
|
||||||
|
<p>
|
||||||
|
<label for="select-note-layout">note layout:</label>
|
||||||
|
<select name="select-note-layout" id="select-note-layout">
|
||||||
|
<option value="isomorphic-2-1" selected>isomorphic 2,1</option>
|
||||||
|
<option value="isomorphic-5-2">isomorphic 5,2</option>
|
||||||
|
<option value="classic">classic piano</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<!-- <p>
|
||||||
<textarea id="input"></textarea>
|
<textarea id="input"></textarea>
|
||||||
</div>
|
</p> -->
|
||||||
|
|
||||||
<h2>notes held</h2>
|
<h2>notes held</h2>
|
||||||
<div id="notes-held"></div>
|
<div id="notes-held"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const inputArea = document.querySelector('#input');
|
// const inputArea = document.querySelector('#input');
|
||||||
|
const selectNoteLayout = document.querySelector('#select-note-layout');
|
||||||
const notesDiv = document.querySelector('#notes-held');
|
const notesDiv = document.querySelector('#notes-held');
|
||||||
const ctx = new AudioContext;
|
const ctx = new AudioContext;
|
||||||
const playing = {};
|
const playing = {};
|
||||||
let tone = {};
|
let tone = {};
|
||||||
let setupCompleted = false;
|
addEventListener('keydown', onKeydown);
|
||||||
inputArea.onfocus = start;
|
addEventListener('keyup', onKeyup);
|
||||||
inputArea.onblur = pause;
|
useLayout(selectNoteLayout.value);
|
||||||
|
selectNoteLayout.onchange = function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
useLayout(ev.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useLayout(layout) {
|
||||||
|
if (layout == 'classic') {
|
||||||
|
useClassicPiano();
|
||||||
|
} else if (layout == 'isomorphic-2-1') {
|
||||||
|
useIsomorphic(2, 1);
|
||||||
|
} else if (layout == 'isomorphic-5-2') {
|
||||||
|
useIsomorphic(5, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// arrange notes like on a piano. the second and fourth rows are each a major scale, but the first and third rows have gaps.
|
// arrange notes like on a piano. the second and fourth rows are each a major scale, but the first and third rows have gaps.
|
||||||
function usePianoKeys() {
|
function useClassicPiano() {
|
||||||
tone = {};
|
tone = {};
|
||||||
("zsxdcvgbhnjm,l.;/q2w3e4rt6y7ui9o0p-[]"
|
("zsxdcvgbhnjm,l.;/q2w3e4rt6y7ui9o0p-[]"
|
||||||
.split('')
|
.split('')
|
||||||
|
|
@ -31,8 +54,16 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// arrange notes "isomorphically". moving sideways is 5 semitones, moving diagonally upwards is 2 semitones.
|
// arrange notes "isomorphically", meaning that transposing chords in frequency space is done by transposing points in regular space.
|
||||||
function useEqualSteps() {
|
//
|
||||||
|
// default: 5, 2. this creates a pleasing "diagonal" layout where octaves can be reached by moving two keys sideways and one key up (5+5+2=12).
|
||||||
|
// the downside of this is that some notes are not reachable unless you have five rows assigned (maybe I can capture the F1-F12 keys somehow?)
|
||||||
|
//
|
||||||
|
// alternative: 2, 1. this is "more like a piano" except that there are no empty spaces (no "gaps" where a black key should be).
|
||||||
|
// this is probably better for playing sheet music but it does suck that common chords are much farther apart. also on a four-row keyboard every note appears twice.
|
||||||
|
// a neat side effect of this is that an octave is exactly 6 column steps, the same as the distance between "a" and "j", which means when your hands are in home row
|
||||||
|
// position, they are exactly one octave apart.
|
||||||
|
function useIsomorphic(stepX = 5, stepY = 2) {
|
||||||
const rows = [
|
const rows = [
|
||||||
"1234567890-=",
|
"1234567890-=",
|
||||||
"qwertyuiop[]",
|
"qwertyuiop[]",
|
||||||
|
|
@ -42,28 +73,14 @@
|
||||||
tone = {};
|
tone = {};
|
||||||
rows.map((row, y) => {
|
rows.map((row, y) => {
|
||||||
row.split('').map((key, x) => {
|
row.split('').map((key, x) => {
|
||||||
const semitone = Math.floor(x * 5 + y * 2 - 28);
|
const semitone = Math.floor(x * stepX + y * stepY - 28);
|
||||||
tone[key] = 440 * 2 ** (semitone / 12);
|
tone[key] = 440 * 2 ** (semitone / 12);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
|
||||||
if (setupCompleted) {
|
|
||||||
addEventListener('keydown', onKeydown);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
useEqualSteps();
|
|
||||||
addEventListener('keydown', onKeydown);
|
|
||||||
addEventListener('keyup', onKeyup);
|
|
||||||
setupCompleted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pause() {
|
|
||||||
removeEventListener('keydown', onKeydown);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeydown(ev) {
|
function onKeydown(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
let key = ev.key;
|
let key = ev.key;
|
||||||
if (key == ' ') key = 'Space';
|
if (key == ' ') key = 'Space';
|
||||||
if (!tone[key]) return;
|
if (!tone[key]) return;
|
||||||
|
|
@ -80,6 +97,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyup(ev) {
|
function onKeyup(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
let key = ev.key;
|
let key = ev.key;
|
||||||
if (key == ' ') key = 'Space';
|
if (key == ' ') key = 'Space';
|
||||||
if (!playing[key]) return;
|
if (!playing[key]) return;
|
||||||
|
|
@ -89,11 +107,11 @@
|
||||||
oscillator.disconnect();
|
oscillator.disconnect();
|
||||||
gain.disconnect();
|
gain.disconnect();
|
||||||
|
|
||||||
delete playing[key];
|
playing[key] = undefined;
|
||||||
showNotes();
|
showNotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNotes() {
|
function showNotes() {
|
||||||
notesDiv.innerText = Object.keys(playing).join(' ');
|
notesDiv.innerText = Object.keys(playing).filter((key) => playing[key]).join(' ');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
Loading…
Add table
Reference in a new issue