我一直在研究使用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
频率来覆盖正弦波。发送更大的音频文件(因此更大的*inBlock
和inFramesToProcess
)会使音频变得不那么粗糙吗?如何在没有的情况下完成此只更改参数的值或长度?
答案 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获得的频谱是复值的,可以看作是以正弦和余弦波的形式表示信号。可以使用逆变换来重建时域波形,该变换将由以下关系给出:
利用频谱对称性,可以表示为:
或等效地:
您可能已经注意到索引0
和N/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;
最后,在生成音调时,阶段参数arg
到sin
和cos
的格式应为2*pi*k*n/inFramesToProcess
(在应用{{1}之前}} factor),其中scale
是时域样本索引,n
是频域索引。最终结果是计算出的频率增量k
应该是freqoffsets
。
备注强>