def say(self, text, voice=None, ttsEngine=None, numRepeats=0, repeatPeriod=0, blockTillDone=False): ''' Given a piece of text, generate corresponding speech at the SpeakEasy node site. @param text: Words to speak. @type text: string @param voice: Name of text-to-speech voice to use. @type voice: string @param ttsEngine: Name of text-to-speech engine to use. @type ttsEngine: string @param numRepeats: Number of times the utterance is to be repeated after the first time. Use -1 if forever. @type numRepeats: int @param repeatPeriod: Time period in fractional seconds to wait between repeated utterances. @type repeatPeriod: float @param blockTillDone: Request to return immediately, or block until the utterance if finished. @type blockTillDone: bool @raises TypeError: if one of the parameters is of incorrect type. ''' if not SpeakeasyUtils.ensureType(numRepeats, int): raise TypeError("Number of repeats must be an integer. Was " + str(numRepeats)); if not SpeakeasyUtils.ensureType(repeatPeriod, int) and not SpeakeasyUtils.ensureType(repeatPeriod, float): raise TypeError("Repeat period must be an integer or a float. Was " + str(repeatPeriod)); ttsRequestMsg = SpeakEasyTextToSpeech(); ttsRequestMsg.command = TTSCommands.SAY; ttsRequestMsg.text = text; if voice is not None: ttsRequestMsg.voiceName = voice; else: ttsRequestMsg.voiceName = ''; if ttsEngine is not None: ttsRequestMsg.engineName = ttsEngine; else: ttsRequestMsg.engineName = ''; with self.textToSpeechLock: self.rosTTSRequestor.publish(ttsRequestMsg); # Keep this block out of the lock! Thread registration will # acquire the lock (thus leading to deadlock, if lock is owned here): if numRepeats > 0 or numRepeats == -1: speechRepeatThread = RoboComm.SpeechReplayDemon(text, voice, ttsEngine, numRepeats, repeatPeriod, self); self.registerSpeechRepeaterThread(speechRepeatThread); speechRepeatThread.start(); if blockTillDone: while self.getTextToSpeechBusy(): rospy.sleep(0.5);
def playMusic(self, songName, volume=None, playheadTime=0.0, timeReference=TimeReference.ABSOLUTE, numRepeats=0, repeatPeriod=0): ''' Play a piece of music (a sound file) at the SpeakEasy node. @param songName: Name of sound file. (See getSongNames()). @type songName: string @param volume: Loudness at which to play. Default uses current volume. Else must be in range 0.0 to 1.0 @type volume: {None | float} @param playheadTime: Offset in fractional seconds into where to start the song. Default: start at beginning. @type playheadTime: float @param timeReference: If playheadTime is provided, specifies whether the given time is intended as absolute (relative to the beginning of the song), or relative to the current playhead position. @type timeReference: TimeReference @param numRepeats: Number of times the song is to be repeated after the first time. Use -1 to play forever. @type numRepeats: int @param repeatPeriod: Time period in fractional seconds to wait between repeats. @type repeatPeriod: float @raise TypeError: if any parameters are of incorrect type. ''' if not SpeakeasyUtils.ensureType(numRepeats, int): raise TypeError("Number of repeats must be an integer. Was " + str(numRepeats)); if not SpeakeasyUtils.ensureType(repeatPeriod, int) and not SpeakeasyUtils.ensureType(repeatPeriod, float): raise TypeError("Repeat period must be an integer or a float. Was " + str(repeatPeriod)); if not SpeakeasyUtils.ensureType(playheadTime, int) and not SpeakeasyUtils.ensureType(playheadTime, float): raise TypeError("Playhead must be an integer or a float. Was " + str(playheadTime)); if (timeReference != TimeReference.ABSOLUTE) and (timeReference != TimeReference.RELATIVE): raise TypeError("Time reference must be TimeReference.RELATIVE, or TimeReference.ABSOLUTE. Was " + str(timeReference)); musicReqMsg = SpeakEasyMusic(); musicReqMsg.command = MusicCommands.PLAY; musicReqMsg.song_name = songName; musicReqMsg.time = playheadTime; musicReqMsg.timeReference = timeReference; if volume is None: musicReqMsg.volume = -1.0; else: musicReqMsg.volume = volume; with self.musicLock: self.rosMusicRequestor.publish(musicReqMsg); # Keep this block out of the lock! Thread registration will # acquire the lock (thus leading to deadlock, if lock is owned here): if numRepeats > 0 or numRepeats == -1: musicRepeatThread = RoboComm.MusicReplayDemon(songName, volume, playheadTime, timeReference, numRepeats, repeatPeriod, self); self.registerMusicRepeaterThread(musicRepeatThread); musicRepeatThread.start();
def setPlayhead(self, playheadTime, timeReference=TimeReference.ABSOLUTE): ''' Change the music playhead position to a particular time within a song. Time may be specified absolute (i.e. relative to the start of the song), or relative to the current playhead. The playhead may be changed during playback, or while a song is paused. @param playheadTime: New time where to position the playhead. @type playheadTime: float @param timeReference: TimeReference.ABSOLUTE or TimeReference.RELATIVE @type timeReference: TimeReference. @raise TypeError: if any parameters are of incorrect type. ''' if not SpeakeasyUtils.ensureType(playheadTime, int) and not SpeakeasyUtils.ensureType(playheadTime, float): raise TypeError("Playhead must be an int or float indicating seconds. Was " + str(playheadTime)); musicReqMsg = SpeakEasyMusic(); musicReqMsg.command = MusicCommands.SET_PLAYHEAD; musicReqMsg.time = playheadTime; musicReqMsg.timeReference = timeReference; with self.musicLock: self.rosMusicRequestor.publish(musicReqMsg);
def setMusicVolume(self, vol): ''' Set default volume of music playback. @param vol: Loudness value between 0.0 and 1.0. @type vol: float @raise TypeError: if any parameters are of incorrect type. ''' if not SpeakeasyUtils.ensureType(vol, float): raise TypeError("Volume must be a float between 0.0 and 1.0. Was " + str(vol)); musicReqMsg = SpeakEasyMusic(); musicReqMsg.command = MusicCommands.SET_VOL; musicReqMsg.volume = vol; with self.musicLock: self.rosMusicRequestor.publish(musicReqMsg);
def playSound(self, soundName, volume=None, numRepeats=0, repeatPeriod=0): ''' Play a sound effect at the SpeakEasy node. @param soundName: Name of the sound effect. (see getSoundEffectNames()). @type soundName: string @param volume: Loudness for the sound effect. If None, current volume is used. Volume must be in range 0.0 to 1.0 @type volume: float. @param numRepeats: Number of times the sound effect is to be repeated after the first time. Use -1 to play forever. @type numRepeats: int @param repeatPeriod: Time period in fractional seconds to wait between repeats. @type repeatPeriod: float @raise TypeError: if any parameter is of the wrong type. ''' if not SpeakeasyUtils.ensureType(numRepeats, int): raise TypeError("Number of repeats must be an integer. Was " + str(numRepeats)); if not SpeakeasyUtils.ensureType(repeatPeriod, int) and not SpeakeasyUtils.ensureType(repeatPeriod, float): raise TypeError("Repeat period must be an integer or a float. Was " + str(repeatPeriod)); soundReqMsg = SpeakEasySound(); soundReqMsg.command = SoundCommands.PLAY; soundReqMsg.sound_name = soundName; if volume is None: soundReqMsg.volume = -1.0; else: soundReqMsg.volume = volume; with self.soundLock: self.rosSoundRequestor.publish(soundReqMsg); # Keep this block out of the lock! Thread registration will # acquire the lock (thus leading to deadlock, if lock is owned here): if numRepeats > 0 or numRepeats == -1: soundRepeatThread = RoboComm.SoundReplayDemon(soundName, self, volume=volume, numRepeats=numRepeats, repeatPeriod=repeatPeriod); self.registerSoundRepeaterThread(soundRepeatThread) soundRepeatThread.start();
def broadcastButtonProgram(text, voice, ttsEngine): # Have we broadcast a button program before? if RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER is None: # No, let's create a ros publisher: if SpeakeasyUtils.rosMasterRunning(): # Declare us to be a ROS node. # Allow multiple GUIs to run simultaneously. Therefore # the anonymous=True: RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER = rospy.Publisher('SpeakEasyButtonProgram', SpeakEasyButtonProgram); nodeInfo = rospy.init_node('speakeasy_remote_gui', anonymous=True); else: raise NotImplementedError("ROS master must be running to broadcast button programs.") msg = SpeakEasyButtonProgram() msg.text = text msg.voiceName = voice msg.engineName = ttsEngine RoboComm.ROS_BUTTON_PROGRAM_BROADCASTER.publish(msg)
def setSoundVolume(self, volume, soundName=None): ''' Change the sound effect default volume. @param volume: New default volume for sound effects. Value must be in range 0.0 to 1.0. @type volume: float @param soundName: Optionally the name of the sound whose volume is to be changed. @type soundName: string @raises TypeError: if any parameters are of incorrect type. ''' if not SpeakeasyUtils.ensureType(volume, float): raise TypeError("Volume must be a float between 0.0 and 1.0. Was " + str(volume)); soundReqMsg = SpeakEasySound(); soundReqMsg.command = SoundCommands.SET_VOL; if soundName is None: soundName = ''; soundReqMsg.sound_name = soundName; soundReqMsg.volume = volume; with self.soundLock: self.rosSoundRequestor.publish(soundReqMsg);
def getAvailableLocalSoundEffectFileNames(self): # scriptDir = os.path.dirname(os.path.realpath(__file__)); # soundDir = os.path.join(scriptDir, "../../sounds"); if not os.path.exists(SpeakEasyController.SOUND_DIR): raise ValueError("No sound files found.") fileAndDirsList = os.listdir(SpeakEasyController.SOUND_DIR) fileList = [] # Grab all usable sound file names: for fileName in fileAndDirsList: fileExtension = SpeakeasyUtils.fileExtension(fileName) if (fileExtension == "wav") or (fileExtension == "ogg"): fileList.append(fileName) sound_file_basenames = [] for (i, full_file_name) in enumerate(fileList): baseName = os.path.basename(full_file_name) SpeakEasyController.soundPaths["SOUND_" + str(i)] = full_file_name # Chop extension off the basename (e.g. rooster.wav --> rooster): sound_file_basenames.append(os.path.splitext(os.path.basename(full_file_name))[0]) # Map basename (e.g. 'rooster.wav') to its full file name. self.soundPathsFull[baseName] = os.path.join(SpeakEasyController.SOUND_DIR, full_file_name) return sound_file_basenames
def __init__(self): ''' Initialize sound to play at the robot. Initializes self.sound_file_names to a list of sound file names for use with play-sound calls to the Robot via ROS. @raise NotImplementedError: if ROS initialization failed. @raise IOError: if SpeakEasy node is online, but service call to it failed. ''' if not ROS_IMPORT_OK: raise NotImplementedError("ROS is not installed on this machine."); self.rosSpeakEasyNodeAvailable = False; self.latestCapabilitiesReply = None; self.nodeStatusLock = threading.Lock(); self.textToSpeechLock = threading.Lock(); self.musicLock = threading.Lock(); self.soundLock = threading.Lock(); # Place to remember threads that repeat voice/sound/music. # These lists are used when stopping those threads: self.speechThreads = []; self.soundThreads = []; self.musicThreads = []; # init_node hangs indefinitely if roscore is not running. # Therefore: check for that. if not SpeakeasyUtils.rosMasterRunning(): raise NotImplementedError("The roscore process is not running."); # We now know that ROS is installed, and that roscore is running. # Publishers of requests for sound effects, music, and text-to-speech: self.rosSoundRequestor = rospy.Publisher('speakeasy_sound_req', SpeakEasySound); self.rosMusicRequestor = rospy.Publisher('speakeasy_music_req', SpeakEasyMusic); self.rosTTSRequestor = rospy.Publisher('speakeasy_text_to_speech_req', SpeakEasyTextToSpeech); # Declare us to be a ROS node. # Allow multiple GUIs to run simultaneously. Therefore # the anonymous=True: nodeInfo = rospy.init_node('speakeasy_remote_gui', anonymous=True); # Don't know why, but without a bit of delay after this init, the first # published message will not be transmitted, no matter what the message type: rospy.sleep(1.0); # Wait for Ros SpeakEasy service for a limited time; there might be none: waitTime = 4; # seconds secsWaited = 0; while not self.robotAvailable() and (secsWaited < waitTime): rospy.loginfo("No SpeakEasy node available. Keep checking for %d more second(s)..." % (waitTime - secsWaited)); rospy.sleep(1); secsWaited += 1; if secsWaited >= waitTime: rospy.logerr("Speech/sound/music capabilities service is offline."); self.rosSpeakEasyNodeAvailable = False; raise NotImplementedError("Speech capabilities service is offline."); rospy.loginfo("Speech capabilities service online.");