在 IOS 中高效地生成正弦波

Efficiently generate a Sine wave in IOS

为设备生成正弦波的最有效方法是什么 运行 IOS。出于练习的目的,假设频率为 440Hz,采样率为 44100Hz 和 1024 个样本。

香草 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 函数吗?

就此而言,是否可以使用完全不同的算法来用正弦波填充缓冲区?

鉴于您正在计算以固定增量增加的相位参数的正弦,通常使用 this "How to Create Oscillators in Software" post and some more in this "DSP Trick: Sinusoidal Tone Generator" post, both on dspguru:

中描述的递归方程实现信号生成要快得多
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);

如果需要效率,您可以预加载 440hz (44100 / 440) 正弦波形查找 table 并围绕它循环而不进一步映射或预加载 1hz (44100 / 44100)正弦波查找 table 并通过跳过样本循环以达到 440hz,就像您通过递增相位计数器所做的那样。使用查找 tables 应该比计算 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随着频率上升和接近奈奎斯特频率而容易出现混叠,但如果合成​​合理的低频,如你的例子中的那个。