如何串联正弦波而没有相位跳变

时间:2018-06-24 02:50:05

标签: python numpy signal-processing pyaudio sine

我需要制作一个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等附近存在不连续性。

2 个答案:

答案 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

enter image description here

为什么希尔伯特变换?因为它可以在某种程度上处理调制信号:示例(蓝色原色,绿色来自重构相位):

enter image description here

生成信号的代码:

# 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 的参数不受限制地增长,从而导致数值误差。

希望事情变得更清楚