2026-10-15 14:38:23 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""固定调和弦六弦音高:RTT STRING_OK 对照《自动伴奏新更改和弦规则.xlsx》。
|
|
|
|
|
|
|
|
|
|
|
|
普通模式按下指板键后读 USE_MIDI(六弦→一弦)。先 chord xpose 再 chord key,
|
|
|
|
|
|
因为换调命令本身不重填六弦。
|
|
|
|
|
|
|
|
|
|
|
|
python tools/test_fixed_key_voicing.py
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
import re
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
TOOLS = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
sys.path.insert(0, TOOLS)
|
|
|
|
|
|
|
|
|
|
|
|
from k1_harness.transports.rtt_session import open_session # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
NOTE_RE = re.compile(r"STRING_OK s=(\d+) note=(\d+)")
|
|
|
|
|
|
|
|
|
|
|
|
# (调名, GUI_TRANSPOSE, 键, 和弦名, 六弦→一弦 MIDI)
|
|
|
|
|
|
# 默认品质:I/IV/V 大三,II/III/VI/VII 小三。数值来自各调 sheet 对应行。
|
|
|
|
|
|
CASES = [
|
|
|
|
|
|
("C", 0, 1, "C", (43, 48, 52, 55, 60, 64)),
|
|
|
|
|
|
("C", 0, 4, "Dm", (41, 45, 50, 57, 62, 65)),
|
|
|
|
|
|
("C", 0, 7, "Em", (40, 47, 52, 55, 59, 64)),
|
|
|
|
|
|
("C", 0, 10, "F", (41, 48, 53, 57, 60, 65)),
|
|
|
|
|
|
("C", 0, 13, "G", (43, 47, 50, 55, 59, 67)),
|
|
|
|
|
|
("C", 0, 16, "Am", (40, 45, 52, 57, 60, 64)),
|
|
|
|
|
|
("C", 0, 19, "Bm", (42, 47, 50, 54, 59, 62)),
|
|
|
|
|
|
# G:源品位 V/VI/VII/I/II/III/IV+1
|
|
|
|
|
|
("G", 7, 1, "G", (43, 47, 50, 55, 59, 67)),
|
|
|
|
|
|
("G", 7, 4, "Am", (40, 45, 52, 57, 60, 64)),
|
|
|
|
|
|
("G", 7, 7, "Bm", (42, 47, 50, 54, 59, 62)),
|
|
|
|
|
|
("G", 7, 10, "C", (43, 48, 52, 55, 60, 64)),
|
|
|
|
|
|
("G", 7, 13, "D", (42, 45, 50, 57, 62, 66)),
|
|
|
|
|
|
("G", 7, 16, "Em", (40, 47, 52, 55, 59, 64)),
|
2026-10-15 14:58:28 +08:00
|
|
|
|
# 键 19 出厂是小三:C 调 IV 的 Fm 行再 +1
|
|
|
|
|
|
("G", 7, 19, "#Fm", (42, 49, 54, 57, 61, 66)),
|
2026-10-15 14:38:23 +08:00
|
|
|
|
# D I = C 调 II 行;F# I = F 调 I 行全体 +1
|
|
|
|
|
|
("D", 2, 1, "D", (42, 45, 50, 57, 62, 66)),
|
|
|
|
|
|
("F#", 6, 1, "F#", (42, 49, 54, 58, 61, 66)),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def last_string_note(text: str, string_n: int) -> int | None:
|
|
|
|
|
|
found = None
|
|
|
|
|
|
for m in NOTE_RE.finditer(text or ""):
|
|
|
|
|
|
if int(m.group(1)) == string_n:
|
|
|
|
|
|
found = int(m.group(2))
|
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
|
fails = 0
|
|
|
|
|
|
print("=== 固定调六弦音高 RTT ===", flush=True)
|
2026-10-15 14:58:28 +08:00
|
|
|
|
with open_session(reset=True) as sess:
|
2026-10-15 14:38:23 +08:00
|
|
|
|
sess.cmd("tone pick f", settle=1.0)
|
2026-10-15 14:58:28 +08:00
|
|
|
|
sess.cmd("log level E", settle=0.3)
|
2026-10-15 14:38:23 +08:00
|
|
|
|
sess.cmd("chord key 23", settle=0.3)
|
|
|
|
|
|
sess.cmd("chord key 0", settle=0.2)
|
|
|
|
|
|
cur_xp = None
|
|
|
|
|
|
for key_name, xp, key, chord, notes in CASES:
|
|
|
|
|
|
if xp != cur_xp:
|
|
|
|
|
|
sess.cmd_ack(f"chord xpose {xp}", "CHORD_XPOSE_OK", timeout_s=2.0, retries=4)
|
|
|
|
|
|
cur_xp = xp
|
|
|
|
|
|
sess.cmd_ack(f"chord key {key}", "CHORD_KEY_OK", timeout_s=3.0, retries=4)
|
|
|
|
|
|
time.sleep(0.15)
|
|
|
|
|
|
print(f"\n-- {key_name} {chord} key={key} xp={xp}", flush=True)
|
|
|
|
|
|
for s in range(6, 0, -1):
|
|
|
|
|
|
expect = int(notes[6 - s])
|
2026-10-15 14:58:28 +08:00
|
|
|
|
got = None
|
|
|
|
|
|
for _try in range(4):
|
|
|
|
|
|
n0 = len(sess.lines)
|
|
|
|
|
|
sess.cmd(f"string {s}", settle=0.02)
|
|
|
|
|
|
deadline = time.monotonic() + 0.6
|
|
|
|
|
|
while time.monotonic() < deadline:
|
|
|
|
|
|
got = last_string_note("\n".join(sess.lines[n0:]), s)
|
|
|
|
|
|
if got is not None:
|
|
|
|
|
|
break
|
|
|
|
|
|
sess.pump(0.04)
|
|
|
|
|
|
if got is not None:
|
|
|
|
|
|
break
|
|
|
|
|
|
sess.cmd(f"string {s} off", settle=0.02)
|
2026-10-15 14:38:23 +08:00
|
|
|
|
ok = got == expect
|
|
|
|
|
|
if not ok:
|
|
|
|
|
|
fails += 1
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" str{s} expect={expect} got={got} {'PASS' if ok else 'FAIL'}",
|
|
|
|
|
|
flush=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
sess.cmd_ack("chord xpose 0", "CHORD_XPOSE_OK", timeout_s=2.0, retries=2)
|
|
|
|
|
|
sess.cmd("chord key 0", settle=0.2)
|
|
|
|
|
|
print(f"\nFAIL={fails} / {len(CASES) * 6}", flush=True)
|
|
|
|
|
|
return 1 if fails else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
raise SystemExit(main())
|