--- /dev/null
+#!/usr/bin/python3
+# (c) Copyright 2013 TJ <hacker@iam.tj>
+# Licensed on the terms of the GNU General Public License version 3 (see COPYING)
+#
+# Game engine: Sound channels
+
+import os, random, pygame
+import engine
+
+class Channel:
+ """ Represents an audio output. Can represent a user-created SDL channel or the single
+ Music channel (which can load/play Ogg, MP3 and other audio media). The Music channel
+ can be primed with a playlist which will shuffle and loop.
+ Many methods have dual functionality depending on whether the instance object represents
+ the Music channel or a Sound channel, since those PyGame Classes have some differences in
+ functionality.
+ """
+ # use integers to control volume which avoids float rounding error issues
+ MAX = 100
+ MIN = 0
+ volume_step = 5
+ def __init__(self, volume=1.0, channel_id=0, debug=False):
+ """ Constructor.
+ volume: an SDL/PyGame floating-point with range 0.00 to 1.00. It is stored internally as an
+ integer with range 0 to 100 to avoid rounding errors when stepping the volume.
+ channel_id: -1 == create the single Music channel. 0,1,2,3... one of several Sound channels.
+ debug: enables debug messages written to console
+
+ self.channel == None indicates this is the Music channel.
+ """
+ self.volume = int(volume*100)
+ self.channel_id = 0
+ self.debug = debug
+ self.paused = False
+ if channel_id == -1:
+ self.channel_id = channel_id
+ self.channel = None # flag to show this uses pygame.mixer.music
+ elif channel_id < pygame.mixer.get_num_channels():
+ self.channel_id = channel_id
+ self.channel = pygame.mixer.Channel(self.channel_id)
+
+ def set_volume(self, new_level):
+ """ Ensure the new level is within legal range and alter the mixer level """
+ if new_level >= Channel.MIN and new_level <= Channel.MAX:
+ self.volume = new_level
+ if self.channel == None:
+ pygame.mixer.music.set_volume(self.volume/100)
+ else:
+ self.channel.set_volume(self.volume/100)
+ if self.debug: engine.debug_pr("Channel.set_volume(", self.volume, ")")
+
+ def volume_up(self):
+ """ Increase volume by a single step """
+ if self.volume + self.volume_step <= Channel.MAX:
+ self.set_volume(self.volume + self.volume_step)
+
+ def volume_down(self):
+ """ Decrease volume by a single step """
+ if self.volume - self.volume_step >= Channel.MIN:
+ self.set_volume(self.volume - self.volume_step)
+
+ def get_volume(self):
+ return self.volume
+
+ def queue(self, sound):
+ """ Sound channels can queue one Sound in addition to the currently playing Sound """
+ if self.channel != None:
+ self.channel.queue(sound)
+
+ def get_queue(self):
+ """ Retrieve the queued Sound (if any) """
+ return self.channel.get_queue() if self.channel != None else None
+
+ def fadeout(self, time):
+ """ Fade out the sound nicely
+ time: milliseconds
+ """
+ if self.channel == None:
+ pygame.mixer.music.fadeout(time)
+ else:
+ self.channel.fadeout(time)
+
+ def play(self, sound, loops=0, maxtime=0, fade_ms=0):
+ """ Play a Music track or Sound
+ sound:
+ loops: number of repeats (after initial play - see pyGame API docs)
+ maxtime: seconds to play. 0 == to the end
+ fade_ms: fade-out time in milliseconds
+ """
+ if self.channel != None:
+ # allow 'sound' to be a path string or an object
+ sound = pygame.mixer.Sound(sound)
+ self.channel.play(sound, loops, maxtime, fade_ms)
+ else:
+ pygame.mixer.music.load(sound)
+ pygame.mixer.music.play(loops)
+
+ def pause(self):
+ """ Toggle the play/pause state of the channel """
+ if self.paused:
+ self.paused = False
+ if self.channel == None:
+ pygame.mixer.music.unpause()
+ else:
+ self.channel.unpause()
+ else:
+ self.paused = True
+ if self.channel == None:
+ pygame.mixer.music.pause()
+ else:
+ self.channel.pause()
+
+ def playlist(self, path):
+ """ Create a playlist from a given file-system directory
+ path: absolute or relative path to a directory containing *only* valid media files (Ogg, MP3, etc.)
+
+ See pygame.mixer.music API docs
+ """
+ self.music_path = path
+ # build a list of the filenames in the directory (ignores sub-directories)
+ self.playlist = [ f for f in os.listdir(self.music_path) if os.path.isfile(os.path.join(self.music_path, f)) ]
+ random.shuffle(self.playlist) # randomise the play order
+
+ if self.debug: engine.debug_pr("playlist=", self.playlist)
+
+ self.playlist_index = -1 # flag for 'start from the beginning of list'
+ self.playlist_next() # begin play-back
+ # generate an event when the current track ends
+ pygame.mixer.music.set_endevent(pygame.USEREVENT)
+
+ def playlist_next(self):
+ """ Begin playing the next track in the playlist. Shuffle and loop when it reaches the end of the list. """
+ if self.playlist_index == len(self.playlist)-1:
+ random.shuffle(self.playlist)
+ self.playlist_index = self.playlist_index + 1 if self.playlist_index < len(self.playlist)-1 else 0
+ if self.debug: engine.debug_pr("Channel.playlist_next() = [%d] %s" %(self.playlist_index, self.playlist[self.playlist_index] ))
+
+ pygame.mixer.music.load(os.path.join(self.music_path, self.playlist[self.playlist_index]))
+ pygame.mixer.music.play()
+