Files
blackbird/pybot/tests/test_speaker.py
qing 01126a5786 feat(mtc): .mt 脚本编译器落地 + pybot 扬声器静音语义
mtc 新模块:命令式动作脚本(Python DSL)→ .mt 矩阵编译器,非阻塞时间轴 IR(重叠截断/rest 静止值)+ 10ms 栅格化,产物可由 pybot/bot4cj 直接执行;34 测试全绿(含 pybot parser e2e)。

pybot speaker.py:frequency<=0 改为静音节拍(仅 sleep 保时序,不再钳到 min_f 低鸣),对齐仓颉版 sleep 占位语义;mtc Beep 通道 rest=0,段前/段间/段后回落静音,两侧语义闭环。

修复 test_actpred_compatibility 过时断言(99c1bc1 首行分解为重复微步 19→20 帧时漏改,14→15)。

文档三处同步:根 README 回填 memdb/pybot/actpred 并新增 mtc(模块表+端口一览+章节),MODULES/ARCHITECTURE 同步 mtc,新建 mtc/README.md。
2026-08-15 13:54:47 +08:00

83 lines
2.7 KiB
Python
Raw Permalink 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.
"""Speaker 测试:静音语义 / 频率钳制 / SpeakerVar 参数透传
静音语义(2026-08-15 修复):frequency <= 0 不出声、仅保持 delay 节拍
(对齐仓颉版 sleep 占位语义);此前 0Hz 被钳到 min_f 发出 20Hz 低鸣,
破坏了 .mt 首行 freq=0(jump.mt/std.mt)的静默意图。
"""
import logging
import pytest
from pybot.hardvars import SpeakerVar
from pybot.hd.speaker import Speaker
class _FakeSpeaker:
"""记录 play 调用的桩(SpeakerVar 透传测试用)"""
def __init__(self):
self.calls = []
def play(self, freq, delay_ms):
self.calls.append((freq, delay_ms))
# ---------------- 静音语义 ----------------
def test_zero_frequency_is_silent_and_keeps_delay(monkeypatch):
"""freq=0:不合成波形、不播放,仅 sleep 保持节拍"""
spk = Speaker(dry_run=False)
slept, synths = [], []
monkeypatch.setattr("pybot.hd.speaker.time.sleep", lambda s: slept.append(s))
monkeypatch.setattr(spk, "_synth", lambda *a: synths.append(a) or b"")
spk.play(0, 100)
assert slept == [0.1]
assert synths == [] # 未走波形合成路径
def test_negative_frequency_is_silent(monkeypatch):
spk = Speaker(dry_run=False)
slept = []
monkeypatch.setattr("pybot.hd.speaker.time.sleep", lambda s: slept.append(s))
spk.play(-5, 50)
assert slept == [0.05]
def test_zero_frequency_dry_run_logs_silence(caplog):
"""dry_run:仅记日志立即返回(不 sleep),日志标识 silence"""
spk = Speaker(dry_run=True)
with caplog.at_level(logging.INFO, logger="pybot.speaker"):
spk.play(0, 100)
assert "silence 100ms" in caplog.text
assert "Hz" not in caplog.text
# ---------------- 正频率行为不破坏 ----------------
def test_positive_frequency_still_clamped(caplog):
"""正频率钳制不变:5Hz → min_f(20),25000Hz → max_f"""
spk = Speaker(dry_run=True, min_f=20.0, max_f=5000.0)
with caplog.at_level(logging.INFO, logger="pybot.speaker"):
spk.play(5, 10)
spk.play(25000, 10)
assert "20.0Hz 10ms" in caplog.text
assert "5000.0Hz 10ms" in caplog.text
# ---------------- SpeakerVar 透传 ----------------
def test_speakervar_passes_zero_frequency_through():
""".mt 行 freq=0 经 SpeakerVar 原样透传到 Speaker.play(进入静音分支)"""
fake = _FakeSpeaker()
var = SpeakerVar("spk", fake)
assert var.play({"frequency": "0", "delay": "100"}) == {"status": "OK"}
assert fake.calls == [(0.0, 100)]
def test_speakervar_requires_frequency_key():
"""缺 frequency 键仍 KeyError(契约不变:delay 列必须与 frequency 成对)"""
var = SpeakerVar("spk", _FakeSpeaker())
with pytest.raises(KeyError):
var.play({"delay": "100"})