光谱质心操作

信息处理 fft IFFT
2022-02-22 15:00:37

因此,我创建了一个简单的声音分析应用程序,我实现的功能之一是频谱质心(如此处所述https://en.wikipedia.org/wiki/Spectral_centroid)。为了获得可靠的结果,我们需要对我们的光谱质心数据设置一个阈值,因为 FFT 总是在实际上没有任何能量的 bin 中返回一些能量。

现在,我希望能够操纵频谱质心(例如,将值向上或向下推,这在某种程度上类似于音高移动),然后使用逆 FFT 操作将值转换为波形数据。这样,我们就可以听到更改,但我不确定如何执行此操作。

频谱质心是通过使用幅度而不是原始 FFT 数据计算的。幅度计算如下

mag[i] = 10*log10(sqrt((pFFTLReal[i] * pFFTLReal[i] + pFFTLImg[i] * pFFTLImg[i])/fftNorm));//fftNorm is basically a correction factor that depends on a type of window (blackman, hamming etc)

那么,考虑到这一切,我将如何将频谱质心更改 10% 并查看生成(操纵)波形的变化?

这甚至可能吗?

请,如果这还不够清楚,我会尝试提供更多信息。

谢谢

1个回答

自从我提出这个问题以来已经有一段时间了,在对这个主题做了一些工作之后,我认为是时候重新审视它了。应该注意的是,虽然光谱质心与任何其他光谱特征非常相似,是使用 FFT 幅度而不是原始 FFT 数据计算的,但我们不必直接操纵幅度。相反,我们修改 FFT 数据,这反过来会影响幅度。这使得该过程比必须操纵幅度要容易得多。

光谱质心是单个值(每个 FFT 帧),因此在 STFT 的情况下,我们拥有与 STFT 帧一样多的质心。操纵光谱质心的过程非常简单。例如,如果频谱质心为 600Hz,并且我们希望将其值增加 10% 以使新值为 660Hz,我们只需将频率 bin 重新排列一定数量(bin step)。实际计算很简单

const binStep = frameLenHalf - frameLenHalf/(1+(float)freqDelta/100); 
   //frequency delta % (e.g. 10%, 20%, -10%, -20% etc)

    //for increasing frequency
    float *pCfftLReal = new float[frameLen];
    float *pCfftLImg =  new float[frameLen];
    //for increasing frequency
    for(j=frameLenHalf-1;j>binStep;j--){
            pCfftLReal[j] = pFFTReal0[j-binStep];
            pCfftLImg[j] = pFFTImg0[j-binStep];
                }
        }
    //for decreasing frequency
    for(int j=0; j<frameLenHalf-1-binStep; j++){
            pCfftLReal[j] = pFFTReal0[j+binStep];
            pCfftLImg[j] = pFFTImg0[j+binStep];
                } 

不用说,这个过程存在舍入误差(主要是计算 binStep),但一般来说,bin 分辨率越大,误差越大。这可以通过具有更大的帧长度来缓解(但对于 STFT,这通常是不可取的)。其他更复杂的方法也是可能的,但我没有研究过这些方法。

此外,如果频移量超过 Nyqiust 频率并且我们保持原始采样率,有时结果可能会出现偏差(上采样在这里会有所帮助)。

在我们真正听到我们操纵的声音之前,应该对我们修改的数据执行逆 FFT。

我在这里描述的只是一种粗略、幼稚的光谱质心操作方法。还应该说,这种操作不会保留原始谐波比,因为频率增量偏移在每个 bin 上均等应用。保持谐波比不变将需要为每个频率计算一个新的 bin 步长,这仅比上面给出的算法稍微复杂一些。

我已经尝试了这两种方法(使用恒定的 bin 步长和不断变化的 bin 步长),并且在感知上,第一个并没有真正改变原始声音(我想这与高频影响音色有关,而不是低频和第一个方法对较高频率的改变小于对较低频率的改变)。

我还没有完成这个,所以一旦我有有趣的事情要分享,我会更新这篇文章。