ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Simulink音频混响模拟:梳状滤波器原理、参数推导与调优

Simulink音频混响模拟:梳状滤波器原理、参数推导与调优

做音频算法的人大概都经历过这个阶段:一开始觉得混响不就是"加几个延迟、乘一个衰减系数",等真把它塞进 Simulink 跑起来,听到的声音却像有人在铁皮桶里敲弹簧——金属味重、尾巴发抖、低频糊成一团。我在给一个语音交互的模拟项目做前端处理时,就整整卡在这上面两天。后来才明白,问题不在代码写错,而在于梳状滤波器(Comb Filter)这套结构本身有很强的"个性",你不顺着它的物理规律调,它就用最难听的梳齿回敬你。这篇就围绕基于 Simulink 的音频与语音处理滤波器场景,把梳状滤波器在音频混响模拟中的那一整套东西摊开讲:它是什么、为什么混响要用它、参数怎么算、Simulink 里怎么搭、跑起来之后哪几个坑最要命。无论你是刚接触离散滤波器的新手,还是已经能用延迟线拼出简单效果器的老手,下面这些参数推导和排查过程应该都能直接用上。

1. 先把"混响"拆开:为什么一个延迟加一路反馈就撑起了整个模型

1.1 房间脉冲响应的两种读法

拿一支气球在空房间里炸一下,录下来的那条曲线就是房间脉冲响应。它有两种读法。第一种是时域读法:曲线开头几十毫秒内散布着一簇离散的尖峰,这些是早期反射,来自墙面、地面、天花板的第一次弹射;过了大约 50 到 80 毫秒,尖峰密到分辨不出来,连成一片缓慢下降的噪声状包络,这部分叫晚期混响。第二种是频域读法:如果把这条脉冲响应做傅里叶变换,会看到一条极其密集的、近似随机的幅度谱,峰谷之间的间隔非常窄。

这两种读法对应两种完全不同的建模思路。频域那套直接拿真实脉冲响应做卷积,音质最好但不可调;时域这套用延迟线加反馈去"生长"出那条尾巴,参数可调、计算量小,梳状滤波器就是这条路线上的第一块积木。我习惯把梳状滤波器理解成一间"只有两面平行墙"的房间:声音在两面墙之间来回弹,每次弹回来幅度乘以一个小于 1 的系数,弹一次花固定的时间。这个极简模型虽然离真实房间差得远,但它抓住了晚期混响最本质的两个特征——离散的重复周期和指数的能量衰减。

理解了这一点,后面所有参数调整就都有了物理依据。延迟时间决定"这间房子有多大",反馈增益决定"墙面吸音棉贴了多少",而不是靠耳朵瞎试。

1.2 梳状滤波器在信号链里补的到底是哪块短板

有人会问,既然卷积混响音质更好,为什么还要费劲搭梳状网络?答案在实际部署场景里。卷积混响需要一个几秒长的脉冲响应文件,做一次卷积就是几千次乘加,在资源紧张的嵌入式设备、或者需要实时调混响时间的交互式语音系统里,成本压不下来。而梳状网络的核心计算只有"取一个延迟样本、乘一个增益、加回输入",一路延迟线只占一个长度为 N 的缓冲区,N 通常在一千到两千个样本之间。

更关键的是可调性。卷积混响的混响时间是录制那一刻就定死的,想改就得换一整套脉冲响应、重新加载;梳状网络的混响时间由反馈增益一个系数直接决定,运行时实时改这个系数,尾巴长度立刻跟着变。这在语音交互模拟中特别有用——同一个场景里,用户可能从会议室走到走廊再走到大厅,混响时间需要平滑过渡,梳状网络天然支持这种连续调节。

所以梳状滤波器不是卷积混响的廉价替代品,它是另一种设计哲学:用极少的自由度去逼近混响的统计特征,牺牲的是细节真实度,换来的是可控性、低延迟和低算力。搞清楚这个定位,你就不会拿它去跟卷积混响比"像不像真房间",而是去比"参数响应快不快、听感自不自然"。

