我需要制作一个python脚本,它生成给定频率的正弦波并使用pyaudio(阻塞模式)播放它们,我还需要能够在运行时更改该频率,对其进行调制并使用pyqttraph绘制现在我有一个线程生成数据块,我的方法“连接”这些正弦是为了得到FFT,然后计算角度(NUMPY。角度),把它存储在变量中,并用它作为下一个块的相位偏移量,但是我没有得到我预期的结果,也许我丢失了一些东西或者把它们混合起来。

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()

演示图
在链接的图像中,可以看到x=1024、x=2048等处存在不连续性。

最佳答案

你正在用

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

其中t范围大于[0 .. CHUNK/RATE)
启动下一个块时,t将重置为零要生成连续波形,需要修改con以生成与上一个样本相同的结果相位值。
使用fft是行不通的,因为你产生的信号不是采样窗口的精确倍数,加上你实际上对采样窗口末尾的相位感兴趣,而不是对开始的相位感兴趣。
相反,你只需要生成函数在t=t_端的相位值,模2π。
也就是说,你可以简单地使用:
con = 2.0 * np.pi * f * CHUNK/RATE + con

但是这个值会增加,如果你把很多频率很高的块连接在一起,可能会导致最终的数值问题。因为正弦函数是周期性的,所以只需要将结束相位正规化到0到2π的范围:
con = math.fmod(2.0 * np.pi * f * CHUNK/RATE + con, 2.0 * np.pi)

如果将生成函数修改为:
    a * sin(2π * (f * t + con))

然后,con表示前一个夹头的一个完整循环的分数,你可以避免2π的模除,这可能会稍微提高精度。
con = math.modf(f * CHUNK/RATE + con)[0]

试图更清楚地解释:
注意:此技术之所以有效,是因为您确切地知道前一个块的生成公式,并且正在生成以下块。如果这两个条件中的任何一个发生变化,则需要使用不同的技术来匹配块。
前一个块是使用以下序列的sin()生成的:
2πf₁*0/RATE+con₁, 2πf₁*1/RATE+con₁, ..., 2πf₁*1022/RATE+con₁, 2πf₁*1023/RATE+con₁

应该清楚的是,为了顺利过渡到下一个块,该块应该以sin()2πf₁*1024/RATE+con₁开始。
下一个块以sin()2πf₂*0/RATE+con₂开始。
因此,如果:
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函数中写入:
con = 2.0 * np.pi * f * CHUNK/RATE + con

这就是我在上面的答案中“不知从何而来”的等式。从这一点出发,由于sin()函数是2π周期性的,我只是执行模2π约化,以防止sin()的参数无界增长,从而导致数值不精确。
希望这能让事情更清楚。

关于python - 如何连接正弦波而没有相位跳变,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/51006591/

10-09 02:35