变频器使用FFT

时间:2017-01-30 23:08:28

标签: c++ audio fft

我一直在研究使用Rosetta Code提供的原始FFT算法的移频器。我理解,为了对采样信号进行频移,可以对原始音频应用FFT,将每个得到的正弦波的频率乘以频移系数(用户定义),然后将正弦波加回去。当我运行我的算法时,输出的质量极低,好像在算法中没有收集到足够的正弦波,以便首先正确地再现信号。该算法在头文件中的类中实现,并在其他地方调用(正确)。

#include <complex>
#include <valarray>

typedef std::complex<double> Complex;
typedef std::valarray<Complex> CArray;

class FrequencyShifter {
float sampleRate;
public:
    FrequencyShifter() {

    }
    void setSampleRate(float inSampleRate) {
        sampleRate = inSampleRate;
    }
    double abs(double in0) {
        if (in0>=0) return in0;
        else return -in0;
    }
    void fft(CArray& x)
    {
        const size_t N = x.size();
        if (N <= 1) return;

        // divide
        CArray even = x[std::slice(0, N/2, 2)];
        CArray  odd = x[std::slice(1, N/2, 2)];

        // conquer
        fft(even);
        fft(odd);

        // combine
        for (size_t k = 0; k < N/2; ++k)
        {
            Complex t = std::polar(1.0, -2 * PI * k / N) * odd[k];
            x[k    ] = even[k] + t;
            x[k+N/2] = even[k] - t;
        }
    }
    double convertToReal(double im, double re) {
        return sqrt(abs(im*im - re*re));
    }
    void processBlock(float *inBlock, const int inFramesToProcess, float scale) {
        //inFramesToProcess is the amount of samples in inBlock
        Complex *copy = new Complex[inFramesToProcess];
        for (int frame = 0; frame<inFramesToProcess; frame++) {
            copy[frame] = Complex((double)inBlock[frame], 0.0);
        }
        CArray data(copy, inFramesToProcess);
        fft(data);
        const float freqoffsets = sampleRate/inFramesToProcess;
        for (float x = 0; x<data.size()/2; x++) {
            for (float frame = 0; frame<inFramesToProcess; frame++) {
                inBlock[(int)frame] = (float)(convertToReal(data[(int)x].imag(), data[(int)x].real())*sin(freqoffsets*x*frame*scale));
            }
        }
    }
};

我假设问题的一部分是我只包括sampleRate/inFramesToProcess频率来覆盖正弦波。发送更大的音频文件(因此更大的*inBlockinFramesToProcess)会使音频变得不那么粗糙吗?如何在没有的情况下完成此只更改参数的值或长度?

1 个答案:

答案 0 :(得分:2)

以下是processBlock的更新版本,其中包含了实施频移所需的一些调整,我将在下面介绍:

void processBlock(float *inBlock, const int inFramesToProcess, float scale) {
    //inFramesToProcess is the amount of samples in inBlock
    Complex *copy = new Complex[inFramesToProcess];
    for (int frame = 0; frame<inFramesToProcess; frame++) {
        copy[frame] = Complex((double)inBlock[frame], 0.0);
    }
    CArray data(copy, inFramesToProcess);
    fft(data);
    const float freqoffsets = 2.0*PI/inFramesToProcess;
    const float normfactor  = 2.0/inFramesToProcess;
    for (int frame = 0; frame<inFramesToProcess; frame++) {
        inBlock[frame] = 0.5*data[0].real();
        for (int x = 1; x<data.size()/2; x++) {
            float arg = freqoffsets*x*frame*scale;
            inBlock[frame] += data[x].real()*cos(arg) - data[x].imag()*sin(arg);
        }
        inBlock[frame] *= normfactor;
    }
}

<强>推导

从FFT获得的频谱是复值的,可以看作是以正弦和余弦波的形式表示信号。可以使用逆变换来重建时域波形,该变换将由以下关系给出: enter image description here

利用频谱对称性,可以表示为:

enter image description here

或等效地:

enter image description here

您可能已经注意到索引0N/2处的术语是频域中纯实系数的特殊情况。为简单起见,假设频谱不会一直到N/2,您可以删除N/2项并仍然得到合理的近似值。对于其他术语,您将获得可以实现为

的贡献
normfactor = 2.0/inFramesToProcess;
normfactor*(data[x].real()*cos(arg) - data[x].imag()*sin(arg))

您当然需要将所有这些贡献添加到最终缓冲区inBlock[frame]中,而不是简单地覆盖以前的结果:

inBlock[frame] += normfactor*(data[x].real()*cos(arg) - data[x].imag()*sin(arg));
//             ^^ 

注意,可以对循环后的最终结果进行归一化,以减少乘法次数。在这样做时,我们必须特别注意索引0处的DC项(其系数为1/N而不是2/N):

inBlock[frame] = 0.5*data[0].real();
for (int x = 1; x<data.size()/2; x++) {
    float arg = freqoffsets*x*frame*scale;
    inBlock[frame] += data[x].real()*cos(arg) - data[x].imag()*sin(arg);
}
inBlock[frame] *= normfactor;

最后,在生成音调时,阶段参数argsincos的格式应为2*pi*k*n/inFramesToProcess(在应用{{1}之前}} factor),其中scale是时域样本索引,n是频域索引。最终结果是计算出的频率增量k应该是freqoffsets

备注

  • FFT算法的工作原理是您的基础时域信号是周期性的块长度周期。因此,块之间可能存在可听见的不连续性。
  • 未来的读者应该意识到,这不会使频谱偏移恒定的数量,而是在频率按乘法因子缩放时挤压或扩展频谱。例如,包括100-200Hz分量的信号可能被压缩到75-150Hz,系数为0.75。注意下限是如何向下移动25Hz,而上限向下移动50Hz。