1.3 从"回声"到"混响"的临界点在哪里

单个梳状滤波器最容易翻车的地方,是延迟时间选得不对,导致听感从"混响"滑向"回声"。人耳有个大约 50 到 80 毫秒的回声感知阈值,具体数值跟声音类型有关:对 clicks 这类瞬态信号,阈值在 10 毫秒左右就能听出分离感;对持续性的语音,要 50 毫秒以上才明显。这意味着如果单路梳状的延迟设成 100 毫秒,你听到的是清清楚楚的"喂——喂——喂",而不是混响。

我一般在混响网络里把单路梳状延迟控制在 25 到 45 毫秒这个区间。短于 20 毫秒,梳齿间隔太宽(对应 50Hz 以上),会在语音的基频附近形成明显染色;长于 50 毫秒,又开始有可辨识的重复感。这个区间不是死规矩,但作为起点很可靠。还有一个必须注意的细节:多路梳状的延迟长度不能成整数倍关系,否则它们的梳齿会周期性重合,重合处能量叠加,听起来就是突兀的嗡鸣。业内常用互质(素数)长度来错开,这个做法后面会详细算。

2. 前馈梳状与反馈梳状:同一个名字,两套完全不同的听感

2.1 前馈梳状的传递函数与梳齿分布

前馈梳状(Feedforward Comb)的结构是:输入信号分成两路,一路直通,一路延迟 N 个样本后乘以增益 g,两路相加。写成差分方程是 y[n] = x[n] + g·x[n−N],对应的传递函数 H(z) = 1 + g·z⁻ᴺ。

这个式子的幅度响应很有特点。把 z 换成 e^{jω},得到 |H(e^{jω})| = |1 + g·e^{−jωN}|,展开后是 sqrt(1 + g² + 2g·cos(ωN))。当 ωN = 2πk 时 cos 取 1,幅度是 1 + g,这是峰值;当 ωN = (2k+1)π 时 cos 取 −1,幅度是 |1 − g|,这是谷值。峰谷比是 (1+g)/(1−g)。g = 0.7 时,这个比值是 5.67,约 15dB。

峰和谷在频率轴上等间隔排列,间隔是 fs/N,也就是延迟时间的倒数。这就解释了"梳状"这个名字的由来——频响曲线上一排排的齿。以 fs = 48000、N = 1200(25 毫秒)为例,齿的间隔是 40Hz,在 20kHz 带宽内会有一共 500 个齿。齿这么密,在音乐上会产生一种"金属味"的染色。

前馈梳状有个绕不开的问题:它的冲激响应只有两个脉冲——一个在 0 时刻,一个在 N 时刻,然后就没了。也就是说它只能造出"一下回声",造不出尾巴。所以在混响模拟里,前馈梳状通常不用来生成混响主体,而是用于其他用途,比如做单点延迟、做梳状滤波效果、或者跟反馈结构配合做全通。

2.2 反馈梳状的极点和指数衰减

反馈梳状(Feedback Comb)的结构是:输入先跟"延迟输出乘以 g"相加,再送进延迟线。差分方程 y[n] = x[n] + g·y[n−N],传递函数 H(z) = 1/(1 − g·z⁻ᴺ)。

这一改,性质完全变了。它的冲激响应是 g⁰、g¹、g²、g³……每隔 N 个样本出现一个,幅度按几何级数衰减,一直延伸到无穷。这才是有"尾巴"的结构。

关键在于极点位置。分母 1 − g·z⁻ᴺ = 0 意味着 zᴺ = g,也就是 N 个极点在复平面上均匀分布在半径 g^(1/N) 的圆上。注意这个细节:极点的模是 g 的 N 次方根,不是 g 本身。g = 0.85、N = 1200 时,极点模长是 0.85^(1/1200) ≈ 0.99986,非常靠近单位圆。这解释了为什么反馈梳状对增益变化如此敏感——g 从 0.85 涨到 0.9,极点模长从 0.99986 涨到 0.99991,看着差别极小,但对应的时间常数变化很大。

