Files
blackbird/mtc/mtc/emit.py
T
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

81 lines
3.1 KiB
Python

""".mt 序列化 —— 硬件块(依赖序/声明序)+ @Action 栅格矩阵
输出契约(与 pybot parser 对齐):
@块名 / @ 定界;行 0 表头;行尾 ';';列间空格分隔;
@Action 表头为 '设备.参数' 长名(Q05 校验恰含一个 '.'),
末列 '<speaker>.delay' 恒 10(栅格节拍),无 Speaker 块则无此列。
栅格行语义:t=10,20,…,T,行值为该 10ms 结束时刻的绝对姿态;
pybot run() 对 delay=10 的行分解为 1 个微步且插值系数 1.0,
即直接到位 —— 与栅格语义严格等价。
"""
from .timeline import STEP_MS
def fmt_num(v):
"""数值格式化:整数无小数点,小数保留至多 3 位(栅格插值值)"""
f = float(v)
if f == int(f):
return str(int(f))
return f"{f:.3f}".rstrip("0")
def _fmt_block(lines, name, columns, rows):
lines.append(f"@{name}")
lines.append(" ".join(columns) + ";")
for row in rows:
lines.append(" ".join(str(v) for v in row) + ";")
lines.append("@")
def emit_mt(blocks, active_channels, t_total, speaker=None):
"""生成 .mt 全文
blocks: [(block_name, columns, row_values_str)](声明序)
active_channels: [Channel](有运动段的通道,注册序)
t_total: 编译总时长 ms(10 的倍数)
speaker: delay 节拍列所属设备名;None 则无节拍列
"""
from .dsl import CompileError # 避免环:dsl 依赖 emit
lines = []
merged = [] # 相邻同名块合并为一个块多行(.mt 惯例)
for block_name, columns, row in blocks:
if merged and merged[-1][0] == block_name and merged[-1][1] == columns:
merged[-1][2].append(row)
else:
merged.append((block_name, columns, [row]))
for block_name, columns, rows in merged:
_fmt_block(lines, block_name, columns, rows)
lines.append("") # 块间空行(MatrixSaver 风格)
delay_col = f"{speaker}.delay" if speaker else None
freq_col = f"{speaker}.frequency" if speaker else None
# pybot 契约:SpeakerVar.play 无条件读 frequency+delay 两键,
# delay 节拍列必须与同设备 frequency 列成对出现(std.mt 惯例:
# spk.F spk.delay;无发声需求时 frequency 恒 0,对齐 jump.mt 首行)
active_names = {ch.name for ch in active_channels}
fill_freq = bool(freq_col) and freq_col not in active_names
header = [ch.name for ch in active_channels]
if fill_freq:
header.append(freq_col)
if delay_col:
header.append(delay_col)
if not header:
raise CompileError("@Action 无列(既无运动通道也无 Speaker 节拍列)")
n = int(round(t_total / STEP_MS))
rows = []
for k in range(1, n + 1):
t = k * STEP_MS
row = [fmt_num(ch.value_at(t)) for ch in active_channels]
if fill_freq:
row.append("0")
if delay_col:
row.append(str(STEP_MS))
rows.append(row)
_fmt_block(lines, "Action", header, rows)
return "\n".join(lines) + "\n"