1

I'm currently working on a game in Pygame, and I've been trying to think of a way to have a music track that loops at a point that isn't the beginning of the track. So essentially, it plays an introduction, then moves onto another section that repeats without revisiting that introduction.

I've thought of a couple of ways that almost worked, but they have problems.

The first was to have two separate audio files for the introduction and the looping section, then to use pygame.music.set_endevent(), and just load the second audio file once the first is finished. This left quite an obvious gap and click though.

The second was to also use two audio files but to queue in the second as the first is loaded. The problem with this is that it seems like you can't change the play mode from 0 (play once) to -1 (looping) for the new queued track...

I feel like there has to be a way of doing this, I'd really appreciate any help.

Kingsley
  • 14,398
  • 5
  • 31
  • 53
MrLog
  • 45
  • 1
  • 6
  • Have you tried setting a timer to start the repeating-track playing (in another mixer channel) timed such-that it synchronises better with the end of the intro-track? – Kingsley Feb 23 '20 at 22:05
  • That sounds like a good idea, but I'm not entirely sure how that would look in code. Could you give an example of how to get the timer to work and how you'd make it use multiple mixer channels? – MrLog Feb 25 '20 at 00:13

1 Answers1

1

In the example below, PyGame's sound channels are used for multiple tracks. Here an event is created, such that after 1500 milliseconds, a second sound in played (at the same time as the looping track).

For your suggested use-case, the code could play the intro-music at start, but also set an event-timer for /intro-length/ milliseconds in the future. When that timer-event is received, the looping-part of your music could play continuously, as the intro should have just stopped. Using multiple channels, it should not matter if the two sounds overlap by a few milliseconds (of silence/fadeout), as long as the user does not perceive it of course! Maybe it will be tricky to get the timing 100% correct on vastly different systems, but it should get you close.

Note that in the example, the sounds are already initialised into PyGame Sound objects, I expect this would cut-down on startup latency.

import pygame

# Window size
WINDOW_WIDTH    = 400
WINDOW_HEIGHT   = 400

DARK_BLUE = (   3,   5,  54)

### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("Multi Sound with Timer")

### sound
# create separate Channel objects for simultaneous playback
channel1 = pygame.mixer.Channel(0) # argument must be int
channel2 = pygame.mixer.Channel(1)

# Rain sound from: https://www.freesoundslibrary.com/sound-of-rain-falling-mp3/ (CC BY 4.0)
rain_sound = pygame.mixer.Sound( 'rain-falling.ogg' )
channel1.play( rain_sound, -1 )   # loop the rain sound forever

# Car Horn sound from: https://www.freesoundslibrary.com/car-horn-sound-effect/ (CC BY 4.0)
horn_sound = pygame.mixer.Sound( 'car-horn.ogg' )

# Create a timer, which will (after the delay-time) post an event to the main loop
pygame.time.set_timer( pygame.USEREVENT, 1500 )   # play the horn in 1500 milliseconds



### Main Loop
clock = pygame.time.Clock()
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.USEREVENT ):
            # Timer expired, play the sound
            channel2.play( horn_sound )

    # Movement keys
    #keys = pygame.key.get_pressed()
    #if ( keys[pygame.K_UP] ):
    #    print("up")

    # Update the window, but not more than 60fps
    window.fill( DARK_BLUE )
    pygame.display.flip()

    # Clamp FPS
    clock.tick_busy_loop(60)

pygame.quit()
Kingsley
  • 14,398
  • 5
  • 31
  • 53
  • Thanks for giving a thorough example like that, it gave me everything I needed and now I've actually got it working. Cheers! – MrLog Mar 02 '20 at 13:29
  • Okay actually I have an issue now - occasionally the timer seems to go little bit out of sync for whatever reason (seems random), and the music loops a little bit before or after it should. This is something that is more noticeable the longer the loop time, because it has the chance to get more out of sync. Is there a way of getting the playback position in milliseconds of a sound, that way I'd be able to sync the timer back up every so often? I know that it's possible to do with `pygame.mixer.music.get_pos()`, is there something like that that that'd work here? – MrLog Apr 03 '20 at 12:02
  • @MrLog - Well obviously the plain mixer doesn't have that function. Could you use something `Channel.set_endevent()`, and then re-sync both channels when one ends? Perhaps with some kid of fadeout or suchlike? – Kingsley Apr 03 '20 at 19:12
  • there isn't a way of setting the playback starting point with what `Mixer.play()` right? Would that be necessary to sync the sound from what you're describing or am I misinterpreting something? – MrLog Apr 05 '20 at 17:46
  • Or perhaps are you implying I split each of my tracks up into smaller segments? Because that would be a lot more work than I'd like to do ideally... – MrLog Apr 05 '20 at 20:47