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
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