为运行IOS的设备生成正弦波的最有效方法是什么。为了练习的目的,假设频率为440Hz,采样率为44100Hz和1024个样本。
vanilla C实现看起来像。
#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100
int main(int argc, const char * argv[]) {
float samples[SAMPLES];
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = sin(currentPhase);
currentPhase += phaseIncrement;
}
return 0;
}
要利用Accelerate Framework和vecLib vvsinf函数,可以将循环更改为仅进行添加。
#define SAMPLES 1024
#define TWO_PI (3.14159 * 2)
#define FREQUENCY 440
#define SAMPLING_RATE 44100
int main(int argc, const char * argv[]) {
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = currentPhase;
currentPhase += phaseIncrement;
}
vvsinf(results, samples, SAMPLES);
return 0;
}
但就效率而言,我应该尽量应用vvsinf函数吗?
我不太了解Accelerate框架,知道我是否也可以替换循环。我可以使用vecLib或vDSP功能吗?
对于这个问题,是否可以使用完全不同的算法来填充正弦波缓冲区?
答案 0 :(得分:3)
鉴于您正在计算以固定增量增加的相位参数的正弦值,使用this "How to Create Oscillators in Software" post中描述的递推方程实现信号生成通常要快得多,{{3}中更多},this "DSP Trick: Sinusoidal Tone Generator" post:
y[n] = 2*cos(w)*y[n-1] - y[n-2]
请注意,此递归方程可能会受到数值舍入误差累积的影响,您应该避免一次计算太多样本(您选择SAMPLES == 1024
应该没问题)。获得前两个值y[0]
和y[1]
(初始条件)后,可以使用此递推方程。由于您的初始阶段为0,因此只需:
samples[0] = 0;
samples[1] = sin(phaseIncrement);
或更一般地说是一个任意的初始阶段(特别有用的是经常重新初始化递推方程,以避免我前面提到的数值舍入误差累积):
samples[0] = sin(initialPhase);
samples[1] = sin(initialPhase+phaseIncrement);
然后可以直接用以下方法实现递归方程:
float scale = 2*cos(phaseIncrement);
// initialize first 2 samples for the 0 initial phase case
samples[0] = 0;
samples[1] = sin(phaseIncrement);
for (int i = 2; i < SAMPLES; i ++){
samples[i] = scale * samples[i-1] - samples[i-2];
}
请注意,此实现可以通过计算多个音调(每个音频具有相同的频率,但样本之间的相位增量更大)进行矢量化,并具有适当的相对相移,然后交叉结果以获得原始音调语气(例如计算sin(4*w*n)
,sin(4*w*n+w)
,sin(4*w*n+2*w)
和sin(4*w*n+3*w)
)。然而,这将使实现更加模糊,获得相对较小的收益。
或者,可以通过使用vDsp_deq22
:
// setup dummy array which will hold zeros as input
float nullInput[SAMPLES];
memset(nullInput, 0, SAMPLES * sizeof(float));
// setup filter coefficients
float coefficients[5];
coefficients[0] = 0;
coefficients[1] = 0;
coefficients[2] = 0;
coefficients[3] = -2*cos(phaseIncrement);
coefficients[4] = 1.0;
// initialize first 2 samples for the 0 initial phase case
samples[0] = 0;
samples[1] = sin(phaseIncrement);
vDsp_deq22(nullInput, 1, coefficients, samples, 1, SAMPLES-2);
答案 1 :(得分:0)
如果需要效率,您可以预加载440hz(44100/440)正弦波形查找表并在其周围循环而无需进一步映射或预加载1hz(44100/44100)正弦波形查找表通过跳过样本达到440hz并循环,就像增加相位计数器一样。使用查找表应该比计算sin()更快。
方法A(使用440hz正弦波形):
#define SAMPLES 1024
#define FREQUENCY 440
#define SAMPLING_RATE 44100
#define WAVEFORM_LENGTH (SAMPLING / FREQUENCY)
int main(int argc, const char * argv[]) {
float waveform[WAVEFORM_LENGTH];
LoadSinWaveForm(waveform);
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
for (int i = 0; i < SAMPLES; i ++){
samples[i] = waveform[i % WAVEFORM_LENGTH];
}
vvsinf(results, samples, SAMPLES);
return 0;
}
方法B(使用1hz正弦波形):
#define SAMPLES 1024
#define FREQUENCY 440
#define TWO_PI (3.14159 * 2)
#define SAMPLING_RATE 44100
#define WAVEFORM_LENGTH SAMPLING_RATE // since it's 1hz
int main(int argc, const char * argv[]) {
float waveform[WAVEFORM_LENGTH];
LoadSinWaveForm(waveform);
float samples[SAMPLES] __attribute__ ((aligned));
float results[SAMPLES] __attribute__ ((aligned));
float phaseIncrement = TWO_PI * FREQUENCY / SAMPLING_RATE;
float currentPhase = 0.0;
for (int i = 0; i < SAMPLES; i ++){
samples[i] = waveform[floor(currentPhase) % WAVEFORM_LENGTH];
currentPhase += phaseIncrement;
}
vvsinf(results, samples, SAMPLES);
return 0;
}
请注意:
由于假设您的频率始终正确地划分采样率,因此方法A容易受到频率误差的影响,这是不正确的。这意味着你可能会因故障而得到441hz或440hz。
方法B容易出现混叠,因为频率上升越接近奈奎斯特频率,但如果合成合理的低频率,如性能,质量和内存消耗,它是一个很好的平衡点。在你的例子中有一个。