Skip to content

Commit 332b1c3

Browse files
[update] 更新按键显示逻辑
1. 当载入音频文件并且点击开始演奏按钮后,键盘监听事件才启动,避免非演奏场景下,正常的键盘输入被捕捉
1 parent 91da6f6 commit 332b1c3

File tree

6 files changed

+102
-65
lines changed

6 files changed

+102
-65
lines changed

main_multi.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
"""
4-
Windows 钢琴自动演奏 - 多人模式 (去和弦 + 多音时间分散)
5-
说明:
6-
- 针对多人模式中存在的漏音/丢音问题,提供一种退化策略:
7-
1. 去掉所有和弦(C, Dm, Em, F, G, Am, G7),仅保留单音旋律,减少同时按键数量;
8-
2. 对同一时间戳内的多音进行“时间分散”,为每个音施加一个相对偏移(毫秒级,可正可负),
9-
以降低游戏短时间内的输入堆叠,从而减少被丢弃概率;
10-
3. 偏移在内存中处理,不修改原始 .lrcp 文件。
11-
- 其他参数(速度比例、起始倒计时、全局延迟)与单人模式相同。
12-
偏移输入:
13-
例如:-15,0,15 表示第一音提前15ms,第二音不变,第三音延后15ms,第4音再循环使用第一偏移(-15ms)依此类推。
14-
允许范围:-50 ~ 50 (ms)。超过范围自动裁剪。
15-
16-
"""
173
import tkinter as tk
184

195
from src.app_multi import MultiApp

main_single.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
"""
4-
Windows 钢琴自动演奏 - 可视化脚本 (Tkinter)
5-
功能:
6-
- 载入乐谱(LRC 风格时间戳),解析为事件队列
7-
- 支持延长音:一行两个时间戳 [start][end] TOKENS -> 按下后保持到 end 再释放
8-
- 单时间戳仍兼容,视为即刻点按(tap)
9-
- 一键开始/停止自动按键(模拟键盘)
10-
- 支持节奏倍速、起始延迟(倒计时)、全局延迟(打穿游戏输入延迟)
11-
- 面板展示按键映射关系,便于校对
12-
注意:
13-
- 需在 Windows 上运行,并确保游戏窗口在“开始演奏”后处于焦点
14-
- 发送键盘事件默认使用 pyautogui
15-
"""
163
import tkinter as tk
174

185
from src.app_single import SingleApp

src/app.py

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ def __init__(self, root: tk.Tk, title: str, create_key_display: bool = True):
4040
self._create_params_frame()
4141
self._create_control_frame()
4242
self._create_tips_frame()
43-
43+
4444
# 根据参数决定是否创建按键显示框架
4545
if create_key_display:
4646
self._create_key_display_frame()
47-
47+
4848
# 初始化按键显示相关变量(窗口内)
4949
self.keys = deque(maxlen=14) # 显示最近maxlen个按键
5050
self.last_press_time = {}
5151
self.running = True
52+
self.key_listener = None # 键盘监听器实例
53+
self.key_listener_active = False # 键盘监听器是否激活
5254

