Module Audio
[hide private]
[frames] | no frames]

Source Code for Module Audio

  1  ##################################################################### 
  2  # -*- coding: iso-8859-1 -*-                                        # 
  3  #                                                                   # 
  4  # Frets on Fire                                                     # 
  5  # Copyright (C) 2006 Sami Kyöstilä                                  # 
  6  #                                                                   # 
  7  # This program is free software; you can redistribute it and/or     # 
  8  # modify it under the terms of the GNU General Public License       # 
  9  # as published by the Free Software Foundation; either version 2    # 
 10  # of the License, or (at your option) any later version.            # 
 11  #                                                                   # 
 12  # This program is distributed in the hope that it will be useful,   # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of    # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     # 
 15  # GNU General Public License for more details.                      # 
 16  #                                                                   # 
 17  # You should have received a copy of the GNU General Public License # 
 18  # along with this program; if not, write to the Free Software       # 
 19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        # 
 20  # MA  02110-1301, USA.                                              # 
 21  ##################################################################### 
 22   
 23  import pygame 
 24  import Log 
 25  import time 
 26  from Task import Task 
 27   
 28  try: 
 29    import ogg.vorbis 
 30  except ImportError: 
 31    Log.warn("PyOGG not found. OGG files will be fully decoded prior to playing; expect absurd memory usage.") 
 32    ogg = None 
 33   
