从属于杂项.html

关于频谱曲线平滑渲染算法

前提

由于推入环形缓冲区进行FFT样本的大小相同,因此渲染时FFT返回的结果需要一定的时间,但ui线程异步调用,即使以每60hz的频率调用绘制方法,里面也会有许多相同的FFT结果被绘制,造成卡顿的视觉效果,所以这里尝试在FFT返回的上一个结果和当前结果中插入更多不同的数据,让频谱图呈现一种更平滑的效果,避免使用更高的采样率来加快填充样本空间的速率

思路

指数移动平均(英语:exponential moving average,EMA或EWMA)是以指数式递减加权的移动平均。 各数值的加权影响力随时间而指数式递减,越近期的数据加权影响力越重,但较旧的数据也给予一定的加权值。

因此这里采用指数移动平均法使频谱曲线平滑移动。根据分析,需要三个数组存储相应的FFT返回的高度数据,分别定义为

float scopeData[scopeSize];
float gapSmoothedScopeData[scopeSize];
float scopeDataStorage[scopeSize];

为什么需要三个?

我们设g′=S−Pg' = S - P,SS是当前保存的数据,其中PP为保存的上一个SS数据

假设g′′=g′∗0.8g'' = g' * 0.8

那么我们下一帧就要渲染S′=S−g′′S' = S - g''

*这里需要注意的是在这里的分析中,g′g'是一个负数,所以g′′g''也是一个负数,因此要加上g''的绝对值,也就变为了减号

最后保存当前数据P′=S′P' = S'

显然在分析中P′=0.2S+0.8PP' = 0.2S + 0.8P,它们的系数和为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];