5355
# 覆盖层(窗口外)默认设置与实例(默认开启)
5456
self.keycast_settings = {
@@ -63,16 +65,15 @@ def __init__(self, root: tk.Tk, title: str, create_key_display: bool = True):
6365
self.keycast_overlay = KeyCastOverlay(self.root, self.keycast_settings)
6466
except Exception:
6567
self.keycast_overlay = None
66-
67-
# 启动按键监听(窗口内展示)
68-
self._start_key_listener()
6968

7069
def _create_instrument_frame(self):
7170
frm = ttk.LabelFrame(self.frm, text="乐器")
7271
frm.pack(fill="x", pady=(0, 8))
7372
self.instrument_var = tk.StringVar(value="piano")
74-
ttk.Radiobutton(frm, text="钢琴 (.lrcp / .mid)", variable=self.instrument_var, value="piano").pack(side="left", padx=4)
75-
ttk.Radiobutton(frm, text="架子鼓 (.lrcd / .mid)", variable=self.instrument_var, value="drum").pack(side="left", padx=8)
73+
ttk.Radiobutton(frm, text="钢琴 (.lrcp / .mid)", variable=self.instrument_var, value="piano").pack(side="left",
74+
padx=4)
75+
ttk.Radiobutton(frm, text="架子鼓 (.lrcd / .mid)", variable=self.instrument_var, value="drum").pack(side="left",
76+
padx=8)
7677

7778
def get_instrument(self) -> str:
7879
v = self.instrument_var.get().strip().lower()
@@ -100,7 +101,8 @@ def _create_file_bar(self):
100101
right_box.pack(side="right")
101102
ttk.Label(right_box, text="主题:").pack(side="left")
102103
self.theme_var = tk.StringVar(value=current_theme)
103-
self.cbo_theme = ttk.Combobox(right_box, width=16, state="readonly", values=theme_names, textvariable=self.theme_var)
104+
self.cbo_theme = ttk.Combobox(right_box, width=16, state="readonly", values=theme_names,
105+
textvariable=self.theme_var)
104106
self.cbo_theme.pack(side="left", padx=4)
105107

106108
def on_theme_changed(event=None):
@@ -109,6 +111,7 @@ def on_theme_changed(event=None):
109111
self.style.theme_use(name)
110112
except Exception:
111113
pass
114+
112115
self.cbo_theme.bind("<<ComboboxSelected>>", on_theme_changed)
113116
except Exception:
114117
pass
@@ -117,7 +120,8 @@ def _create_params_frame(self):
117120
params = ttk.LabelFrame(self.frm, text="参数")
118121
params.pack(fill="x", pady=8)
119122
ttk.Label(params, text="速度比例(1.0为原速):").grid(row=0, column=0, sticky="e")
120-
self.ent_speed = ttk.Combobox(params, width=8, state="readonly", values=["0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0", "2.25", "2.5"])
123+
self.ent_speed = ttk.Combobox(params, width=8, state="readonly",
124+
values=["0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0", "2.25", "2.5"])
121125
self.ent_speed.set("1.0")
122126
self.ent_speed.grid(row=0, column=1, sticky="w", padx=6)
123127
ttk.Label(params, text="起始倒计时(秒):").grid(row=0, column=2, sticky="e")
@@ -133,13 +137,14 @@ def _create_params_frame(self):
133137
self.ent_latency.delete(0, "end")
134138
self.ent_latency.insert(0, "0")
135139
self.ent_latency.grid(row=0, column=5, sticky="w", padx=6)
136-
140+
137141
# 添加进度更新频率配置
138142
ttk.Label(params, text="进度更新频率:").grid(row=1, column=0, sticky="e")
139143
self.ent_progress_freq = ttk.Combobox(params, width=8, state="readonly", values=["1", "2", "3", "5", "10"])
140144
self.ent_progress_freq.set("1")
141145
self.ent_progress_freq.grid(row=1, column=1, sticky="w", padx=6)
142-
ttk.Label(params, text="(1=每个动作都更新, 2=每2个动作更新, 以此类推)").grid(row=1, column=2, columnspan=4, sticky="w")
146+
ttk.Label(params, text="(1=每个动作都更新, 2=每2个动作更新, 以此类推)").grid(row=1, column=2, columnspan=4,
147+
sticky="w")
143148

144149
# 记录参数控件,便于统一禁用/启用
145150
self.param_widgets = [
@@ -160,12 +165,13 @@ def _create_control_frame(self):
160165
self.btn_keycast = ttk.Button(ctrl, text="按键显示设置", command=self.open_keycast_settings)
161166
self.btn_keycast.pack(side="left", padx=4)
162167
# 新增:动作录制按钮(按当前乐器类型)
163-
self.btn_record = ttk.Button(ctrl, text="动作录制", command=lambda: open_recorder_window(self.root, self.get_instrument()))
168+
self.btn_record = ttk.Button(ctrl, text="动作录制",
169+
command=lambda: open_recorder_window(self.root, self.get_instrument()))
164170
self.btn_record.pack(side="left", padx=4)
165-
171+
166172
self.lbl_status = ttk.Label(ctrl, text="状态:等待载入乐谱")
167173
self.lbl_status.pack(side="left", padx=10)
168-
174+
169175
# 添加进度条框架
170176
progress_frame = ttk.Frame(self.frm)
171177
progress_frame.pack(fill="x", pady=4)
@@ -184,12 +190,11 @@ def _create_tips_frame(self):
184190
"4) 如无响应尝试以管理员身份运行。"
185191
)).pack(fill="x")
186192

