4

I currently try to repeat a sound every x ms - where x is dependent on an UDP packet I receive via socket - and I decided to use pygame for that. I used this SO answer to repeat something every x ms: https://stackoverflow.com/a/18954902/3475778

But now I have the problem, that the sound is played very irregular and made a minimal-working example where the problem persists:

import pygame
from pygame.locals import *

pygame.mixer.init()
sound = pygame.mixer.Sound('sound.wav')

def play_sound():
    sound.stop()
    sound.play()

pygame.init()
clock = pygame.time.Clock()

pygame.time.set_timer(USEREVENT+1, 200)

while True:
    # clock.tick(30)
    for event in pygame.event.get():
        if event.type == USEREVENT+1:
            play_sound()

Here is the waveform of what I have recorded from the script via Audacity:

enter image description here

You see that for some reason some samples were played longer than the others. Not very nice for some kind of metronome I want to build.

edit UPDATE: It is not a problem of pygame.time.set_timer, because this code doesn't solve the problem and doesn't rely on pygame.time.set_timer:

import pygame
from datetime import datetime

d = datetime.now()

pygame.mixer.init()
sound = pygame.mixer.Sound('horn_short.wav')

pygame.init()

while True:
    if (datetime.now() - d).total_seconds() > 0.2:
        sound.play()
        d = datetime.now()

has the same problem. The Problem is also under Ubuntu 16.04, Python 3.5 64bit (Anaconda) and a fresh installed pygame.

Community
  • 1
  • 1
capitalg
  • 593
  • 2
  • 9
  • 18
  • 2
    Not at my computer at the moment, but my guess is that the resolution of pygame's timer is not great if it is built on SDL's timer. Try using the python `time` module and query `time.time()` in the while loop, comparing the previous and the current time until x ms have elapsed. – CodeSurgeon Dec 25 '16 at 13:09
  • Thanks for your repsonse, I have also though of this. I'll make an issue on the pygame repository. – capitalg Dec 25 '16 at 13:57
  • 1
    This doesn't solve the problem, see update above. – capitalg Dec 25 '16 at 17:31
  • I put `print()` in `play_sound` and as for me this function is executed with correct time but mixer has problem with running music. As I know mixer runs sound in separated thread and maybe it has problem with communication with thread. – furas Dec 25 '16 at 19:16
  • from [PyGame mixer doc](http://pygame.org/docs/ref/mixer.html): `"NOTE: Not to get less laggy sound, use a smaller buffer size."`. Maybe it can help. BTW: I don't know if `Sound()` keep all file in memory or read it everytime you use `play()` but you could use some buffer in memory. – furas Dec 25 '16 at 19:20
  • @capitalg [This](http://stackoverflow.com/q/18273722/2588654) question has some answers that suggest some pygame buffer parameters you can try out which decrease, but do not eliminate, the amount of delay. I do have a solution that works using PyAL (python bindings to OpenAL) that does play the sound nicely and can be used with pygame, but I am not sure if you want to necessarily install both the OpenAL library (for Ubuntu 16.04, try `sudo apt-get install libalut-dev`) and PyAL (using `pip install PyAL`) on your system. I can post that code if you would like even if it is not a direct answer. – CodeSurgeon Dec 26 '16 at 04:40
  • 1
    The Problem is not a delay in the sound, the problem is that if I fire a Sound every 200 ms it works 10 times but the 11th time it takes 450 ms until i hear it. I now have moved the playback of regular timed stuff to simpleaudio until I have a fix for this in PyGame. – capitalg Dec 26 '16 at 11:15

3 Answers3

0

Here is an idea for an alternative approach. If the goal is to play a sound in regular intervals, you might get better results if you (dynamically) pad the sound to the desired interval length, and then simply loop it with Sound.play(loops=-1).

If there are just a handful of valid intervals, it might be easiest to store dedicated sound files and load the appropriate one.

Otherwise, the pygame.sndarray module provides access to a numpy array of the raw sound data, which makes it possible to dynamically generate sound objects of the desired length:

import pygame
import numpy


# Helper function
def getResizedSound(sound, seconds):

    frequency, bits, channels = pygame.mixer.get_init()

    # Determine silence value
    silence = 0 if bits < 0 else (2**bits / 2) - 1

    # Get raw sample array of original sound
    oldArray = pygame.sndarray.array(sound)

    # Create silent sample array with desired length
    newSampleCount = int(seconds * frequency)
    newShape = (newSampleCount,) + oldArray.shape[1:]
    newArray = numpy.full(newShape, silence, dtype=oldArray.dtype)

    # Copy original sound to the beginning of the
    # silent array, clipping the sound if it is longer
    newArray[:oldArray.shape[0]] = oldArray[:newArray.shape[0]]

    return pygame.mixer.Sound(newArray)


pygame.mixer.init()
pygame.init()

sound = pygame.mixer.Sound('sound.wav')

resizedSound = getResizedSound(sound, 0.2)
resizedSound.play(loops=-1)

while True:
    pass

Using just pygame (and numpy), this approach should have a good chance for an accurate playback.

Meyer
  • 1,662
  • 7
  • 21
  • 20
0

For me the thing worked much better, if i did:

pygame.mixer.pre_init(44100, -16, 2, 256)

before any of the pygame init functions.

Fusselgesicht
  • 189
  • 2
  • 6
0

Ok, you should try using the other way to load a sound:

pygame.mixer.music.load(file=file_directory str)

To play the sound use:

pygame.mixer.music.play(loops=*optional* int, start=*optional* float)

Your code may look like this:

import pygame

pygame.init()
pygame.mixer.init()

sound = pygame.mixer.music.load('sound.wav')
def playSound():
    sound.pause()
    sound.play()

while True:
    pass

For me, that worked better but I am on python 3.7.2 . I don't know about python 3.5, but there aren't many difference between 3.5 and 3.7.2. That should work!

Song
  • 298
  • 5
  • 20