K1Guitar/tools/_test_normal_pick.py

182 lines
6.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""RTT verify normal-mode: hold pad + pick sounds; release stops.
Uses real physical pad hold (inject is cleared by TM1629 scan within one tick).
"""
from __future__ import annotations
import struct
import sys
import time
sys.path.insert(0, ".")
from rtt_pitch_reg_test import ( # noqa: E402
RttSession,
connect_jlink,
find_rtt_control_block,
wait_rtt_ready,
)
SPEED_TAIL = struct.pack("<HHH", 40, 260, 1)
GUI_SWITCH_SIZE = 8
TAB_INDEX = 5
def count_pitch(lines: list[str]) -> int:
return sum(1 for L in lines if "[PITCH]" in L and " on " in L)
def parse_tab(lines: list[str]) -> int | None:
for L in reversed(lines):
if "TONE tab=" not in L:
continue
try:
return int(L.split("tab=")[1].split()[0])
except Exception:
continue
return None
def with_halt(jlink, fn):
was = jlink.halted()
if not was:
jlink.halt()
try:
return fn()
finally:
if not was:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
time.sleep(0.02)
def find_pattern(jlink, needle: bytes, ram_base=0x20000000, ram_size=0x18000) -> int | None:
def _find():
chunk = 0x1000
for off in range(0, ram_size, chunk):
data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off)))
idx = data.find(needle)
if idx >= 0:
return ram_base + off + idx
return None
return with_halt(jlink, _find)
def mem_write(jlink, addr: int, data: bytes) -> None:
with_halt(jlink, lambda: jlink.memory_write8(addr, list(data)))
def mem_read(jlink, addr: int, n: int) -> bytes:
return with_halt(jlink, lambda: bytes(jlink.memory_read8(addr, n)))
def force_tab_normal(jlink) -> bool:
hit = find_pattern(jlink, SPEED_TAIL)
if hit is None:
print("!! GUI_SPEED row not found", flush=True)
return False
base = (hit - 2) - GUI_SWITCH_SIZE
tab_addr = base + TAB_INDEX * GUI_SWITCH_SIZE
mem_write(jlink, tab_addr, struct.pack("<H", 2))
got = struct.unpack("<H", mem_read(jlink, tab_addr, 2))[0]
print(f"mGuiData @ 0x{base:08X} forced TAB={got}", flush=True)
return got == 2
def wait_enter(msg: str, seconds: float) -> None:
print(f"\n>>> {msg}", flush=True)
print(f" ({seconds:.0f}s countdown)", flush=True)
for left in range(int(seconds), 0, -1):
print(f" {left}...", flush=True)
time.sleep(1.0)
def main() -> int:
j = connect_jlink("Cortex-M4")
cb = find_rtt_control_block(j)
if not cb:
raise SystemExit("RTT CB not found")
print(f"RTT CB @ 0x{cb:08X}", flush=True)
j.rtt_start(cb)
wait_rtt_ready(j)
sess = RttSession(j)
print("\n=== Setup: 1.bin + TAB=普通(2) ===", flush=True)
sess.cmd("log clear", 0.2)
sess.cmd_ack("tone bin1 0", "TONE_BIN1", timeout_s=4.0, retries=4)
if not force_tab_normal(j):
j.rtt_stop()
j.close()
return 3
sess.cmd_ack("tone status", "TONE tab=", timeout_s=3.0, retries=4)
print(f"tab={parse_tab(sess.lines)}", flush=True)
# Stop any leftover
sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
force_tab_normal(j)
print("\n=== TEST1: pick with NO pad hold (expect silence) ===", flush=True)
print("请双手离开指板。", flush=True)
wait_enter("双手离开指板后等待自动拨片测试", 3)
force_tab_normal(j)
sess.cmd("log clear", 0.2)
sess.cmd_ack("tone start", "TONE_START_DONE", timeout_s=3.0, retries=5)
n1 = count_pitch(sess.pump(1.5))
print(f"TEST1 PITCH on={n1} (expect 0)", flush=True)
print("\n=== TEST2: hold pad + pick (expect sound) ===", flush=True)
wait_enter("请按住指板任意和弦键不放建议按住第2排中部", 5)
force_tab_normal(j)
sess.cmd("log clear", 0.2)
# keep KEY from physical hold; tone start without clearing if we use 'k'
# But we don't know KEY — physical hold already set KEY_ID+PressFlag via scan.
# tone start WITHOUT k clears KEY_ID! Must use tone start k — but then KEY kept.
# If user is holding, KEY_ID is set; PressFlag is set. tone start sets StartFlag=0
# then clears KEY unless k. Use: don't clear — need tone start k AND physical KEY already set.
sess.cmd_ack("tone start k", "TONE_START_DONE", timeout_s=3.0, retries=5)
play = sess.pump(2.5)
n2 = count_pitch(play)
print(f"TEST2 PITCH on={n2} (expect >0) — keep holding!", flush=True)
for L in play:
if "[PITCH]" in L:
print(" ", L, flush=True)
break
print("\n=== TEST3: release pad (expect stop) ===", flush=True)
wait_enter("请松开指板(松手)", 3)
force_tab_normal(j)
sess.cmd("log clear", 0.15)
post = sess.pump(2.0)
n3 = count_pitch(post)
print(f"TEST3 PITCH on after release={n3} (expect 0)", flush=True)
print("\n=== TEST4: hold again + pick (expect sound) ===", flush=True)
wait_enter("请再次按住指板和弦键不放", 5)
force_tab_normal(j)
sess.cmd("log clear", 0.2)
sess.cmd_ack("tone start k", "TONE_START_DONE", timeout_s=3.0, retries=5)
n4 = count_pitch(sess.pump(2.0))
print(f"TEST4 PITCH on={n4} (expect >0)", flush=True)
print("可松开指板。", flush=True)
time.sleep(1.0)
sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
t1, t2, t3, t4 = (n1 == 0), (n2 >= 1), (n3 == 0), (n4 >= 1)
print("\n======== RESULT ========", flush=True)
print(f"TEST1 no-hold silence: {'PASS' if t1 else 'FAIL'} (n={n1})", flush=True)
print(f"TEST2 hold+pick sound: {'PASS' if t2 else 'FAIL'} (n={n2})", flush=True)
print(f"TEST3 release stop: {'PASS' if t3 else 'FAIL'} (n={n3})", flush=True)
print(f"TEST4 re-trigger: {'PASS' if t4 else 'FAIL'} (n={n4})", flush=True)
ok_all = t1 and t2 and t3 and t4
print("OVERALL:", "PASS" if ok_all else "FAIL", flush=True)
j.rtt_stop()
j.close()
return 0 if ok_all else 1
if __name__ == "__main__":
raise SystemExit(main())