Emacspeak on Android
by Tim Makarios
Hi,
I've been trying to get Emacspeak working on my mobile phone, which is
running LineageOS 17.1 (based on Android 10). So far, I've tried a couple
of things, but neither has quite worked.
First, I tried installing Emacspeak on debian-buster from proot-distro in
Termux. Unfortunately, it's silent, and running espeak on its own results
in a long list of errors, starting with
> ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
so I guess Termux's proot-distro isn't giving Debian access to the audio.
For another attempt, I tried building Emacspeak directly in Termux. This
required building TclX, too, which seemed to work, but when I tried running
`./servers/espeak` it gave these errors
> couldn't load file
> "/data/data/com.termux/files/usr/lib/tclx8.4/libtclx8.4.so": dlopen
> failed: cannot locate symbol "rresvport" referenced by
> "/data/data/com.termux/files/usr/lib/tclx8.4/libtclx8.4.so"...
> while executing
> "load /data/data/com.termux/files/usr/lib/tclx8.4/libtclx8.4.so Tclx"
> ("package ifneeded Tclx 8.4" script)
> invoked from within
> "package require Tclx"
> (file "./servers/espeak" line 37)
so I guess TclX relies on rresvport, whatever that is, and Termux doesn't
provide it.
Does anyone have any clues about how I might be able to get Emacspeak
running on my phone?
Thanks,
Tim
<><
12 months
Latest espeak-ng from master is silent with emacspeak, but works with speech-dispatcher
by Zahari Yurukov
Hi,
I'm using the latest emacs from master, as well as the latest emacspeak from master. Fedora 33 with the latest updates.
I compiled the latest espeak-ng from master with sonic support enabled. It works fine with Orca via speech-dispatcher, but emacspeak is silent without giving any errors.
Until that the espeak-ng package from Fedora's repositories worked fine with emacspeak. I've priviously have used espeak-ng compiled by myself with the exact same configuration without any problem with emacspeak.
Emacspeak's Auditory Icons do work. I've tried the following from a separate console and I can hear espeak-ng:
speak-ng -s 1000 --stdout hello |play -
I recompiled the eemacspeak's espeak module with :
make -B espeak
Can you reproduce this? Do you have any clue where the problem could be.
--
Best wishes,
Zahari
1 year, 5 months
[PATCH] Add dtk-set-variant command to allow espeak's variants to be selected
by Krzysztof Drewniak
dtk-set-variant is bound to C-e d M-v.
(In addition, fix indentation in servers/espeak and fix close_tags in
tclsespeak.cpp to be more memory efficient).
I'm resending this since it didn't seem to get any respnonse last time.
If I missed a response, sorry about that.
---
info/docs.texi | 25 +++++-
info/tts.texi | 9 +++
lisp/dtk-speak.el | 18 +++++
lisp/emacspeak-keymap.el | 1 +
servers/espeak | 42 ++++++++---
servers/native-espeak/tclespeak.cpp | 113 +++++++++++++++++-----------
6 files changed, 156 insertions(+), 52 deletions(-)
diff --git a/info/docs.texi b/info/docs.texi
index ac3a3e908..35cbf4a68 100644
--- a/info/docs.texi
+++ b/info/docs.texi
@@ -4,7 +4,7 @@
@include intro-docs.texi
-This chapter documents a total of 1144 commands and 146 options.
+This chapter documents a total of 1145 commands and 146 options.
@menu
* amixer::Control AMixer from Emacs.
@@ -732,6 +732,29 @@ current local value to the result.
@end format
@end deffn
+@subsection dtk-set-variant
+@deffn {Command} dtk-set-variant (&optional variant)
+
+@table @kbd
+@item C-e d M-v
+@kindex C-e d M-v
+@item <fn> d M-v
+@kindex <fn> d M-v
+@end table
+
+@findex dtk-set-variant
+@format
+Set voice variant used by the speech engine to VARIANT.
+
+VARIANT may be any value supported by the speech engine.
+In the case of espeak, it may be a string name or an integer ID.
+
+Omitting VARIANT or setting it to NIL resetss this value to default.
+
+(fn &optional VARIANT)
+@end format
+@end deffn
+
@subsubsection dtk-stop
@deffn {Command} dtk-stop (&optional all)
@table @kbd
diff --git a/info/tts.texi b/info/tts.texi
index c856ec658..52a6f19c2 100644
--- a/info/tts.texi
+++ b/info/tts.texi
@@ -130,6 +130,15 @@ Dectalks, e.g. the Dectalk Express. Possible
values are `math, name,
europe, spell', all of which can be turned on or off. Argument STATE
specifies new state.
+@findex dtk-set-variant
+@kindex C-e d M-v
+@item C-e d M-v
+@code{dtk-set-variant}
+
+Set the voice variant used by the speech engine, if the engine has
+a concept of variants, to VARIANT. If VARIANT is blank or nil, resets
+the variant setting to its default value.
+
@findex dtk-toggle-split-caps
@kindex C-e d s
@item C-e d s
diff --git a/lisp/dtk-speak.el b/lisp/dtk-speak.el
index e0b34ac49..a0c1f9c93 100644
--- a/lisp/dtk-speak.el
+++ b/lisp/dtk-speak.el
@@ -826,6 +826,16 @@ then set the current local value to the result."
dtk-character-scale
(if prefix "" "locally")))))
+(defun dtk-set-variant (&optional variant)
+ "Set voice variant used by the speech engine to VARIANT.
+
+VARIANT may be any value supported by the speech engine.
+In the case of espeak, it may be a string name or an integer ID.
+
+Omitting VARIANT or setting it to NIL resetss this value to default."
+ (interactive "sVariant name (blank selects default): ")
+ (dtk-interp-set-variant variant (called-interactively-p 'interactive)))
+
(ems-generate-switcher
'dtk-toggle-quiet
'dtk-quiet
@@ -1892,6 +1902,14 @@ Notification is logged in the notifications
buffer unless `dont-log' is T. "
dtk-speaker-process
(format "tts_set_punctuations %s\nd\n" mode)))
+;;}}}
+;;{{{ variant
+(defsubst dtk-interp-set-variant (variant say-it)
+ (cl-declare (special dtk-speaker-process))
+ (process-send-string
+ dtk-speaker-process
+ (format "tts_set_variant %S %S" variant say-it)))
+
;;}}}
;;{{{ reset
diff --git a/lisp/emacspeak-keymap.el b/lisp/emacspeak-keymap.el
index 83a46a086..24eecf9ee 100644
--- a/lisp/emacspeak-keymap.el
+++ b/lisp/emacspeak-keymap.el
@@ -359,6 +359,7 @@
("C-n" dtk-notify-initialize)
("C-o" outloud)
("C-v" global-voice-lock-mode)
+ ("M-v" dtk-set-variant)
("d" dtk-select-server)
("L" dtk-local-server)
("N" dtk-set-next-language)
diff --git a/servers/espeak b/servers/espeak
index 3f19451d7..f20cab5f7 100755
--- a/servers/espeak
+++ b/servers/espeak
@@ -56,6 +56,10 @@ source $wd/tts-lib.tcl
# For example, if there are three available languages:
# langsynth(top)=2
+# langsynth(variant): name (or ID) of the language variant in use
+# If unset, the server defaults to an autoselected male variant
+# Set by the application
+
# voicename: name of the current voice for announcements
# This variable is set by tclespeak
@@ -128,7 +132,7 @@ proc set_previous_lang {say_it} {
set langsynth(current) $index
set langcode(current) $langcode($index)
setLanguage $langsynth(current)
-puts stderr "Language: $langsynth(current) Voice: $voicename"
+ puts stderr "Language: $langsynth(current) Voice: $voicename"
if { [info exists say_it]} {
tts_say "$voicename "
}
@@ -140,15 +144,15 @@ proc set_lang {{name "en"} {say_it "nil"}} {
global langsynth
global langalias
global langcode
-global voicename
- if { ![info exists langalias($name)]} {
- return
- }
+ global voicename
+ if { ![info exists langalias($name)]} {
+ return
+ }
- if { $langalias($name) == $langsynth(current) } {
+ if { $langalias($name) == $langsynth(current) } {
return
- }
-
+ }
+
set langsynth(current) $langalias($name)
set langcode(current) $langcode($langalias($name))
setLanguage $langsynth(current)
@@ -169,6 +173,26 @@ proc set_preferred_lang {alias lang} {
set langalias($alias) $langalias($lang)
}
+# tts_set_variant - clears the variant
+# tts_set_variant "victor" - sets the variant
+# tts_set_variant 1 - sets the variant by index
+proc tts_set_variant {{new_variant ""} {say_it "nil"}} {
+ global langsynth
+ global voicename
+ set new_variant [string trim $new_variant]
+ if { $new_variant == "" || $new_variant == "nil" } {
+ unset langsynth(variant)
+ } else {
+ set langsynth(variant) $new_variant
+ }
+ # Re-set the current voice, having updated the variant
+ setLanguage $langsynth(current)
+ if { $say_it != "nil" } {
+ tts_say "$voicename "
+ }
+
+}
+
#debug
proc list_lang {} {
global langcode
@@ -198,7 +222,7 @@ proc tts_set_punctuations {mode} {
proc tts_set_speech_rate {rate} {
global tts
- set factor $tts(char_factor)
+ set factor $tts(char_factor)
set tts(speech_rate) $rate
setRate 0 $rate
service
diff --git a/servers/native-espeak/tclespeak.cpp
b/servers/native-espeak/tclespeak.cpp
index 4c29f1a98..790daf30d 100644
--- a/servers/native-espeak/tclespeak.cpp
+++ b/servers/native-espeak/tclespeak.cpp
@@ -39,12 +39,13 @@
#include <assert.h>
#include <espeak-ng/speak_lib.h>
+#include <set>
+#include <sstream>
#include <stdlib.h>
#include <string.h>
+#include <string>
#include <sys/time.h>
#include <tcl.h>
-#include <set>
-#include <string>
#include <vector>
using std::set;
using std::string;
@@ -74,7 +75,7 @@ int Synchronize(ClientData, Tcl_Interp *, int, Tcl_Obj
*CONST[]);
int Pause(ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[]);
int Resume(ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[]);
-static void initLanguage(Tcl_Interp *interp);
+static int initLanguage(Tcl_Interp *interp);
static int getLangIndex(Tcl_Interp *interp, unsigned long *theIndex);
//>
@@ -121,8 +122,7 @@ int Tclespeak_Init(Tcl_Interp *interp) {
TclEspeakFree);
//>
- initLanguage(interp);
- return TCL_OK;
+ return initLanguage(interp);
}
int GetRate(ClientData handle, Tcl_Interp *interp, int objc,
@@ -166,17 +166,13 @@ int SetRate(ClientData handle, Tcl_Interp *interp,
int objc,
//>
//<say
-static bool closeTags(string input, string &output) {
- char *tag_orig = (char *)malloc(sizeof(char) * (input.size() + 1));
- strncpy(tag_orig, input.c_str(), input.size());
- output = "";
-
+static bool closeTags(const string input, string &output) {
+ std::ostringstream closingTags;
// check that a text (non whitespace) is present
- char *tag = tag_orig;
int a_tag_count = 0;
bool a_text_is_present = false;
- while (*tag) {
+ for (auto tag = input.cbegin(); tag != input.cend(); ++tag) {
if (*tag == '<') {
a_tag_count++;
}
@@ -188,31 +184,33 @@ static bool closeTags(string input, string &output) {
if ((*tag == '>') && a_tag_count) {
a_tag_count--;
}
- tag++;
}
if (a_text_is_present) {
- tag = tag_orig;
- while (tag) {
+ string::size_type tag_pos = input.size();
+ if (string::npos == tag_pos) {
+ fprintf(stderr, "Synthesizer argument of size (size_t)(-1),
ignoring "
+ "last chraracter\n");
+ --tag_pos;
+ }
+ while (string::npos != tag_pos) {
// look for a '<'
- tag = strrchr(tag_orig, '<');
-
- if (tag) {
- char *end = strchr(tag, ' ');
- if (!end && (NULL == strchr(tag, '/'))) {
- end = strchr(tag, '>');
+ tag_pos = input.find_last_of('<', tag_pos);
+ if (string::npos != tag_pos) {
+ string::size_type end = input.find_first_of(' ', tag_pos);
+ if ((string::npos != end) &&
+ (string::npos == input.find_first_of('/', tag_pos))) {
+ end = input.find_first_of('>', tag_pos);
}
- if (end && (tag + 1 < end)) {
- *end = 0;
- output += "</" + string(tag + 1) + ">";
+ if ((string::npos != end) && (tag_pos + 1 < end)) {
+ closingTags << "</" << input.substr(tag_pos + 1, end) << ">";
}
- *tag = 0;
+ tag_pos--; // Start search before previous tag to avoid
infinite loop
}
}
}
- free(tag_orig);
-
+ output.assign(closingTags.str());
return a_text_is_present;
}
@@ -228,8 +226,14 @@ int Say(ClientData handle, Tcl_Interp *interp, int
objc,
string a_ssml = a_begin_ssml + a_end_ssml;
unsigned int unique_identifier = 0;
- espeak_Synth(a_ssml.c_str(), a_ssml.length() + 1, 0,
POS_CHARACTER, 0,
- espeakCHARS_UTF8 | espeakSSML, &unique_identifier,
NULL);
+ if (EE_OK != espeak_Synth(a_ssml.c_str(), a_ssml.length() + 1, 0,
+ POS_CHARACTER, 0,
+ espeakCHARS_UTF8 | espeakSSML,
+ &unique_identifier, NULL)) {
+ Tcl_AppendResult(
+ interp, "Could not synthesize string: ", a_ssml.c_str(),
NULL);
+ return TCL_ERROR;
+ }
}
}
}
@@ -363,17 +367,37 @@ int getTTSVersion(ClientData handle, Tcl_Interp
*interp, int objc,
static vector<string> available_languages;
-static void SetLanguageHelper(Tcl_Interp *interp, size_t aIndex) {
- espeak_VOICE *current_voice = NULL;
- espeak_VOICE a_voice;
- memset(&a_voice, 0, sizeof(espeak_VOICE));
- a_voice.languages = (char *)available_languages[aIndex].c_str();
- a_voice.gender = 1;
- espeak_SetVoiceByProperties(&a_voice);
- current_voice = espeak_GetCurrentVoice();
+static int SetLanguageHelper(Tcl_Interp *interp, size_t aIndex) {
+ espeak_ERROR voice_status = espeak_ERROR::EE_OK;
+ Tcl_Obj *variant_name = Tcl_GetVar2Ex(interp, "langsynth", "variant", 0);
+ if (variant_name) {
+ int variant_name_len = 0;
+ char *variant_name_data =
+ Tcl_GetStringFromObj(variant_name, &variant_name_len);
+ string variant_name_str(variant_name_data, variant_name_len);
+ string name = available_languages[aIndex] + "+" + variant_name_str;
+ voice_status = espeak_SetVoiceByName(name.c_str());
+ if (espeak_ERROR::EE_OK != voice_status) {
+ fprintf(stderr,
+ "Could not load voice %s, falling back to language-based
search",
+ name.c_str());
+ }
+ }
+
+ if (!variant_name || espeak_ERROR::EE_OK != voice_status) {
+ espeak_VOICE a_voice;
+ memset(&a_voice, 0, sizeof(espeak_VOICE));
+ a_voice.languages = (char *)available_languages[aIndex].c_str();
+ a_voice.gender = 1;
+ voice_status = espeak_SetVoiceByProperties(&a_voice);
+ }
+ if (espeak_ERROR::EE_OK != voice_status) {
+ Tcl_AppendResult(interp, "could not set voice");
+ return TCL_ERROR;
+ }
+ espeak_VOICE *current_voice = espeak_GetCurrentVoice();
Tcl_SetVar(interp, "voicename", current_voice->name, 0);
- // But what if we couldn't set the voice? Need some better error
handling.
- return;
+ return TCL_OK;
}
int SetLanguage(ClientData eciHandle, Tcl_Interp *interp, int objc,
@@ -381,8 +405,9 @@ int SetLanguage(ClientData eciHandle, Tcl_Interp
*interp, int objc,
unsigned long aIndex = 0;
if (getLangIndex(interp, &aIndex)) {
- SetLanguageHelper(interp, aIndex);
+ return SetLanguageHelper(interp, aIndex);
}
+ // TODO: Error reporting for this
return TCL_OK;
}
@@ -404,7 +429,7 @@ static vector<string> ParseLanguages(const char
*lang_str) {
return voice_langs;
}
-static void initLanguage(Tcl_Interp *interp) {
+static int initLanguage(Tcl_Interp *interp) {
// List the available languages
set<string> unique_languages;
int i = 0;
@@ -471,11 +496,15 @@ static void initLanguage(Tcl_Interp *interp) {
Tcl_SetVar2(interp, "langsynth", "current", buffer, 0);
Tcl_SetVar2(interp, "langcode", "current", "en", 0);
}
- SetLanguageHelper(interp, default_index);
+
+ if (TCL_OK != SetLanguageHelper(interp, default_index)) {
+ return TCL_ERROR;
+ }
// Presumably we have at least one language, namely English,
// so no chance of underflowing size_t with this subtraction:
snprintf(buffer, sizeof(buffer), "%lu", lang_count - 1);
Tcl_SetVar2(interp, "langsynth", "top", buffer, 0);
+ return TCL_OK;
}
static int getLangIndex(Tcl_Interp *interp, unsigned long *theIndex) {
--
2.25.1
dtk-set-variant is bound to C-e d M-v.
(In addition, fix indentation in servers/espeak and fix close_tags in
tclsespeak.cpp to be more memory efficient).
---
info/docs.texi | 25 +++++-
info/tts.texi | 9 +++
lisp/dtk-speak.el | 18 +++++
lisp/emacspeak-keymap.el | 1 +
servers/espeak | 42 ++++++++---
servers/native-espeak/tclespeak.cpp | 113 +++++++++++++++++-----------
6 files changed, 156 insertions(+), 52 deletions(-)
diff --git a/info/docs.texi b/info/docs.texi
index ac3a3e908..35cbf4a68 100644
--- a/info/docs.texi
+++ b/info/docs.texi
@@ -4,7 +4,7 @@
@include intro-docs.texi
-This chapter documents a total of 1144 commands and 146 options.
+This chapter documents a total of 1145 commands and 146 options.
@menu
* amixer::Control AMixer from Emacs.
@@ -732,6 +732,29 @@ current local value to the result.
@end format
@end deffn
+@subsection dtk-set-variant
+@deffn {Command} dtk-set-variant (&optional variant)
+
+@table @kbd
+@item C-e d M-v
+@kindex C-e d M-v
+@item <fn> d M-v
+@kindex <fn> d M-v
+@end table
+
+@findex dtk-set-variant
+@format
+Set voice variant used by the speech engine to VARIANT.
+
+VARIANT may be any value supported by the speech engine.
+In the case of espeak, it may be a string name or an integer ID.
+
+Omitting VARIANT or setting it to NIL resetss this value to default.
+
+(fn &optional VARIANT)
+@end format
+@end deffn
+
@subsubsection dtk-stop
@deffn {Command} dtk-stop (&optional all)
@table @kbd
diff --git a/info/tts.texi b/info/tts.texi
index c856ec658..52a6f19c2 100644
--- a/info/tts.texi
+++ b/info/tts.texi
@@ -130,6 +130,15 @@ Dectalks, e.g. the Dectalk Express. Possible
values are `math, name,
europe, spell', all of which can be turned on or off. Argument STATE
specifies new state.
+@findex dtk-set-variant
+@kindex C-e d M-v
+@item C-e d M-v
+@code{dtk-set-variant}
+
+Set the voice variant used by the speech engine, if the engine has
+a concept of variants, to VARIANT. If VARIANT is blank or nil, resets
+the variant setting to its default value.
+
@findex dtk-toggle-split-caps
@kindex C-e d s
@item C-e d s
diff --git a/lisp/dtk-speak.el b/lisp/dtk-speak.el
index e0b34ac49..a0c1f9c93 100644
--- a/lisp/dtk-speak.el
+++ b/lisp/dtk-speak.el
@@ -826,6 +826,16 @@ then set the current local value to the result."
dtk-character-scale
(if prefix "" "locally")))))
+(defun dtk-set-variant (&optional variant)
+ "Set voice variant used by the speech engine to VARIANT.
+
+VARIANT may be any value supported by the speech engine.
+In the case of espeak, it may be a string name or an integer ID.
+
+Omitting VARIANT or setting it to NIL resetss this value to default."
+ (interactive "sVariant name (blank selects default): ")
+ (dtk-interp-set-variant variant (called-interactively-p 'interactive)))
+
(ems-generate-switcher
'dtk-toggle-quiet
'dtk-quiet
@@ -1892,6 +1902,14 @@ Notification is logged in the notifications
buffer unless `dont-log' is T. "
dtk-speaker-process
(format "tts_set_punctuations %s\nd\n" mode)))
+;;}}}
+;;{{{ variant
+(defsubst dtk-interp-set-variant (variant say-it)
+ (cl-declare (special dtk-speaker-process))
+ (process-send-string
+ dtk-speaker-process
+ (format "tts_set_variant %S %S" variant say-it)))
+
;;}}}
;;{{{ reset
diff --git a/lisp/emacspeak-keymap.el b/lisp/emacspeak-keymap.el
index 83a46a086..24eecf9ee 100644
--- a/lisp/emacspeak-keymap.el
+++ b/lisp/emacspeak-keymap.el
@@ -359,6 +359,7 @@
("C-n" dtk-notify-initialize)
("C-o" outloud)
("C-v" global-voice-lock-mode)
+ ("M-v" dtk-set-variant)
("d" dtk-select-server)
("L" dtk-local-server)
("N" dtk-set-next-language)
diff --git a/servers/espeak b/servers/espeak
index 3f19451d7..f20cab5f7 100755
--- a/servers/espeak
+++ b/servers/espeak
@@ -56,6 +56,10 @@ source $wd/tts-lib.tcl
# For example, if there are three available languages:
# langsynth(top)=2
+# langsynth(variant): name (or ID) of the language variant in use
+# If unset, the server defaults to an autoselected male variant
+# Set by the application
+
# voicename: name of the current voice for announcements
# This variable is set by tclespeak
@@ -128,7 +132,7 @@ proc set_previous_lang {say_it} {
set langsynth(current) $index
set langcode(current) $langcode($index)
setLanguage $langsynth(current)
-puts stderr "Language: $langsynth(current) Voice: $voicename"
+ puts stderr "Language: $langsynth(current) Voice: $voicename"
if { [info exists say_it]} {
tts_say "$voicename "
}
@@ -140,15 +144,15 @@ proc set_lang {{name "en"} {say_it "nil"}} {
global langsynth
global langalias
global langcode
-global voicename
- if { ![info exists langalias($name)]} {
- return
- }
+ global voicename
+ if { ![info exists langalias($name)]} {
+ return
+ }
- if { $langalias($name) == $langsynth(current) } {
+ if { $langalias($name) == $langsynth(current) } {
return
- }
-
+ }
+
set langsynth(current) $langalias($name)
set langcode(current) $langcode($langalias($name))
setLanguage $langsynth(current)
@@ -169,6 +173,26 @@ proc set_preferred_lang {alias lang} {
set langalias($alias) $langalias($lang)
}
+# tts_set_variant - clears the variant
+# tts_set_variant "victor" - sets the variant
+# tts_set_variant 1 - sets the variant by index
+proc tts_set_variant {{new_variant ""} {say_it "nil"}} {
+ global langsynth
+ global voicename
+ set new_variant [string trim $new_variant]
+ if { $new_variant == "" || $new_variant == "nil" } {
+ unset langsynth(variant)
+ } else {
+ set langsynth(variant) $new_variant
+ }
+ # Re-set the current voice, having updated the variant
+ setLanguage $langsynth(current)
+ if { $say_it != "nil" } {
+ tts_say "$voicename "
+ }
+
+}
+
#debug
proc list_lang {} {
global langcode
@@ -198,7 +222,7 @@ proc tts_set_punctuations {mode} {
proc tts_set_speech_rate {rate} {
global tts
- set factor $tts(char_factor)
+ set factor $tts(char_factor)
set tts(speech_rate) $rate
setRate 0 $rate
service
diff --git a/servers/native-espeak/tclespeak.cpp
b/servers/native-espeak/tclespeak.cpp
index 4c29f1a98..790daf30d 100644
--- a/servers/native-espeak/tclespeak.cpp
+++ b/servers/native-espeak/tclespeak.cpp
@@ -39,12 +39,13 @@
#include <assert.h>
#include <espeak-ng/speak_lib.h>
+#include <set>
+#include <sstream>
#include <stdlib.h>
#include <string.h>
+#include <string>
#include <sys/time.h>
#include <tcl.h>
-#include <set>
-#include <string>
#include <vector>
using std::set;
using std::string;
@@ -74,7 +75,7 @@ int Synchronize(ClientData, Tcl_Interp *, int, Tcl_Obj
*CONST[]);
int Pause(ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[]);
int Resume(ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[]);
-static void initLanguage(Tcl_Interp *interp);
+static int initLanguage(Tcl_Interp *interp);
static int getLangIndex(Tcl_Interp *interp, unsigned long *theIndex);
//>
@@ -121,8 +122,7 @@ int Tclespeak_Init(Tcl_Interp *interp) {
TclEspeakFree);
//>
- initLanguage(interp);
- return TCL_OK;
+ return initLanguage(interp);
}
int GetRate(ClientData handle, Tcl_Interp *interp, int objc,
@@ -166,17 +166,13 @@ int SetRate(ClientData handle, Tcl_Interp *interp,
int objc,
//>
//<say
-static bool closeTags(string input, string &output) {
- char *tag_orig = (char *)malloc(sizeof(char) * (input.size() + 1));
- strncpy(tag_orig, input.c_str(), input.size());
- output = "";
-
+static bool closeTags(const string input, string &output) {
+ std::ostringstream closingTags;
// check that a text (non whitespace) is present
- char *tag = tag_orig;
int a_tag_count = 0;
bool a_text_is_present = false;
- while (*tag) {
+ for (auto tag = input.cbegin(); tag != input.cend(); ++tag) {
if (*tag == '<') {
a_tag_count++;
}
@@ -188,31 +184,33 @@ static bool closeTags(string input, string &output) {
if ((*tag == '>') && a_tag_count) {
a_tag_count--;
}
- tag++;
}
if (a_text_is_present) {
- tag = tag_orig;
- while (tag) {
+ string::size_type tag_pos = input.size();
+ if (string::npos == tag_pos) {
+ fprintf(stderr, "Synthesizer argument of size (size_t)(-1),
ignoring "
+ "last chraracter\n");
+ --tag_pos;
+ }
+ while (string::npos != tag_pos) {
// look for a '<'
- tag = strrchr(tag_orig, '<');
-
- if (tag) {
- char *end = strchr(tag, ' ');
- if (!end && (NULL == strchr(tag, '/'))) {
- end = strchr(tag, '>');
+ tag_pos = input.find_last_of('<', tag_pos);
+ if (string::npos != tag_pos) {
+ string::size_type end = input.find_first_of(' ', tag_pos);
+ if ((string::npos != end) &&
+ (string::npos == input.find_first_of('/', tag_pos))) {
+ end = input.find_first_of('>', tag_pos);
}
- if (end && (tag + 1 < end)) {
- *end = 0;
- output += "</" + string(tag + 1) + ">";
+ if ((string::npos != end) && (tag_pos + 1 < end)) {
+ closingTags << "</" << input.substr(tag_pos + 1, end) << ">";
}
- *tag = 0;
+ tag_pos--; // Start search before previous tag to avoid
infinite loop
}
}
}
- free(tag_orig);
-
+ output.assign(closingTags.str());
return a_text_is_present;
}
@@ -228,8 +226,14 @@ int Say(ClientData handle, Tcl_Interp *interp, int
objc,
string a_ssml = a_begin_ssml + a_end_ssml;
unsigned int unique_identifier = 0;
- espeak_Synth(a_ssml.c_str(), a_ssml.length() + 1, 0,
POS_CHARACTER, 0,
- espeakCHARS_UTF8 | espeakSSML, &unique_identifier,
NULL);
+ if (EE_OK != espeak_Synth(a_ssml.c_str(), a_ssml.length() + 1, 0,
+ POS_CHARACTER, 0,
+ espeakCHARS_UTF8 | espeakSSML,
+ &unique_identifier, NULL)) {
+ Tcl_AppendResult(
+ interp, "Could not synthesize string: ", a_ssml.c_str(),
NULL);
+ return TCL_ERROR;
+ }
}
}
}
@@ -363,17 +367,37 @@ int getTTSVersion(ClientData handle, Tcl_Interp
*interp, int objc,
static vector<string> available_languages;
-static void SetLanguageHelper(Tcl_Interp *interp, size_t aIndex) {
- espeak_VOICE *current_voice = NULL;
- espeak_VOICE a_voice;
- memset(&a_voice, 0, sizeof(espeak_VOICE));
- a_voice.languages = (char *)available_languages[aIndex].c_str();
- a_voice.gender = 1;
- espeak_SetVoiceByProperties(&a_voice);
- current_voice = espeak_GetCurrentVoice();
+static int SetLanguageHelper(Tcl_Interp *interp, size_t aIndex) {
+ espeak_ERROR voice_status = espeak_ERROR::EE_OK;
+ Tcl_Obj *variant_name = Tcl_GetVar2Ex(interp, "langsynth", "variant", 0);
+ if (variant_name) {
+ int variant_name_len = 0;
+ char *variant_name_data =
+ Tcl_GetStringFromObj(variant_name, &variant_name_len);
+ string variant_name_str(variant_name_data, variant_name_len);
+ string name = available_languages[aIndex] + "+" + variant_name_str;
+ voice_status = espeak_SetVoiceByName(name.c_str());
+ if (espeak_ERROR::EE_OK != voice_status) {
+ fprintf(stderr,
+ "Could not load voice %s, falling back to language-based
search",
+ name.c_str());
+ }
+ }
+
+ if (!variant_name || espeak_ERROR::EE_OK != voice_status) {
+ espeak_VOICE a_voice;
+ memset(&a_voice, 0, sizeof(espeak_VOICE));
+ a_voice.languages = (char *)available_languages[aIndex].c_str();
+ a_voice.gender = 1;
+ voice_status = espeak_SetVoiceByProperties(&a_voice);
+ }
+ if (espeak_ERROR::EE_OK != voice_status) {
+ Tcl_AppendResult(interp, "could not set voice");
+ return TCL_ERROR;
+ }
+ espeak_VOICE *current_voice = espeak_GetCurrentVoice();
Tcl_SetVar(interp, "voicename", current_voice->name, 0);
- // But what if we couldn't set the voice? Need some better error
handling.
- return;
+ return TCL_OK;
}
int SetLanguage(ClientData eciHandle, Tcl_Interp *interp, int objc,
@@ -381,8 +405,9 @@ int SetLanguage(ClientData eciHandle, Tcl_Interp
*interp, int objc,
unsigned long aIndex = 0;
if (getLangIndex(interp, &aIndex)) {
- SetLanguageHelper(interp, aIndex);
+ return SetLanguageHelper(interp, aIndex);
}
+ // TODO: Error reporting for this
return TCL_OK;
}
@@ -404,7 +429,7 @@ static vector<string> ParseLanguages(const char
*lang_str) {
return voice_langs;
}
-static void initLanguage(Tcl_Interp *interp) {
+static int initLanguage(Tcl_Interp *interp) {
// List the available languages
set<string> unique_languages;
int i = 0;
@@ -471,11 +496,15 @@ static void initLanguage(Tcl_Interp *interp) {
Tcl_SetVar2(interp, "langsynth", "current", buffer, 0);
Tcl_SetVar2(interp, "langcode", "current", "en", 0);
}
- SetLanguageHelper(interp, default_index);
+
+ if (TCL_OK != SetLanguageHelper(interp, default_index)) {
+ return TCL_ERROR;
+ }
// Presumably we have at least one language, namely English,
// so no chance of underflowing size_t with this subtraction:
snprintf(buffer, sizeof(buffer), "%lu", lang_count - 1);
Tcl_SetVar2(interp, "langsynth", "top", buffer, 0);
+ return TCL_OK;
}
static int getLangIndex(Tcl_Interp *interp, unsigned long *theIndex) {
--
2.25.1
1 year, 5 months
Emacspeak on a ChromeBook
by Devin Prater
Hi all. I've found that you can use Emacspeak, inside the Debian Linux
container on a Chromebook. You can use it with Voxin even, if you're
running a non-ARM CPU. The speech is a little clipped at the beginning, but
it's very responsive.
Devin Prater
r.d.t.prater(a)gmail.com
gemini://tilde.pink/~devinprater/
1 year, 5 months
Emacspeak at the forefront of innovation yet again
by Tim Cross
Hi Raman,
just thought I'd mention I just noticed in the newest ipadOS 15 they
have added a new 'focus' feature - basically, ability to play sounds in
the background to help improve focus. In simple terms, the same idea as
your sound-scapes you introduced in Emacspeak some years back.
Like the use of voice locking, which I also notice some screen readers
have added (at a far more rudimentary level), Emacspeak is still leading
the way with innovation for assistive technology.
We get so use to Emacspeak I think we sometimes forget how innovative it
is and how it brings new ideas and alternative perspectives to what is
generally offered by other assistive tech.
Great work and thanks for your many years of dedication in this area.
Much of what you have done is not only great for us emacspeak users, but
it has also contributed to better solutions and enhancements outside the
emacspeak sphere as ideas like voice locking, auditory icons and sound
scapes are borrowed by other solutions. I for one certainly appreciate
this effort.
regards,
Tim
1 year, 5 months