def speak(self, speechSequence: SpeechSequence, priority: Spri): log._speechManagerUnitTest("speak (priority %r): %r", priority, speechSequence) interrupt = self._queueSpeechSequence(speechSequence, priority) self._doRemoveCancelledSpeechCommands() # If speech isn't already in progress, we need to push the first speech. push = self._hasNoMoreSpeech() or not self._synthStillSpeaking() log._speechManagerDebug( f"Will interrupt: {interrupt}" f" Will push: {push}" f" | _indexesSpeaking: {self._indexesSpeaking!r}" f" | _curPriQueue valid: {not self._hasNoMoreSpeech()}" f" | _shouldPushWhenDoneSpeaking: {self._shouldPushWhenDoneSpeaking}" f" | _cancelledLastSpeechWithSynth {self._cancelledLastSpeechWithSynth}" ) if interrupt: log._speechManagerDebug("Interrupting speech") getSynth().cancel() self._indexesSpeaking.clear() self._cancelCommandsForUtteranceBeingSpokenBySynth.clear() push = True if push: log._speechManagerDebug("Pushing next speech") self._pushNextSpeech(True) else: log._speechManagerDebug("Not pushing speech")
def _onSynthIndexReached(self, synth=None, index=None): log._speechManagerUnitTest( f"synthReachedIndex: {index}, synth: {synth}") if synth != getSynth(): return # This needs to be handled in the main thread. queueHandler.queueFunction(queueHandler.eventQueue, self._handleIndex, index)
def _onSynthDoneSpeaking(self, synth: Optional[ synthDriverHandler.SynthDriver] = None): log._speechManagerUnitTest(f"synthDoneSpeaking synth:{synth}") if synth != getSynth(): return # This needs to be handled in the main thread. queueHandler.queueFunction(queueHandler.eventQueue, self._handleDoneSpeaking)
def _pushNextSpeech(self, doneSpeaking: bool): log._speechManagerDebug( f"pushNextSpeech - doneSpeaking: {doneSpeaking}") queue = self._getNextPriority() if not queue: # No more speech. log._speechManagerDebug("No more speech") self._curPriQueue = None return if self._hasNoMoreSpeech(): # First utterance after no speech. self._curPriQueue = queue elif queue.priority > self._curPriQueue.priority: # Preempted by higher priority speech. if self._curPriQueue.enteredProfileTriggers: if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._exitProfileTriggers( self._curPriQueue.enteredProfileTriggers) self._curPriQueue = queue elif queue.priority < self._curPriQueue.priority: # Resuming a preempted, lower priority queue. if queue.enteredProfileTriggers: if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._restoreProfileTriggers(queue.enteredProfileTriggers) self._curPriQueue = queue while queue.pendingSequences and isinstance( queue.pendingSequences[0][0], ConfigProfileTriggerCommand): if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._switchProfile() if not queue.pendingSequences: # The last commands in this queue were profile switches. # Call this method again in case other queues are waiting. return self._pushNextSpeech(True) seq = self._buildNextUtterance() if seq: # So that we can handle any accidentally skipped indexes. for item in seq: if isinstance(item, IndexCommand): self._indexesSpeaking.append(item.index) self._cancelledLastSpeechWithSynth = False log._speechManagerUnitTest(f"Synth Gets: {seq}") getSynth().speak(seq)
def _handleIndex(self, index: int): log._speechManagerDebug(f"Handle index: {index}") # A synth (such as OneCore) may skip indexes # If before another index, with no text content in between. # Therefore, detect this and ensure we handle all skipped indexes. handleIndexes = [] for oldIndex in list(self._indexesSpeaking): if self._isIndexABeforeIndexB(oldIndex, index): log.debugWarning("Handling skipped index %s" % oldIndex) handleIndexes.append(oldIndex) handleIndexes.append(index) valid, endOfUtterance = False, False for i in handleIndexes: try: self._indexesSpeaking.remove(i) except ValueError: log.debug( "Unknown index %s, speech probably cancelled from main thread." % i) break # try the rest, this is a very unexpected path. if i != index: log.debugWarning("Handling skipped index %s" % i) # we must do the following for each index, any/all of them may be end of utterance, which must # trigger _pushNextSpeech _valid, _endOfUtterance = self._removeCompletedFromQueue(i) valid = valid or _valid endOfUtterance = endOfUtterance or _endOfUtterance if _valid: callbackCommand = self._indexesToCallbacks.pop(i, None) if callbackCommand: try: log._speechManagerUnitTest( f"CallbackCommand Start: {callbackCommand!r}") callbackCommand.run() log._speechManagerUnitTest("CallbackCommand End") except Exception: log.exception("Error running speech callback") self._doRemoveCancelledSpeechCommands() shouldPush = ( endOfUtterance and not self._synthStillSpeaking() # stops double speaking errors ) if shouldPush: if self._indexesSpeaking: log._speechManagerDebug( f"Indexes speaking: {self._indexesSpeaking!r}," f" queue: {self._curPriQueue.pendingSequences}") # Even if we have many indexes, we should only push next speech once. self._pushNextSpeech(False)
def cancel(self): log._speechManagerUnitTest("Cancel") getSynth().cancel() if self._curPriQueue and self._curPriQueue.enteredProfileTriggers: self._exitProfileTriggers(self._curPriQueue.enteredProfileTriggers) self._reset()
def removeCancelledSpeechCommands(self): log._speechManagerUnitTest("removeCancelledSpeechCommands") self._doRemoveCancelledSpeechCommands()