Skip to content

Commit 68930fc

Browse files
committed
Preserve pause-generating punctuation in espeakbridge
1 parent 32b95f8 commit 68930fc

2 files changed

Lines changed: 65 additions & 16 deletions

File tree

src/piper/espeakbridge.c

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
#define CLAUSE_TYPE_CLAUSE 0x00040000
1313
#define CLAUSE_TYPE_SENTENCE 0x00080000
14+
#define CLAUSE_TYPE_EOF 0x00010000
15+
16+
#define CLAUSE_INTONATION_TYPE 0x00007000
17+
#define CLAUSE_PAUSE 0x00000FFF
1418

1519
#define CLAUSE_PERIOD (40 | CLAUSE_INTONATION_FULL_STOP | CLAUSE_TYPE_SENTENCE)
1620
#define CLAUSE_COMMA (20 | CLAUSE_INTONATION_COMMA | CLAUSE_TYPE_CLAUSE)
@@ -20,6 +24,11 @@
2024
#define CLAUSE_COLON (30 | CLAUSE_INTONATION_FULL_STOP | CLAUSE_TYPE_CLAUSE)
2125
#define CLAUSE_SEMICOLON (30 | CLAUSE_INTONATION_COMMA | CLAUSE_TYPE_CLAUSE)
2226

27+
#define EXCLAMATION_PAUSE (CLAUSE_EXCLAMATION & CLAUSE_PAUSE)
28+
#define PERIOD_PAUSE (CLAUSE_PERIOD & CLAUSE_PAUSE)
29+
#define SEMICOLON_PAUSE (CLAUSE_SEMICOLON & CLAUSE_PAUSE)
30+
#define COMMA_PAUSE (CLAUSE_COMMA & CLAUSE_PAUSE)
31+
2332
static PyObject *py_initialize(PyObject *self, PyObject *args) {
2433
const char *data_dir;
2534
if (!PyArg_ParseTuple(args, "s", &data_dir)) {
@@ -64,29 +73,36 @@ static PyObject *py_get_phonemes(PyObject *self, PyObject *args) {
6473
(const void **)&text, espeakCHARS_AUTO, espeakPHONEMES_IPA,
6574
&terminator);
6675

67-
// Categorize terminator
68-
terminator &= 0x000FFFFF;
69-
70-
if (terminator == CLAUSE_PERIOD) {
71-
terminator_str = ".";
72-
} else if (terminator == CLAUSE_QUESTION) {
73-
terminator_str = "?";
74-
} else if (terminator == CLAUSE_EXCLAMATION) {
75-
terminator_str = "!";
76-
} else if (terminator == CLAUSE_COMMA) {
77-
terminator_str = ",";
78-
} else if (terminator == CLAUSE_COLON) {
79-
terminator_str = ":";
80-
} else if (terminator == CLAUSE_SEMICOLON) {
81-
terminator_str = ";";
76+
if (!(terminator & CLAUSE_TYPE_EOF)) {
77+
int pause = terminator & CLAUSE_PAUSE;
78+
int intonation = terminator & CLAUSE_INTONATION_TYPE;
79+
80+
if (pause >= PERIOD_PAUSE) {
81+
if (intonation == CLAUSE_INTONATION_EXCLAMATION) {
82+
terminator_str = "!";
83+
} else if (intonation == CLAUSE_INTONATION_QUESTION) {
84+
terminator_str = "?";
85+
} else {
86+
terminator_str = ".";
87+
}
88+
} else if (pause >= SEMICOLON_PAUSE) {
89+
if (intonation == CLAUSE_INTONATION_FULL_STOP) {
90+
terminator_str = ":";
91+
} else {
92+
terminator_str = ";";
93+
}
94+
} else if (pause >= COMMA_PAUSE) {
95+
terminator_str = ",";
96+
}
8297
}
8398

8499
PyList_Append(phonemes_and_terminators,
85100
Py_BuildValue("(ssO)", phonemes, terminator_str,
86101
(terminator & CLAUSE_TYPE_SENTENCE) ==
87102
CLAUSE_TYPE_SENTENCE
88103
? Py_True
89-
: Py_False));
104+
: Py_False
105+
));
90106
}
91107

92108
return phonemes_and_terminators;

tests/test_espeakbridge.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
3+
from piper import espeakbridge
4+
5+
terminator_tests = [
6+
# exact matches for punctuation are retained verbatim
7+
(
8+
"en-us",
9+
"Hello, world! Colons: important? Maybe; maybe not.",
10+
[",", "!", ":", "?", ";", "."],
11+
),
12+
# ellipses get normalized to semicolons
13+
("en-us", "An ellipsis… Could it be true?", [";", "?"]),
14+
("en-us", "An ASCII ellipsis... Could it be true?", [";", "?"]),
15+
("en-us", "A long ellipsis.... Could it be true?", [";", "?"]),
16+
# em dashes get normalized to semicolons
17+
("en-us", "Em dashes — for all your punctuation needs.", [";", "."]),
18+
# various Chinese punctuation gets normalized to ASCII equivalents
19+
(
20+
"cmn",
21+
"你好,世界!省略号……一、二,三。问号?",
22+
[",", "!", ";", ",", ",", ".", "?"],
23+
),
24+
]
25+
26+
27+
@pytest.mark.parametrize("locale,text,expect", terminator_tests)
28+
def test_get_phonemes_terminator(locale: str, text: str, expect: list[str]):
29+
espeakbridge.set_voice(locale)
30+
31+
result = espeakbridge.get_phonemes(text)
32+
33+
assert [result[i][1] for i in range(len(result)) if result[i][1]] == expect

0 commit comments

Comments
 (0)