2 # (c) Copyright 2013 TJ <hacker@iam.tj>
3 # Licensed on the terms of the GNU General Public License version 3 (see COPYING)
5 # Game engine: Sound channels
7 import os, random, pygame
11 """ Represents an audio output. Can represent a user-created SDL channel or the single
12 Music channel (which can load/play Ogg, MP3 and other audio media). The Music channel
13 can be primed with a playlist which will shuffle and loop.
14 Many methods have dual functionality depending on whether the instance object represents
15 the Music channel or a Sound channel, since those PyGame Classes have some differences in
18 # use integers to control volume which avoids float rounding error issues
24 def __init__(self, volume=50, channel_id=0, debug=False):
26 volume: an SDL/PyGame floating-point with range 0.00 to 1.00. It is stored internally as an
27 integer with range 0 to 100 to avoid rounding errors when stepping the volume.
28 channel_id: -1 == create the single Music channel. 0,1,2,3... one of several Sound channels.
29 debug: enables debug messages written to console
31 self.channel == None indicates this is the Music channel.
37 self.channel_id = channel_id
38 self.channel = None # flag to show this uses pygame.mixer.music
39 elif channel_id < pygame.mixer.get_num_channels():
40 self.channel_id = channel_id
41 self.channel = pygame.mixer.Channel(self.channel_id)
43 self.set_volume(int(volume))
45 def set_volume(self, new_level):
46 """ Ensure the new level is within legal range and alter the mixer level """
47 if new_level >= Channel.MIN and new_level <= Channel.MAX:
48 self.volume = new_level
49 if self.channel == None:
50 pygame.mixer.music.set_volume(self.volume/100)
52 self.channel.set_volume(self.volume/100)
53 if self.debug: engine.debug_pr("Channel.set_volume(", self.volume, ")")
56 """ Increase volume by a single step """
57 if self.volume + self.volume_step <= Channel.MAX:
58 self.set_volume(self.volume + self.volume_step)
60 def volume_down(self):
61 """ Decrease volume by a single step """
62 if self.volume - self.volume_step >= Channel.MIN:
63 self.set_volume(self.volume - self.volume_step)
68 def queue(self, sound):
69 """ Sound channels can queue one Sound in addition to the currently playing Sound """
70 if self.channel != None:
71 self.channel.queue(sound)
74 """ Retrieve the queued Sound (if any) """
75 return self.channel.get_queue() if self.channel != None else None
77 def fadeout(self, time):
78 """ Fade out the sound nicely
81 if self.channel == None:
82 pygame.mixer.music.fadeout(time)
84 self.channel.fadeout(time)
86 def play(self, sound, loops=0, maxtime=0, fade_ms=0):
87 """ Play a Music track or Sound
89 loops: number of repeats (after initial play - see pyGame API docs)
90 maxtime: seconds to play. 0 == to the end
91 fade_ms: fade-out time in milliseconds
93 if self.channel != None:
94 # allow 'sound' to be a path string or an object
95 sound = pygame.mixer.Sound(sound)
96 self.channel.play(sound, loops, maxtime, fade_ms)
98 pygame.mixer.music.load(sound)
99 pygame.mixer.music.play(loops)
102 """ Toggle the play/pause state of the channel """
105 if self.channel == None:
106 pygame.mixer.music.unpause()
108 self.channel.unpause()
111 if self.channel == None:
112 pygame.mixer.music.pause()
116 def playlist(self, path):
117 """ Create a playlist from a given file-system directory
118 path: absolute or relative path to a directory containing *only* valid media files (Ogg, MP3, etc.)
120 See pygame.mixer.music API docs
122 self.music_path = path
123 # build a list of the filenames in the directory (ignores sub-directories)
124 self.playlist = [ f for f in os.listdir(self.music_path) if os.path.isfile(os.path.join(self.music_path, f)) ]
125 random.shuffle(self.playlist) # randomise the play order
127 if self.debug: engine.debug_pr("playlist=", self.playlist)
129 self.playlist_index = -1 # flag for 'start from the beginning of list'
130 self.playlist_next() # begin play-back
131 # generate an event when the current track ends
132 pygame.mixer.music.set_endevent(pygame.USEREVENT)
134 def playlist_next(self):
135 """ Begin playing the next track in the playlist. Shuffle and loop when it reaches the end of the list. """
136 if self.playlist_index == len(self.playlist)-1:
137 random.shuffle(self.playlist)
138 self.playlist_index = self.playlist_index + 1 if self.playlist_index < len(self.playlist)-1 else 0
139 if self.debug: engine.debug_pr("Channel.playlist_next() = [%d] %s" %(self.playlist_index, self.playlist[self.playlist_index] ))
141 pygame.mixer.music.load(os.path.join(self.music_path, self.playlist[self.playlist_index]))
142 pygame.mixer.music.play()