关于会不会发散:只要 |g| < 1,所有极点严格落在单位圆内,系统就收敛。g 越接近 1,衰减越慢,尾巴越长。这个界限是硬性的,但在实际实现里,如果反馈路径里还有别的滤波器(后面要讲的阻尼低通、全通),它们的增益会跟 g 相乘,总环路增益可能超出预期,那就真的要炸了。这一点后面单独说。

2.3 一张表看清两者的取舍

对比维度前馈梳状 1 + g·z⁻ᴺ反馈梳状 1/(1 − g·z⁻ᴺ)
冲激响应长度2 个脉冲无限长
峰谷比(1+g)/(1−g)(1+g)/(1−g)
能否造出混响尾巴不能能
对增益的敏感度线性,很温和指数,极其敏感
极点位置无极点,只有 N 个零点N 个极点,模长 g^(1/N)
主要用途单点延迟、梳状染色效果、结构组件混响网络的混响主体

这张表是我自己在选型时反复对照的。要造混响,主体必然是反馈梳状;前馈梳状更多是配角。但有一点容易被忽视:两者在频响上的梳齿形状是一样的,峰谷比公式完全一致。区别只在于群延迟——前馈结构的相位响应比较平缓,反馈结构的相位在极点附近变化剧烈,这也是反馈混响听起来比前馈"厚重"的原因之一。

还有一个实用的推论:既然峰谷比是 (1+g)/(1−g),那 g 大到一定程度,某些频率会被严重衰减。g = 0.9 时峰谷比是 19,约 25.6dB。如果单路梳状单独听,会出现明显的"某些频段被吃掉了"的感觉。这就是为什么单个梳状滤波器永远不够,必须靠多路并联把彼此的谷填上——下一节展开。

3. 在 Simulink 里把反馈梳状搭起来(含完整参数推导)

3.1 采样时间与信号维度的前置决定

搭模型之前必须先定一个事:信号是按单个样本传,还是按帧传。这个决定看着不起眼,实际上是我踩过最大的一个坑。

Simulink 里的 Delay 模块,延迟长度是按输入信号的样本维度算的。如果信号是一个标量(每步一个样本),设 1501 就是延迟 1501 个样本;但如果信号是一个 1024 维的帧(很多音频模块默认输出这种格式),Delay 模块设 1501 就变成了延迟 1501 帧,也就是 1501 × 1024 = 1537024 个样本,约 32 秒。我第一次搭的时候就是这个结果,等了半天只见输出迟迟不来,还以为是反馈回路卡住了。

排查方法是打开 Signal Attributes 看一眼信号维度。解决办法有两条:

  • 方案一:在信号源后面不接 Buffer,直接让每一时刻只有 1 个样本。Sine Wave 模块默认输出标量,From Workspace 也可以配成标量。这样做最干净,Delay 模块的参数就是你想要的样本数。
  • 方案二:如果确实要用帧信号(为了提升仿真效率),就不能用普通 Delay 模块,得在自己的 MATLAB Function 块里写环形缓冲区,或者用 "延迟 + 缓冲组合" 把帧拆开。

我一般先用方案一把结构跑通,把参数调对了,再考虑性能优化。因为方案一的仿真是逐步进行的,48kHz 采样率下跑 10 秒就是 48 万步,速度确实慢,但胜在每步都能看波形、能随时暂停检查内部信号,调试阶段这点比速度重要得多。

求解器配置也要跟着改。在 Model Settings 里把 Solver 设成 Fixed-step,Solver 选 discrete (no continuous states),Fixed-step size 填 1/48000。用变步长求解器跑离散反馈回路,会出现"过零检测"反复触发的问题,仿真速度掉到几乎不可用。

