88键钢琴音
172 2025-11-13 10:09 2025-11-13 20:20
我问问题的步骤是
sound_objects.get(key)区分大小写吗?
对非会员隐藏
为什么我按Q会发出双音
我需要大写字母为半音补充
然后kimi给了我答案
明白了!
你的需求是:
你的需求是:
-
小写字母 → 正常音(白键)
-
大写字母(按住 Shift 再按字母)→ 对应高半音(黑键)
-
不能出现“按一次 Q 触发 q 和 Q 两个音”的双音 bug
✅ 正确做法:
只监听物理按键,在回调里实时判断 Shift 状态,决定播放小写还是大写对应的文件。
✅ 修改后的完整代码(支持半音,不双触发)
对非会员隐藏
✅ 使用说明
表格
复制
| 按键 | 效果 |
|---|---|
q |
C5(白键) |
Shift+q |
C#5(黑键) |
ESC |
退出 |
✅ 文件准备
把你对应的 mp3 文件按命名放好即可,例如:
复制
53.mp3 # C#5
55.mp3 # D#5
...
需要我帮你自动生成黑键文件名或者做音量/复音优化吗?
for key in SOUNDS:
keyboard.on_press_key
和keyboard.hook(on_key)方案分析
下面把两种做法底层机制、优缺点、适用场景一次性对比清楚,方便你直接选型。
(先给结论,再给细节)
(先给结论,再给细节)
一、一句话结论
表格
复制
| 方案 | 是否推荐 | 理由 |
|---|---|---|
for key in SOUNDS: keyboard.on_press_key(key, callback) |
❌ 不推荐 | 物理键监听,大小写同名键会双触发;注册次数多;无法动态判断 Shift |
keyboard.hook(on_key) |
✅ 推荐 | 一次注册捕获所有按键事件,事件自带 name;可实时看 Shift;无双音 bug |
二、底层机制对比
-
on_press_key(key, callback)
-
内部调用 OS 接口给单个物理键注册回调。
-
参数
key是物理键名,例如'q'与'Q'被当成同一把物理键;因此:
– 你注册'q'和'Q'两个回调,按一次 Q 键,两个回调都会进队,于是双音。 -
回调里拿不到“当前这次按键到底是 q 还是 Q”——因为 OS 只告诉你“Q 键被按下”。
-
注册次数 =
len(SOUNDS),每次导入都会遍历,>=60 次系统调用;事件多时有微小延迟累积。
-
keyboard.hook(on_key)
-
一次性向 OS 申请全局键盘事件流(Windows 的 LowLevelKeyboardProc / Linux evdev / macOS CGEventTap)。
-
每个事件自带字段:
–event.name字符侧名称(已根据 Shift/Caps 解析成'q'或'Q')
–event.scan_code物理侧 -
因此在回调里实时判断 Shift 即可决定播放哪个样本,绝对不会双触发。
-
只注册一次,后续全是 Python 层分发,延迟最低。
三、实测现象
表格
复制
| 操作 | on_press_key | hook |
|---|---|---|
| 单按 Q | 同时触发 'q' 和 'Q' 两个回调 → 双音 |
仅得到 'q' → 单音 |
| Shift+Q | 同上,仍然双音 | 得到 'Q' → 黑键单音 |
| 快速跑句 | 偶发多音叠加 | 干净 |
四、额外差异
表格
复制
| 维度 | on_press_key | hook |
|---|---|---|
| 代码量 | 需两层循环(注册/卸载) | 一个函数 + 一行 hook |
| 动态改键 | 需先 unhook_key 再注册 |
直接改 on_key 内部字典 |
| 非字母键 | 需额外列名单 | 同一套逻辑 |
| 线程安全 | 每个回调独立线程 | 单线程顺序派发,无竞态 |
| 退出时清理 | 必须 keyboard.unhook_all() |
同样一句即可 |
五、最小可运行模板(hook 版)
Python
复制
import keyboard, pygame, os
pygame.mixer.init()
sound = {k: pygame.mixer.Sound(v) for k, v in SOUNDS.items() if os.path.exists(v)}
def on_key(e):
if e.event_type != 'down':
return
k = e.name.lower() if not keyboard.is_pressed('shift') else e.name.upper()
if k in sound:
pygame.mixer.find_channel(True).play(sound[k])
keyboard.hook(on_key)
keyboard.wait('esc')
六、什么时候可以用 on_press_key?
-
只做单字符、无大小写区分的快捷键(如 F1、ctrl+s)
-
明确给不同物理键注册不同功能(数字小键盘 vs 主键盘)
-
需要单独卸载某一个键的监听(hook 只能全拆)
但凡涉及**“同一物理键、不同逻辑键”**的场景(钢琴、游戏热键、Shift 组合),优先 hook。
选 hook,安心写音乐逻辑即可。
全部评论