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 +(a)subsection dtk-set-variant +(a)deffn {Command} dtk-set-variant (&optional variant) + +(a)table @kbd +(a)item C-e d M-v +(a)kindex C-e d M-v +(a)item <fn> d M-v +(a)kindex <fn> d M-v +(a)end table + +(a)findex dtk-set-variant +(a)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) +(a)end format +(a)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. +(a)findex dtk-set-variant +(a)kindex C-e d M-v +(a)item C-e d M-v +(a)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 +(a)subsection dtk-set-variant +(a)deffn {Command} dtk-set-variant (&optional variant) + +(a)table @kbd +(a)item C-e d M-v +(a)kindex C-e d M-v +(a)item <fn> d M-v +(a)kindex <fn> d M-v +(a)end table + +(a)findex dtk-set-variant +(a)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) +(a)end format +(a)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. +(a)findex dtk-set-variant +(a)kindex C-e d M-v +(a)item C-e d M-v +(a)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
|May 1995 - Last Year|Current Year|
If you have questions about this archive or had problems using it, please contact us.