Fade Between Two Music Tracks in-progress in Pygam

2019-02-06 19:41发布

问题:

My intention is to have two music tracks, which are similar in nature, fade between each other at various times. When such a fade occurs, one music track should fade from full volume to muted in a short period of time, and, simultaneously, the other track should fade from 0 to 100 and continue playing from the same time index. They must be able to do this dynamically at any time - when a certain action occurs, the fade will occur and the new track will start playing at the same position that the other one left off at.

This might be plausible by either using volume manipulation or by starting and stopping the music (however, it appears that only a "fadeout" option exists, and there is a lack of a "fadein" option). How can I do this? What is the best method, if any, that exists? If it is impossible using Pygame, alternatives to Pygame are acceptable.

回答1:

Try this, it's pretty straight forward..

import pygame

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

# Maybe you can subclass the pygame.mixer.Sound and
# add the methods below to it..
class Fader(object):
    instances = []
    def __init__(self, fname):
        super(Fader, self).__init__()
        assert isinstance(fname, basestring)
        self.sound = pygame.mixer.Sound(fname)
        self.increment = 0.01 # tweak for speed of effect!!
        self.next_vol = 1 # fade to 100 on start
        Fader.instances.append(self)

    def fade_to(self, new_vol):
        # you could change the increment here based on something..
        self.next_vol = new_vol

    @classmethod
    def update(cls):
        for inst in cls.instances:
            curr_volume = inst.sound.get_volume()
            # print inst, curr_volume, inst.next_vol
            if inst.next_vol > curr_volume:
                inst.sound.set_volume(curr_volume + inst.increment)
            elif inst.next_vol < curr_volume:
                inst.sound.set_volume(curr_volume - inst.increment)

sound1 = Fader("1.wav")
sound2 = Fader("2.wav")
sound1.sound.play()
sound2.sound.play()
sound2.sound.set_volume(0)

# fading..
sound1.fade_to(0)
sound2.fade_to(1)


while True:
    Fader.update() # a call that will update all the faders..


回答2:

Pseudocode:

track1 = ...
track2 = ...

track1.play_forever()
track1.volume = 100
track2.play_forever()
track2.volume = 0

playing = track1
tracks = [track1, track2]


def volume_switcher():
    while True:
        playing.volume = min(playing.volume + 1, 100)

        for track in tracks:
            if track != playing:
                track.volume = max(track.volume - 1, 100)

        time.sleep(0.1)

Thread(target=volume_switcher).start()


回答3:

So it looks like what you want to do in pygame is create two 'Sound' objects, and create a linear interpolation on the volume between the two of them.

I would create two vectors, each from [0,100], and relate them inversely with some constant. So when sound A is at 100, sound b is at 0. Then when an action occurs, you modify the constant.

t=0
A: [0 ... 100]
B: [0 ... 100]

t=1
ACTION

t=1.1
A:[0 .. 50 .. 100]
B:[0 .. 50 .. 100]

t=2
A:[0 ... 100]
B:[0 ... 100]

Now some code. I'm not familiar with pygame, but this should put you on the right track.

class Song(object):
    def __init__(self, songfilename):
        self.song = pygame.mixer.Sound(songfilename)

    def setVolume(self, somenumber):
        #number validation
        #possibly do some volume curve here if you wanted
        self.song.set_volume(somenumber)

class SongFader(object):
    def __init__(self, song1, song2):
        self.song1 = song1
        self.song2 = song2
        self.__xAxisMax = 100
        self.__xAxisMin = 0

    def fade(self, xaxis):
        assert(self.__xAxisMin <= xaxis <= self.__xAxisMax) 
          #could be any numbers you want. 
          #i chose 0-100 for convenience
        self.song1.setVolume(xaxis)
        self.song2.setVolume(self.__xAxisMax-xaxis)

song1 = Song('Song1.wav')
song2 = Song('Song2.wav')
fader = SongFader(song1, song2)

#Inside some event loop when you action is triggered
fader.fade(100) #Only song2 is playing
fader.fade(50)  #Songs are evenly split
fader.fade(0)   #Only left song is playing

edit

The linear interpolation is probably the more important concept here, so i have modified the fader class, with inspiration from Eric 's thread idea.

class SongFader(object):
    def __init__(self, song1, song2):
        self.song1 = song1
        self.song2 = song2
        self.lefttoright = False
        self.starttime = 0
        self.endtime = 0


    def fade(self, starttime, fadeleft):
        self.lefttoright = fadeleft == True #Being verbose here
        self.starttime = starttime #assuming time is in millis
        self.endtime = starttime + 1000
        Thread(target = self.fadeHelper).start()

    #this is where you define how the two songs are faded
    def fadeHelper(self):
        #if using thread, make sure you mutex the 'self.' variables
        starttime = self.starttime
        endtime = self.endtime
        lefttoright = self.lefttoright
        while starttime < endtime:
            fadevalue = (starttime - endtime) / 1000 #a val between [0,1]
            if lefttoright:
                self.song1.setVolume(fadevalue)
                self.song2.setVolume(1-fadevalue)
            else:
                self.song1.setVolume(1-fadevalue)
                self.song2.setVolume(fadefalue)
            starttime = getGameTimeFromSomewhere()


回答4:

This isn't exactly an answer to the question, but for future-googlers I wrote a script to fade-in my music from volume 0 in the morning and this is what I used:

max_volume = 40 
current_volume = 0

# set the volume to the given percent using amixer
def set_volume_to(percent):
    subprocess.call(["amixer", "-D", "pulse", "sset", "Master", 
                     str(percent) + "%", "stdout=devnull"])

# play the song and fade in the song to the max_volume 
def play_song(song_file):
    global current_volume
    print("Song starting: " + song_file)
    pygame.mixer.music.load(song_file)
    pygame.mixer.music.play()

    # gradually increase volume to max
    while pygame.mixer.music.get_busy():
        if current_volume < max_volume: 
            set_volume_to(current_volume)
            current_volume += 1

        pygame.time.Clock().tick(1)

 play_song("foo.mp3")