Package rstem :: Package speaker
[hide private]
[frames] | no frames]

Source Code for Package rstem.speaker

  1  # 
  2  # Copyright (c) 2014, Scott Silver Labs, LLC. 
  3  # 
  4  # Licensed under the Apache License, Version 2.0 (the "License"); 
  5  # you may not use this file except in compliance with the License. 
  6  # You may obtain a copy of the License at 
  7  # 
  8  #       http://www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  # Unless required by applicable law or agreed to in writing, software 
 11  # distributed under the License is distributed on an "AS IS" BASIS, 
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 13  # See the License for the specific language governing permissions and 
 14  # limitations under the License. 
 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  # global constants 
 28  SAMPLERATE = 44100  # 38000 == chipmunks 
 29  BITSIZE = -16  # unsigned 16 bit 
 30  CHANNELS = 2   # 1 == mono, 2 == stereo 
 31  BUFFER = 1024  # audio buffer size in no. of samples 
 32  FRAMERATE = 30 # how often to check if playback has finished 
 33   
 34  # set of current talking process (used for is_talking()) 
 35  talking_procs = set() 
 36   
 37   
 38  # engine used to create text to speech 
 39  voice_engine = None 
 40   
41 -def _init():
42 """Initializes pygame""" 43 if pygame.mixer.get_init() is None: 44 pygame.mixer.init(SAMPLERATE, BITSIZE, CHANNELS, BUFFER)
45
46 -def is_talking():
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: # if proc.poll() gives us a returncode, its terminated 54 return True 55 return False # all processes were terminated, therefore not talking
56 57
58 -def set_voice_engine(engine="espeak"):
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 # check if espeak is installed 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 # check if espeak is installed 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
99 -def get_volume():
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
111 -def set_volume(value):
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
138 -def cleanup():
139 """Cleans up initialization of pygame""" 140 if pygame.mixer.get_init(): 141 pygame.mixer.quit()
142 143 currently_playing_filename = None 144
145 -class Sound(object):
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 # initialize if the first time 155 _init() 156 # check if filename exists.. (pygame doesn't check) 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
189 - def stop(self):
190 """Stops playback of sound.""" 191 if self.background and currently_playing_filename != self.filename: 192 return 193 self.sound.stop()
194
195 - def get_volume(self):
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
202 - def set_volume(self, value):
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
214 - def is_playing(self):
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
223 - def queue(self):
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
231 -class Speech(Sound):
232 """Sound using Text to Speech software (espeak)"""
233 - def __init__(self, text):
234 """ 235 @param text: Text to be converted to speech 236 @type text: string 237 """ 238 # setup voice engine to default espeak if not set up 239 if voice_engine is None: 240 set_voice_engine() 241 242 # initialize pygame 243 _init() 244 245 # create temp file to use 246 self.filename = tempfile.mkstemp(suffix=".mp3")[1] 247 self.background = False 248 249 # create wav file of the text, using voice_engine 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
274 -class Note(Sound):
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)) # split into two for stereo 299 self.sound = pygame.sndarray.make_sound(array)
300