基于梳状滤波器的混响算法Simulink建模与调参实践

发布时间:2026/11/4 14:51:22
基于梳状滤波器的混响算法Simulink建模与调参实践

1. 先把混响这层窗户纸捅破:梳状滤波器为什么能模拟空间

音频混响模拟这件事,说穿了就是在跟一串脉冲打交道。你在一个真实房间里拍一下手,麦克风收到的不是一声干脆的"啪",而是直达声之后跟着几十上百个越来越密、越来越弱的反射声,最后糊成一片尾巴。做算法的人要把这条尾巴造出来,最省钱也最经典的办法,就是拿梳状滤波器(Comb Filter)当积木。我在几个音频算法项目里反复用过这套结构,它便宜、可解释、参数少,而且放到 Simulink 里做验证特别顺手——你可以一边跑仿真一边实时听,调一个增益立刻知道听感变了多少。这篇文章面向的是手上有信号处理基础、想动手把混响算法跑起来的人,不管你是做音频插件、游戏音频中间件、车载音响调音,还是做空间音频和声学仿真的,这套思路都能直接搬过去用。

1.1 房间脉冲响应拆开来看是什么

任何一个线性时不变的空间,它的声音特征都浓缩在房间脉冲响应(Room Impulse Response,RIR)里。这条响应可以粗暴地切成三段:第一段是直达声,就是声源到耳朵最短路径那一下,通常在几毫秒内;第二段是早期反射,来自墙面、地面、天花板、桌面这些大平面的首次反射,时间大概在 5 毫秒到 80 毫秒之间,它们是离散的、可分辨的几根尖峰,人耳靠这一段判断房间大小和声源方位;第三段是晚期混响,反射次数多到无法分辨单根,能量呈指数衰减,形成一条连续的"雾"。

人耳对这三段的敏感度完全不同。早期反射决定"空间感"和"清晰度",晚期混响决定"空间大小"和"距离感"。所以做混响算法,本质是分别建模这三段:预延迟负责把直达声和混响尾巴拉开距离,抽头延迟线负责堆早期反射,而梳状滤波器加全通滤波器这一套,专门负责生成那条指数衰减的晚期尾巴。把复杂问题拆成三段分别处理,比用一个巨型卷积去硬扛整条 RIR,可控性高得多,这也是我为什么一直推荐从 Schroeder 结构入门的原因——它的每一个参数都对应一个物理直觉,调起来心里有数。

1.2 一个延迟加一个反馈,就长出了指数衰减的尾巴

反馈型梳状滤波器的差分方程只有一行:

y[n] = x[n] + g * y[n - M]

M是延迟长度(样本数),g是反馈增益。它的意思非常朴素:把输出延迟 M 个样本,乘上一个小于 1 的系数,再叠回输入。你喂一个脉冲进去,它会吐出1, 0, 0, ..., g, 0, 0, ..., g², ...这样一串等间隔、按几何级数衰减的脉冲。这串东西在时域上看着很"假",但如果间隔足够短、数量足够多,人耳就分辨不出单根脉冲,只会感知成一条平滑的衰减尾巴——这正是混响晚期的数学本质。

我经常用生活里的事物打比方:这就像一个人在两面平行的光滑墙壁之间拍球,球每次撞墙回来都比上一次低一点,频率固定、高度递减。真实房间不会只有两面墙,所以单个梳状滤波器听起来是"嗡嗡"的、有明显的音高感,业内管这叫"金属声"或者"管风琴效应"。解决思路不是把单个滤波器做得更复杂,而是并联多个延迟长度互不相同的梳状滤波器,让各自的周期互相错开,脉冲密度叠起来就自然了。这个"多个并联"的动作,是整个混响算法里性价比最高的一步。

1.3 为什么我先在Simulink里搭而不是直接写C

很多人上来就写 C 或者用现成的音频框架,跑起来有问题再去猜。我不太推荐这种做法,尤其是在你还没把参数和听感的对应关系摸清楚之前。Simulink 的价值在于,它把信号流可视化了:延迟块、增益块、求和块、低通块摆在那里,信号从哪来、到哪里去、在哪里反馈,一眼看得见。调试的时候我可以直接拿示波器(Scope)或者频谱仪(Spectrum Analyzer)挂上去,看反馈环是不是发散、看频响上的梳齿有没有按预期排列,比在代码里打断点要快得多。

