#!/usr/bin/env python3 """读 Collaplex 响度归一化的共享状态(诊断 / web 面板数据源) 共享区布局(见 loudness_norm.py 顶部注释): 每声道 32 字节, 两声道: +0 f32 e_smooth 平滑能量(响度判据) —— 归一化的"输入电平" +4 f32 e_block 本块能量(快响应) +8 f32 peak 本块峰值 +12 f32 gain_db 本块实际施加的增益 dB -> 推子 +16 f64 t 时间戳(monotonic) +24 u32 n 已处理块数(判活) +64 f32 target_db 目标响度 +68 u32 rate 采样率 用法: python3 read_state.py 打印一次 python3 read_state.py --watch 每 0.5 秒刷新(诊断动态平衡过程) """ import json import math import mmap import os import struct import sys import time SHM_PATH = "/dev/shm/collaplex-loudness" CH_STRIDE = 32 SHM_SIZE = 80 TARGET_OFF = 64 RATE_OFF = 68 STALE_S = 1.0 EPS = 1e-12 def db(x: float) -> float: """线性能量/幅度 -> dBFS(0 以下)。""" return 10.0 * math.log10(x + EPS) def read_slot(mm: mmap.mmap, i: int) -> dict[str, float]: base = i * CH_STRIDE e_smooth = struct.unpack_from(" dict[str, object]: if not os.path.exists(SHM_PATH): return {"ok": False, "err": "共享区不存在(响度归一化未启用)"} fd = os.open(SHM_PATH, os.O_RDONLY) try: mm = mmap.mmap(fd, SHM_SIZE, prot=mmap.PROT_READ) try: target = struct.unpack_from(" 0 else -120.0 out["raw_" + name] = round(raw_db, 2) if c["alive"] else None out["raw_peak_" + name] = round(raw_pk, 2) if c["alive"] else None out["post_" + name] = round(raw_db + c["gain_db"], 2) if c["alive"] else None out["gain_db_" + name] = round(c["gain_db"], 2) if c["alive"] else None # 平滑响度(归一化真正依据的那条) out["loud_" + name] = round(db(c["e_smooth"]), 2) if c["alive"] else None if ch[0]["alive"] and ch[1]["alive"]: joint = ch[0]["e_smooth"] + ch[1]["e_smooth"] out["loud_joint"] = round(db(joint), 2) out["deviation_db"] = round(target - db(joint), 2) # >0 = 还要提, <0 = 还要压 return out def main() -> None: watch = "--watch" in sys.argv try: if not watch: print(json.dumps(snapshot(), ensure_ascii=False, indent=2)) return print("每 0.5 秒刷新 (Ctrl-C 退出)") print("%8s %9s %9s %9s %9s %8s" % ( "时间", "原始L", "原始R", "处理后L", "处理后R", "增益dB")) t0 = time.time() while time.time() - t0 < 3600: s = snapshot() if not s.get("ok"): print(" (无数据: %s)" % s.get("err", "声道未在跑")) else: fmt = lambda k: ("%9.2f" % s[k]) if s.get(k) is not None else "%9s" % "-" _g = s.get("gain_db_L") gv = float(_g) if isinstance(_g, (int, float)) else 0.0 print("%8.1f %s %s %s %s %8.2f" % ( time.time() - t0, fmt("raw_L"), fmt("raw_R"), fmt("post_L"), fmt("post_R"), gv)) sys.stdout.flush() time.sleep(0.5) except KeyboardInterrupt: pass if __name__ == "__main__": main()