engine: convert to 'new style' classes
[base2-runner.git] / engine / Channel.py
1 #!/usr/bin/python3
2 # (c) Copyright 2013 TJ <hacker@iam.tj>
3 # Licensed on the terms of the GNU General Public License version 3 (see COPYING)
4 #
5 # Game engine: Sound channels
6
7 import os, random, pygame
8 import engine
9
10 class Channel(object):
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
16  functionality.
17  """
18  # use integers to control volume which avoids float rounding error issues
19  MAX = 100
20  MIN = 0
21  volume = 50
22  volume_step = 5
23
24  def __init__(self, volume=50, channel_id=0, debug=False):
25   """ Constructor.
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
30
31    self.channel == None indicates this is the Music channel.
32   """
33   self.channel_id = 0
34   self.debug = debug
35   self.paused = False
36   if channel_id == -1:
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)
42
43   self.set_volume(int(volume))
44
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)
51    else:
52     self.channel.set_volume(self.volume/100)
53    if self.debug: engine.debug_pr("Channel.set_volume(", self.volume, ")")
54
55  def volume_up(self):
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)
59
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)
64
65  def get_volume(self):
66   return self.volume
67
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)
72
73  def get_queue(self):
74   """ Retrieve the queued Sound (if any) """
75   return self.channel.get_queue() if self.channel != None else None
76
77  def fadeout(self, time):
78   """ Fade out the sound nicely
79    time: milliseconds   
80   """
81   if self.channel == None:
82    pygame.mixer.music.fadeout(time)
83   else:
84    self.channel.fadeout(time)
85
86  def play(self, sound, loops=0, maxtime=0, fade_ms=0):
87   """ Play a Music track or Sound
88    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
92   """
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)
97   else:
98    pygame.mixer.music.load(sound)
99    pygame.mixer.music.play(loops)
100
101  def pause(self):
102   """ Toggle the play/pause state of the channel """
103   if self.paused:
104    self.paused = False
105    if self.channel == None:
106     pygame.mixer.music.unpause()
107    else:
108     self.channel.unpause()
109   else:
110    self.paused = True
111    if self.channel == None:
112     pygame.mixer.music.pause()
113    else:
114     self.channel.pause()
115
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.)
119
120    See pygame.mixer.music API docs
121   """
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
126
127   if self.debug: engine.debug_pr("playlist=", self.playlist)
128
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)
133
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] ))
140
141   pygame.mixer.music.load(os.path.join(self.music_path, self.playlist[self.playlist_index]))
142   pygame.mixer.music.play()
143