我需要制作一个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/