187-
188193
def _create_key_display_frame(self):
189194
"""创建按键显示框架(窗口内)"""
190195
key_frame = ttk.LabelFrame(self.frm, text="按键显示")
191196
key_frame.pack(fill="x", pady=8)
192-
197+
193198
# 创建按键显示标签(使用主题样式)
194199
if self._ttkb is not None:
195200
self.lbl_keys = self._ttkb.Label(
@@ -208,7 +213,7 @@ def _create_key_display_frame(self):
208213
anchor="center",
209214
)
210215
self.lbl_keys.pack(fill="x", padx=4, pady=4)
211-
216+
212217
# 添加说明文字
213218
ttk.Label(key_frame, text="实时显示当前按下的按键 (窗口内)", font=("微软雅黑", 9)).pack(anchor="w", padx=4)
214219

@@ -246,9 +251,11 @@ def open_keycast_settings(self):
246251

247252
ttk.Label(win, text="透明度(0.2~1.0):").grid(row=row, column=0, sticky="e", padx=6, pady=6)
248253
if self._ttkb is not None and hasattr(self._ttkb, 'Scale'):
249-
opacity_scale = self._ttkb.Scale(win, from_=0.2, to=1.0, orient="horizontal", length=200, variable=var_opacity)
254+
opacity_scale = self._ttkb.Scale(win, from_=0.2, to=1.0, orient="horizontal", length=200,
255+
variable=var_opacity)
250256
else:
251-
opacity_scale = tk.Scale(win, from_=0.2, to=1.0, orient="horizontal", resolution=0.05, variable=var_opacity, length=200)
257+
opacity_scale = tk.Scale(win, from_=0.2, to=1.0, orient="horizontal", resolution=0.05, variable=var_opacity,
258+
length=200)
252259
opacity_scale.grid(row=row, column=1, sticky="w", padx=6)
253260
row += 1
254261

@@ -262,20 +269,22 @@ def open_keycast_settings(self):
262269

263270
ttk.Label(win, text="每个按键显示(秒):").grid(row=row, column=0, sticky="e", padx=6, pady=6)
264271
if self._ttkb is not None and hasattr(self._ttkb, 'Spinbox'):
265-
disptime_spin = self._ttkb.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time, width=8)
272+
disptime_spin = self._ttkb.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time,
273+
width=8)
266274
else:
267275
disptime_spin = tk.Spinbox(win, from_=0.5, to=10.0, increment=0.5, textvariable=var_disp_time, width=8)
268276
disptime_spin.grid(row=row, column=1, sticky="w", padx=6)
269277
row += 1
270278

271279
ttk.Label(win, text="位置:").grid(row=row, column=0, sticky="e", padx=6, pady=6)
272-
ttk.Combobox(win, state="readonly", values=list(pos_map.keys()), textvariable=var_position, width=14).grid(row=row, column=1, sticky="w", padx=6)
280+
ttk.Combobox(win, state="readonly", values=list(pos_map.keys()), textvariable=var_position, width=14).grid(
281+
row=row, column=1, sticky="w", padx=6)
273282
row += 1
274283

