source: src/mlx/sound.py@ 425:fc6aec5ffc8f

xplane
Last change on this file since 425:fc6aec5ffc8f was 422:af0176532466, checked in by István Váradi <ivaradi@…>, 12 years ago

Added sound support for Linux

File size: 11.9 KB
Line 
1
2from util import utf2unicode
3
4import os
5import traceback
6
7#------------------------------------------------------------------------------
8
9## @package mlx.sound
10#
11# Sound playback handling.
12#
13# This is the low level sound playback handling. The \ref initializeSound
14# function should be called to initialize the sound handling with the directory
15# containing the sound files. Then the \ref startSound function should be
16# called to start the playback of a certain sound file. A callback may be
17# called when the playback of a certain file has finished.
18#
19# See also the \ref mlx.soundsched module.
20
21#------------------------------------------------------------------------------
22
23if os.name=="nt":
24 import time
25 import threading
26 from ctypes import windll, c_buffer
27
28 class MCIException(Exception):
29 """MCI exception."""
30 def __init__(self, mci, command, errorCode):
31 """Construct an MCI exception for the given error code."""
32 message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode))
33 super(MCIException, self).__init__(message)
34
35 class MCI:
36 """Interface for the Media Control Interface."""
37 def __init__(self):
38 """Construct the interface."""
39 self.w32mci = windll.winmm.mciSendStringA
40 self.w32mcierror = windll.winmm.mciGetErrorStringA
41
42 def send(self, command):
43 """Send the given command to the MCI."""
44 buffer = c_buffer(255)
45 errorCode = self.w32mci(str(command), buffer, 254, 0)
46 if errorCode:
47 raise MCIException(self, command, errorCode)
48 else:
49 return buffer.value
50
51 def getErrorString(self, errorCode):
52 """Get the string representation of the given error code."""
53 buffer = c_buffer(255)
54 self.w32mcierror(int(errorCode), buffer, 254)
55 return buffer.value
56
57 class SoundThread(threading.Thread):
58 """The thread controlling the playback of sounds."""
59 def __init__(self, soundsDirectory):
60 threading.Thread.__init__(self)
61
62 self._soundsDirectory = soundsDirectory
63 self._mci = MCI()
64
65 self._requestCondition = threading.Condition()
66 self._requests = []
67 self._pending = []
68 self._count = 0
69
70 self.daemon = True
71
72 def requestSound(self, name, finishCallback = None, extra = None):
73 """Request the playback of the sound with the given name."""
74 path = name if os.path.isabs(name) \
75 else os.path.join(self._soundsDirectory, name)
76 with self._requestCondition:
77 self._requests.append((path, (finishCallback, extra)))
78 self._requestCondition.notify()
79
80 def run(self):
81 """Perform the operation of the thread.
82
83 It waits for a request or a timeout. If a request is received, that
84 is started to be played back. If a timeout occurs, the file is
85 closed."""
86
87 while True:
88 with self._requestCondition:
89 if not self._requests:
90 if self._pending:
91 timeout = max(self._pending[0][0] - time.time(),
92 0.0)
93 else:
94 timeout = 10.0
95
96 #print "Waiting", timeout
97 self._requestCondition.wait(timeout)
98
99 requests = []
100 for (path, finishData) in self._requests:
101 requests.append((path, finishData, self._count))
102 self._count += 1
103 self._requests = []
104
105 now = time.time()
106 toClose = []
107 while self._pending and \
108 self._pending[0][0]<=now:
109 toClose.append(self._pending[0][1])
110 del self._pending[0]
111
112 for (alias, (finishCallback, extra)) in toClose:
113 success = True
114 try:
115 print "Closing", alias
116 self._mci.send("close " + alias)
117 print "Closed", alias
118 except Exception, e:
119 print "Failed closing " + alias + ":",
120 print utf2unicode(str(e))
121 success = False
122
123 if finishCallback is not None:
124 try:
125 finishCallback(success, extra)
126 except:
127 traceback.print_exc()
128
129 for (path, finishData, counter) in requests:
130 try:
131 alias = "mlxsound%d" % (counter,)
132 print "Starting to play", path, "as", alias
133 self._mci.send("open \"%s\" alias %s" % \
134 (path, alias))
135 self._mci.send("set %s time format milliseconds" % \
136 (alias,))
137 lengthBuffer = self._mci.send("status %s length" % \
138 (alias,))
139 self._mci.send("play %s from 0 to %s" % \
140 (alias, lengthBuffer))
141 length = int(lengthBuffer)
142 timeout = time.time() + length / 1000.0
143 with self._requestCondition:
144 self._pending.append((timeout, (alias, finishData)))
145 self._pending.sort()
146 print "Started to play", path
147 except Exception, e:
148 print "Failed to start playing " + path + ":",
149 print utf2unicode(str(e))
150 (finishCallback, extra) = finishData
151 if finishCallback is not None:
152 try:
153 finishCallback(None, extra)
154 except:
155 traceback.print_exc()
156
157 _thread = None
158
159 def initializeSound(soundsDirectory):
160 """Initialize the sound handling with the given directory containing
161 the sound files."""
162 global _thread
163 _thread = SoundThread(soundsDirectory)
164 _thread.start()
165
166 def startSound(name, finishCallback = None, extra = None):
167 """Start playing back the given sound.
168
169 name should be the name of a sound file relative to the sound directory
170 given in initializeSound."""
171 _thread.requestSound(name, finishCallback = finishCallback,
172 extra = extra)
173
174#------------------------------------------------------------------------------
175
176else: # os.name!="nt"
177 import threading
178 try:
179 import pyglet
180
181 class SoundThread(threading.Thread):
182 """A thread executing the pyglet event loop that directs the
183 playback of the sound files."""
184 class Player(pyglet.media.ManagedSoundPlayer):
185 """Player which handles the end-of-stream condition
186 properly."""
187
188 def __init__(self, finishCallback, extra):
189 """Construct the player with the given data."""
190 super(SoundThread.Player, self).__init__()
191
192 self._finishCallback = finishCallback
193 self._extra = extra
194
195 def _on_eos(self):
196 if self._finishCallback is not None:
197 self._finishCallback(True, self._extra)
198 return super(SoundThread.Player, self)._on_eos()
199
200 class EventLoop(pyglet.app.EventLoop):
201 """Own implementation of the event loop that collects the
202 requested sound files and plays them."""
203
204 def __init__(self, soundsDirectory):
205 """Construct the event loop."""
206 super(SoundThread.EventLoop, self).__init__()
207
208 self._soundsDirectory = soundsDirectory
209
210 self._lock = threading.Lock()
211 self._requestedSounds = []
212
213 def startSound(self, name, finishCallback, extra):
214 """Add the sound with the given name"""
215 with self._lock:
216 path = os.path.join(self._soundsDirectory, name)
217 self._requestedSounds.append( (path,
218 finishCallback, extra) )
219
220 def idle(self):
221 """The idle callback."""
222 with self._lock:
223 requestedSounds = self._requestedSounds
224 self._requestedSounds = []
225
226 for (path, finishCallback, extra) in requestedSounds:
227 try:
228 media = pyglet.media.load(path)
229 player = SoundThread.Player(finishCallback, extra)
230 player.queue(media)
231 player.play()
232 except Exception, e:
233 print "mlx.SoundThread.EventLoop.idle: " + str(e)
234 if finishCallback is not None:
235 finishCallback(False, extra)
236
237 timeout = super(SoundThread.EventLoop, self).idle()
238 return 0.1 if timeout is None else min(timeout, 0.1)
239
240 def __init__(self, soundsDirectory):
241 """Construct the sound playing thread with the given
242 directory."""
243 super(SoundThread, self).__init__()
244
245 self.daemon = True
246 self.eventLoop = SoundThread.EventLoop(soundsDirectory)
247
248 def run(self):
249 """Run the event loop."""
250 self.eventLoop.run()
251
252 def startSound(self, name, finishCallback = None, extra = None):
253 """Start the playback of the given sound."""
254 self.eventLoop.startSound(name, finishCallback, extra)
255
256 _thread = None
257
258 def initializeSound(soundsDirectory):
259 """Initialize the sound handling with the given directory containing
260 the sound files."""
261 global _thread
262 _thread = SoundThread(soundsDirectory)
263 _thread.start()
264
265
266 def startSound(name, finishCallback = None, extra = None):
267 """Start playing back the given sound."""
268 _thread.startSound(name, finishCallback = finishCallback,
269 extra = extra)
270
271 except:
272 print "The pyglet library is missing from your system. It is needed for sound playback on Linux"
273 def initializeSound(soundsDirectory):
274 """Initialize the sound handling with the given directory containing
275 the sound files."""
276 pass
277
278 def startSound(name, finishCallback = None, extra = None):
279 """Start playing back the given sound.
280
281 FIXME: it does not do anything currently, but it should."""
282 print "sound.startSound:", name
283
284#------------------------------------------------------------------------------
285#------------------------------------------------------------------------------
286
287if __name__ == "__main__":
288 def callback(result, extra):
289 print "callback", result, extra
290
291 initializeSound("e:\\home\\vi\\tmp")
292 startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
293 time.sleep(5)
294 startSound("ding.wav", finishCallback = callback, extra="ding1.wav")
295 time.sleep(5)
296 startSound("ding.wav", finishCallback = callback, extra="ding2.wav")
297 time.sleep(5)
298 startSound("ding.wav", finishCallback = callback, extra="ding3.wav")
299 time.sleep(50)
300
301#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.