3.2 Delay-Sum-Gain 回路的搭建与代数环

反馈梳状的 Simulink 拓扑其实非常短:

输入 → Sum(+) → Delay(length = N) → 输出 ↑ | └──── Gain(g) ←────────┘

具体接法:一个 Sum 模块配两个输入端口,符号设成 "++"(或者 "+-" 看你从哪边取反馈)。Sum 的输出接 Delay 模块的输入,Delay 的输出分两路,一路作为整个系统的输出,一路经过 Gain 模块回到 Sum 的第二个输入。

这里有个经典问题:这个结构看起来像代数环,因为 Delay 的输出回到了它自己的输入路径上。实际上不是——Delay 模块本身会断开代数环,因为它在当前步的输出只取决于历史步的输入,不依赖当前步。所以 Simulink 不会报代数环错误。

但有两个变种会真的触发代数环报警:

  • 把 Delay 换成 Gain 或者 Sum,也就是纯组合逻辑构成回路,那就死循环了。
  • 在反馈路径上误加了 Direct Feedthrough 的模块(比如某些自定义的 S-Function),打破了"输出不依赖当前输入"这个前提。

我遇到过一次更隐蔽的:Delay 模块的延迟长度被设成了 0。有些版本的 Delay 模块允许这么设,设成 0 之后就退化成直通,代数环立刻出现。所以延迟长度至少要是 1,实际用的时候都是上千,这个坑只会出现在刚开始搭骨架验证结构的时候。

3.3 延迟样本数与反馈增益的联动计算

这一段是整篇文章最值得抄走的部分。很多人调混响就是试参数,其实这两个数之间有精确的换算关系。

先推导。反馈梳状每循环一次,经过的时间是 N·Ts(Ts 是采样周期),幅度乘以 g。经过 k 次循环,幅度是 g^k,时间经过 k·N·Ts。所谓 RT60,就是幅度衰减到初始值的千分之一(−60dB)所需的时间。令 g^k = 10^(−3),两边取对数得 k = −3/log₁₀(g),因为 log₁₀(g) 是负数,可以写成 k = 3/log₁₀(1/g)。

代入 RT60 = k·N·Ts,得到:

RT60 = 3·N·Ts / log₁₀(1/g)

反过来解 g:

g = 10^(−3·N·Ts/RT60)

这个反解公式是实际搭模型时用的那个。给定想要的混响时间和延迟长度,直接算出增益,不用试。

举个具体例子。fs = 48000,Ts = 1/48000 ≈ 20.83 微秒。选四个延迟长度 1501、1627、1753、1879 个样本,对应的物理延迟分别是 31.27、33.90、36.52、39.15 毫秒。这四个数都是我特意挑的素数,互不成整数倍关系,梳齿不会重合。目标是 RT60 = 1.4 秒。

计算过程写成 MATLAB 代码:

fs = 48000; Ts = 1/fs; N = [1501 1627 1753 1879]; RT60 = 1.4; g = 10.^(-3 * N * Ts / RT60); fprintf('%d 样本 -> g = %.4f\n', [N; g]);

跑出来的结果是 0.8570、0.8461、0.8351、0.8242。

注意这个结果里最重要的信息:四路梳状的目标混响时间相同,但增益各不相同,延迟越长增益越小。这是很多人忽略的点。如果四路都用同一个 g,那延迟长的支路衰减慢、延迟短的支路衰减快,最后整体包络会变成双段衰减,听起来头部一阵密、尾部一阵稀,很不自然。按公式分别算,才能让四条尾巴以同一速率消退。

还有一点:四个数这么接近(0.857 到 0.824),说明在实际工程里对增益做微小调整,混响时间就能明显变化。g 每变化 0.01,RT60 大约变化 10% 到 15%。这也是为什么调参时不能用鼠标拖 Gain 模块的数字框,要用代码或变量统一算,不然手一抖就过冲了。

3.4 反馈支路里的阻尼低通

