source: src/mlx/sound.py@ 485:b254e5cd222e

Last change on this file since 485:b254e5cd222e was 401:15ad3c16ee11, checked in by István Váradi <ivaradi@…>, 12 years ago

The exception strings are converted from UTF-8 to unicode for proper logging (re #170)

File size: 8.0 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 def initializeSound(soundsDirectory):
178 """Initialize the sound handling with the given directory containing
179 the sound files."""
180 pass
181
182 def startSound(name, finishCallback = None, extra = None):
183 """Start playing back the given sound.
184
185 FIXME: it does not do anything currently, but it should."""
186 print "sound.startSound:", name
187
188#------------------------------------------------------------------------------
189#------------------------------------------------------------------------------
190
191if __name__ == "__main__":
192 def callback(result, extra):
193 print "callback", result, extra
194
195 initializeSound("e:\\home\\vi\\tmp")
196 startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
197 time.sleep(5)
198 startSound("ding.wav", finishCallback = callback, extra="ding1.wav")
199 time.sleep(5)
200 startSound("ding.wav", finishCallback = callback, extra="ding2.wav")
201 time.sleep(5)
202 startSound("ding.wav", finishCallback = callback, extra="ding3.wav")
203 time.sleep(50)
204
205#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.