Search code examples
pythonloopsaudiopygamemixer

Pygame - can I make music have an introduction and then a loop point?


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.


Solution

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