纯反馈梳状有个物理上不真实的毛病:所有频率以同样的速率衰减。真实房间里,高频被空气吸收、被软性表面吸收,衰减得比低频快得多。这个差异正是"混响听起来有空间感"的关键之一。用纯梳状网络做出来的混响,尾巴是"白"的,所有频率一起消失,听感偏硬、偏电子。

解决办法是在反馈回路里串一个一阶低通。差分方程是 y[n] = (1−d)·x[n] + d·y[n−1],其中 d 在 0 到 1 之间。d 越大,截止频率越低,高频衰减越厉害。

截止频率的近似换算:fc ≈ fs·(1−d)/(2π·d)。d = 0.5 时 fc ≈ 7640Hz,d = 0.7 时 fc ≈ 3270Hz,d = 0.9 时 fc ≈ 850Hz。做语音场景的室内混响,d 取 0.4 到 0.6 之间比较合适;做大厅,可以到 0.7。

但这里有个必须处理的问题:这个低通的直流增益是 1/(1−d),不是 1。d = 0.6 时,直流增益是 2.5。这意味着反馈回路的总增益变成了 g × 2.5,原本算好的 0.85 一下子变成 2.1,立刻发散。

两个修法。一是把低通写成归一化形式 y[n] = (1−d)·x[n] + d·y[n−1],然后在 Gain 模块里把 g 提前除以 (1−d) 的补偿……等等,这样会让直流增益变回 1 但有效反馈增益也变了。更干净的做法是把低通直接写成y[n] = a·x[n] + (1−a)·y[n−1],其中 a 在 0 到 1 之间,这个形式直流增益严格等于 1,a 越小截止频率越低。

关系是 fc ≈ fs·a/(2π·(1−a))。a = 0.5 时 fc ≈ 7640Hz,a = 0.3 时 fc ≈ 3270Hz,a = 0.1 时 fc ≈ 850Hz。跟前面那个 d 表述正好是互补的(a = 1−d),但归一化后的行为完全不同,不容易搞混。

加入阻尼后,实际 RT60 会跟公式算的略有偏差,因为低频和高频的衰减速率不一样了。我一般用中频段(500Hz 到 2kHz)测出来的衰减斜率当作标称 RT60,这样跟公式对得上。

4. 从单路梳状到完整混响网络:并联、串联与全通

4.1 为什么单路梳状必然"金属味"

单路反馈梳状最突出的听感问题是"金属味"(metallic ringing)。原因不复杂:它的冲激响应是一串等间隔、等比衰减的脉冲,这在频域上就是一组等间隔的谐波。用信号处理的说法,它的频谱是一把梳子;用听觉的说法,它把任何输入都染上了一个固定音高色彩,音高等于 1/(N·Ts)。

以 N = 1501、fs = 48000 算,这个重复频率是 31.98Hz。这个频率本身听不见,但它的各次谐波覆盖了整个可听频段,而且在每个谐波位置都有能量堆积。输入一个宽带噪声,输出听起来像是有个 32Hz 的低频在"嗡嗡"地调制,这就是金属味。

要打散这个规律性,思路有两条:一是多路并联,让各路梳齿错位、互相填充;二是加调制,让延迟长度随时间微小变化,把固定音高糊掉。前者是基础,后者是进阶。

判断金属味有没有缓解,有个简单的量化指标:看脉冲响应的回声密度。理想混响的脉冲响应在两个时间点上,回声数量的期望随时间的平方增长(因为房间里的反射路径数随时间增加);单路梳状的回声数是严格线性增长的(每个延迟周期增加一个)。并联多路后,回声数变成各路线性增长之和,密度上来了,但因为各路延迟不成整数倍,重合点随机化,听感就散了。

4.2 四并两串的具体参数表

经典的组合方式是四路反馈梳状并联,后面串两到四个全通滤波器。这是我用的那套参数,全是 48kHz 下的样本数:

