I have code that needs to wait for the text-to-speech engine to wait until no speech is being spoken. When I use Tolk.is_speaking() with NVDA, it won't report the state correctly. SAPI reports just fine.
I don't know what I'm missing here. Is there something better to use than Tolk that I just haven't found yet?
*** CODE ***
# Author: Stephen Luttrell
# File: testMessageBox.py
# Description: Tests the simple_message_box function in dynamic_menu.py * see below
from dynamic_menu import simple_message_box
from audio import shut_down_audio
from create_window import display
import pygame
display('test message box')
quit = True
box = simple_message_box()
box.set_text('If we desire to know what ideas men held in heathen times about the life beyond the grave, it is natural to turn first to the evidence of archaeology.')
box.set_confirm_button_text('mChange.wav')
box.set_speak_ok(False)
#box.set_text('intro.wav')
#box.set_is_filename(True)
while quit:
for e in pygame.event.get():
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_RETURN:
box.run()
elif e.key == pygame.K_ESCAPE:
quit = False
shut_down_audio()
print('done')
# File: dynamic_menu.py
# Description: Creates an extensible menu and also some simple data gathering and display objects
from audio import *
import pygame
import time
# A message box that speaks or plays some message then waits for confirmation
class simple_message_box: # Object for a text box with a single dismissal action
def __init__(self):
self.text = '' # The body of the message
self.confirmButton = 'ok' # The confirm alert when the message is done
self.speakOK = True # True sends the text of the confirm button to the text-to-speech engine: False sends it to the sound device as a file path of a sound to be played
self.playSound = False # False sends the contents of the message box to the text-to-speech engine: True sends it to the sound device as a file path to be played
# Changes the message contents of the body to text to be spoken or the path of a file to be played
def set_text(self, text):
self.text = text
# Changes the default text of the confirm button (or to the path of a file to be played)
def set_confirm_button_text(self, text):
self.confirmButton = text
# Sets the text body to text to be spoken (False) or the path of a sound to be played (True)
def set_is_filename(self, value):
self.playSound = value
# Sets the confirm button to text to be spoken (True) or the path of a sound to be played (False)
def set_speak_ok(self, value):
self.speakOK = value
def run(self):
# Method variables
quit = True
playOK = 0 # Tells the function when it's time to play or speak the confirm button (0: waiting to be played, 1: play the button, 2: has played the button and is not to play it again)
source = play_text(self.text, self.playSound)
while quit:
time.sleep(.025) # Be kind to processors (This time it also serves to delay the loop from running long enough for the text-to-speech engine to report as active)
sound_cleanup()
if self.playSound: # This is a sound file
if not is_playing(source): # The sound has finished playing
if not playOK == 2: # playOK tells the method to speak the confirm button or play a confirm sound when the text or audio message is finished. It will only play if playOK is 1 (setting it to 2 prevents it from triggering multiple times).
playOK = 1
else: # This is a block of text
if not is_speaking(): # The text-to-speech engine is finished with the text.
if not playOK == 2:
playOK = 1
if playOK == 1: # Play the confirm sound or speak the confirm button
if not self.speakOK: # This is a confirm sound
play_sound(self.confirmButton) # Play the sound once
else: # This is a text button
speak(self.confirmButton) # Speak the confirm button using text-to-speech
playOK = 2 # Don't play the confirm button again
for e in pygame.event.get(): # Poll the key events
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_RETURN or e.key == pygame.K_ESCAPE or e.key == pygame.K_SPACE:
# Stop the sound/speech and exit the text box regardless if either is finished
if self.playSound: # This is a audio message
stop_sound(source) # Stop the sound
else:
stop_speaking()
quit = False
if e.key == pygame.K_UP: # Replay the contents
if self.playSound:
if not is_playing(source): # The contents have finished playing
playOK = 0 # Also replay the confirm button
source = play_text(self.text, self.playSound)
else:
if not is_speaking():
playOK = 0
speak(self.text)
# A helper function to replay sounds and text and such
def play_text(text, isFilename):
if isFilename:
return play_sound(text)
else:
speak(text)
return 0
*** Skip to the bottom for the TTS functions ***
# File: audio.py
# Description: Various audio and tts functions
from openal import *
import pygame
import random
import time
import Tolk
pool = {} # Dictionary of sound sources
# adds the source file to the sound pool and Plays the audio file
def play_sound(filename):
# Function variables
global pool
if not filename == "": # If there is a valid path to a sound that can be played
index = time.time() # Generate an index based on the number of seconds since the beginning of the robot apocalypse began
while index in pool: # Make sure the index does not already exist. Not likely, but possible. So this makes sure it can't happen.
index = index + (index * (random.randint(1, 100)/100))
pool = {index: oalOpen(filename)} # Add the source to the sound pool dictionary
pool[index].play() # play the sound
return index # returns the index for sound management
# Plays a company intro (skippable with any key)
def play_intro(filename='intro.wav'): # intro is the default file name (Change to reflect file structure)
# Function variables
global pool
quit = False
source = play_sound(filename) # Play the sound
while not quit: # Loop until the intro is done playing
time.sleep(.025) # Be kind to processors
if not is_playing(source): # Intro is finished
sound_cleanup() # Remove the source from the sound pool
quit = True # End the loop
for e in pygame.event.get(): # Query the event pool
if e.type == pygame.KEYDOWN: # Any key
stop_sound(source) # End the intro early and remove it from the sound pool
quit = True # End the loop
# Returns the state of the audio source (whether it is playing or not)
def is_playing(index):
# Function variables
global pool
if index in pool: # If the source still exists
if pool[index].get_state() == AL_PLAYING: # The audio is playing
return True
return False # If the audio is not playing or no longer exists
# Stops a sound source from playing and removes it from the pool
def stop_sound(index):
# Function variables
global pool
if index in pool: # If the source still exists
if is_playing(index): # The sound is still playing
pool[index].stop() # So stop it first
pool.pop(index) # And remove it from the pool regardless
# Removes idle sounds from the sound pool
def sound_cleanup():
global pool
listOfIndexes = []
for index in pool: # Loop through the indexes stored in the sound pool
listOfIndexes.append(index) # Add it to the list for removal
for index in listOfIndexes: # loop through the list of indexes
if not is_playing(index): # The sound is not playing (idle)
pool.pop(index) # So remove it from the pool
# Text-to-speech functions
# Speak the text
def speak(text):
if not Tolk.is_loaded(): # load Tolk if it has not been already
Tolk.try_sapi(True) # Default to SAPI if no other speech engine (e.g. NVDA) is available
# Tolk.prefer_sapi(True) # Use SAPI as default speech engine
Tolk.load()
if len(text) == 1: # This is a single character
if ord(text) >= 65 and ord(text) <= 90: # This is a capital letter
text = 'cap ' + text # Add "cap" to the output to alert the user to upper case
Tolk.output(text) # Send the text to Tolk to be spoken
# Returns the state of the text-to-speach engine
def is_speaking():
return Tolk.is_speaking()
# Cancels the speach of the text-to-speech engine
def stop_speaking():
Tolk.silence()
# Finalize and end all the audio and text-to-speech stuff
def shut_down_audio():
# Function variables
global pool
for index in pool: # Make sure all the sounds, music, and such are stopped
pool[index].stop()
pool.clear() # Clear the sound pool from memory
oalQuit() # Shutdown OpenAL devices
Tolk.unload() # Close out text-to-speech
You can follow me on twitter @s_luttrell and an almost never used Facebook account at skluttrell.