还有一个实际原因:Simulink 的 Delay 块天生带状态,天然能处理跨帧的延迟样本。音频处理里经常按帧处理,一帧 256 或 1024 个样本,而延迟长度动辄上千样本,跨帧是必然的。手写 C 的时候如果没处理好帧间状态保持,就会出现每帧开头爆一下的"咔哒"声,这种 bug 极难定位。用 Simulink 先验证算法、确定参数,再去做定点化和手写实现,这个流程我走过很多次,仿真阶段多花的两小时,通常能在后期省掉两天的排查。

2. 梳状滤波器的数学骨架与参数推导

动手连块之前,得先把几个参数算明白。混响算法调参最忌讳"凭感觉拧旋钮",因为你拧的不只是一个数字,而是背后的一整条频率响应。这个章节我把关键推导和选取原则摆出来,后面搭模型的时候直接代进去就行。

2.1 前馈型与反馈型的频率响应差异

梳状滤波器有两种接法,名字很容易混:

类型差分方程传递函数频响特征在混响里的角色
前馈型(FIR)y[n] = x[n] + g·x[n-M]H(z) = 1 + g·z^-M周期性凹陷,H
反馈型(IIR)y[n] = x[n] + g·y[n-M]H(z) = 1/(1 - g·z^-M)周期性尖峰,无限长脉冲响应做晚期混响主体

前馈型是有限长的,脉冲响应只有两个尖峰,它产生的是"凹陷"而不是"尾巴",适合模拟那几根早期反射,或者做电话音、机器人音的梳状染色效果。真正的混响尾巴必须用反馈型,因为只有无限长的几何衰减才符合晚期混响的物理规律。反馈型的频响是周期性的极点,在频率f = k·fs/M处出现尖峰,间隔为fs/M。这个间隔很关键:M 越大,梳齿越密,音高感越弱,但衰减也越慢;M 越小,梳齿越稀,金属味越重。

顺带提一句稳定性。反馈型的极点在z^M = g,稳定条件就一条:|g| < 1。看起来简单,但实际模型里反馈路径上还挂了一阶低通(阻尼滤波器),那个低通在直流处的增益是 1,理论上不影响稳定性,可如果它的系数写错了,或者某个中间环节误加了增益,整个环就发散。我在调试期一律把 Scope 挂在输出上,先跑一段白噪声看包络,确认不发散再上耳朵听。

2.2 从RT60反推反馈增益g

真正实用的地方在这里:混响时间 RT60 是唯一一个人能直接说出口的听感指标——"这个厅大概两秒的混响"。而g是模型参数。两者之间的换算必须搞明白。

推导过程不复杂。反馈梳状滤波器的脉冲响应第 k 个脉冲的幅度是g^k,它出现在时刻k·M/fs秒。RT60 的定义是声能衰减 60 dB,也就是幅度衰减到 10^-3 的时间。设这段时间内脉冲重复了 N 次,那么:

g^N = 0.001 → N · log10(g) = -3 N = RT60 · fs / M

代入消掉 N,得到:

g = 10^(-3M / (RT60 · fs))

这个公式我建议直接写成脚本,因为你要为 8 个不同的 M 各算一次 g,手算必然出错。下面这段是我常用的参数生成脚本,采样率按 48 kHz、目标 RT60 按 2 秒来:

fs = 48000; rt60 = 2.0; base_M = [1116 1188 1277 1356 1422 1491 1557 1617]; % 44.1k下的经典长度 M = round(base_M * fs / 44100); % 按采样率比例缩放 g = 10.^(-3 * M ./ (rt60 * fs)); disp(table(M.', g.', 'VariableNames', {'DelaySamples','FeedbackGain'}));

跑出来的 g 大概在 0.90 到 0.92 之间。这里有个容易踩的坑:RT60 这个指标对"并联结构"并不精确成立。8 个梳状滤波器并联之后,它们的尾巴互相叠加,实际听感衰减会比单个算出来的慢一点。我的经验是把目标 RT60 按 0.7 到 0.8 倍去代公式,再靠耳朵微调,比死磕公式准得多。而且g对听感极度敏感,0.90 和 0.93 的差别可能就是从"房间"到"大教堂",调的时候务必小步走,一次改 0.01。

2.3 延迟长度的选取:互质原则与密度感

