
本文教你从零实现一个基于网页的虚拟钢琴,能实时捕获键盘多键按下事件、识别同时按下的“和弦”并格式化为类似 a b [cd] 的字符串,同时结合 Web Audio API 发声,适合初学者快速上手。
本文教你从零实现一个基于网页的虚拟钢琴,能实时捕获键盘多键按下事件、识别同时按下的“和弦”并格式化为类似 `a b [cd]` 的字符串,同时结合 web audio api 发声,适合初学者快速上手。
构建一个支持和弦输入与实时发声的虚拟钢琴,核心在于精准捕获按键时序状态(哪些键当前被按下)、合理映射物理按键到音高,以及安全触发/释放音频资源。下面我们将分步实现:状态管理 → 键盘布局映射 → 音频合成 → 和弦格式化输出。
✅ 1. 实时按键状态管理(关键基础)
使用 Set 跟踪当前所有被按下的键,配合 keydown / keyup 事件确保状态准确同步:
const [pressedKeys, setPressedKeys] = useState(new Set());
useEffect(() => {
const handleKeyDown = (e) => {
e.preventDefault(); // 防止输入框失焦或页面滚动
setPressedKeys(prev => new Set(prev).add(e.key.toLowerCase()));
};
const handleKeyUp = (e) => {
setPressedKeys(prev => {
const next = new Set(prev);
next.delete(e.key.toLowerCase());
return next;
});
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keyup', handleKeyUp);
};
}, []);
⚠️ 注意:务必调用
e.preventDefault(),尤其在<input>或<textarea></textarea>失焦时,否则部分按键(如空格、方向键)可能触发默认行为。
✅ 2. QWERTY 键盘映射为钢琴音阶
我们采用紧凑的 24 键布局(覆盖两个八度),将字母键映射为半音阶偏移量(以 A4=440Hz 为基准):
// 按照物理键盘位置排列(从左到右,自上而下) const pianoLayout = "awsedftgyhujkolp;']"; const keyToSemitone = new Map( Array.from(pianoLayout, (key, index) => [key, index - 9]) // 中央C附近对齐 );
该映射使 'a' 对应 A3(≈220 Hz),'s' 为 A#3,'d' 为 B3……直至 ']' 为 C5,完整覆盖常用演奏范围。
✅ 3. 使用 Web Audio 合成正弦/方波音色
避免加载音频文件,直接用 OscillatorNode 动态生成音符:
function playNote(audioContext, semitone) {
const baseFreq = 440; // A4
const freq = baseFreq * Math.pow(2, (semitone) / 12);
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'square'; // 更具钢琴质感可尝试 'sawtooth' 或叠加滤波器
osc.frequency.setValueAtTime(freq, audioContext.currentTime);
gain.gain.setValueAtTime(0.2, audioContext.currentTime); // 防止爆音
osc.connect(gain).connect(audioContext.destination);
osc.start();
return { osc, gain };
}
function stopNote(osc, gain) {
osc.stop();
gain.disconnect();
}
? 提示:实际项目中建议复用
AudioContext实例,并在首次用户交互(如点击按钮)后才调用audioContext.resume(),以满足现代浏览器的自动播放策略。
✅ 4. 实现和弦格式化输出(如 "a b [cd]")
这是本教程的特色功能:区分单音与同时按下的“和弦”。我们不依赖毫秒级时间戳比对(易受延迟影响),而是在每次状态更新时,对比前一帧的按键集合,提取新增的并发键组:
const [formattedOutput, setFormattedOutput] = useState('');
useEffect(() => {
const keysArray = Array.from(pressedKeys);
if (keysArray.length === 0) {
setFormattedOutput('');
return;
}
// 简单策略:所有当前按下的键视为一组(即一次“和弦”输入)
// 如需更精细逻辑(例如仅合并新按下的键),可引入 lastPressedKeys 缓存
const chordPart = keysArray.length > 1
? `[${keysArray.join('')}]`
: keysArray[0];
// 若希望保留历史序列(如先按 a→b→再按 cd),需额外维护操作栈;此处展示实时快照
setFormattedOutput(chordPart);
}, [pressedKeys]);
若需还原问题中 "a b [cd]" 的线性历史格式(即记录按键顺序 + 合并并发),推荐扩展为事件队列 + 时间窗口聚类(例如:Date.now() 差值
? 完整整合建议
- 将
AudioContext提升为全局 Ref,避免重复创建; - 添加视觉反馈(如高亮对应琴键 DOM 元素);
- 支持音高微调(tuning offset)、音色切换、力度模拟(通过
gain.gain动态控制); - 导出为
.txt或 MIDI(需额外库)便于乐谱分享。
✅ 总结:你不需要复杂框架就能做出一个可用的数字钢琴原型——关键是理解
keydown/keyup的状态机本质、Web Audio 的即时性,以及用纯逻辑表达“和弦”这一音乐概念。从Set开始,到Oscillator发声,再到[cd]的括号封装,每一步都是可验证、可调试的增量开发。
现在,打开你的编辑器,复制粘贴上述代码片段,点击页面任意处聚焦,然后用左手按 a s d,右手按 j k l——你已拥有一台属于自己的网页钢琴。?
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











