K1Guitar/tools/_test_normal_pick.py

182 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())