Interrupt espeak on a new utterance (#7207)

The SAPI and AVSpeechSynthesizer backends both drop what they are saying when
a new utterance arrives (SPF_PURGEBEFORESPEAK, stopSpeakingAtBoundary). espeak
never did, so on Linux every menu item scrolled past queued another utterance
with no way to skip ahead.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
David Racine
2026-09-16 20:13:59 -04:00
committed by GitHub
parent e628cf0574
commit c9e3ef7652
2 changed files with 8 additions and 1 deletions
@@ -13,9 +13,10 @@ bool ESpeakSpeechSynthesizer::DoInit() {
this->Initialize = (speak_Initialize)dlsym(espeak, "espeak_Initialize");
this->SetVoiceByProperties = (speak_SetVoiceByProperties)dlsym(espeak, "espeak_SetVoiceByProperties");
this->Synth = (speak_Synth)dlsym(espeak, "espeak_Synth");
this->Cancel = (speak_Cancel)dlsym(espeak, "espeak_Cancel");
this->Terminate = (speak_Terminate)dlsym(espeak, "espeak_Terminate");
if (this->Initialize == NULL || this->SetVoiceByProperties == NULL || this->Synth == NULL ||
this->Terminate == NULL) {
this->Cancel == NULL || this->Terminate == NULL) {
SPDLOG_INFO("Failed to load espeak-ng");
dlclose(espeak);
return false;
@@ -39,6 +40,10 @@ void ESpeakSpeechSynthesizer::Speak(const char* text, const char* language) {
if (this->espeak == NULL) {
SPDLOG_INFO("Spoken Text ({}): {}", language, text);
} else {
// Match the SAPI and AVSpeechSynthesizer backends, which both drop what they are saying when a new
// utterance arrives. Without this, scrolling a menu queues one utterance per item with no way to skip.
this->Cancel();
if (language != this->mLanguage) {
espeak_VOICE voice = { .languages = language };
if (this->SetVoiceByProperties(&voice)) {
@@ -12,6 +12,7 @@ typedef ESPEAK_API espeak_ERROR (*speak_SetVoiceByProperties)(espeak_VOICE* voic
typedef ESPEAK_API espeak_ERROR (*speak_Synth)(const void* text, size_t size, unsigned int position,
espeak_POSITION_TYPE position_type, unsigned int end_position,
unsigned int flags, unsigned int* unique_identifier, void* user_data);
typedef ESPEAK_API espeak_ERROR (*speak_Cancel)(void);
}
class ESpeakSpeechSynthesizer : public SpeechSynthesizer {
@@ -30,5 +31,6 @@ class ESpeakSpeechSynthesizer : public SpeechSynthesizer {
speak_Initialize Initialize = NULL;
speak_SetVoiceByProperties SetVoiceByProperties = NULL;
speak_Synth Synth = NULL;
speak_Cancel Cancel = NULL;
speak_Terminate Terminate = NULL;
};