#----------------------------------------------------------------------------
# Name: 		pyTTS
# Author:		Gary Bishop
# Created:		?
# Copyright (c) 2002 Gary Bishop, University of North Carolina-Chapel Hill
# Licensed under the Academic Free License version 1.1.
#----------------------------------------------------------------------------
from win32com.client import constants
import os.path
import win32com.client
import pythoncom
import threading
import MSSAPI

# constants used as flags for the tts.Speak method
#tts_tmp = Dispatch('SAPI.SpVoice')   # COM Voice Object, must be created to create constants
tts_default = constants.SVSFDefault
tts_async = constants.SVSFlagsAsync
tts_purge_before_speak = constants.SVSFPurgeBeforeSpeak
tts_is_xml = constants.SVSFIsXML
tts_is_filename = constants.SVSFIsFilename
tts_is_not_xml = constants.SVSFIsNotXML
tts_persist_xml = constants.SVSFPersistXML
tts_nlp_speak_punc = constants.SVSFNLPSpeakPunc

class pyTTS:
	""" Text to Speech Module Using MS Speech SDK 5.1 COM Objects """

	def __init__(self):
		self.speech = MSSAPI.SpVoice() #Dispatch('SAPI.SpVoice')
		self.speechStream = MSSAPI.SpFileStream() 
#Dispatch('SAPI.SpFileStream')
		# valid flags are all powers of 2 (and 0)
		self.validFlags = [pow(2, x) for x in range(0, 7)]
		self.validFlags.append(0)
		
		self.voiceDict = self.GetVoices()
	
	def Speak(self, text, *flags):
		""" text: The text to be spoken, or if the tts_is_filename flag is 
included in the flags parameter, the path of the file to be spoken.
		flags: Optional, default value is tts_default """
		for flag in flags:
			if flag not in self.validFlags:
				raise ValueError, "Invalid Flag"
		flagsum = reduce(lambda x, y: x+y, flags, 0)
		self.speech.Speak(text, flagsum)

	def SpeakFromWave(self, file):
		'''Speak to a wave file.'''
		self.speechStream.Open(file, constants.SSFMOpenForRead)
		self.speech.SpeakStream(self.speechStream)
		self.speechStream.Close()

	def SpeakToWave(self, file, text):
		'''Create a wave file from speech.'''
		#store a reference to the original speach stream
		audio_stream = self.speech.AudioOutputStream
		
		#redirect the speech to the file
		self.speechStream.Open(file, constants.SSFMCreateForWrite)
		self.speech.AudioOutputStream = self.speechStream
		self.Speak(text)
		self.speechStream.Close()
		
		#restore the reference to the original stream
		self.speech.AudioOutputStream = audio_stream
		
	def GetVoices(self):
		""" Returns a dictionary of voices and their voice objects"""
		voiceDict = {}
		voiceList = self.speech.GetVoices()
		for voice in voiceList:
			id = os.path.basename(voice.Id)
			voiceDict[id] = voice
		return voiceDict
	
	def SetVoice(self, voiceObject):
		""" Sets current voice to voiceObject """
		self.speech.Voice = voiceObject
		
	def SetVoiceByName(self, voiceName):
		""" Sets current voice to voiceObject by name """
		try:
			self.SetVoice(self.voiceDict[voiceName])
		except:
			pass
		
	def GetRate(self):
		""" Rate can range from -10 to 10. """
		return self.speech.Rate
	
	def SetRate(self, rate):
		""" Valid values for the rate property range from -10 to 10. """
		if not -10 <= rate <= 10:
			raise ValueError, "Rate must be between -10 and 10"
		self.speech.Rate = rate
		
	def Pause(self):
		""" Pause the the voice at next stopping point and close the output 
device allowing its use by other voices. """
		self.speech.Pause()

	def Resume(self):
		""" Resume speaking when paused. """
		self.speech.Resume()
		
	def Skip(self, numItems, typeItems="Sentence"):
		""" Skips the voice forward or backward by the specified number of 
items within the current input text stream.
		numItems: number of items to skip forward.	A negative value specifies 
skipping backwards.
		typeItems: only supported type is Sentence"""
		self.speech.Skip(typeItems, numItems)
		
	def Stop(self):
		""" Stop current voice from speaking.  Only works on a voice speaking 
asynchronously. """
		self.speech.Speak('', tts_purge_before_speak)
		
	def GetVolume(self):
		""" Volume can range from 0 to 100 """
		return self.speech.Volume
	
	def SetVolume(self, vol):
		""" Valid values for vol are 0 to 100 """
		if not 0 <= vol <= 100:
			raise ValueError, "Volume must be between 0 and 100"
		self.speech.Volume = vol

# class SpeechRecognition:
# 	""" Initialize the speech recognition with the passed in list of 
#words """
# 	def __init__(self, wordsToAdd):
# 		# For speech recognition - first create a listener
# 		self.listener = win32com.client.Dispatch("SAPI.SpSharedRecognizer")
# 		# Then a recognition context
# 		self.context = self.listener.CreateRecoContext()
# 		# which has an associated grammar
# 		self.grammar = self.context.CreateGrammar()
# 		# Do not allow free word recognition - only command and control
# 		# recognizing the words in the grammar only
# 		self.grammar.DictationSetState(0)
# 		# Create a new rule for the grammar, that is top level (so it begins
# 		# a recognition) and dynamic (ie we can change it at runtime)
# 		self.wordsRule = self.grammar.Rules.Add("wordsRule",
# 						constants.SRATopLevel + constants.SRADynamic, 0)
# 		# Clear the rule (not necessary first time, but if we're changing it
# 		# dynamically then it's useful)
# 		self.wordsRule.Clear()
# 		# And go through the list of words, adding each to the rule
# 		[ self.wordsRule.InitialState.AddWordTransition(None, word) for word in wordsToAdd ]
# 		# Set the wordsRule to be active
# 		self.grammar.Rules.Commit()
# 		self.grammar.CmdSetRuleState("wordsRule", 1)
# 		# Commit the changes to the grammar
# 		self.grammar.Rules.Commit()
# 		# And add an event handler that's called back when recognition occurs
# 		self.eventHandler = ContextEvents(self.context)
# 
# The callback class that handles the events raised by the speech object.
# 	See "Automation | SpSharedRecoContext (Events)" in the MS Speech SDK
# 	online help for documentation of the other events supported. 
# class ContextEvents(win32com.client.getevents("SAPI.SpSharedRecoContext")):
# 	#Called when a word/phrase is successfully recognized  -
# 	#	 ie it is found in a currently open grammar with a sufficiently high
# 	#	 confidence
# 	def OnRecognition(self, StreamNumber, StreamPosition, RecognitionType, Result):
# 		newResult = win32com.client.Dispatch(Result)
# 		print "You said in speech reg: ",newResult.PhraseInfo.GetText() 	   
# 
# class ListenThread(threading.Thread):
# 	def run(self):
# 		wordsToAdd = [ "One", "Two", "Three", "Four", "exit" ]
# 		speechRecog = SpeechRecognition(wordsToAdd)
# 		while 1:
# 			pythoncom.PumpWaitingMessages()

if __name__=='__main__':
# 	tts = pyTTS()
# 	tts.Speak('Testing!')
# 	v = tts.GetVoices()
# 	m = v['LHMICHELLE']
# 	tts.SetVoice(m)
# 	tts.Speak('This is a test')
	pyTTS = pyTTS()
	voices = pyTTS.GetVoices()
	michelle = voices['LHMICHELLE']
	pyTTS.SetVoice(michelle) # causes exception in line with Speak()
	pyTTS.Speak('Hello')