我需要制作一个python脚本,该脚本生成给定频率的正弦波并使用pyaudio(阻止模式)播放它们,我还需要能够在运行时更改此频率,对其进行调制并使用pyqtgraph对其进行绘制。现在,我有一个线程来生成数据块,而我的方法“连接”这些罪孽是要获取fft,然后计算角度(numpy.angle),将其存储在变量中,并将其用作下一个相位偏移量。块,但我没有得到预期的结果,也许我遗漏了一些东西或将它们混淆了。
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等附近存在不连续性。
答案 0 :(得分:3)
以下解决方案不是100%令人满意的,但是如果您能够在关节处丢弃每个片段的信号的话,似乎效果很好。
它使用Hilbert transform重建相位。不幸的是,在信号的边缘附近往往会出现伪像(例如,在图中红色信号的末尾),我只能通过将其切除来消除这种伪像。
>>> import numpy as np
>>> from scipy.signal import hilbert
>>>
# create two sinewaves and join them discarding some signal near the joint
# this is the black curve in the plot
>>> A = np.sin(np.linspace(0, 30, 10000))
>>> B = np.cos(np.linspace(0, 15, 10000))
>>> Cjump = np.concatenate([A[:-1000], B[1000:]])
>>>
# join the same sinewaves using Hilbert
>>> AP = np.unwrap(np.angle(hilbert(A)))
>>> BP = np.unwrap(np.angle(hilbert(B)))
>>> CP = np.concatenate([AP[:-1000], BP[1000:] - BP[1000] + AP[-1000]])
>>> C = np.cos(CP)
# this is the red curve in the plot
为什么希尔伯特变换?因为它可以在某种程度上处理调制信号:示例(蓝色原色,绿色来自重构相位):
生成信号的代码:
# original (blue):
>>> A = np.sin(np.convolve(np.random.uniform(0.2, 1, 10).repeat(1000), np.ones(10)/1000, 'same').cumsum())
>>> AP = np.unwrap(np.angle(hilbert(A)))
# recon (green):
>>> AR = np.cos(AP)
答案 1 :(得分:3)
您正在用
生成信号 a * sin(2πf * t + con)
其中t
的范围超过[0 .. CHUNK/RATE)
。
开始下一个块时,t
被重置为零。要生成连续波形,您需要修改con
以生成与上一个样本相同的结果相位值。
使用FFT无效,因为生成的信号不是采样窗口的精确倍数,而且您实际上对采样窗口末尾的相位感兴趣,而不是对采样窗口末尾的相位感兴趣。开始。
相反,您只需在t = t_end取生成函数的相位值,模为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()
的{{1}}开头
下一块从2πf₁*1024/RATE+con₁
的{{1}}开始。
因此,如果发生以下情况,我们将实现平稳过渡:
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
函数是2π周期的,所以我只是执行模2π约简,以防止con = 2.0 * np.pi * f * CHUNK/RATE + con
的参数不受限制地增长,从而导致数值误差。
希望事情变得更清楚