环节延迟样本数物理延迟反馈增益(RT60=1.4s)备注
梳状 1150131.27 ms0.8570素数长度
梳状 2162733.90 ms0.8461素数长度
梳状 3175336.52 ms0.8351素数长度
梳状 4187939.15 ms0.8242素数长度
全通 12254.69 ms0.50梳状输出后串联
全通 255611.58 ms0.50全通 1 输出后串联

并联的四路输出先相加,再乘以 0.25 做归一化,然后进全通链。归一化这一步不能省,四路信号的功率叠加会让电平涨到大约 2 倍(相干处更高),后面稍微一放大就削波。

全通滤波器在这里的结构跟前馈梳状有点像,但增益符号反了。它的差分方程是 y[n] = −g·x[n] + x[n−N] + g·y[n−N],传递函数 H(z) = (−g + z⁻ᴺ)/(1 − g·z⁻ᴺ)。分子分母的模在单位圆上相等,所以幅度响应平坦——不管频率多少,幅度都是 1。它只改相位,不改幅度谱。

在 Simulink 里搭这个全通,需要两个 Sum 模块、一个 Delay、一个 Gain(值是 −g 那个分支)和一路直通。具体接法:

x → Sum1(−, +) → Sum2(+) → Delay(N) → ×g → Sum2 第二输入 ↑ | └──── x[n] 直通 ───────┘

稍微绕。更简单的办法是把全通也看成一组方程,直接在 MATLAB Function 块里写环形缓冲区实现。我搭过一次模块化的全通子系统,封装好之后当黑盒用,省了很多连线的麻烦。

全通的作用是增加回声密度而不改变频谱。梳状网络出来的信号,回声虽然在时间上散了,但每个回声之间还是有明显的空隙感;串上两个全通之后,每个回声被展开成一小簇,密度上去了,听感立刻从"数字味"变成"空间感"。

4.3 为什么全通的增益必须和梳状分开算

这里有一个很容易犯的错误:把全通的增益也按 RT60 公式算。不行。

全通结构的环路增益是 g,但因为它分子分母同构,幅度响应恒为 1,对整个系统的能量衰减贡献不是简单的指数形式。它主要影响的是瞬态密度,不是包络斜率。所以全通的 g 取固定值就行,0.5 是常用值,取到 0.7 会开始有轻微的"喷气"感(因为全通的群延迟在低频处很大),取 0.3 以下效果就不明显了。

真正决定整体 RT60 的是那四路并联梳状。测出来的 RT60 应该跟设定值吻合,如果不吻合,先检查是不是某一路的反馈增益算错了,或者阻尼低通的归一化有没有做对。

5. 实测排查:几个把我卡了大半天的现象

5.1 帧信号把 Delay 模块的延迟放大了一千多倍

这个坑前面提过,值得单独展开,因为它太容易发生、排查起来又太不直观。

现象是这样的:模型跑起来,输出端只有开头一点点信号,后面全是静默;或者反过来,输入已经停了很久,输出还在持续出声音。用 Scope 看延迟模块内部,缓冲区里存的是一整块 1024 点的数据,而不是单个样本。

排查链路我总结成三步:

第一步,看信号维度。在 Delay 模块的输入线上加一个 Display 模块,或者打开信号属性,看维度是 [1×1] 还是 [1024×1]。这是最直接的验证。

第二步,看延迟模块的参数含义。Simulink 的 Delay 模块,"Delay length" 这个参数在帮助文档里写得很清楚,单位是"采样时间"而不是"样本数"。当信号的采样时间不是 1/fs 的时候,这两个概念就脱节了。帧信号的采样时间是帧周期,一个帧周期里包含 1024 个样本,所以延迟 1501 个采样时间就是 1501 × 1024 个样本。

第三步,确认上游模块。很多音频输入模块(包括一些文件读取模块)默认输出帧信号,因为帧处理对块运算更友好。想让它输出标量,得改 Buffer 设置,或者中间插一个 Unbuffer 模块(信号处理工具箱里有)。