275284
# 按钮
276285
btns = ttk.Frame(win)
277286
btns.grid(row=row, column=0, columnspan=2, pady=10)
278-
287+
279288
def on_ok():
280289
new_cfg = {
281290
"opacity": max(0.2, min(1.0, float(var_opacity.get()))),
@@ -291,44 +300,78 @@ def on_ok():
291300
# 同步到记录
292301
self.keycast_settings["enabled"] = bool(var_enabled.get())
293302
win.destroy()
294-
303+
295304
def on_cancel():
296305
# 取消不更改设置
297306
win.destroy()
298-
307+
299308
ttk.Button(btns, text="确认", command=on_ok).pack(side="left", padx=8)
300309
ttk.Button(btns, text="取消", command=on_cancel).pack(side="left", padx=8)
301310

302311
def _start_key_listener(self):
303312
"""启动按键监听线程(窗口内展示)"""
313+
self.keycast_overlay.start_key_listener()
314+
if self.key_listener_active:
315+
return # 已经启动,避免重复启动
316+
304317
try:
305318
from pynput import keyboard
306-
319+
307320
def on_press(key):
308321
"""键盘按下事件"""
322+
if not self.key_listener_active:
323+
return
324+
309325
try:
310326
k = key.char.upper()
311327
except AttributeError:
312328
k = str(key).replace("Key.", "").upper()
313-
329+
314330
self.keys.append(k)
315331
self.last_press_time[k] = time.time()
316332
self._update_key_display()
317-
333+
318334
# 启动键盘监听器
319335
self.key_listener = keyboard.Listener(on_press=on_press)
320336
self.key_listener.start()
321-
337+
self.key_listener_active = True
338+
322339
# 启动清理过期按键的线程
323340
threading.Thread(target=self._cleanup_keys_loop, daemon=True).start()
324-
341+
325342
except ImportError:
326343
# 如果没有安装pynput,显示提示信息
327344
if hasattr(self, 'lbl_keys'):
328345
self.lbl_keys.config(
329346
text="需要安装 pynput 模块\npip install pynput"
330347
)
331348

349+
def _stop_key_listener(self):
350+
"""停止按键监听线程"""
351+
if not self.key_listener_active:
352+
return # 已经停止,避免重复停止
353+
354+
self.key_listener_active = False
355+
356+
if self.key_listener:
357+
try:
358+
self.key_listener.stop()
359+
self.key_listener = None
360+
except Exception:
361+
pass
362+
363+
# 清空按键显示
364+
self.keys.clear()
365+
self.last_press_time.clear()
366+
self._update_key_display()
367+
368+
# 清空覆盖层显示
369+
if self.keycast_overlay:
370+
try:
371+
self.keycast_overlay.clear_keys()
372+
except Exception:
373+
pass
374+
332375
def _update_key_display(self):
333376
"""更新按键显示(窗口内)"""
334377
if hasattr(self, 'lbl_keys'):
@@ -340,7 +383,7 @@ def _update_key_display(self):
340383

341384
def _cleanup_keys_loop(self):
342385
"""后台循环,清理过期按键(窗口内)"""
343-
while self.running:
386+
while self.running and self.key_listener_active:
344387
now = time.time()
345388
removed = False
346389
for k in list(self.keys):
@@ -450,6 +493,8 @@ def stop_play(self):
450493
self.player = None
451494
self.reset_progress()
452495
self.enable_params()
496+
# 停止键盘监听
497+
self._stop_key_listener()
453498
# 恢复按钮与状态
454499
self.btn_start.config(state="normal", text="开始演奏")
455500
self.btn_stop.config(state="disabled")

src/app_multi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ def start_play(self):
7979

8080
# 每次开始前按当前偏移重新生成
8181
self.update_play_events()
82-
# 开始后:启用停止按钮;开始按钮显示为“暂停”并保持可点击
82+
83+
# 启动键盘监听
84+
self._start_key_listener()
85+
86+
# 开始后:启用停止按钮;开始按钮显示为"暂停"并保持可点击
8387
self.btn_start.config(state="normal", text="暂停")
8488
self.btn_stop.config(state="normal")
8589
self.lbl_status.config(text=f'演奏中… (总 {len(self.play_events)} 单音事件)')

src/app_single.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ def start_play(self):
8282
except:
8383
progress_freq = 1
8484

85+
# 启动键盘监听
86+
self._start_key_listener()
87+
8588
self.btn_start.config(state="normal", text="暂停")
8689
self.btn_stop.config(state="normal")
8790
self.lbl_status.config(text="演奏中…(切到游戏保持焦点)")

0 commit comments

Comments
 (0)