[pygame] Confusing values from mixer.Channel.get_sound()

13 views
Skip to first unread message

Pavils Jurjans

unread,
Dec 25, 2020, 9:45:28 AM12/25/20
to pygame...@seul.org
I am getting strange behavior when reading channel.get_sound() value from a supposedly silent channel.

I first play a sound (get the channel object in return), and let it play until the end. channel.get_sound() returns None, as it should. Then I play this sound a second time (storing the returned channel object in another variable). While the sound is still playing, if I query the first channel.get_sound(), I get the reference to the sound object.

Is this a bug or it's by design? This behavior messes up my application logic, because I store the returned channel objects to query later if the sound is still playing on them.

I tested the problem on both Pygame 1.9.6 and 2.0.0.

Here's my test code:

import pygame
from time import sleep

mixer = pygame.mixer
mixer.pre_init(44100, -16, 1, 8192)
mixer.init()
mixer.set_num_channels(10)
pygame.init()

dog = mixer.Sound('dog.ogg')
dog = mixer.Sound('apps/sounds/media/animal: dog.ogg')

# Test #1: use channel.get_sound() to see what sound the channel is playing. It should return None, when no sound is playing. This test passes.
def channel_test_1():
    channel1 = dog.play()
    sleep(1)
    print('After 1 second:', channel1.get_sound() == dog)
    sleep(6)
    print('After 7 seconds:', channel1.get_sound() == dog)

# Test #2: play sound on one channel. Let it play to the end. The channel1.get_sound() returns None. Then play the sound on channel2. The channel1.get_sound() should still return None, however once the sound starts playing on channel2, the channel1.get_sound() is no more None. This test fails.
def channel_test_2():
    print('Play the sound on channel1')
    channel1 = dog.play()
    sleep(1)
    print('After 1 second: channel1 - ', channel1.get_sound() == dog)
    sleep(6)
    print('After 7 seconds: channel1 - ', channel1.get_sound() == dog)
    print('Play the sound on channel2')
    channel2 = dog.play()
    sleep(1)
    print('After 1 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
    sleep(3)
    print('After 4 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
    sleep(3)
    print('After 7 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
 
channel_test_2()

Here are my results for the channel_test_2():

>>> channel_test_2()
Play the sound on channel1
After 1 second: channel1 -  True
After 7 seconds: channel1 -  False
Play the sound on channel2
After 1 seconds: channel1 -  True , channel2 -  True
After 4 seconds: channel1 -  True , channel2 -  True
After 7 seconds: channel1 -  False , channel2 -  False


The dog.ogg sound is 5.5 seconds long. For the sake of complete representation, I attach the audio file of this good boy to this post.
dog.ogg

Greg Ewing

unread,
Dec 25, 2020, 7:49:42 PM12/25/20
to pygame...@seul.org
On 26/12/20 3:38 am, Pavils Jurjans wrote:
> I first play a sound (get the channel object in return), and let it play
> until the end. channel.get_sound() returns None, as it should. Then I
> play this sound a second time (storing the returned channel object in
> another variable). While the sound is still playing, if I query the
> first channel.get_sound(), I get the reference to the sound object.

Because you started playing the second sound after the first one
finished, it re-used the same channel.

I added a couple of lines to print out the repr of the sound
object returned by each play() call. This is what I got:

Play the sound on channel1
channel1 = <Channel object at 0x10ddf1030>
After 1 second: channel1 - True
After 7 seconds: channel1 - False
Play the sound on channel2
channel2 = <Channel object at 0x10ddf1030>
After 1 seconds: channel1 - True , channel2 - True
After 4 seconds: channel1 - True , channel2 - True
After 7 seconds: channel1 - False , channel2 - False

You can see that channel1 and channel2 are actually the same
channel object.

I don't think there's anything you can do about that; you'll
have to re-think your method of detecting whether a sound is
still playing.

Note that you can use Channel.set_endevent() to arrange for an
event to be posted when a sound finishes playing.

--
Greg

Pavils Jurjans

unread,
Dec 26, 2020, 4:58:12 AM12/26/20
to pygame...@seul.org
I actually was aware that such reuse could take place, so I did check if they point to the same object. But in my case, those reprs don't give the same output as in your case.

Here's my updated code and output:

def channel_test_2():
    print('Play the sound on channel1')
    channel1 = dog.play()
    print('channel1=', channel1)
    sleep(1)
    print('After 1 second: channel1 - ', channel1.get_sound() == dog)
    sleep(6)

    print('After 7 seconds: channel1 - ', channel1.get_sound() == dog)
    print('Play the sound on channel2')
    channel2 = dog.play()
    print('channel2=', channel2)

    sleep(1)
    print('After 1 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
    sleep(3)
    print('After 4 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
    sleep(3)
    print('After 7 seconds: channel1 - ', channel1.get_sound() == dog, ', channel2 - ', channel2.get_sound() == dog)
    print('channel1=', channel1)
    print('channel2=', channel2)
    print('Are they the same?', channel1 == channel2)

Play the sound on channel1
channel1= <Channel object at 0xb642bfb0>

After 1 second: channel1 -  True
After 7 seconds: channel1 -  False
Play the sound on channel2
channel2= <Channel object at 0xb5563450>

After 1 seconds: channel1 -  True , channel2 -  True
After 4 seconds: channel1 -  True , channel2 -  True
After 7 seconds: channel1 -  False , channel2 -  False
channel1= <Channel object at 0xb642bfb0>
channel2= <Channel object at 0xb5563450>
Are they the same? False

As you can see, in my case the channels point to different addresses, but still behave as if they are linked. Maybe it is important on what platform I am running this code?
My platform:
- Raspberry Pi Zero W
- Raspbian GNU/Linux 10 (buster)
- Python 3.7.3
- pygame 2.0.0 (SDL 2.0.9)

I am aware of the channel end_event but since the number of custom event IDs is very limited, I had to implement my own solution (a wrapper class for the Channel object) for executing a function when the sound has finished playing, using a timer object, set to sound.get_length(). I have now implemented extra property in my wrapper class that tracks if the sound is currently playing. This works fine, but it still remains a mystery, why those channels behave as they do, even though they point to different addresses (in my case).

Greg Ewing

unread,
Dec 26, 2020, 8:59:07 AM12/26/20
to pygame...@seul.org
On 26/12/20 10:57 pm, Pavils Jurjans wrote:
> As you can see, in my case the channels point to different addresses,
> but still behave as if they are linked. Maybe it is important on what
> platform I am running this code?

Perhaps. I suspect they still refer to the same channel id, even
though they're different objects. Unfortunately there doesn't seem to
be a way to get the channel id from a channel object, so I can't
think of a way to test that.

> I am aware of the channel end_event but since the number of custom event
> IDs is very limited, I had to implement my own solution

Yeah, it's annoying that you can't specify any other data to attach
to the event. That part of the API doesn't seem to have been thought
out very well.

--
Greg

Reply all
Reply to author
Forward
0 new messages