我的经验是:只要是做样本级延迟的算法(梳状、全通、延迟线混响都属于这一类),一律在信号链最前面把帧拆成标量,后面就不需要再操心这个问题了。代价是仿真变慢,但正确性优先。

5.2 输出削波和直流累积

削波的表现很直观:输出波形被削平在 ±1 的位置,听感上是刺耳的失真,而且是那种"破碎"的失真,不是模拟电路过载的柔和饱和。原因是混响是能量累积型处理,稳态增益可以远超 1。四路并联梳状虽然每路反馈增益小于 1,但四路叠加之后,在梳齿重合处的瞬时增益可能达到几倍。

处理办法是归一化:并联输出除以路数。我在 4.2 的表里写的 0.25 就是这个。更保守一点可以用 0.2,留 6dB 的余量。另外在系统输出端串一个限幅器(Saturation 模块,上下限设 ±0.98)作为最后一道防线,防止极端输入把后面的设备冲坏。

直流累积是另一个隐蔽的问题。反馈回路对直流分量的增益是 g/(1−g),g = 0.85 时是 5.67 倍,g = 0.9 时是 9 倍。只要输入里有一点点直流偏移(比如某些信号源开机瞬间的偏置),就会在回路里被反复放大,最后把整个可用动态范围吃掉,表现为输出缓慢漂移到一边。

解决办法是在反馈梳状的输入端串一个高通。最简单的一阶高通:y[n] = a·(y[n−1] + x[n] − x[n−1]),a 取 0.995 左右,截止频率大约几个赫兹,对音频内容几乎没有影响,但能把直流彻底压住。我一般把这个高通放在整个混响网络的最前面,一路就够,不用每路都加。

5.3 梳齿太尖、尾巴发抖、低频糊成一坨

这三个现象是调参阶段最常遇到的,对应的原因各不相同。

梳齿太尖,听感上是明显的音高染色,输入什么都有同一个"调"。原因是并联路数不够,或者延迟长度凑得太近。检查方法:把四路延迟值列出来,看有没有公约数。1501、1627、1753、1879 这四个数都是素数,最大公约数是 1,安全。如果用了 1500、1600、1700 这种整数,它们的最小公倍数是 13600,意味着每 13600 个样本(283 毫秒)就会出现一次四路对齐的强重合,这就是听到的"重影"。改动方案:全部换成附近的素数。

尾巴发抖,听感上是混响尾音有周期性的音量起伏,像有人在快速推拉音量旋钮。原因通常是反馈增益太高,环路处于临界状态附近,数值误差被放大成可听的振荡。检查方法:看 RT60 实测值,如果比设定值长很多(比如设定 1.4 秒、实测 3 秒以上),说明有效增益超了。修法是把 g 乘以 0.95 降一点,或者检查阻尼低通的归一化有没有做对。

低频糊成一坨,听感上是低频拖得很长、糊在一切上面,中高频却消失得很快。这是阻尼低通设得太重(a 太小)的典型表现,因为低通让高频衰减快、低频衰减慢,反差被放大了。检查方法:把阻尼旁路掉跑一遍,如果低频恢复正常,就是阻尼的问题;把 a 从 0.4 提到 0.6 试试。另一个可能是输入信号本身低频能量太强,可以在输入端加个 100Hz 以下的高通。

6. 再往前走一步:时变调制与卷积方案的取舍

6.1 给延迟做微小的时变调制

梳状网络还有一个顽固问题:即使四路并联、全通加密,长时间听还是能感觉到一个固定的"音高感",尤其在处理持续音(比如长音的人声、弦乐)的时候。这是因为整个网络的延迟长度是恒定的,梳齿位置固定。

解决办法是让延迟长度随时间做极其缓慢、幅度极小的变化。常见做法是用一个 0.5 到 2Hz 的低频正弦,幅度在 ±3 到 ±8 个样本之间,去调制每一路梳状的延迟长度。幅度必须小,大了会变成明显的"颤音"效果;频率必须低,高了会觉得声音在抖。