延迟长度的排布直接决定了混响的"密度感"和"金属味"。这里有两条我一直在用的经验。

第一条,避免整数倍关系。如果八个延迟长度是 1000、2000、3000 这种,它们的脉冲会在同一时刻重合,叠加起来还是稀疏的,等于白并联。理想的排布是让各路的回波尽量错开,业界常用一组看似随机、实则精心挑选的数。上面脚本里那 8 个数字就是这么来的,最小 1116、最大 1617,比例接近但互不整除,回波重合的概率很低。

第二条,总时长要够。延迟太短(比如 M 小于 500 样本,对应 10 毫秒出头),回波间隔太密,会形成强烈的音高感,听起来像弹簧混响或者金属管。延迟太长(M 大于 3000 样本,60 毫秒以上),回波开始能被单独分辨,混响就变成了"回声"(Echo),不再是一体的。经验的舒适区在20 毫秒到 50 毫秒之间,对应 48 kHz 下的 960 到 2400 个样本。八个梳状滤波器的延迟值散布在这个区间内、彼此无序,密度感最好。

另外提一句,延迟长度最好别选 2 的整数次幂。有些实现图省事用 1024、2048 这种数,结果在频域上极点和 FFT 的频点重合,测试的时候会看到异常的频谱尖刺,自己给自己制造困扰。

2.4 阻尼低通的插入位置与截止频率

真实的房间里,高频比低频衰减得快,因为空气吸收和墙面软性材料的吸声系数在高频段更高。这个现象如果不在模型里体现,混响会显得"亮"而"硬",听起来像金属盒子而不是房间。

实现方式很简单:在反馈路径里串一个一阶低通。形式是:

y_lp[n] = (1 - d) · y[n] + d · y_lp[n-1]

d是阻尼系数,0 到 1 之间。d越大,低通截止越低,高频衰减越快。它的位置必须在反馈环内部,也就是每次循环都滤一次,这样高频成分在多次循环中被反复削弱,形成自然的频率相关衰减。如果你把低通放在环外,只在输出端滤一次,那只能改变音色,改不了衰减行为,这是个很常见的实现错误。

截止频率的选取取决于你想模拟的空间材质。粗略地讲,d取 0.2 到 0.4 是比较通用的范围,对应高频比中频早衰减 20% 到 40%。我一般把d做成一个可调参数,调的时候先设 0,听"金属盒子",然后慢慢往上加,加到金属味刚好消失、房间感出来为止,通常在 0.25 附近。

这里有个隐藏的稳定性细节:一阶低通的直流增益是 1,但它的峰值增益在直流处,环路总增益会略微提升。为了绝对安全,我会把反馈增益再乘一个 0.995 左右的余量系数,或者干脆把低通写成严格的无增益形式(1-d)·x + d·y,系数和恒为 1,不可能放大信号。

3. Simulink模型搭建全流程

理论聊得差不多了,开始连线。这一章我把整个模型从顶层到子系统一层层拆开,参数全部给到具体数值,你可以照着复现。

3.1 顶层信号流与采样率设定

先规划顶层,别一上来就堆块。我的模型顶层分五个区域,信号从左到右流动:

  1. 输入源:三种可选——Audio File Reader读一段干声素材、Audio Device Reader接麦克风实时输入、或者Signal From Workspace灌一段测试噪声。
  2. 预延迟:一个纯延迟块,PreDelay参数,典型 20 到 40 毫秒。
  3. 混响核心:并联梳状组 + 串联全通组的复合子系统。
  4. 干湿混合:两路增益相加。
  5. 输出:Audio Device Writer实时听,或者To Workspace存下来做客观分析。

采样率在模型里统一设成 48000,Model Configuration Parameters里的Fixed-step size设成1/48000,求解器用discrete (no continuous states)。这一步很重要,因为延迟块的长度是按样本数算的,采样率一变,所有 M 都要重算。我习惯在模型初始化回调(InitFcn)里用脚本自动算好参数推到工作区,避免每次改采样率都手动改一堆块。

注意:音频块的帧长(Samples per frame)要和延迟块的处理方式匹配。我一般设 256,实时性好、延迟低;做离线分析时设 1024 或更大,跑得快。

3.2 单个反馈梳状子系统的封装

