First, here's the live application I'm working on as a study: turbo_synth
I'm making it using VueJS, however, I believe the issue is unrelated to Vue.
The issue: It all works fine and dandy, except when trying to play certain combinations of notes, for example, try playing the keys Q, W, and then 2. You'll notice the last note isn't being played and not even shown as pressed, while you could play Q, W, E, R and Y at the same time. So it doesn't seem like there is a limit, as I thought previously?
The code: I'm using vue-keypress to easily handle the key events globally.
Template part
<template>
<div id="app">
<h1>Basic oscillator test</h1>
<label for="waveType">Choose a wave type:</label>
<select
name="waveType"
id="waveType"
v-model="wave"
>
<option value="sine">sine</option>
<option value="square">square</option>
<option value="triangle">triangle</option>
<option value="sawtooth">sawtooth</option>
</select>
<hr>
<ul class="keyboard">
<li
v-for="note in testBoard"
:key="note.id"
:class="[note.class, getKeyByValue(testBoard, note).charAt(0), {'pressed': pressedNotes.includes(note.freq)}]"
@mousedown="playSound(note.freq, 1)"
>
<p>{{getKeyByValue(testBoard, note).replace('s', '#')}}</p>
<p>{{String.fromCharCode(note.keycode)}}</p>
</li>
</ul>
<Keypress
v-for="note in testBoard"
:key="note.id"
key-event="keydown"
:key-code="note.keycode"
@success="playSound(note.freq)"
/>
<Keypress
v-for="note in testBoard"
:key="note.id"
key-event="keyup"
:key-code="note.keycode"
@success="removeNote(note.freq)"
/>
</div>
</template>
Script part:
<script>
import { noteValues, testBoard } from './assets/notevalues.js';
export default {
name: 'App',
data() {
return {
noteValues,
testBoard,
selectedNote: null,
wave: 'sine',
pressedNotes: [],
}
},
components: {
Keypress: () => import('vue-keypress')
},
methods: {
getKeyByValue(object, value) {
return Object.keys(object).find(key => object[key] === value);
}
,
playSound(note, clicked) {
if (this.pressedNotes.includes(note)) {
return;
} else {
this.pressedNotes.push(note);
const context = new AudioContext();
const o = context.createOscillator();
const g = context.createGain();
o.connect(g);
g.connect(context.destination);
o.type = this.wave;
const frequency = note;
o.frequency.value = frequency;
o.start(0);
o.stop(context.currentTime + 1)
g.gain.exponentialRampToValueAtTime(
0.00001, context.currentTime + 2.5
);
setTimeout(() => {
context.close();
}, 1000);
if (clicked === 1) {
setTimeout(() => {
this.removeNote(note);
}, 50)
}
}
},
removeNote(note) {
const index = this.pressedNotes.indexOf(note);
if (index > -1) {
this.pressedNotes.splice(index, 1);
}
}
},
}
</script>
And here's the list of notes:
export let testBoard = {
'C3': { keycode: 81, freq: 130.81, class: 'white' },
'Cs3': { keycode: 50, freq: 138.59, class: 'black' },
'D3': { keycode: 87, freq: 146.83, class: 'white' },
'Ds3': { keycode: 51, freq: 155.56, class: 'black' },
'E3': { keycode: 69, freq: 164.81, class: 'white' },
'F3': { keycode: 82, freq: 174.61, class: 'white' },
'Fs3': { keycode: 53, freq: 185.00, class: 'black' },
'G3': { keycode: 84, freq: 196.00, class: 'white' },
'Gs3': { keycode: 54, freq: 207.65, class: 'black' },
'A3': { keycode: 89, freq: 220.00, class: 'white' },
'As3': { keycode: 55, freq: 233.08, class: 'black' },
'B3': { keycode: 85, freq: 246.94, class: 'white' },
'C4': { keycode: 90, freq: 261.63, class: 'white' },
'Cs4': { keycode: 83, freq: 277.18, class: 'black' },
'D4': { keycode: 88, freq: 293.66, class: 'white' },
'Ds4': { keycode: 68, freq: 311.13, class: 'black' },
'E4': { keycode: 67, freq: 329.63, class: 'white' },
'F4': { keycode: 86, freq: 349.23, class: 'white' },
'Fs4': { keycode: 71, freq: 369.99, class: 'black' },
'G4': { keycode: 66, freq: 392.00, class: 'white' },
'Gs4': { keycode: 72, freq: 415.30, class: 'black' },
'A4': { keycode: 78, freq: 440.00, class: 'white' },
'As4': { keycode: 74, freq: 466.16, class: 'black' },
'B4': { keycode: 77, freq: 493.88, class: 'white' }
}
I've also tried other people's pianos made with vue or different tech, and there's always a similar problem. I might be missing something crucial, who knows, but I cannot find the info I need.
Thanks a bunch
There's nothing wrong with your code, and there's nothing you can do to fix it — this is a hardware limitation on many keyboards.
First, imagine the keyboard laid out as a rectangular grid (in other words, 1, Q, A, and Z are in the same column, even though they're usually not directly above one another).
The limitation is that no three keys that form three corners of a rectangle can be recognized at the same time. If you hold down two keys in a row, then a third key can't be in the same column as either of the first two. If you hold down two keys in a column, then a third key can't be in the same row as either of the first two. If you hold down Q and Z, then any key on the row starting with A will work fine, but W, E, X, C, etc. will all be locked out.
Alternatively, some machines might give you "ghost" keypresses at the fourth corner of the rectangle — holding down Q and Z and pressing E will register a keypress for E, but also one for C at the same time, even though nobody pressed C.
All of this has to do with the way keyboards are built electronically, and there's nothing you can do about it in software. There are some keyboards that don't have this limitation, but you can't count on your users to have them.