在 Simulink 里实现时变延迟,不能用普通的 Delay 模块,因为它只支持固定长度。三个选择:

一是用 Variable Integer Delay 模块,延迟端口接受一个整数输入。这个模块能直接接正弦调制,但要求延迟值在每一步都是非负整数,所以调制信号要先经过取整和限幅。取整会引入量化台阶,在慢速调制下可能出现阶梯感的噪声。

二是分数延迟滤波器,用拉格朗日插值或 Thiran 全通来做亚样本精度的延迟。音质最好,但计算量大,每一路要好几次乘加。Thiran 全通的做法是把一个一阶全通串在延迟后面,系数随目标延迟小数部分变化,能实现平滑的连续延迟。

三是自己写 MATLAB Function 块,用环形缓冲区加线性插值。灵活性最高,也最容易出错,插值系数的边界处理需要格外小心。

我自己的偏好是第二条:音质和复杂度的平衡最好。给四路梳状各配一个低频调制源,相位错开(比如 0°、90°、180°、270°),这样四路的调制不会同步,避免整体音高一起漂。

调制会带来一个副作用:RT60 的实测值会略微变化,因为有效延迟长度在变。变化幅度一般在 5% 以内,听感上无感。

6.2 什么时候该彻底放弃梳状网络改用卷积

我需要说一句实话:如果项目对混响的真实度要求高(比如做音乐制作工具、影视后期),梳状网络再怎么调也追不上卷积。卷积直接拿真实空间的脉冲响应,所有早期反射、频率相关的衰减、扩散特性都是现成的,梳状网络的参数化模型在物理上就表达不了这些细节。

判断该用哪种,看三个条件:

第一个是实时可调性要求。如果用户需要在运行中连续改变混响时间、房间大小、阻尼,梳状网络几乎是唯一选择,因为卷积方案的参数是"烧"在脉冲响应里的。当然也有折中方案——预生成多套脉冲响应然后做插值,但那个复杂度和内存占用会迅速失控。

第二个是算力预算。梳状网络每路延迟线每样本只做一次乘加,四路加两个全通,总共也就十几次乘加;卷积混响做 2 秒的脉冲响应,每样本要做十万次乘加。相差四个数量级。嵌入式场景基本没得选。

第三个是混响的"功能性"还是"艺术性"。如果混响只是为了模拟"这个声音是在某个空间里录的",或者为了让语音交互系统在不同房间条件下都表现一致,那功能性优先,梳状网络的可控性更重要。如果混响本身是创作的一部分,那真实度优先,上卷积。

还有一个我很少见人提但很实际的判断依据:你的信号是什么。语音信号的能量集中在 300Hz 到 4kHz,而且有很强的时变结构,梳状网络在这个频段的表现完全可以接受,参数化带来的规律性也被语音本身的非平稳性掩盖了。音乐信号宽带、持续,梳状网络的固定音高染色就很容易暴露。所以做语音处理的项目,用梳状网络搭混响是性价比很高的选择;做音乐,就得掂量一下。

我在那个语音交互项目里最后就是用的梳状方案,四并两串加低频调制,跑在一块算力有限的板子上,实时性毫无压力。后来做过一次对比测试,同一段语音分别过梳状网络和卷积混响,让几个同事盲听,在"像不像在房间里说话"这个维度上评价差异不大;但在"长时间听的疲劳感"上,卷积确实更舒服。所以怎么选,还得看你的实际场景更在乎哪个。

最后分享一个小技巧:调梳状混响的时候,别用音乐当测试信号,用一段有明显停顿的语音。语音的停顿处会暴露出混响尾巴的所有问题——金属味、抖动、低频堆积,在连续音乐里全被掩盖了。我每次改完参数,都会用同一段带停顿的语音听三遍,比看频谱图管用得多。

返回列表