八个梳状滤波器结构完全一样、只是参数不同,务必做成一个 Subsystem 然后复制或者用 For Each 子系统,别手搭八遍。子系统内部只有四个块:

  • Input端口进Sum块
  • Sum输出一分两路:一路去Delay,一路去输出
  • Delay输出乘Gain(就是g)
  • 乘完之后进阻尼低通,低通输出回到Sum

这里有几个必须注意的点。第一,Delay 块不能用Unit Delay,因为它只能延迟一个样本;要用Delay块(Discrete 库里的)或者 DSP System Toolbox 里的Integer Delay,长度设为 M。第二,反馈环天然存在代数环的风险,但因为环内有一个样本以上的延迟,Simulink 能自动解算,不会报错;如果你的 M 不小心设成了 0,立刻会报代数环,这也算一个免费的自检机制。第三,延迟块的初始条件设 0,避免仿真开头爆音。

如果你想让延迟长度可调(做参数扫描时很有用),把Delay换成Variable Integer Delay,把 M 从工作区喂进去。但要注意这个块在反馈环路里会拖慢仿真速度,做最终验证时换回固定延迟。子系统参数用 Mask 封装,把M、g、d做成三个可填的编辑框,这样复制八个实例只需要填三次数字,效率高很多。

3.3 并联梳状组与串联全通组

八个梳状子系统的输出全部接到一个Sum块(符号全正),然后除以 8 做归一化——这一步不能省,否则八路叠加后的幅度是单路的八倍,直接削顶。除以 8 之后信号进全通组。

全通滤波器的作用是打散周期性。梳状滤波器的回波虽然长度不同,但仍然是有规律的,叠加后容易在频谱上留下等间隔的尖峰,人耳感知为"金属味"。全通滤波器的妙处在于它不改变幅度响应,只改变相位,把时间上的规律性搅乱,让回波分布更自然。差分方程是:

y[n] = -g·x[n] + x[n-M] + g·y[n-M]

串联三到四个就够,不同的 M 依次递减,常用的比例是 1 : 0.8 : 0.6 左右。全通里的g一般取 0.5 上下,取太大相位扰动过强,声音会发飘。

用一个MATLAB Function块实现全通最省事,代码短、可读性好,也方便后面转 C:

function y = allpass(x, g, M, fs) %#codegen persistent buf idx if isempty(buf) buf = zeros(M, 1); idx = 1; end delayed = buf(idx); y = -g * x + delayed + g * y_prev_persist(); buf(idx) = x + g * delayed; idx = idx + 1; if idx > M idx = 1; end end

上面这个片段为了说明思路,把反馈项抽象成了一个占位函数。实际工程里更稳妥的做法是用 Simulink 原生块搭:Sum+Delay+Gain三件套,和前馈支路组合,逻辑一目了然,也避免MATLAB Function块里persistent变量带来的仿真重置问题。我的建议是:原型阶段用原生块,验证通过后要生成代码了再考虑MATLAB Function或者 Stateflow。

3.4 预延迟、干湿混合与输出保护

预延迟这一个小环节,对最终效果的提升往往比调半天参数还大。它的作用是把干声和混响尾巴在时间上拉开 20 到 40 毫秒,让听者先听清直达声,再感知空间。没有预延迟的混响,人声会明显"糊",字头被尾巴盖住,清晰度骤降。做语音或者唱歌的场景,预延迟我一般设 25 毫秒起步。

干湿混合用两个Gain块加一个Sum。这里有个细节:不要用线性比例混合(比如湿 0.5、干 0.5),因为人耳感知的是能量而不是幅度,线性混合在中间位置会有明显的"凹陷感"。用等功率混合:

Gain_dry = cos(θ), Gain_wet = sin(θ), θ ∈ [0, π/2]

θ 从 0 转到 π/2 就是从全干到全湿,中间任意位置的响度感知都是平稳的。这个细节不起眼,但实际对比一听就分得出来。

输出端必须加保护。混响的反馈结构在极端参数下可能产生超过 0 dBFS 的峰值,直接送到声卡就是刺耳的爆音,还可能伤耳机。我会在最后串一个Saturation块,上下限设 ±0.98,再加一个一阶高通(截止 40 Hz 左右)滤掉直流和次低频,避免低频能量在反馈环里堆积。做完这两步,模型才算"能安全上耳朵"。

4. 上线试听:调参、排查与避坑实录

