简体   繁体   中英

How to play sound samples continuously in pygame?

I want to play a continuous sine wave (for now) which changes its frequency (for now) according to the mouse pointer position (for now). So, I generate and play a wave of the length of one frame (1/2s in the code below). The resulting sound has small gaps in the sound, enough to be very notable. Increasing the duration of the individual sounds does not help, changing the buffer size does not help, using dtick instead of dt does not help.

Is there a way to achieve a continuous sound this way? Or any other way?

Thank you

Versions: Python 3.9.3, pygame 2.0.1

Minimal (still lengthy) code:

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

You can directly modify the Sound data while playing it, preventing the need to call .play() every frame.

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

and in main:

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)

Note that this will run into the problem desribed and solved here , eg sin-wave transitions aren't smooth. But that is out of scope of this question and my knowledge.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM