[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Search]

[Emacspeak] [PATCH] Add dtk-set-variant command to allow espeak's variants to be selected



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.

Contact Info Page