模型跑通不等于效果好。这个章节是我在几个实际项目里踩出来的坑,按症状分类整理,遇到问题可以对照着查。

4.1 自激啸叫的三条排查路径

啸叫是最常见也最吓人的问题,声音一路飙升到满刻度,耳朵遭罪。出现这种情况,按这个顺序查:

第一,看反馈增益是不是越界。单个梳状的 g 必须小于 1,但八路并联之后,如果某一路的 g 已经接近 0.99,和其他路的输出叠加就可能局部超过 1。解决办法是给每路 g 统一乘一个 0.99 的安全系数,或者做整体归一化。

第二,看阻尼低通有没有引入额外增益。如果你的低通写成了y = (1+d)·x - d·y_prev之类的形式,直流增益就不再是 1 了。用freqz或者 Simulink 的Linear Analysis点一下反馈环的开环增益,直流处必须严格小于等于 0 dB。

第三,看延迟块的长度是不是动态为 0。如果用Variable Integer Delay且 M 来自一个可能被改成 0 的变量,反馈环就退化成了直通,瞬间发散。做参数扫描的时候,M 的下限必须加保护。

提示:仿真时先把Audio Device Writer的音量调到很低,或者干脆先不接声卡只存 Workspace,等波形包络确认正常了再开声音。这个习惯帮我避免过好几次耳朵受伤。

4.2 金属声、浑浊感与回声分离度

这三个是听感层面的问题,比啸叫难定位,因为它们没有明显的客观指标。

金属声(Metallic),表现是混响尾巴带有明显的音高,像敲钢管。原因通常是回波密度不够。对策三条:增加并联路数(从 4 路加到 8 路甚至 12 路)、把延迟长度散布到更宽的区间、增加全通级数。我遇到过最顽固的一次是因为四个延迟长度恰好形成了近似 2:3:4:6 的比例,把其中一个改成完全无关的数,金属声立刻消失。

浑浊感(Muddy),表现是人声字头被淹没、整体发闷。这个问题多半出在预延迟太短,或者湿声的低频太多。对策是把预延迟拉到 30 毫秒以上,并在湿声路径上单独加一个 200 Hz 左右的高通,让低频早衰减。真实房间里低频混响确实更持久,但做音乐时不需要那么"真实",清晰度优先。

回声分离度(Echo),表现是混响不再连续,能听出一颗颗单独的重复。这说明延迟太长或者密度太低。把最长延迟压到 50 毫秒以内,同时增加路数,通常能解决。

4.3 参数扫描的快速试听流程

耳朵会疲劳,听十分钟之后判断力就下降了。我摸索出一套流程,能在半小时内把参数大致定下来:

  1. 准备三段素材:一段干声朗读(判断清晰度)、一段鼓组(判断瞬态响应)、一段弦乐或者人声长音(判断尾巴的平滑度)。
  2. 固定 RT60 和预延迟,只扫反馈增益 g,从 0.85 到 0.95 每次加 0.01,各听 15 秒。
  3. 定下 g 之后,扫阻尼系数 d,从 0 到 0.5,判断金属感消失的位置。
  4. 最后微调延迟长度,只听鼓组,找瞬态不糊的那一组。

为了让这个流程自动化,我把参数写成一个结构体数组,用parfor或者Simulink.SimulationInput批量跑,输出成 wav 文件编号,然后用播放器顺序听。这比一次一次改参数点运行快十倍不止。如果你做的是离线处理而不是实时效果器,强烈建议上这套批量流程。

4.4 常见问题速查表

症状可能原因排查方法对策
输出瞬间爆音、发散反馈增益≥1,或延迟为0看输出包络是否呈上升趋势g 乘 0.99;锁死 M 下限
声音带明显音高回波密度不足调出频响看梳齿间隔增路数、扩大延迟散布、加全通
人声发闷、字头不清预延迟过短、低频湿声过多对比干声听清晰度预延迟≥25ms,湿声高通
完全听不出空间感湿声比例太低或 RT60 太短检查干湿增益等功率混合,RT60 提到 1.5s 以上
尾巴有周期性颗粒延迟长度成整数比例检查八个数是否有公约数重新选互不整除的值
仿真速度极慢用了 Variable Integer Delay,或步长太小看仿真实时比换固定延迟;离线跑用大步长
输出电平忽大忽小未归一化,或增益随时间变化长时间播放观察并联后统一缩放;固定参数

