修削峰: 总音量输出级 np.clip 硬削 -> soft_limit 软限幅(上限-1dBFS, tanh 渐近); +12dB 时贴顶样本 64.6%->0%, 峰值 -1.01 dBFS
This commit is contained in:
@@ -48,6 +48,16 @@ bash 脚本/面板.sh # http://127.0.0.1:8789
|
||||
- 参数落点:`/dev/shm/collaplex-eq`(前端写、DSP 读,版本号保证不成半更新);响度目标写在
|
||||
`~/.local/state/cinema-spatial/loudness.json`(归一化 DSP 每秒重读)
|
||||
|
||||
## 关于"放大"与削波(2026-09-19 修)
|
||||
|
||||
总音量级的输出**不能硬削**(原实现是 `np.clip(out, -1.0, 1.0)`): 硬削把波形削成平顶,
|
||||
听感就是"炸麦"; 而被削掉的峰值**不增加响度**, 所以出现"声音没变大、却削峰了"。
|
||||
|
||||
改用**软限幅** `dsp/eq.py: soft_limit()`: 上限 -1 dBFS, 幅值在 (上限−拐点) 以内**完全透明**,
|
||||
超出部分用 `tanh` 平滑渐近上限 —— 不产生平顶, 波形其余部分不动, 响度真实提升。
|
||||
|
||||
实测(峰值 -12 dBFS 素材, 总音量 +12 dB): 输出峰值 **-1.01 dBFS**, 贴顶样本自 **64.6% → 0%**。
|
||||
|
||||
## 实测(2026-09-19)
|
||||
|
||||
| 指标 | 数据 |
|
||||
|
||||
@@ -47,6 +47,27 @@ def build_sos(gains: list[float], fs: int) -> np.ndarray:
|
||||
for freq, gain in zip(common.EQ_FREQS, gains)], dtype=np.float64)
|
||||
|
||||
|
||||
CEILING = 0.891 # -1 dBFS 上限
|
||||
KNEE = 0.25 # 软拐点宽度
|
||||
|
||||
|
||||
def soft_limit(x: np.ndarray, ceiling: float = CEILING, knee: float = KNEE) -> np.ndarray:
|
||||
"""软限幅: 幅值在 ceiling-knee 以内的样本原样通过, 超过的部分用 tanh 平滑渐近 ceiling。
|
||||
|
||||
与 np.clip 的关键区别: 不产生平顶。硬削(平顶)听感就是"炸麦", 而且削掉的峰值
|
||||
不增加响度; 软限幅只把峰值压下来, 波形其余部分不动, 所以放大后响度能真正上来。
|
||||
"""
|
||||
threshold = ceiling - knee
|
||||
magnitude = np.abs(x)
|
||||
over = magnitude > threshold
|
||||
if not bool(np.any(over)):
|
||||
return x
|
||||
shaped = threshold + knee * np.tanh((magnitude[over] - threshold) / knee)
|
||||
result = x.copy()
|
||||
result[over] = np.sign(x[over]) * shaped
|
||||
return result
|
||||
|
||||
|
||||
def level_db(block: np.ndarray) -> tuple[float, float]:
|
||||
"""返回 (RMS dBFS, 峰值 dBFS)。"""
|
||||
if block.size == 0:
|
||||
@@ -88,13 +109,13 @@ def main() -> None:
|
||||
x = np.resize(x, N)
|
||||
|
||||
y, zi = sosfilt(sos, x, zi=zi)
|
||||
out = y * volume
|
||||
out = soft_limit(y * volume)
|
||||
|
||||
in_rms, in_peak = level_db(x)
|
||||
out_rms, out_peak = level_db(np.clip(out, -1.0, 1.0))
|
||||
out_rms, out_peak = level_db(out)
|
||||
common.write_state(store, in_rms, in_peak, out_rms, out_peak, volume_db)
|
||||
|
||||
stdout.write(np.clip(out, -1.0, 1.0).astype("<f4").tobytes())
|
||||
stdout.write(out.astype("<f4").tobytes())
|
||||
stdout.flush()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user