1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os
18 import pygame.mixer
19 import pygame.sndarray
20 import pygame.time
21 import numpy
22 import subprocess
23 import time
24 import tempfile
25 import urllib2, urllib
26
27
28 SAMPLERATE = 44100
29 BITSIZE = -16
30 CHANNELS = 2
31 BUFFER = 1024
32 FRAMERATE = 30
33
34
35 talking_procs = set()
36
37
38
39 voice_engine = None
40
45
47 """
48 @returns: True if speaker is currently saying text (from the L{say} function)
49 @rtype: boolean
50 """
51 global talking_procs
52 for proc in talking_procs:
53 if proc.poll() is None:
54 return True
55 return False
56
57
59 """Set the voice engine and do and intial configures if necessary
60 @param engine: Alias of the engine to use. (Currently only "espeak" is supported)
61 @type engine: string
62 """
63 global voice_engine
64 if engine == "espeak":
65
66 proc = subprocess.Popen('dpkg-query -s espeak', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
67 output, error = proc.communicate()
68 if output.find("install ok installed") == -1:
69 raise Exception("espeak is not installed")
70 voice_engine = "espeak"
71
72 elif engine == "google":
73 voice_engine = "google"
74 else:
75 raise ValueError("Voice Engine is not supported.")
76
77
78 -def say(text, wait=False):
79 """Plays a voice speaking the given text.
80
81 @param text: text to play
82 @type text: string
83
84 @raise Exception: espeak errors out (most likely due to not being installed)
85 @note: Must have espeak installed
86 """
87
88 proc = subprocess.Popen('dpkg-query -s espeak', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
89 output, error = proc.communicate()
90 if output.find("install ok installed") == -1:
91 raise Exception("espeak is not installed")
92 proc = subprocess.Popen('espeak -s 200 "' + text + '"', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
93 if wait:
94 proc.wait()
95 else:
96 global talking_procs
97 talking_procs.add(proc)
98
100 """Gets the master volume
101 @rtype: float
102 @returns: the volume between 0-100
103 """
104 proc = subprocess.Popen('amixer sget Master', shell=True, stdout=subprocess.PIPE)
105 amixer_stdout = proc.communicate()[0].split('\n')[4]
106 proc.wait()
107 find_start = amixer_stdout.find('[') + 1
108 find_end = amixer_stdout.find('%]', find_start)
109 return float(amixer_stdout[find_start:find_end])
110
112 """Set the master volume
113 @param value: Volume to set (must be between 0-100)
114 @type value: int
115 """
116 value = float(int(value))
117 proc = subprocess.Popen('amixer sset Master ' + str(value) + '%', shell=True, stdout=subprocess.PIPE)
118 proc.wait()
119
120 -def stop(background=True):
121 """Stops all playback including background music unless background = False"""
122 pygame.mixer.stop()
123 if background:
124 pygame.mixer.music.stop()
125
126 -def pause(background=True):
127 """Pauses all playback including background music unless background = False"""
128 pygame.mixer.pause()
129 if background:
130 pygame.mixer.music.pause()
131
132 -def play(background=True):
133 """Unpauses all playback including background music unless background = False"""
134 pygame.mixer.unpause()
135 if background:
136 pygame.mixer.music.unpause()
137
139 """Cleans up initialization of pygame"""
140 if pygame.mixer.get_init():
141 pygame.mixer.quit()
142
143 currently_playing_filename = None
144
146 """Basic foreground or background sound from a file"""
147 - def __init__(self, filename, background=False):
148 """
149 @param filename: Relative path to sound file
150 @type filename: string
151 @param background: Whether to play the sound as background music or as a sound effect
152 @type background: boolean
153 """
154
155 _init()
156
157 if not os.path.isfile(filename):
158 raise IOError("Filename doesn't exist.")
159
160 self.background = background
161 self.filename = filename
162 if background:
163 self.sound = pygame.mixer.music
164 else:
165 self.sound = pygame.mixer.Sound(filename)
166
167 - def play(self, loops=0, wait=False):
168 """Plays sound a certain number of times
169 @param loops: number of loops to play the sound (-1 to play infinitly)
170 @type loops: int
171 @param wait: If true, blocks until playback is finished
172 @type wait: boolean
173 """
174 global currently_playing_filename
175 if self.background and currently_playing_filename != self.filename:
176 self.sound.load(self.filename)
177 currently_playing_filename = self.filename
178
179 clock = pygame.time.Clock()
180 self.sound.play(loops)
181 if wait:
182 if self.background:
183 while pygame.mixer.music.get_busy():
184 clock.tick(FRAMERATE)
185 else:
186 while pygame.mixer.get_busy():
187 clock.tick(FRAMERATE)
188
194
196 """Gets the volume of individual sound
197 @returns: volume (a value between 0-100)
198 @rtype: int
199 """
200 return self.sound.get_volume()*100
201
203 """Sets the volume of given sound.
204 @param value: volume to set sound at (between 0-100)
205 @type value: int
206 """
207 if self.background and currently_playing_filename != self.filename:
208 return
209 """Sets teh volume of individual sound"""
210 if not (0 <= value <= 100):
211 raise ValueError("Volume must be between 0 and 100.")
212 self.sound.set_volume(float(value/100.))
213
215 """
216 @returns: True if sound is currently playing
217 @rtype: boolean
218 """
219 if self.background and currently_playing_filename != self.filename:
220 return False
221 self.sound.get_busy()
222
224 """Queues sound to play after other queued sounds have finished playing.
225 @raises ValueError: Sounds is not a background sound. (Only supports background music)
226 """
227 if not self.background:
228 raise ValueError("Queue only supports background sounds.")
229 pygame.mixer.music.queue(self.filename)
230
232 """Sound using Text to Speech software (espeak)"""
234 """
235 @param text: Text to be converted to speech
236 @type text: string
237 """
238
239 if voice_engine is None:
240 set_voice_engine()
241
242
243 _init()
244
245
246 self.filename = tempfile.mkstemp(suffix=".mp3")[1]
247 self.background = False
248
249
250 if voice_engine == "espeak":
251 proc = subprocess.Popen('espeak "' + text + '" -w ' + self.filename, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
252 output, error = proc.communicate()
253 if len(error) > 0:
254 raise IOError(error)
255 self.sound = pygame.mixer.Sound(self.filename)
256
257 elif voice_engine == "google":
258 text = urllib.quote(text)
259 url = 'http://translate.google.com/translate_tts?tl=en&q=' + text
260 req = urllib2.Request(url)
261 req.add_header('User-Agent', 'Konqueror')
262 fp = open(self.filename, 'wb')
263 try:
264 response = urllib2.urlopen(req)
265 fp.write(response.read())
266 time.sleep(.5)
267 except urllib2.URLError as e:
268 print ('%s' % e)
269 fp.close()
270 self.sound = pygame.mixer.Sound(self.filename)
271 else:
272 raise ValueError("%s voice engine is not supported" % voice_engine)
273
275 """A sine wave of given frequeny"""
276 - def __init__(self, frequency, amplitude=100, duration=1):
277 """
278 @param frequency: frequency of the sine wave in Hz
279 @type frequency: float or int
280 @param amplitude: amplitude of the sine wave
281 @type amplitude: float or int
282 @param duration: length of sine wave in seconds
283 @type duration: float or int
284 """
285 _init()
286 self.filename = None
287 self.background = False
288 self.frequency = frequency
289 self.amplitude = amplitude
290 self.duration = duration
291
292 length = 2*SAMPLERATE/float(frequency)
293 omega = numpy.pi * 2 / length
294 xvalues = numpy.arange(int(length)) * omega
295 array = numpy.array(amplitude * numpy.sin(xvalues), dtype="int8")
296 array = numpy.resize(array, (SAMPLERATE*duration,))
297 if CHANNELS == 2:
298 array = numpy.array(zip(array,array))
299 self.sound = pygame.sndarray.make_sound(array)
300