34 -class Audio:
35 - def pre_open(self, frequency = 22050, bits = 16, stereo = True, bufferSize = 1024):
36 pygame.mixer.pre_init(frequency, -bits, stereo and 2 or 1, bufferSize) 37 return True
38
39 - def open(self, frequency = 22050, bits = 16, stereo = True, bufferSize = 1024):
40 try: 41 pygame.mixer.quit() 42 except: 43 pass 44 45 try: 46 pygame.mixer.init(frequency, -bits, stereo and 2 or 1, bufferSize) 47 except: 48 Log.warn("Audio setup failed. Trying with default configuration.") 49 pygame.mixer.init() 50 51 Log.debug("Audio configuration: %s" % str(pygame.mixer.get_init())) 52 53 return True
54
55 - def getChannelCount(self):
56 return pygame.mixer.get_num_channels()
57
58 - def getChannel(self, n):
59 return Channel(n)
60
61 - def close(self):
62 pygame.mixer.quit()
63
64 - def pause(self):
65 pygame.mixer.pause()
66
67 - def unpause(self):
68 pygame.mixer.unpause()
69
70 -class Music(object):
71 - def __init__(self, fileName):
72 pygame.mixer.music.load(fileName)
73 74 @staticmethod
75 - def setEndEvent(event):
76 pygame.mixer.music.set_endevent(event)
77
78 - def play(self, loops = -1, pos = 0.0):
79 pygame.mixer.music.play(loops, pos)
80
81 - def stop(self):
82 pygame.mixer.music.stop()
83
84 - def rewind(self):
85 pygame.mixer.music.rewind()
86
87 - def pause(self):
88 pygame.mixer.music.pause()
89
90 - def unpause(self):
91 pygame.mixer.music.unpause()
92
93 - def setVolume(self, volume):
94 pygame.mixer.music.set_volume(volume)
95
96 - def fadeout(self, time):
97 pygame.mixer.music.fadeout(time)
98
99 - def isPlaying(self):
100 return pygame.mixer.music.get_busy()
101
102 - def getPosition(self):
103 return pygame.mixer.music.get_pos()
104
105 -class Channel(object):
106 - def __init__(self, id):
107 self.channel = pygame.mixer.Channel(id)
108
109 - def play(self, sound):
110 self.channel.play(sound.sound)
111
112 - def stop(self):
113 self.channel.stop()
114
115 - def setVolume(self, volume):
116 self.channel.set_volume(volume)
117
118 - def fadeout(self, time):
119 self.channel.fadeout(time)
120
121 -class Sound(object):
122 - def __init__(self, fileName):
123 self.sound = pygame.mixer.Sound(fileName)
124
125 - def play(self, loops = 0):
126 self.sound.play(loops)
127
128 - def stop(self):
129 self.sound.stop()
130
131 - def setVolume(self, volume):
132 self.sound.set_volume(volume)
133
134 - def fadeout(self, time):
135 self.sound.fadeout(time)
136 137 if ogg: 138 import struct 139 # Must use Numeric instead of numpy, since PyGame 1.7.1 is not compatible with the former 140 import Numeric 141
142 - class OggStream(object):
143 - def __init__(self, inputFileName):
144 self.file = ogg.vorbis.VorbisFile(inputFileName)
145
146 - def read(self, bytes = 4096):
147 (data, bytes, bit) = self.file.read(bytes) 148 return data[:bytes]
149
150 - class StreamingOggSound(Sound, Task):
151 - def __init__(self, engine, channel, fileName):
152 Task.__init__(self) 153 self.engine = engine 154 self.fileName = fileName 155 self.channel = channel.channel 156 self.playing = False 157 self.bufferSize = 1024 * 64 158 self.bufferCount = 8 159 self.volume = 1.0 160 self.buffer = Numeric.zeros((2 * self.bufferSize, 2), typecode = "s") 161 self.decodingRate = 4 162 self._reset()
163
164 - def _reset(self):
165 self.stream = OggStream(self.fileName) 166 self.buffersIn = [pygame.sndarray.make_sound(Numeric.zeros((self.bufferSize, 2), typecode = "s")) for i in range(self.bufferCount + 1)] 167 self.buffersOut = [] 168 self.buffersBusy = [] 169 self.bufferPos = 0 170 self.done = False 171 self.lastQueueTime = time.time() 172 173 while len(self.buffersOut) < self.bufferCount and not self.done: 174 self._produceSoundBuffers()
175
176 - def __del__(self):
177 self.engine.removeTask(self)
178
179 - def play(self):
180 if self.playing: 181 return 182 183 self.engine.addTask(self, synchronized = False) 184 self.playing = True 185 186 while len(self.buffersOut) < self.bufferCount and not self.done: 187 self._produceSoundBuffers() 188 189 self.channel.play(self.buffersOut.pop())
190
191 - def stop(self):
192 self.playing = False 193 self.channel.stop() 194 self.engine.removeTask(self) 195 self._reset()
196
197 - def setVolume(self, volume):
198 self.volume = volume
199
200 - def fadeout(self, time):
201 self.stop()
202
203 - def _decodeStream(self):
204 # No available buffers to fill? 205 if not self.buffersIn or self.done: 206 return 207 208 data = self.stream.read() 209 210 if not data: 211 self.done = True 212 else: 213 data = struct.unpack("%dh" % (len(data) / 2), data) 214 samples = len(data) / 2 215 self.buffer[self.bufferPos:self.bufferPos + samples, 0] = data[0::2] 216 self.buffer[self.bufferPos:self.bufferPos + samples, 1] = data[1::2] 217 self.bufferPos += samples 218 219 # If we have at least one full buffer decode, claim a buffer and copy the 220 # data over to it. 221 if self.bufferPos >= self.bufferSize or (self.done and self.bufferPos): 222 # Claim the sound buffer and copy the data 223 if self.bufferPos < self.bufferSize: 224 self.buffer[self.bufferPos:] = 0 225 soundBuffer = self.buffersIn.pop() 226 pygame.sndarray.samples(soundBuffer)[:] = self.buffer[0:self.bufferSize] 227 228 # Discard the copied sound data 229 n = max(0, self.bufferPos - self.bufferSize) 230 self.buffer[0:n] = self.buffer[self.bufferSize:self.bufferSize+n] 231 self.bufferPos = n 232 233 return soundBuffer
234
235 - def _produceSoundBuffers(self):
236 # Decode enough that we have at least one full sound buffer 237 # ready in the queue if possible 238 while not self.done: 239 for i in xrange(self.decodingRate): 240 soundBuffer = self._decodeStream() 241 if soundBuffer: 242 self.buffersOut.insert(0, soundBuffer) 243 if self.buffersOut: 244 break
245
246 - def run(self, ticks):
247 if not self.playing: 248 return 249 250 self.channel.set_volume(self.volume) 251 252 if len(self.buffersOut) < self.bufferCount: 253 self._produceSoundBuffers() 254 255 if not self.channel.get_queue() and self.buffersOut: 256 # Queue one decoded sound buffer and mark the previously played buffer as free 257 soundBuffer = self.buffersOut.pop() 258 self.buffersBusy.insert(0, soundBuffer) 259 self.lastQueueTime = time.time() 260 self.channel.queue(soundBuffer) 261 if len(self.buffersBusy) > 2: 262 self.buffersIn.insert(0, self.buffersBusy.pop()) 263 264 if not self.buffersOut and self.done and time.time() - self.lastQueueTime > 4: 265 self.stop()
266
267 -class StreamingSound(Sound, Task):
268 - def __init__(self, engine, channel, fileName):
269 Task.__init__(self) 270 Sound.__init__(self, fileName) 271 self.channel = channel
272
273 - def __new__(cls, engine, channel, fileName):
274 frequency, format, stereo = pygame.mixer.get_init() 275 if fileName.lower().endswith(".ogg"): 276 if frequency == 44100 and format == -16 and stereo: 277 return StreamingOggSound(engine, channel, fileName) 278 else: 279 Log.warn("Audio settings must match stereo 16 bits at 44100 Hz in order to stream OGG files.") 280 return super(StreamingSound, cls).__new__(cls, engine, channel, fileName)
281
282 - def play(self):
283 self.channel.play(self)
284
285 - def stop(self):
286 Sound.stop(self) 287 self.channel.stop()
288
289 - def setVolume(self, volume):
290 Sound.setVolume(self, volume) 291 self.channel.setVolume(volume)
292
293 - def fadeout(self, time):
294 Sound.fadeout(self, time) 295 self.channel.fadeout(time)
296