K1Guitar/tools/test_fixed_key_voicing.py

91 lines
3.3 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.

# -*- 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)),
("G", 7, 19, "#Fm", (42, 49, 54, 58, 61, 66)),
# 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)
with open_session(reset=False) as sess:
sess.cmd("tone pick f", settle=1.0)
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])
n0 = len(sess.lines)
sess.cmd(f"string {s}", settle=0.12)
sess.cmd(f"string {s} off", settle=0.05)
got = last_string_note("\n".join(sess.lines[n0:]), s)
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())