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。
72 lines
4.2 KiB
Plaintext
72 lines
4.2 KiB
Plaintext
# Collaplex HRTF(双耳虚拟环绕)+ 房间混响 —— 本文件由 脚本/安装.sh 生成
|
|
# 占位符: __HRIR__ 脉冲响应目录 / __REVERB__ 房间 IR / __TARGET__ 数字输出 / __WET__ 湿量
|
|
# 链路: 全局音频 -> 虚拟声卡 -> 响度归一化 -> [本图] 双耳 HRTF -> 房间混响(湿量 __WET__) -> 数字输出
|
|
#
|
|
# HRTF: 左耳 = L·h(FL→L) + R·h(FR→L), 右耳 = L·h(FL→R) + R·h(FR→R)
|
|
# 混响: 干路直通(Gain 1 = 1.0) + 湿路经房间 IR(Gain 2 = 湿量) —— 按能量归一, 湿量 G 时湿路电平 = 干路 + 20log10(G)
|
|
context.modules = [
|
|
{ name = libpipewire-module-filter-chain
|
|
flags = [ nofail ]
|
|
args = {
|
|
node.description = "Collaplex HRTF (双耳虚拟环绕)"
|
|
media.name = "Collaplex HRTF"
|
|
filter.graph = {
|
|
nodes = [
|
|
{ type = builtin label = copy name = iL }
|
|
{ type = builtin label = copy name = iR }
|
|
{ type = builtin label = convolver name = cFLL config = { filename = "__HRIR__/FL_L.wav" } }
|
|
{ type = builtin label = convolver name = cFLR config = { filename = "__HRIR__/FL_R.wav" } }
|
|
{ type = builtin label = convolver name = cFRL config = { filename = "__HRIR__/FR_L.wav" } }
|
|
{ type = builtin label = convolver name = cFRR config = { filename = "__HRIR__/FR_R.wav" } }
|
|
{ type = builtin label = mixer name = hL control = { "Gain 1" = 1.0 "Gain 2" = 1.0 } }
|
|
{ type = builtin label = mixer name = hR control = { "Gain 1" = 1.0 "Gain 2" = 1.0 } }
|
|
{ type = builtin label = convolver name = revL config = { filename = "__REVERB__" channel = 0 gain = 0.61 } }
|
|
{ type = builtin label = convolver name = revR config = { filename = "__REVERB__" channel = 1 gain = 0.61 } }
|
|
{ type = builtin label = pipe name = wetgL config = { command = "__WET_WRAPPER__" } }
|
|
{ type = builtin label = pipe name = wetgR config = { command = "__WET_WRAPPER__" } }
|
|
{ type = builtin label = mixer name = wetL control = { "Gain 1" = 1.0 "Gain 2" = 1.0 } }
|
|
{ type = builtin label = mixer name = wetR control = { "Gain 1" = 1.0 "Gain 2" = 1.0 } }
|
|
]
|
|
links = [
|
|
{ output = "iL:Out" input = "cFLL:In" }
|
|
{ output = "iL:Out" input = "cFLR:In" }
|
|
{ output = "iR:Out" input = "cFRL:In" }
|
|
{ output = "iR:Out" input = "cFRR:In" }
|
|
{ output = "cFLL:Out" input = "hL:In 1" }
|
|
{ output = "cFRL:Out" input = "hL:In 2" }
|
|
{ output = "cFLR:Out" input = "hR:In 1" }
|
|
{ output = "cFRR:Out" input = "hR:In 2" }
|
|
{ output = "hL:Out" input = "wetL:In 1" }
|
|
{ output = "hL:Out" input = "revL:In" }
|
|
{ output = "revL:Out" input = "wetgL:In" }
|
|
{ output = "wetgL:Out" input = "wetL:In 2" }
|
|
{ output = "hR:Out" input = "wetR:In 1" }
|
|
{ output = "hR:Out" input = "revR:In" }
|
|
{ output = "revR:Out" input = "wetgR:In" }
|
|
{ output = "wetgR:Out" input = "wetR:In 2" }
|
|
]
|
|
inputs = [ "iL:In" "iR:In" ]
|
|
outputs = [ "wetL:Out" "wetR:Out" ]
|
|
}
|
|
capture.props = {
|
|
media.class = Audio/Sink
|
|
node.name = collaplex_hrtf_in
|
|
node.description = "Collaplex HRTF 接入"
|
|
audio.channels = 2
|
|
audio.position = [ FL FR ]
|
|
# 同 10-: 整条链都不能空闲休眠, 否则客户端会停推
|
|
node.always-process = true
|
|
node.pause-on-idle = false
|
|
}
|
|
playback.props = {
|
|
node.name = collaplex_hrtf_out
|
|
audio.channels = 2
|
|
audio.position = [ FL FR ]
|
|
node.target = "__EQ_IN__"
|
|
node.always-process = true
|
|
node.pause-on-idle = false
|
|
}
|
|
}
|
|
}
|
|
]
|