5c6a982210
诊断见 README「实测(2026-09-20)」: 链路的**线性**部分干净(无驻波、失真 −116 dB), "低频脏 + 沙沙"来自电平被顶穿之后软限幅常年介入 —— 归一化把内容提到满刻度, 宽频信号 一削, 互调产物堆在低频(听感"脏")、高频碎屑(听感"沙沙")。实测超软限幅阈值样本 50.49%、 峰值因子被从 14 dB 压到 2.0 dB。 四条修复: 1. LN_MAX_BOOST 20 -> 12(默认值): 20 dB 的提升上限让任何轻内容都被顶穿满刻度 2. HRIR 同相合并响应均衡(lowshelf 500Hz −7.2dB Q=0.707, 12 条共用一条曲线, 方向线索 ILD/ITD 不受影响): 这套 HRIR 是原始测量数据、没做过漫反射场均衡。 ★ 口径注意: 标准 DF-EQ 用各方向**功率平均**做参考, 只要求压 1.5 dB; 但真实内容 L≈R 时两耳信号是**相加**的, 同相合并响应才对应实测的 7.5 dB 3. 房间 IR 低频校平(lowshelf 300Hz −21dB Q=0.70): 低频比中频高 24.5 dB -> 压到 +5.8 dB (留一点厚度); IR 峰值随之掉 3.7 dB, 湿路 gain 0.4 -> 0.61 补偿, 保持混响响度不变 4. EQ 微笑曲线(20~63Hz +2.5 / 高频 +3)改回平直 复测(同素材同脚本): 低频 20~200Hz **+13.6 -> +0.2 dB**; 整链峰值因子 **2.0 -> 12.2 dB**; 超阈值样本 **50.49% -> 0.00%**; 峰值/RMS −0.1/−2.1 -> −6.2/−18.4 dBFS。 响度目标回到 −14 dB 也不会再顶穿。 新增: 工具/分析均衡曲线.py(量曲线)、工具/均衡IR.py(施加校正, 可重跑); README 加「IR 校正」节 + 修复前后对照 + 两条测量方法上的坑。 IR 备份: hrir/*.pre-eq.wav 与 reverb/*.wav.pre-eq.wav(频响校正前)、 reverb/*.orig.wav(剪直达声前)。重跑顺序: 先 重做混响IR.py, 再 均衡IR.py。
154 lines
6.0 KiB
Python
154 lines
6.0 KiB
Python
"""分析出一条"该补多少"的曲线, 供设计均衡滤波器用。
|
|
|
|
两件事:
|
|
① HRIR 漫反射场均衡(DF-EQ): 这套 HRIR 是原始测量数据, 没做过漫反射场均衡,
|
|
表现为"对侧耳低频比自身 1 kHz 高 7~8 dB、所有 IR 高频高 4~5 dB" -> 链路低频过量。
|
|
标准做法: 用所有方向 IR 的**功率平均**当参考, 取 1/sqrt(P_avg) 作为均衡曲线。
|
|
② 房间 IR 的频响校平: 混响 IR 低频(20~200 Hz)比中高频高 20~25 dB, 湿路灌低频。
|
|
|
|
只做分析、打印曲线, 不写任何文件。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import glob
|
|
import os
|
|
import warnings
|
|
|
|
import numpy as np
|
|
from scipy.io import wavfile
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
RATE = 96000
|
|
PROJ = "/home/lou/桌面/工作区/实验/collaplex音效"
|
|
N = 1 << 15 # FFT 长度 -> 频率分辨率 2.93 Hz
|
|
|
|
BANDS: list[float] = []
|
|
_f = 20.0
|
|
while _f <= 20000.0:
|
|
BANDS.append(round(_f, 2))
|
|
_f *= 2 ** (1 / 6) # 1/6 倍频程
|
|
|
|
|
|
def read_mono(path: str) -> np.ndarray:
|
|
_sr, data = wavfile.read(path)
|
|
a = np.asarray(data, dtype=np.float64)
|
|
if a.ndim > 1:
|
|
a = a[:, 0]
|
|
return a
|
|
|
|
|
|
def smooth_log(freq: np.ndarray, curve: np.ndarray, frac: float = 1 / 6) -> np.ndarray:
|
|
"""在 log 频率轴上按 1/frac 倍频程做移动平均(曲线要平滑, 不然是把噪声也均衡了)。"""
|
|
out = np.empty_like(curve)
|
|
for i, fc in enumerate(freq):
|
|
if fc <= 0:
|
|
out[i] = curve[i]
|
|
continue
|
|
m = (freq >= fc * 2 ** (-frac / 2)) & (freq <= fc * 2 ** (frac / 2))
|
|
out[i] = float(np.mean(curve[m])) if bool(np.any(m)) else curve[i]
|
|
return out
|
|
|
|
|
|
def main() -> None:
|
|
freq = np.fft.rfftfreq(N, 1.0 / RATE)
|
|
files = sorted(glob.glob(os.path.join(PROJ, "hrir", "*.wav")))
|
|
|
|
power = np.zeros(N // 2 + 1)
|
|
for path in files:
|
|
a = read_mono(path)
|
|
spec = np.fft.rfft(a, N)
|
|
power += np.abs(spec) ** 2
|
|
power /= len(files)
|
|
|
|
# 漫反射场均衡曲线: 1/sqrt(功率平均), 归一到 1 kHz
|
|
eq = 1.0 / np.sqrt(power + 1e-30)
|
|
eq = smooth_log(freq, eq, 1 / 6)
|
|
ref_mask = (freq >= 900) & (freq <= 1120)
|
|
eq /= float(np.mean(eq[ref_mask])) if bool(np.any(ref_mask)) else 1.0
|
|
eq_db = 20 * np.log10(eq + 1e-30)
|
|
|
|
print(f"HRIR 文件 {len(files)} 条, 功率平均后取 1/sqrt(P) 得均衡曲线")
|
|
print("① HRIR 所需均衡(相对 1 kHz, dB) —— 负值 = 要压掉")
|
|
line: list[str] = []
|
|
for fc in BANDS:
|
|
if fc > 12000:
|
|
break
|
|
m = (freq >= fc * 2 ** (-1 / 12)) & (freq <= fc * 2 ** (1 / 12))
|
|
v = float(np.mean(eq_db[m])) if bool(np.any(m)) else 0.0
|
|
line.append(f"{fc:>7.0f}Hz:{v:+5.1f}")
|
|
for i in range(0, len(line), 5):
|
|
print(" " + " ".join(line[i:i + 5]))
|
|
|
|
print()
|
|
for lo, hi, tag in ((20, 200, "低频 20-200"), (200, 1000, "中低 200-1k"),
|
|
(1000, 3000, "中高 1k-3k"), (3000, 12000, "高频 3k-12k")):
|
|
m = (freq >= lo) & (freq <= hi)
|
|
print(f" 均值 {tag:>14}: {float(np.mean(eq_db[m])):+6.2f} dB")
|
|
|
|
# ---- ② 房间 IR 的频响校平需求 ----
|
|
ir = read_mono(os.path.join(PROJ, "reverb", "房间混响IR-96k.wav"))
|
|
n_ir = 1 << 18 # 2.73s IR -> 用大 FFT
|
|
spec_ir = np.abs(np.fft.rfft(ir, n_ir)) ** 2
|
|
f_ir = np.fft.rfftfreq(n_ir, 1.0 / RATE)
|
|
# 用 1/6 倍频程平滑
|
|
sm = np.zeros_like(spec_ir)
|
|
for i in range(len(f_ir)):
|
|
fc = f_ir[i]
|
|
if fc <= 0:
|
|
continue
|
|
m = (f_ir >= fc * 2 ** (-1 / 12)) & (f_ir <= fc * 2 ** (1 / 12))
|
|
sm[i] = float(np.mean(spec_ir[m])) if bool(np.any(m)) else spec_ir[i]
|
|
ir_db = 10 * np.log10(sm + 1e-30)
|
|
ref_m = (f_ir >= 700) & (f_ir <= 1500)
|
|
ir_db -= float(np.mean(ir_db[ref_m]))
|
|
|
|
print()
|
|
print("② 房间 IR 自己的频响(相对 0.7-1.5 kHz 均值, dB) —— 正值 = 该频段过强")
|
|
line = []
|
|
for fc in BANDS:
|
|
if fc > 12000:
|
|
break
|
|
m = (f_ir >= fc * 2 ** (-1 / 12)) & (f_ir <= fc * 2 ** (1 / 12))
|
|
v = float(np.mean(ir_db[m])) if bool(np.any(m)) else 0.0
|
|
line.append(f"{fc:>7.0f}Hz:{v:+6.1f}")
|
|
for i in range(0, len(line), 5):
|
|
print(" " + " ".join(line[i:i + 5]))
|
|
for lo, hi, tag in ((20, 200, "低频 20-200"), (200, 1000, "中低 200-1k"),
|
|
(1000, 3000, "中高 1k-3k"), (3000, 12000, "高频 3k-12k")):
|
|
m = (f_ir >= lo) & (f_ir <= hi)
|
|
print(f" 均值 {tag:>14}: {float(np.mean(ir_db[m])):+6.2f} dB")
|
|
|
|
# ---- ③ 同相合并响应: 实际内容大多是相关的(人声/低频居中), 两耳信号是**相加** ----
|
|
# 功率平均(①)假设各方向互不相关, 会低估低频; 同相合并才是复核实测的那个口径。
|
|
def merge_power(a_name: str, b_name: str) -> np.ndarray:
|
|
xa = np.fft.rfft(read_mono(os.path.join(PROJ, "hrir", a_name)), N)
|
|
xb = np.fft.rfft(read_mono(os.path.join(PROJ, "hrir", b_name)), N)
|
|
return np.abs(xa + xb) ** 2
|
|
|
|
p_merge = (merge_power("FL_L.wav", "FR_L.wav") + merge_power("FL_R.wav", "FR_R.wav")) / 2.0
|
|
eq_m = 1.0 / np.sqrt(p_merge + 1e-30)
|
|
eq_m = smooth_log(freq, eq_m, 1 / 6)
|
|
eq_m /= float(np.mean(eq_m[ref_mask])) if bool(np.any(ref_mask)) else 1.0
|
|
eq_m_db = 20 * np.log10(eq_m + 1e-30)
|
|
|
|
print()
|
|
print("③ 同相合并响应所需的均衡(左耳 = FL_L+FR_L, 右耳 = FL_R+FR_R 取平均)")
|
|
line = []
|
|
for fc in BANDS:
|
|
if fc > 12000:
|
|
break
|
|
m = (freq >= fc * 2 ** (-1 / 12)) & (freq <= fc * 2 ** (1 / 12))
|
|
v = float(np.mean(eq_m_db[m])) if bool(np.any(m)) else 0.0
|
|
line.append(f"{fc:>7.0f}Hz:{v:+5.1f}")
|
|
for i in range(0, len(line), 5):
|
|
print(" " + " ".join(line[i:i + 5]))
|
|
for lo, hi, tag in ((20, 200, "低频 20-200"), (200, 1000, "中低 200-1k"),
|
|
(1000, 3000, "中高 1k-3k"), (3000, 12000, "高频 3k-12k")):
|
|
m = (freq >= lo) & (freq <= hi)
|
|
print(f" 均值 {tag:>14}: {float(np.mean(eq_m_db[m])):+6.2f} dB")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|