如何串联正弦波而没有相位跳变 [英] How to concatenate sine waves without phase jumps

查看:128
本文介绍了如何串联正弦波而没有相位跳变的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要制作一个python脚本,该脚本生成给定频率的正弦波并使用pyaudio(阻止模式)播放它们,我还需要能够在运行时更改此频率,对其进行调制并使用pyqtgraph对其进行绘制.现在,我有一个线程来生成数据块,而我的连接"那些罪恶的方法是获取fft,然后计算角度(numpy.angle),将其存储在变量中,并将其用作下一个相位偏移量.块,但我没有得到预期的结果,也许是我遗漏了一些东西或将它们混淆了.

I need to make a python script which generates sine waves of a given frequency and plays them using pyaudio (blocking mode), I also need to be able to change this frequency while it runs, modulate it and plot it using pyqtgraph. For now I have a thread generating chunks of data and my approach to 'connect' those sines was to get the fft and then calculate the angle (numpy.angle), store it in a variable and use it as the phase offset to the next chunk, but I'm not getting the results I expected, maybe I'm missing something or mixing them up.

import matplotlib.pyplot as plt
import numpy as np
import pyaudio

#-----------------------
CHUNK = 1024
RATE =  44100
CHANNELS = 2
FORMAT = pyaudio.paFloat32
#-----------------------

samples = int(CHUNK)
t = np.arange(samples) / RATE
con = 0


def generate_sine(a: float = 0.5, freq: float = 440.0):

    global con

    sine = a * np.sin(2.0 * np.pi * freq * t + con)

    # get the angle of the wave

    phase = np.angle(np.fft.fft(sine))

    # update ref var to generate subsequent sines
    # begining where the last ended

    con = phase[-1]

    return sine


def play_sine(data):

    pa = pyaudio.PyAudio()

    stream = pa.open(format=FORMAT,
                         channels=CHANNELS,
                         rate=RATE,
                         input=False,
                         output=True,
                         frames_per_buffer=CHUNK)

    stream.write(np.array(data).astype(np.float32).tostring())

    stream.close()

if __name__ == '__main__':

    f = 80

    chunks = generate_sine(freq=f)

    for i in range(0,4):

        chunks = np.concatenate((chunks, generate_sine(freq=f)))

    #for i in range(0,10):

    #play_sine(chunks)

    plt.plot(chunks)

    plt.show()

演示图

您可以在链接的imagem中看到x = 1024,x = 2048等不连续.

You can see in the imagem linked that there are discontinuities around x=1024, x=2048 and so on.

推荐答案

您正在使用

    a * sin(2πf * t + con)

其中t的范围是[0 .. CHUNK/RATE).

当您开始下一个块时,t将重置为零.要生成连续波形,需要修改con以生成与上一个样本相同的结果相位值.

When you start the next chunk, t is reset to zero. To generate a continuous waveform, you need to modify con to generate the same resulting phase value as the previous sample.

使用FFT无效,因为您生成的信号不是采样窗口的精确倍数,而且您实际上对采样窗口末尾的相位感兴趣,而不是对采样窗口末尾的相位感兴趣.开始.

Using an FFT isn't going to work because the signal you are generating isn't an exact multiple of the sample window, plus you are actually interested in the phase at the end of the sample window, not the phase at the beginning.

相反,您只需要在t = t_end取生成函数的相位值,以2π为模.

Instead, you simply need the generating function's phase value at t=t_end, modulo 2π.

即,您可以简单地使用:

Ie, you could simply use:

con = 2.0 * np.pi * f * CHUNK/RATE + con

但是该值会增加,如果将频率较高的大量块连接在一起,可能会最终导致数值问题.由于正弦函数是周期性的,因此您只需要将结束相位归一化为0到2π范围即可:

but that value will grow, possibly causing eventual numerical issues if you concatenate a lot of chunks together, where the frequencies are high. Since the sine function is periodic, you just need to normalize the end phase into the 0 to 2π range:

con = math.fmod(2.0 * np.pi * f * CHUNK/RATE + con, 2.0 * np.pi)


如果您将生成函数修改为:


If you modify your generation function to be:

    a * sin(2π * (f * t + con))

然后con表示从前一个卡盘继承来的整个周期的分数,可以避免将模除以2π,这可能会稍微提高精度.

then con represents the fraction of a full cycle carried forward from the previous chuck, and you can avoid the modulo division by 2π, which might improve accuracy slightly.

con = math.modf(f * CHUNK/RATE + con)[0]


尝试进行更清晰的解释:


Attempting a clearer explanation:

注意:这项技术之所以有效,是因为您完全知道前一个块的生成方程式,并且正在生成下一个块.如果这些条件中的任何一个发生了变化,那么您将需要一种不同的技术来匹配这些块.

Note: This technique only works because you exactly know the generation equation for the previous chunk, and are generating the following chunk. If either of those conditions change, you'll need a different technique to match the chunks.

上一个块是使用以下序列的sin()生成的:

The previous chunk is generated using the sin() of the following sequence:

2πf₁*0/RATE+con₁, 2πf₁*1/RATE+con₁, ..., 2πf₁*1022/RATE+con₁, 2πf₁*1023/RATE+con₁

很明显,为了顺利过渡到下一个块,该块应以2πf₁*1024/RATE+con₁sin()开头

It should be clear that, for a smooth transition to the next chunk, that chunk should start with the sin() of 2πf₁*1024/RATE+con₁

下一个块以2πf₂*0/RATE+con₂sin()开头.

因此,如果发生以下情况,我们将实现平稳过渡:

Therefore, we will have a smooth transition if:

2πf₂*0/RATE + con₂ = 2πf₁*1024/RATE + con₁

     0      + con₂ = 2πf₁*1024/RATE + con₁

              con₂ = 2πf₁*1024/RATE + con₁

可以在您的generate_sine函数中编写为:

which can be written in your generate_sine function as:

con = 2.0 * np.pi * f * CHUNK/RATE + con

在我上面的答案中,这是无处不在"的方程式.从那时起,由于sin()函数是2π周期的,所以我只是执行模2π折减,以防止sin()的参数不受限制地增长,从而导致数值误差.

which is the equation that came "out of nowhere" in my above answer. From that point, since the sin() function is 2π periodic, I'm just performing modulo 2π reductions, to keep the argument to sin() from growing without bound, causing numerical inaccuracies.

希望事情变得更清楚

这篇关于如何串联正弦波而没有相位跳变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