从属于杂项.html

高频频谱曲线渲染优化

前提

从FFT中获取的ScopeData显然是线性的,但是根据人耳对频率的感知并不是线性的,因此需要将获得的数据映射到对数坐标上,这样才符合我们大部分人类的直觉。但是这也引出了一个问题,经过对数映射后的数据会在高频挤在一起,为了性能和视觉效果,就必须要省略掉这些不必要的数据

思路

省略掉数据并不是简单地间隔几个像素抽出数据,因为那会忽略掉关键的峰值,所以在省略掉一些无关紧要的东西前需要先把峰值捕获了。这里就选择将数据分成一定份数,进行分段绘制,对数频率轴分成若干频段,在每个频段内取一个最大值,并用该最大值对应的原始索引来计算 X 坐标。这样既能把点数从几百降到几十,又能完美捕捉每个频段内的最高峰值,高频尖刺也不会丢失

问题

不过这也有一个缺点,绘制出的峰曲线并不对称,因为在这个方案中,捕获的峰值被绘制在它原本的位置,这就会导致有的频段峰值靠的比较近,有的就比较远,贝塞尔控制点计算出来的切线方向会剧烈摆动,画出来的曲线就会在该处产生扭曲现象,所以原因就是同时做了对数频段划分+峰值抽取,这两者叠加时,会破坏用于绘制贝塞尔曲线所需的点序列的几何连续性,导致曲线出现扭曲

方案

解决这个问题有一下部分方案

一.固定x,只改变y 每个频段用固定中心频率计算x坐标,而不是用峰值所在bin的索引。这意味着峰值点在水平方向会被“约束”到均匀分布的位置,贝塞尔曲线不会再扭曲

二.保留真实x,但改用直线连接 把贝塞尔曲线直接换成 lineTo,256个点在屏幕上已经足够密,纯直线看起来也相当平滑,而且绝不会扭曲

直线这个方案暂且不考虑,而固定x在中间可能会破坏数据(虽然这看起来更正常),但偏移几个像素也无伤大雅

未选方案

一.在生成 points 之后再对路径进行Douglas-Peucker简化,并专门把 Y 值很高的“峰值点”标记为不可删除的关键点。不过对数分段取最大值的方法实现更简单、性能更稳定,且更符合频谱可视化的直觉

二.判断点之间的距离,小于某个距离阈值后只绘制一个点 比如说当相邻频点的水平间距小于4px时,不绘制全部点,只保留该局部区域内的峰值,从而避免高频段因对数缩放导致曲线过密、视觉混乱,乍一看这和思路那个方案十分相似,事实也的确如此,不过这里也来说说为什么没采用这个

  1. 相同的问题

  2. 小于4px后,尝试捕获4px内的峰值,奇怪的是总是无法捕获,debug时锯齿波输入显示的频谱的高频部分很乱,峰值也很低,具体原因没有深入追究(这同时也告诉我这种条件下不要写这种弱耦合的代码——通过px获取点并捕获峰值)

  3. 4px在方法内是固定的,但组件可以拉伸(我可不想维护这些奇怪的特性)

  4. 优化失效。假设你把组件设置得很长,那么4px间距的绘制其实对优化没什么效果了,代码失去了它的意义