如何在 C 中生成具有连续时变频率的正弦波?

信息处理 C
2021-12-26 11:03:47

如何生成具有连续时变频率的正弦波?如何解决以下问题?我想要一个连续图。

在此处输入图像描述

我正在像这样生成它:

for(int i = 0; i < pcm_buffer_size - 1; i += 2) {
    float sample = gain * sin(((float) t * M_PI * 2 * 500) / (float) (sample_rate));

    printf("%f\n", sample);

    t++;
}

for(int i = 0; i < pcm_buffer_size - 1; i += 2) {
    float sample = gain * sin(((float) t * M_PI * 2 * 1000) / (float) (sample_rate));

    printf("%f\n", sample);

    t++;
}

其中 500 和 1000 是频率。

4个回答

好吧,让我们去吧,哈哈

@AnonSubmitter85 给你一个很好的答案,但让我展示我在 matlab 中的方法,这可能很容易移植到 C:

首先,我在采样中创建256样本,看看我是如何累积相位的,在第一个循环结束时,我将相位放在间隔 0 和 2pi 之间......500hz44100hz

很好,现在让我们进入第二个循环以创建更多256样本1000Hz并连续进行,我正在使用最后一个阶段:-),这是代码:

m_phase=0;

signal=[];

f=500;
fs=44100

phaseInc = 2*pi*f/fs;


for i=1:256
   signal(i) = sin(m_phase);
   m_phase = m_phase + phaseInc;
 end

%place the phaser between the 0 and 2pi range
m_phase = mod(m_phase, 2*pi);


f=1000
phaseInc = 2*pi*f/fs;


for i=257:256*2
   signal(i) = sin(m_phase);
   m_phase = m_phase + phaseInc;
end

%place the phaser between the 0 and 2pi range
m_phase = mod(m_phase, 2*pi);

这是上面代码的情节::

在此处输入图像描述

看到这么多答案有点奇怪,但没有一个能用 C 语言给出实际答案或解释如何以及为什么这样做。

一般的想法是保持一个相位增加一个步长,该步长是从频率和采样率计算出来的。这样,您将永远不会出现相位不连续。

这样做时,必须非常小心浮点变量中累积的舍入误差,因为无论数字有多大,相对舍入误差基本保持不变,但绝对舍入误差的增加取决于数量的大小。(在文章What Every Computer Scientist Should Know About Floating-Point Arithmetic中有深入而沉重的解释。)

如果您只是继续将步长添加到相位,它很快就会达到太大的幅度,因此必须对其进行检查。由于我们处理的是单个波形,我们可以通过环绕或取模将相位限制在 0.0 和 1.0 之间。最简单且可能最有效的方法是对相位使用无符号整数,并让 C 编译器处理回绕。C 很烦人,因为很多算术运算都没有定义,但是无符号整数环绕按照我们想要的方式定义的,所以我们可以利用它。

下面的程序使用上述技术在标准输出上输出波形。相位保持在一个无符号整数中,并以根据请求的频率和采样率计算的另一个整数递增。每当相位超过整数大小设置的任何限制时,它将自动回绕为零。

然后将整数相位缩放到正弦函数的所需索引。这可以很容易地更改为使用波表或插值查找。

由于便携性和兼容性,某些部分异常复杂。在固定设置中,可以简化很多内容以提高可读性。同样,您可能希望在某些地方添加更好的舍入。

#include <math.h>
#include <stdio.h>

#define M_TWOPI 6.283185307179586476925286766559

/*
* The phase must be an unsigned integer.
* 'maxphase' is for example 65536 if phase_t is 16 bits.
*/
typedef unsigned long phase_t;
double maxphase = (double)((phase_t)0-(phase_t)1)+1.0;

double fs = 44100;

phase_t hz_to_delta( double hz )
{
    return maxphase*hz/fs+0.5;
}

float sample_phase( phase_t phase )
{
    return sin( phase/maxphase*M_TWOPI );
}

int main( void )
{
    long i;
    phase_t delta, iphase = 0;

    delta = hz_to_delta( 500.0 );
    for( i=0; i<fs; ++i )
        printf( "%e\n", sample_phase( iphase += delta ) );

    delta = hz_to_delta( 1000.0 );
    for( i=0; i<fs; ++i )
        printf( "%e\n", sample_phase( iphase += delta ) );

    return 0;
}

最简单的方法是注意频率是根据定义的相位导数。因此,您可以定义每个样本的频率,然后将其整合以获得相位。这使相位保持连续。例如,在 matlab 中,这看起来像这样:

% The two frequencies of interest.
f1 = 500;
f2 = 1000;

% Sample frequency and locations.
fs = 50 * max( f1, f2 );
T = 0.01;
t = 0 : 1/fs : T;

% Make the first half of the samples have frequency f1
% and the rest have frequency f2.
f = zeros( size( t ) );
f( 1 : floor(length(t)/2) ) = f1;
f( floor(length(t)/2) + 1 : end ) = f2;

% Now integrate the above to get the phase.
phi = cumsum( 2*pi * f ) / fs;

x = cos( phi );
plot( t, x );

在此处输入图像描述

除了增加相位(而不是增加时间和乘以频率,这可能会导致锯齿)之外,还请注意,您的 trig 函数的输入可能需要进行范围限制(或包装),以防止范围减小导致 trig 函数中的精度损失执行。

在增量相位后,我通常将相位(通过循环环绕)限制在 -2pi 和 2pi 之间,或者 -pi 到 +pi 等。