RapidWareTech / pyttsx

Cross-platform text-to-speech wrapper
Other
370 stars 134 forks source link

Engine.isBusy() does not seem to return consistent value, according to documentation #13

Open quattrinili opened 11 years ago

quattrinili commented 11 years ago

As written in the title, isBusy() function does not return consistent value (at least on how I interpreted the documentation), namely True if the engine is currently busy speaking an utterance or False if not.

This can be checked when the engine is initialized, which obviously is not currently busy speaking, but still the function returns true.

The reason why this happen is that when the engine is initialized, the corresponding variable that saves whether the engine is busy or not is initialized to True. Just initializing that value to False does not make the engine working correctly (i.e., using say() and runAndWait()). In the following I report what happens when _busy is initialized to False explaining why it does not work. When say() is called, the command is pushed in the queue and _pump() is called, launching the command to start the utterance. Then, when runAndWait() is called endLoop() is pushed in the command queue and "pumped" (according to _push() implementation) and then startLoop() is called. Note that when endLoop() is pushed in the command queue, variable _busy is False. So, as endLoop() is called, then the utterance is interrupted.

A possible solution is the following: In driver.py (diff w.r.t. the current version on the repository (downloaded on July 20th, 2013) containing the modifications http://pastebin.com/mrvD6G4a):

In espeak.py (diff w.r.t. the current version on the repository (downloaded on July 20th, 2013) containing the modifications: http://pastebin.com/uin8ybPG; note that these modifications should be replicated in the other drivers):

The idea is that instead of setting variable _busy in an ad hoc way so that commands can be pumped, actually pump commands when they are actually processed.

This modification should correctly set the variable _busy (namely True if the engine is currently busy speaking an utterance or False if not), at least in the scenario I considered (i.e., call of say() and runAndWait()). For other scenarios (e.g., using externalLoop()), it should be carefully checked if the system still works.

parente commented 11 years ago

Thanks for the feedback. I haven't had a chance to dig in yet. If this is blocking you in any way and you have a fix + tests, please open a pull request. Otherwise, I'll try to get to it when I have some time to dig in.

LiKe85 commented 11 years ago

I experienced the same thing and wanted to provide some code and "log" data that shows some more information to this issue. This was tested with the v1.1 from the repository without any modifications.

In the docs it says for the function isBusy() that it "Gets if the engine is currently busy speaking an utterance or not." So I made a little test under Windows 7 and Ubuntu 12.04 which revealed that in the literal sense the function does not work as documented as already mentioned here.

The test script:

import os
import sys
sys.path.insert(0, os.path.join('..', '..'))
import pyttsx
import time

def onWord(name, location, length):
   print 'word', name, location, length
   print("Is busy while speaking? - [{}]".format(engine.isBusy()))
   if location > 10:
      engine.stop()

engine = pyttsx.init()
print("Is busy directly after init? - [{}]".format(engine.isBusy()))
time.sleep(2)
print("Is busy 2 seconds after init? - [{}]".format(engine.isBusy()))

engine.connect('started-word', onWord)
print("Is busy after connecting callback? - [{}]".format(engine.isBusy()))

time.sleep(3)
print("Is busy before queueing say command? - [{}]".format(engine.isBusy()))
engine.say('The quick brown fox jumped over the lazy dog.')
print("Is busy after queueing say command? - [{}]".format(engine.isBusy()))

time.sleep(2)

print("Is busy before runAndWait? - [{}]".format(engine.isBusy()))
engine.runAndWait()
print("Is busy after runAndWait? - [{}]".format(engine.isBusy()))

time.sleep(2)

print("Is busy before queueing 2nd say command? - [{}]".format(engine.isBusy()))
engine.say('The quick brown fox jumped over the lazy dog.')
print("Is busy after queueing 2nd say command? - [{}]".format(engine.isBusy()))

time.sleep(2)

print("Is busy before 2nd runAndWait? - [{}]".format(engine.isBusy()))
engine.runAndWait()
print("Is busy after 2nd runAndWait? - [{}]".format(engine.isBusy()))

Output under Windows 7:

$ python interrupting.py
Is busy directly after init? - [True]
Is busy 2 seconds after init? - [True]
Is busy after connecting callback? - [True]
Is busy before queueing say command? - [True]
Is busy after queueing say command? - [True]
Is busy before runAndWait? - [True]
word None 0 3
Is busy while speaking? - [True]
word None 4 5
Is busy while speaking? - [True]
word None 10 5
Is busy while speaking? - [True]
word None 16 3
Is busy while speaking? - [True]
word None 20 6
Is busy while speaking? - [True]
Is busy after runAndWait? - [False]
Is busy before queueing 2nd say command? - [False]
Is busy after queueing 2nd say command? - [True]
Is busy before 2nd runAndWait? - [True]
word None 0 3
Is busy while speaking? - [False]
word None 4 5
Is busy while speaking? - [False]
word None 10 5
Is busy while speaking? - [False]
word None 16 3
Is busy while speaking? - [False]
word None 20 6
Is busy while speaking? - [False]
word None 27 4
Is busy while speaking? - [False]
word None 32 3
Is busy while speaking? - [False]
word None 36 4
Is busy while speaking? - [False]
word None 41 3
Is busy while speaking? - [False]
Is busy after 2nd runAndWait? - [False]

Output under Ubuntu:

Is busy directly after init? - [True]
Is busy 2 seconds after init? - [True]
Is busy after connecting callback? - [True]
Is busy before queueing say command? - [True]
Is busy after queueing say command? - [True]
Is busy before runAndWait? - [True]
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
ALSA lib setup.c:565:(add_elem) Cannot obtain info for CTL elem (MIXER,'IEC958 Playback Default',0,0,0): No such file or directory
ALSA lib setup.c:565:(add_elem) Cannot obtain info for CTL elem (MIXER,'IEC958 Playback Default',0,0,0): No such file or directory
ALSA lib setup.c:565:(add_elem) Cannot obtain info for CTL elem (MIXER,'IEC958 Playback Default',0,0,0): No such file or directory
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib audio/pcm_bluetooth.c:1614:(audioservice_expect) BT_GET_CAPABILITIES failed : Input/output error(5)
ALSA lib audio/pcm_bluetooth.c:1614:(audioservice_expect) BT_GET_CAPABILITIES failed : Input/output error(5)
ALSA lib audio/pcm_bluetooth.c:1614:(audioservice_expect) BT_GET_CAPABILITIES failed : Input/output error(5)
ALSA lib audio/pcm_bluetooth.c:1614:(audioservice_expect) BT_GET_CAPABILITIES failed : Input/output error(5)
ALSA lib pcm_dmix.c:957:(snd_pcm_dmix_open) The dmix plugin supports only playback stream
Cannot connect to server socket err = No such file or directory
Cannot connect to server socket
jack server is not running or cannot be started
word None 0 3
Is busy while speaking? - [True]
word None 4 5
Is busy while speaking? - [True]
word None 10 5
Is busy while speaking? - [True]
word None 16 3
Is busy while speaking? - [True]
Is busy after runAndWait? - [False]
Is busy before queueing 2nd say command? - [False]
Is busy after queueing 2nd say command? - [True]
word None 0 3
Is busy while speaking? - [True]
word None 4 5
Is busy while speaking? - [True]
word None 10 5
Is busy while speaking? - [True]
word None 16 3
Is busy while speaking? - [True]
word None 20 6
Is busy while speaking? - [True]
word None 27 4
Is busy while speaking? - [True]
word None 32 3
Is busy while speaking? - [True]
word None 36 4
Is busy while speaking? - [True]
word None 41 3
Is busy while speaking? - [True]
Is busy before 2nd runAndWait? - [True]
Is busy after 2nd runAndWait? - [False]

One can see, that under both OSs the driver claims to be busy with speaking an utterance right after initialization and during configuration. And it is still busy while queueing the first utterance until the first call to runAndWait(). After speaking out the text the driver is finally not busy anymore. This seems to me like an initialization issue.

The second observation is, that the driver again claims to be busy when a sentence is added with the say() function after the first runAndWait(). The driver should only return that it is busy while making an utterance, not while the are queued commands, that are not processed at the moment.

A third - platform dependent - observation is, that the driver under Windows seems to be not busy while actually making an utterance which seems to be wrong as far as I understand it.

In my experience the errors in the Ubuntu output can be igrnored since I was able to hear the utterance.

You might notice that stopping the utterance in the example code works for the first execution of runAndWait(), but not for the second. This is reported seperately as #16.

parente commented 11 years ago

Finally getting some time to dig into this bug. The problem at hand is that the _busy internal flag is being used both to represent "is the driver ready to process the next utterance" and "is the driver busy outputting an utterance." One way to fix is to follow @quattrinili 's approach: avoid relying on _busy for utterance queue pumping. Another approach is to invent a new _speaking instance var, update it appropriately throughout the code, and return it from isBusy instead of the internal state flag. (The naming is unfortunately, but changing the semantics of isBusy and inventing a new isSpeaking seems wrong.)

Looking at the discrepancies across the driver implementations that had to do with mitigating platform differences in loop pumping behavior, I'm leaning toward the second solution. I think it better avoids regressing cross-platform behavior by duplicating a bit of internal state.

Will give it some more thought and testing before committing to an approach.