console
const context = new AudioContext();
const pianoEl = document.querySelector('.visual-piano');
let keyNotes = [];
const keysMap = 'zsxdcvgbhnjm,l.;/q2w3e4rt6y7ui9o0p-[]'.split('');
const blackKeysMap = new Set("1 3 6 8 10".split(" ").map(Number));
let currFreq = 130.81;
keysMap.forEach((key, index) => {
const newKeyButton = pianoEl.appendChild(document.createElement("button"));
newKeyButton.setAttribute('data-keyval', key)
newKeyButton.setAttribute('data-playing', 'false')
newKeyButton.classList.add("piano-key")
newKeyButton.style.setProperty('--val', `'` + key + `'`);
if (blackKeysMap.has(index % 12)){
newKeyButton.classList.add("piano-key_black")
} else {
newKeyButton.classList.add("piano-key_white")
}
newKeyButton.addEventListener('touchstart', e => play(key))
newKeyButton.addEventListener('mousedown', e => play(key))
newKeyButton.addEventListener('transitionstart', e => play(key))
newKeyButton.addEventListener('touchend', e => stop(key))
newKeyButton.addEventListener('mouseup', e => stop(key))
newKeyButton.addEventListener('mouseleave', e => stop(key))
newKeyButton.addEventListener('touchmove', e => stop(key))
newKeyButton.addEventListener('touchcancel', e => stop(key))
newKeyButton.addEventListener('contextmenu', e => {
e.preventDefault();
e.stopPropagation()
})
const wave = context.createOscillator();
wave.type = "triangle";
wave.frequency.value = currFreq;
wave.start();
keyNotes.push({
keyName:key,
wave: wave,
})
currFreq *= (Math.pow(2, 1/12))
});
function play(eventKey){
if (context.state === "suspended") context.resume();
const key = keyNotes.find(key => key.keyName === eventKey)
if (key){
key.wave.connect(context.destination);
document.querySelector(`[data-keyval='${key.keyName}']`).dataset.playing = 'true';
}
}
function stop(eventKey){
const key = keyNotes.find(key => key.keyName === eventKey)
if (key){
key.wave.disconnect();
document.querySelector(`[data-keyval='${key.keyName}']`).dataset.playing = 'false';
}
}
document.body.addEventListener('keydown', e => play(e.key.toLowerCase()));
document.body.addEventListener('keyup', e => stop(e.key.toLowerCase()));
<div class="visual-piano"></div>
body{
display:flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-height: 80vh;
margin: 0;
}
*{
font-family: monospace;
text-align: center;
text-transform: capitalize;
user-select: none;
}
.piano-key{
cursor: pointer;
outline: none;
display: inline-flex;
justify-content: center;
align-items: flex-end;
padding-bottom: 1rem;
height: 7rem;
width: 2rem;
font-size: 1.2rem;
border: 1px solid #000000;
position: relative;
}
.piano-key_black {
height: 4rem;
max-width: 0rem;
padding: 0;
border: 0;
z-index: 99999999;
vertical-align: top;
}
.piano-key::after{
content: var(--val);
display: block;
}
.piano-key_black::after{
position: absolute;
background: black;
width: 1rem;
height: 4rem;
color: white;
display: flex;
justify-content: center;
align-items: center;
}
.piano-key[data-playing='true']{
background: red;
}
.piano-key_black[data-playing='true']::after{
background: red;
}