5. 从仿真到工程落地

Simulink 里跑通只是第一步,真正要用在设备上还有几道坎。这一章聊工程化的部分,也是很多人做完仿真就卡住的地方。

5.1 定点化与运算开销核算

浮点模型占用的算力,在 PC 上完全不是问题,但搬到嵌入式或者 DSP 上就得精打细算。先算一笔账:8 个梳状滤波器,每个每样本需要一次乘(反馈增益)、两次加(求和与低通)、一次低通乘加,粗算每样本约 5 次乘加;4 个全通每样本约 6 次乘加。合计每样本 60 次乘加左右,48 kHz 采样率下是每秒约 290 万次乘加,也就是 2.9 MFLOPs 量级。这个量级用一颗普通的定点 DSP 或者带 DSP 扩展的 MCU 完全能扛住,甚至有余量。

定点化要注意两点。一是反馈结构的累积误差:每次循环都做舍入,误差在反馈环里会被反复放大,可能出现极限环振荡——就是没输入的时候输出一直在小幅抖动。解决办法是在反馈路径上保留更高的位宽(比如内部 32 位、输出截到 16 位),并且用饱和而不是回绕来处理溢出。二是低通系数的量化:d很小的时候(比如 0.01),定点表示会有较大误差,导致实际截止频率偏离设计值,听感上高频衰减不够。我的做法是d的量化位宽至少留 12 位。

5.2 实时运行的稳定性验证

仿真跑 10 秒没问题,不代表连续跑 10 小时没问题。我一般做三轮验证:

第一轮,白噪声长时注入。灌 30 分钟白噪声,观察输出包络的均值和峰值是否漂移。如果 RMS 缓慢爬升,说明某个环节有微小的增益溢出,在反馈环里被慢慢放大。

第二,极限参数组合。把 RT60 拉到最大值、阻尼设为 0、预延迟设为 0,这组参数最容易出事,跑一遍确认不炸。

第三,实时性余量测试。用Audio Device Reader加Writer做成实时处理,观察 Simulink 的实时比(Real-Time指标),余量至少留 30%。如果实时比接近 1,说明刚好跑得动,一旦系统有别的负载就会掉帧,表现为爆音。

注意:实时跑的时候一定要用Audio Device Writer的BufferSize参数做匹配,缓冲太小容易欠载(under-run),表现为规律的噼啪声;太大则监听延迟明显,调参手感差。我一般从 512 试起。

5.3 立体声扩展与后续玩法

单声道混响听起来是"居中"的一团,缺乏空间包裹感。做立体声最省事的办法是左右两声道共用同一组梳状结构,但给右声道的延迟长度统一加上一个偏移量(比如 +23 个样本)。这几十个样本的差异不足以让人听出方向偏移,但足以让两个声道的回波不相关,听感上立刻变宽。这就是经典的 Stereo Spread 技巧,改动极小、效果明显。

再往外扩,玩法还有不少。多声道的话,可以给每个声道配一组独立但部分共用参数的梳状结构,通过声道间的增益差异来定位声像。如果要做动态混响,可以在反馈增益上挂一个低频振荡器做缓慢调制(LFO 频率 0.1 到 1 Hz,深度 1% 到 3%),让回波随时间轻微漂移,能进一步消除金属感,这就是很多合唱和空间类效果器共用的一套思路。反向的应用也值得一试:把梳状滤波器放进自适应滤波的框架里,做混响消除(dereverberation),用于会议系统的语音增强——同一个结构,正着用是加混响,反着用是减混响。

最后分享一个我在调这类算法时的习惯:每次实验都保存一份参数快照和对应的试听文件,命名带上时间戳和三个核心参数(RT60、g 基准值、d 值)。音乐和音频这类工作,判断标准是耳朵,而耳朵的判断会随时间变化。今天觉得对的参数,明天可能就听出问题了。有了历史记录,回退和对比才有依据。

我在实际操作中的体会是,梳状滤波器这套结构的门槛不在数学,而在"听感和参数的对应关系"这个隐性知识上。公式摆在那里谁都能推,但知道 0.91 和 0.92 之间差多远、知道金属声该往哪个方向调,只能靠一遍遍听出来。所以别急着优化性能,先在 Simulink 里把耳朵喂饱,后面的一切都会顺得多。