Files
collaplex-audio/dsp/common.py
T

101 lines
3.4 KiB
Python

"""Collaplex 效果 DSP 公共部分: 前端 <-> DSP 的共享内存协议。
前端(web/server.py) 与 DSP 进程(pipe 插件载荷)通过这一块共享内存通信:
参数区: 32 段 EQ 增益(dB) + 总音量(dB) <- 前端写, DSP 读
版本区: 参数版本号 <- 前端写完参数后 +1
状态区: 输入/输出 RMS 与峰值(dB) <- DSP 写, 前端读
版本号最后写, DSP 只在版本号变化时重读参数, 所以前端一次改多个值也不会让
DSP 读到半更新的数据。
"""
from __future__ import annotations
import mmap
import os
import struct
EQ_BANDS = 32
EQ_PATH = "/dev/shm/collaplex-eq"
# 1/3 倍频程中心频率(20 Hz 起, 32 段, 覆盖到 25 kHz)
EQ_FREQS: list[float] = [
20.0, 25.0, 31.5, 40.0, 50.0, 63.0, 80.0, 100.0,
125.0, 160.0, 200.0, 250.0, 315.0, 400.0, 500.0, 630.0,
800.0, 1000.0, 1250.0, 1600.0, 2000.0, 2500.0, 3150.0, 4000.0,
5000.0, 6300.0, 8000.0, 10000.0, 12500.0, 16000.0, 20000.0, 25000.0,
]
GAIN_LIMIT = 15.0
VOLUME_LIMIT = 12.0
_DATA = struct.Struct("<33d") # 32 段增益 + 总音量
_VER = struct.Struct("<I") # 参数版本号
_STATE = struct.Struct("<6d") # in_rms in_peak out_rms out_peak 推子(dB) 保留
DATA_OFF = 0
VER_OFF = _DATA.size
STATE_OFF = _DATA.size + _VER.size
SIZE = STATE_OFF + _STATE.size
def open_store() -> mmap.mmap:
"""打开(不存在则创建)共享内存映射。"""
fd = os.open(EQ_PATH, os.O_RDWR | os.O_CREAT, 0o666)
try:
if os.fstat(fd).st_size < SIZE:
os.ftruncate(fd, SIZE)
return mmap.mmap(fd, SIZE)
finally:
os.close(fd)
def read_params(store: mmap.mmap) -> tuple[list[float], float, int]:
"""读参数: (32 段增益 dB, 总音量 dB, 版本号)。"""
store.seek(DATA_OFF)
values = _DATA.unpack(store.read(_DATA.size))
store.seek(VER_OFF)
version = _VER.unpack(store.read(_VER.size))[0]
return list(values[:EQ_BANDS]), float(values[EQ_BANDS]), int(version)
def write_params(store: mmap.mmap, gains: list[float], volume_db: float) -> int:
"""写参数并让版本号 +1, 返回新版本号。"""
if len(gains) != EQ_BANDS:
raise ValueError("gains 长度必须是 %d" % EQ_BANDS)
store.seek(VER_OFF)
version = int(_VER.unpack(store.read(_VER.size))[0])
store.seek(DATA_OFF)
store.write(_DATA.pack(*[float(g) for g in gains], float(volume_db)))
version = (version + 1) & 0xFFFFFFFF
store.seek(VER_OFF)
store.write(_VER.pack(version))
store.flush()
return version
def read_state(store: mmap.mmap) -> dict[str, float]:
"""读状态(DSP 写的电平)。"""
store.seek(STATE_OFF)
in_rms, in_peak, out_rms, out_peak, gain_db, _spare = _STATE.unpack(store.read(_STATE.size))
return {
"in_rms": float(in_rms),
"in_peak": float(in_peak),
"out_rms": float(out_rms),
"out_peak": float(out_peak),
"gain_db": float(gain_db),
}
def write_state(store: mmap.mmap, in_rms: float, in_peak: float, out_rms: float,
out_peak: float, gain_db: float) -> None:
"""写状态(DSP 侧调用)。"""
store.seek(STATE_OFF)
store.write(_STATE.pack(float(in_rms), float(in_peak), float(out_rms),
float(out_peak), float(gain_db), 0.0))
def defaults() -> tuple[list[float], float]:
"""默认参数: 全平直 + 0 dB 总音量。"""
return [0.0] * EQ_BANDS, 0.0