Hello, keyboard interactions
Have a look at this simple demonstration - press the Run button below to see it in action:
function App() {
const [state, setState] = React.useState(new Set())
React.useEffect(() => {
function onkeydown(event) {
setState(s => ISet.add(s, event.key))
}
function onkeyup(event) {
setState(s => ISet.delete(s, event.key))
}
addEventListener("keyup", onkeyup)
addEventListener("keydown", onkeydown)
return () => {
removeEventListener("keyup", onkeyup)
removeEventListener("keydown", onkeydown)
}
}, [])
return <div>
{JSON.stringify(Array.from(state))}
</div>
}
const ISet = {
add(t, v) {
return new Set(t).add(v)
},
delete(t, v) {
const r = new Set(t)
r.delete(v)
return r
}
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<ol>
<li>Click here to focus the demo's iframe</li>
<li>Press any combination of keys on the keyboard</li>
</ol>
<div id="app"></div>
To enhance this further, you can integrate the state changes with various WebAudio functionalities such as -
- Playing piano samples using AudioBufferSourceNode
- Synthesizing audio through OscillatorNode
Hello, music composition
Building upon the previous code snippet, we create a virtual piano
based on a QWERTY keyboard layout -
// W E T Y U O P ]
// A S D F G H J K L ; '
const piano = new Map(Array.from(
"awsedftgyhujkolp;'",
(key, semitone) => [key, semitone]
))
Next, we define a createOsc
function that calculates the frequency for each semitone and plays it -
function createOsc(audioCtx, semitone) {
const hz = 440 * Math.pow(2, semitone / 12)
const osc = audioCtx.createOscillator()
osc.type = "square"
osc.frequency.setValueAtTime(hz, audioCtx.currentTime)
osc.connect(audioCtx.destination)
osc.start();
return osc
}
Press Run below to execute the code and start composing your musical masterpiece:
function App() {
const [state, setState] = React.useState(new Map())
const audioCtx = React.useRef(new AudioContext())
const tuning = React.useRef(-9)
React.useEffect(() => {
function onkeydown(e) {
setState(prev => {
const osc = prev.get(e.key)
if (osc != null) return prev
const semitone = piano.get(e.key)
if (semitone != null) return IMap.add(
prev,
e.key,
createOsc(audioCtx.current, semitone + tuning.current)
)
return prev
})
}
function onkeyup(e) {
setState(prev => {
const osc = prev.get(e.key)
if (osc != null) {
osc.stop()
return IMap.delete(prev, e.key)
}
return prev
})
}
addEventListener("keyup", onkeyup)
addEventListener("keydown", onkeydown)
return () => {
removeEventListener("keyup", onkeyup)
removeEventListener("keydown", onkeydown)
}
}, [])
return <div>
{JSON.stringify(Array.from(state.keys()))}
</div>
}
const piano = new Map(Array.from(
"awsedftgyhujkolp;'",
(key, semitone) => [key, semitone]
))
function createOsc(audioCtx, semitone) {
const hz = 440 * Math.pow(2, semitone / 12)
const osc = audioCtx.createOscillator()
osc.type = "square"
osc.frequency.setValueAtTime(hz, audioCtx.currentTime)
osc.connect(audioCtx.destination)
osc.start();
return osc
}
const IMap = {
add(t, k, v) {
return new Map(t).set(k, v)
},
delete(t, k) {
const r = new Map(t)
r.delete(k)
return r
}
}
ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<ol>
<li>Click here to focus the demo's iframe</li>
<li>Play piano on your QWERTY keyboard</li>
</ol>
<pre>
W E T Y U O P ]
A S D F G H J K L ; '
</pre>
<div id="app"></div>