diff --git a/src/espeak.c b/src/espeak.c
index 60cc996..c9ac908 100644
--- a/src/espeak.c
+++ b/src/espeak.c
@@ -302,15 +302,39 @@ static espeak_ERROR speak_text(struct synth_t *s)
synth_mode |= espeakSSML;
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) {
- char *buf;
+ char *buf = NULL;
int n;
- if (s->buf[0] == ' ')
+ unsigned char c = s->buf[0];
+ if (c == ' ')
n = asprintf(&buf,
" ");
- else
- n = asprintf(&buf,
- "%c",
- s->buf[0]);
+ else if (c < 0x20 || c > 0x7e) {
+ /* Not a printable character; do not embed it in SSML, as
+ * that would produce invalid markup. Fall through to the
+ * raw-synthesis path below. */
+ n = -1;
+ } else {
+ /* Escape characters that are special in XML/SSML so the
+ * resulting markup stays well-formed; otherwise espeak-ng
+ * misparses the element, which is the source of
+ * the spurious high-pitched "ringing" on these characters. */
+ const char *entity = NULL;
+ switch (c) {
+ case '<': entity = "<"; break;
+ case '>': entity = ">"; break;
+ case '&': entity = "&"; break;
+ case '\'': entity = "'"; break;
+ case '"': entity = """; break;
+ }
+ if (entity)
+ n = asprintf(&buf,
+ "%s",
+ entity);
+ else
+ n = asprintf(&buf,
+ "%c",
+ c);
+ }
if (n == -1) {
/* D'oh. Not much to do on allocation failure.
* Perhaps espeak will happen to say the character */