关于频谱曲线平滑渲染算法
前提
由于推入环形缓冲区进行FFT样本的大小相同,因此渲染时FFT返回的结果需要一定的时间,但ui线程异步调用,即使以每60hz的频率调用绘制方法,里面也会有许多相同的FFT结果被绘制,造成卡顿的视觉效果,所以这里尝试在FFT返回的上一个结果和当前结果中插入更多不同的数据,让频谱图呈现一种更平滑的效果,避免使用更高的采样率来加快填充样本空间的速率
思路
指数移动平均(英语:exponential moving average,EMA或EWMA)是以指数式递减加权的移动平均。 各数值的加权影响力随时间而指数式递减,越近期的数据加权影响力越重,但较旧的数据也给予一定的加权值。
因此这里采用指数移动平均法使频谱曲线平滑移动。根据分析,需要三个数组存储相应的FFT返回的高度数据,分别定义为
float scopeData[scopeSize];
float gapSmoothedScopeData[scopeSize];
float scopeDataStorage[scopeSize];为什么需要三个?
我们设,是当前保存的数据,其中为保存的上一个数据
假设
那么我们下一帧就要渲染
*这里需要注意的是在这里的分析中,是一个负数,所以也是一个负数,因此要加上g''的绝对值,也就变为了减号
最后保存当前数据
显然在分析中,它们的系数和为1,不过不需要在代码中直接写出来。接下来只需要循环这个的数据处理流程,就能实现所谓指数移动平均,渲染时的曲线就能慢慢地靠近当前FFT返回的高度数据。上面的常数0.8决定了旧数据向新数据移动的快慢,越低越快。g'代表代码中定义的
gapSmoothedScopeData[index],S代表了scopeData[index]中的数据,P则是scopeDataStorage[index]
float y = juce::jmap(scopeDataStorage[i], mindB, maxdB, initialY + height, initialY);
p.lineTo(currentNum, y);
gapSmoothedScopeData[i] = (scopeData[i] - scopeDataStorage[i]) * 0.50f;
scopeDataStorage[i] = scopeData[i] - gapSmoothedScopeData[i];还需要注意的一点是在行1中,
scopeDataStorage[i - 1]的位置不应该写scopeData[i - 1] - gapSmoothedScopeData[i - 1],因为数据更新的过程中scopeData[i - 1]也会被更新,这会导致渲染时出现奇怪的频闪还有一点,因为这个算法在绘制的方法中,因此平滑移动的快慢也会取决于你所选择的帧率