SoundTouch构造流程初始化的一点补充。
在SoundTouch类构造函数中,我们留意到有这么一个函数calcEffectiveRateAndTempo()
SoundTouch::SoundTouch()
{
// Initialize rate transposer and tempo changer instances
pRateTransposer = RateTransposer::newInstance();
pTDStretch = TDStretch::newInstance();
setOutPipe(pTDStretch);
rate = tempo = 0;
virtualPitch =
virtualRate =
virtualTempo = 1.0;
calcEffectiveRateAndTempo();
channels = 0;
bSrateSet = FALSE;
}
在SoundTouch类的6个成员函数void setRate(float newRate),void setRateChange(float newRate),void setTempo(float newTempo),void setTempoChange(float newTempo),void setPitch(float newPitch),void setPitchOctaves(float newPitch)分别调用。不难想象,应该是对音频处理参数的一些处理,通过对calcEffectiveRateAndTempo的进一步分析,他的实现如下。
// Calculates 'effective' rate and tempo values from the
// nominal control values.
void SoundTouch::calcEffectiveRateAndTempo()
{
float oldTempo = tempo;
float oldRate = rate;
tempo = virtualTempo / virtualPitch;
rate = virtualPitch * virtualRate;
if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate);
if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo);
#ifndef PREVENT_CLICK_AT_RATE_CROSSOVER
if (rate <= 1.0f)
{
if (output != pTDStretch)
{
FIFOSamplePipe *tempoOut;
assert(output == pRateTransposer);
// move samples in the current output buffer to the output of pTDStretch
tempoOut = pTDStretch->getOutput();
tempoOut->moveSamples(*output);
// move samples in pitch transposer's store buffer to tempo changer's input
pTDStretch->moveSamples(*pRateTransposer->getStore());
output = pTDStretch;
}
}
else
#endif
{
if (output != pRateTransposer)
{
FIFOSamplePipe *transOut;
assert(output == pTDStretch);
// move samples in the current output buffer to the output of pRateTransposer
transOut = pRateTransposer->getOutput();
transOut->moveSamples(*output);
// move samples in tempo changer's input to pitch transposer's input
pRateTransposer->moveSamples(*pTDStretch->getInput());
output = pRateTransposer;
}
}
}
主要还是完成了pRateTransposer,pTDStretch两个类的一些参数设置。从而对于整个声音的处理流程大概也有了一个初步的认识。
1、创建一个数字低通滤波器AAFilter,通过加入hamming window来截取sample。
我们分析一下他是如何创建这个低通数字滤波器,主要实现还是在RateTransposer类的构造函数中,构造一个AAFilter类来实现。pAAFilter = new AAFilter(32);
RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer)
{
numChannels = 2;
bUseAAFilter = TRUE;
fRate = 0;
// Instantiates the anti-alias filter with default tap length
// of 32
pAAFilter = new AAFilter(32);
}
我们看一下AAFilter类定义,比较简单,也很好理解。class FIRFilter *pFIR就和前面分析的一样,指向根据CPU派生出支持相应增强指令集优化的类。同样他们只是简单的override数据处理的函数。double cutoffFreq;就是低通截止频率。calculateCoeffs()就是我们应该重点理解的类函数,数字滤波器的主要参数就靠它来实现
class AAFilter
{
protected:
class FIRFilter *pFIR;
/// Low-pass filter cut-off frequency, negative = invalid
double cutoffFreq;
/// num of filter taps
uint length;
/// Calculate the FIR coefficients realizing the given cutoff-frequency
void calculateCoeffs();
public:
AAFilter(uint length);
~AAFilter();
/// Sets new anti-alias filter cut-off edge frequency, scaled to sampling
/// frequency (nyquist frequency = 0.5). The filter will cut off the
/// frequencies than that.
void setCutoffFreq(double newCutoffFreq);
/// Sets number of FIR filter taps, i.e. ~filter complexity
void setLength(uint newLength);
uint getLength() const;
/// Applies the filter to the given sequence of samples.
/// Note : The amount of outputted samples is by value of 'filter length'
/// smaller than the amount of input samples.
uint evaluate(SAMPLETYPE *dest,
const SAMPLETYPE *src,
uint numSamples,
uint numChannels) const;
};
先看一下AAFilter的构造函数,先创建一个FIR滤波器的实例,接着让截取频率等于0.5,需要注意的是,这个是一个角频率。然后设置滤波器的窗体宽度。
AAFilter::AAFilter(uint len)
{
pFIR = FIRFilter::newInstance();
cutoffFreq = 0.5;
setLength(len);
}
在设置宽度的类成员函数SetLength中,调用了类成员函数calculateCoeffs();
// Sets number of FIR filter taps
void AAFilter::setLength(uint newLength)
{
length = newLength;
calculateCoeffs();
}
现在重点介绍一下类成员函数calculateCoeffs(),他就是整个数字滤波器参数实现的核心。源代码如下:
// Calculates coefficients for a low-pass FIR filter using Hamming window
void AAFilter::calculateCoeffs()
{
uint i;
double cntTemp, temp, tempCoeff,h, w;
double fc2, wc;
double scaleCoeff, sum;
double *work;
SAMPLETYPE *coeffs;
assert(length >= 2);
assert(length % 4 == 0);
assert(cutoffFreq >= 0);
assert(cutoffFreq <= 0.5);
work = new double[length];
coeffs = new SAMPLETYPE[length];
fc2 = 2.0 * cutoffFreq;
wc = PI * fc2;
tempCoeff = TWOPI / (double)length;
sum = 0;
for (i = 0; i < length; i ++)
{
cntTemp = (double)i - (double)(length / 2);
temp = cntTemp * wc;
if (temp != 0)
{
h = fc2 * sin(temp) / temp; // sinc function
}
else
{
h = 1.0;
}
w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window
temp = w * h;
work[i] = temp;
// calc net sum of coefficients
sum += temp;
}
...
类函数的前半部分通过assert进行一些必要的判断,例如长度一定要大于2且一定要是4的倍数,才能保证length/2是一个整数,同时保证截取频率在0和0.5之间。接着采用汉明窗作为窗。注意到0.54 + 0.46 * cos(2 * pi * cntTemp / N)和汉明窗函数0.54 - 0.46*cos(2*pi*n/(N-1))形式上有点不一致,其实也不难理解:
i = (0 .. length-1) 且 cntTemp = i - (length/ 2);
0.54 + 0.46 * cos(2 * pi * cntTemp / N)
= 0.54 - 0.46 * cos(2 * pi * cntTemp / N + pi)
= 0.54 - 0.46 * cos(2 * pi * cntTemp / N + pi * N / N)
= 0.54 - 0.46 * cos(2 * pi * (cntTemp + N / 2) / N)
= 0.54 - 0.46 * cos(2 * pi * n / N) where n = 0..N-1
仅仅是一个cos(x) = -cos(x+pi)的变化,很简单却又让人很容易惯性思维,不容易想明白。至于为什么用N不用N-1,我相信以下这段话,可以很清楚明白的表达,在这里,要谢谢一个哈理工老师的指教。“这个N-1如果用N,对称中心N/2不是整数,就不是一个采样点(因为是偶对称,并且N要取奇数----低通滤波器理论上只能这么选取参数”注意到我们在长度中i是从0开始的,到length结束,而length前面通过assert判断一定要大于2且一定是四
的倍数,他不是一个奇数,因此(length-1)/2一定不是一个整数。所以这里可以理解为我们的滤波器是有length+1的长度。
......
// ensure the sum of coefficients is larger than zero
assert(sum > 0);
// ensure we've really designed a lowpass filter...
assert(work[length/2] > 0);
assert(work[length/2 + 1] > -1e-6);
assert(work[length/2 - 1] > -1e-6);
// Calculate a scaling coefficient in such a way that the result can be
// divided by 16384
scaleCoeff = 16384.0f / sum;
for (i = 0; i < length; i ++)
{
// scale & round to nearest integer
temp = work[i] * scaleCoeff;
temp += (temp >= 0) ? 0.5 : -0.5;
// ensure no overfloods
assert(temp >= -32768 && temp <= 32767);
coeffs[i] = (SAMPLETYPE)temp;
}
// Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384
pFIR->setCoefficients(coeffs, length, 14);
delete[] work;
delete[] coeffs;
}
类函数的后半部分,assert用来验证这个低通滤波器是否真的有效,剩下的主要是做一个定点的处理2^14=16384,相当于右移了14位(放大16384倍,结果再左移14位变回来,可以增加精度),同时还assert(temp >= -32768 && temp <= 32767);来验证temp作为一个十六位整数一定不溢出。最后做的事情,就是把低通滤波器参数传递进去FIRxxx的类。然后FIRxxx类就可以抽象成一个数字低通滤波器。至此,所有的初始化工作完毕,可以进入数据的具体处理流程。