混响立体声丢失: 重做 IR 的工具把双声道压成了单声道 -> conf 改用 convolver 的 channel 按声道取
- 工具/重做混响IR.py 原来写 `x = x[:, 0]`, 只取左声道、把右声道丢掉, 写出去的 IR 成了 单声道; 而 20-conf 里 revL/revR 读的是同一个文件 -> 两路湿声完全同源, 混响糊在正中央、 没有宽度(原 IR 本身是双声道真立体声, 左右相关 0.884) - 改为逐声道处理 + 双声道写回; 重新生成 reverb/房间混响IR-96k.wav (285888 x 2, 相关 0.9492) - 20-conf: revL 用 channel = 0 / revR 用 channel = 1 —— PipeWire 的 convolver 支持该键, 从多声道 IR 文件里按索引取声道。实测(通道探针: 左零右真的文件 + channel = 1)右耳尾巴 不塌, 左右差仅 1.1 dB; 若读错声道会掉 40 dB 以上 - 新增取证脚本: 扫频测频响(Farina 反卷积, 确定性信号才测得准)、电平与失真诊断(只改电平的 对照实验)、验证混响声道(文件级 + 物理级检查)、诊断低频沙沙(逐级旁路) - 扫频脚本修了一个 bug: 湿量只在第一轮写, 后面几轮沿用被改过的值, 导致 B/C 两轮测的是同一配置 - README: 坑表加两条(双声道 IR + 验证判据的坑) + 2026-09-20 低频/沙沙诊断实测段
This commit is contained in:
+34
-15
@@ -8,6 +8,12 @@
|
||||
2. 前 55ms 余弦渐入 —— 压掉直达声和过强的早期反射
|
||||
3. 之后原样保留(混响尾)
|
||||
|
||||
★ 声道: 原 IR 是**双声道立体声**(左右相关 0.884), 处理必须逐声道做、保持双声道输出。
|
||||
2026-09-19 版这里写了 `x = x[:, 0]` —— 把右声道丢掉了, 输出的 IR 变成单声道,
|
||||
而 filter-chain 里 revL/revR 读同一个文件 -> 两路湿声 100% 同相: 混响糊在正中间、
|
||||
低频同相叠加 +6 dB。现在改成: 双声道各自处理后原样写回, conf 里用
|
||||
`channel = 0` / `channel = 1` 分别取左右。
|
||||
|
||||
产出 reverb/房间混响IR-96k.wav(原文件备份为 .orig.wav), 并打印标定用的增益。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -26,31 +32,39 @@ TARGET_PEAK = 0.1493 # 原 IR 峰值(保持同量级, 免得到时候电平
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""处理 IR。"""
|
||||
rate, data = wavfile.read(IR)
|
||||
x = data.astype(np.float64)
|
||||
if x.ndim > 1:
|
||||
x = x[:, 0]
|
||||
n0 = len(x)
|
||||
peak0 = float(np.abs(x).max())
|
||||
rms0 = float(np.sqrt((x ** 2).mean()))
|
||||
|
||||
"""处理 IR, 逐声道, 保持双声道。"""
|
||||
if not BAK.exists():
|
||||
shutil.copy2(IR, BAK)
|
||||
print(f"已备份原 IR -> {BAK}")
|
||||
print(f"已备份当前 IR -> {BAK}")
|
||||
|
||||
# ★ 一律从原始备份读: 当前 IR 可能已经被上一版降成单声道了
|
||||
rate, data = wavfile.read(BAK)
|
||||
x = np.asarray(data, dtype=np.float64)
|
||||
if x.ndim == 1:
|
||||
x = x[:, None]
|
||||
n_ch = int(x.shape[1])
|
||||
n0 = int(x.shape[0])
|
||||
if n_ch > 1:
|
||||
corr0 = float(np.corrcoef(x[:, 0], x[:, 1])[0, 1])
|
||||
print(f"源 IR: {n0} 样 x {n_ch} 声道, 左右相关 {corr0:.4f}")
|
||||
else:
|
||||
print(f"源 IR: {n0} 样 x {n_ch} 声道")
|
||||
peak0 = float(np.abs(x).max())
|
||||
rms0 = float(np.sqrt((x ** 2).mean()))
|
||||
|
||||
trim = int(rate * TRIM_MS / 1000)
|
||||
y = x[trim:].copy()
|
||||
|
||||
# 前 FADE_MS 余弦渐入(升余弦的前半段)
|
||||
# 前 FADE_MS 余弦渐入(升余弦的前半段) —— 逐声道同一个窗
|
||||
fade = int(rate * FADE_MS / 1000)
|
||||
if fade < len(y):
|
||||
t = np.arange(fade) / fade
|
||||
y[:fade] *= 0.5 * (1 - np.cos(np.pi * t))
|
||||
w = 0.5 * (1 - np.cos(np.pi * t))
|
||||
y[:fade] *= w[:, None]
|
||||
|
||||
peak1 = float(np.abs(y).max())
|
||||
rms1 = float(np.sqrt((y ** 2).mean()))
|
||||
print(f"原 IR: {n0} 样, 峰值 {peak0:.4f}, RMS {rms0:.6f}")
|
||||
print(f"原 IR: 峰值 {peak0:.4f}, RMS {rms0:.6f}")
|
||||
print(f"新 IR: {len(y)} 样, 峰值 {peak1:.4f}, RMS {rms1:.6f}")
|
||||
print(f"峰值比 {peak1 / peak0:.4f} ({20 * np.log10(peak1 / peak0):+.2f} dB)")
|
||||
print(f"RMS 比 {rms1 / rms0:.4f} ({20 * np.log10(rms1 / rms0):+.2f} dB)")
|
||||
@@ -60,8 +74,13 @@ def main() -> None:
|
||||
wavfile.write(IR, rate, y.astype(np.float32))
|
||||
print(f"已写入 {IR}")
|
||||
|
||||
print("\n新 IR 每 10ms 包络:")
|
||||
z = np.abs(y)
|
||||
if n_ch > 1:
|
||||
corr = float(np.corrcoef(y[:, 0], y[:, 1])[0, 1])
|
||||
print(f"\n保持双声道: 处理后左右相关 = {corr:.4f}")
|
||||
print(f" 左 RMS {np.sqrt((y[:, 0] ** 2).mean()):.6f} / 右 RMS {np.sqrt((y[:, 1] ** 2).mean()):.6f}")
|
||||
|
||||
print("\n新 IR 每 10ms 包络(左声道):")
|
||||
z = np.abs(y[:, 0])
|
||||
for k in range(0, 14):
|
||||
a, b = int(rate * 0.01 * k), int(rate * 0.01 * (k + 1))
|
||||
if b > len(z):
|
||||
|
||||
Reference in New Issue
Block a user