如何在pygame中连续播放声音样本?

问题描述

我想播放一个连续的正弦波(暂时),它根据鼠标指针位置(暂时)改变其频率(暂时)。因此,我生成并播放一帧长度的波浪(以下代码中为 1/2 秒)。由此产生的声音在声音中有很小的间隙,足以非常引人注目。增加单个声音的持续时间没有帮助,改变缓冲区大小没有帮助,使用 dtick 而不是 dt 也没有帮助。

有没有办法以这种方式获得连续的声音?或者其他方式?

谢谢

版本:Python 3.9.3,pygame 2.0.1

最小(仍然很长)代码

import numpy as np
import pygame
from pygame import Color
from pygame.locals import KEYDOWN,KMOD_CTRL,QUIT,K_q

fade_out = np.linspace(1,100)
fade_in = np.linspace(0,1,100)

bits = 16
sample_rate = 44100
max_sample = 2 ** (bits - 1) - 1


def gen_sound(duration,freq):
    n_samples = int(duration / 1000 * sample_rate)
    buf = np.zeros((n_samples,2),dtype=np.int16)
    buf[:,0] = (
        0.25
        * max_sample
        * np.sin(2 * np.pi * freq[0] * np.arange(n_samples) / sample_rate)
    )
    buf[:,1] = (
        0.25
        * max_sample
        * np.sin(2 * np.pi * freq[1] * np.arange(n_samples) / sample_rate)
    )
    buf[-100:,0] = (buf[-100:,0] * fade_out).astype(int)
    buf[-100:,1] = (buf[-100:,1] * fade_out).astype(int)
    buf[:100,0] = (buf[:100,0] * fade_in).astype(int)
    buf[:100,1] = (buf[:100,1] * fade_in).astype(int)

    return pygame.sndarray.make_sound(buf)


def main(argv: list = None):

    # initialize sound
    pygame.mixer.pre_init(sample_rate,-bits,2,512)

    # initialize pygame
    pygame.init()
    size = (1000,800)
    surf = pygame.display.set_mode(size)
    surf.fill(Color("black"))
    clock = pygame.time.Clock()

    # frames per second and duration of a frame in ms
    FPS = 2
    dt = 1000 / FPS
    dtick = 1000 / FPS
    # position of the mouse pointer,None means outside of the window
    mpos = None

    # event loop
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == QUIT:
                return
            # shutdown with C-q
            if e.type == KEYDOWN:
                if e.mod & KMOD_CTRL and e.key == K_q:
                    return

        if pygame.mouse.get_focused():
            mpos = pygame.mouse.get_pos()
        else:
            mpos = None

        freq = mpos if mpos else (440,440)
        sound = gen_sound(dt,freq)
        sound.play(loops=0,fade_ms=0)

        pygame.display.update()
        dtick = clock.tick(FPS)


if __name__ == "__main__":
    main()

解决方法

播放时可以直接修改Sound数据,无需每帧调用.play()

def gen_sound(buf,freq):
    buf[:,0] = (
            0.25
            * max_sample
            * np.sin(2 * np.pi * freq[0] * np.arange(n_samples) / sample_rate)
    )
    buf[:,1] = (
            0.25
            * max_sample
            * np.sin(2 * np.pi * freq[1] * np.arange(n_samples) / sample_rate)
    )

并在主要:

def main(argv: list = None):
    # ...

    clock = pygame.time.Clock()

    buf = np.zeros((n_samples,2),dtype=np.int16)
    sound = pygame.sndarray.make_sound(buf)
    buf = pygame.sndarray.samples(sound) # This is required because make_sound copies the array,but we want a reference
    gen_sound(buf,(440,440))
    sound.play(loops=-1,fade_ms=0)

    # ...
    # event loop
    while True:
        # ...

        freq = mpos if mpos else (440,440)
        gen_sound(buf,freq)

        pygame.display.update()
        dtick = clock.tick(FPS)

请注意,这会遇到所描述和解决的问题 here,例如正弦波转换不平滑。但这超出了这个问题和我的知识范围。