-
Notifications
You must be signed in to change notification settings - Fork 41
/
index.html
333 lines (293 loc) · 14.3 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<!doctype html>
<html lang="en">
<head>
<link rel="canonical" href="https://itinerarium.github.io/phoneme-synthesis/" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Convert IPA phonetic notation text to speech</title>
<meta name="description" content="A browser-based tool to convert International Phonetic Alphabet (IPA) phonetic notation to speech using the meSpeak.js package.">
<style type="text/css">
html {
font-family: Helvetica, sans-serif;
background-color: #404040;
min-width: 320px;
}
* {
margin: 0;
padding: 0;
}
div#container {
width: 60%;
line-height: 1.5;
margin: 0 auto;
padding: 1em;
background-color: #f3f3f3;
}
@media screen and (max-width: 640px) {
div#container {
width: 288px;
}
}
noscript p {
background-color: #ffffff;
padding: 1em;
}
div#tool, div#about, div#note {
background-color: #ffffff;
padding: 1em;
}
p {
margin-bottom: 1em;
}
input#ipa-input {
width: 240px;
text-align: center;
display: block;
margin: 0 auto;
}
input#submit, input#download-button, input#copy-link-button {
width: 248px;
text-align: center;
display: block;
margin: 1em auto;
}
input#copy-link-button {
margin-bottom: 0;
}
hr {
border: 0;
height: 1em;
}
h2 {
margin-bottom: 0.35em;
}
</style>
<script type="text/javascript" src="mespeak.js"></script>
<script type="text/javascript">
meSpeak.loadConfig("mespeak_config.json");
meSpeak.loadVoice("en.json");
</script>
</head>
<body>
<div id="container">
<h1>phoneme synthesis</h1>
<noscript>
<p><strong>A modern JavaScript-enabled browser is required.</strong> As an alternative, consider using <a href="http://ssb22.user.srcf.net/gradint/lexconvert.html">lexconvert</a> in conjunction with <a href="http://espeak.sourceforge.net/">eSpeak</a>.</p>
<p>Sample command for <a href="https://en.wikipedia.org/wiki/Mumbai" rel="nofollow">Mumbai</a>:<br /><span style="font-family: monospace;">python lexconvert.py --try unicode-ipa "/mʊmˈbaɪ/"</span></p>
</noscript>
<script type="text/javascript">
var spoken;
// download function from Matěj Pokorný: https://stackoverflow.com/questions/2897619/using-html5-javascript-to-generate-and-save-a-file/18197511#18197511
function download() {
var pom = document.createElement('a');
pom.setAttribute('href', spoken);
pom.setAttribute('download', "spoken.wav");
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
}
else {
pom.click();
}
}
function clear_download_button() {
document.getElementById("download-button").disabled = true;
}
function copyToClipboard(text) {
t = document.createElement('input');
t.value = text;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
}
function copy_link_button() {
uipa = document.getElementById("ipa-input").value;
url = window.location.href.split('?')[0] + "?w=" + uipa;
copyToClipboard(url);
}
function process() {
uipa = document.getElementById("ipa-input").value;
document.getElementById("download-button").disabled = true;
// nothing to process
if (uipa == null || uipa.length == 0) {
return;
}
//translate
var mappings = [
{ 'src': /^\s*\//g, 'dest': '' },
{ 'src': /\/\s*$/g, 'dest': '' },
{ 'src': /(\.)/g, 'dest': '%' },
{ 'src': /(\u02c8)/g, 'dest': '\'' },
{ 'src': /(\u02cc)/g, 'dest': ',' },
{ 'src': /(\u0251)/g, 'dest': 'A:' },
{ 'src': /(\u02d0)/g, 'dest': ':' },
{ 'src': /(\u0251\u02d0)/g, 'dest': 'A' },
{ 'src': /(\u0251\u0279)/g, 'dest': 'A' },
{ 'src': /(a\u02d0)/g, 'dest': 'A' },
// feedback from formantzero via r/linguistics
{ 'src': /(\u0329)/g, 'dest': 'r' },
// feedback from scharfes_s via r/linguistics
{ 'src': /(\u027e)/g, 'dest': 't' },
{ 'src': /(\xe6)/g, 'dest': 'a' },
{ 'src': /(a)/g, 'dest': 'a' },
{ 'src': /(\u028c)/g, 'dest': 'V' },
{ 'src': /(\u0252)/g, 'dest': '0' },
{ 'src': /(\u0254)/g, 'dest': '0' },
{ 'src': /(a\u028a)/g, 'dest': 'aU' },
{ 'src': /(\xe6\u0254)/g, 'dest': 'aU' },
{ 'src': /(\u0259)/g, 'dest': '@' },
{ 'src': /(\u025a)/g, 'dest': '3' },
{ 'src': /(\u0259\u02d0)/g, 'dest': '3:' },
{ 'src': /(a\u026a)/g, 'dest': 'aI' },
{ 'src': /(\u028c\u026a)/g, 'dest': 'aI' },
{ 'src': /(\u0251e)/g, 'dest': 'aI' },
{ 'src': /(b)/g, 'dest': 'b' },
{ 'src': /(t\u0283)/g, 'dest': 'tS' },
{ 'src': /(\u02a7)/g, 'dest': 'tS' },
{ 'src': /(d)/g, 'dest': 'd' },
{ 'src': /(\xf0)/g, 'dest': 'D' },
{ 'src': /(\u025b)/g, 'dest': 'E' },
{ 'src': /(e)/g, 'dest': 'E' },
{ 'src': /(\u025d)/g, 'dest': '3:' },
{ 'src': /(\u025c\u02d0)/g, 'dest': '3:' },
{ 'src': /(\u025b\u0259)/g, 'dest': 'e@' },
{ 'src': /(e)/g, 'dest': 'E' },
{ 'src': /(\u025d)/g, 'dest': '3:' },
{ 'src': /(\u025c\u02d0)/g, 'dest': '3:' },
{ 'src': /(e\u026a)/g, 'dest': 'eI' },
{ 'src': /(\xe6\u026a)/g, 'dest': 'eI' },
{ 'src': /(f)/g, 'dest': 'f' },
{ 'src': /(\u0261)/g, 'dest': 'g' },
{ 'src': /(g)/g, 'dest': 'g' },
{ 'src': /(h)/g, 'dest': 'h' },
{ 'src': /(\u026a)/g, 'dest': 'I' },
{ 'src': /(\u0268)/g, 'dest': 'I' },
{ 'src': /(\u026a\u0259)/g, 'dest': 'i@' },
{ 'src': /(\u026a\u0279)/g, 'dest': 'i@' },
{ 'src': /(\u026a\u0279\u0259)/g, 'dest': 'i@3' },
{ 'src': /(i)/g, 'dest': 'i:' },
{ 'src': /(i\u02d0)/g, 'dest': 'i:' },
{ 'src': /(d\u0292)/g, 'dest': 'dZ' },
{ 'src': /(\u02a4)/g, 'dest': 'dZ' },
{ 'src': /(k)/g, 'dest': 'k' },
{ 'src': /(x)/g, 'dest': 'x' },
{ 'src': /(l)/g, 'dest': 'l' },
{ 'src': /(d\u026b)/g, 'dest': 'l' },
{ 'src': /(m)/g, 'dest': 'm' },
{ 'src': /(n)/g, 'dest': 'n' },
{ 'src': /(\u014b)/g, 'dest': 'N' },
{ 'src': /(\u0259\u028a)/g, 'dest': 'oU' },
{ 'src': /(o)/g, 'dest': 'oU' },
{ 'src': /(o\u028a)/g, 'dest': 'oU' },
{ 'src': /(\u0259\u0289)/g, 'dest': 'V' },
{ 'src': /(\u0254\u026a)/g, 'dest': 'OI' },
{ 'src': /(o\u026a)/g, 'dest': 'OI' },
{ 'src': /(p)/g, 'dest': 'p' },
{ 'src': /(\u0279)/g, 'dest': 'r' },
{ 'src': /(s)/g, 'dest': 's' },
{ 'src': /(\u0283)/g, 'dest': 'S' },
{ 'src': /(t)/g, 'dest': 't' },
{ 'src': /(\u027e)/g, 'dest': 't' },
{ 'src': /(\u03b8)/g, 'dest': 'T' },
{ 'src': /(\u028a\u0259)/g, 'dest': 'U@' },
{ 'src': /(\u028a\u0279)/g, 'dest': 'U@' },
{ 'src': /(\u028a)/g, 'dest': 'U' },
{ 'src': /(\u0289\u02d0)/g, 'dest': 'u:' },
{ 'src': /(u\u02d0)/g, 'dest': 'u:' },
{ 'src': /(u)/g, 'dest': 'u:' },
{ 'src': /(\u0254\u02d0)/g, 'dest': 'O:' },
{ 'src': /(o\u02d0)/g, 'dest': 'O:' },
{ 'src': /(v)/g, 'dest': 'v' },
{ 'src': /(w)/g, 'dest': 'w' },
{ 'src': /(\u028d)/g, 'dest': 'w' },
{ 'src': /(j)/g, 'dest': 'j' },
{ 'src': /(z)/g, 'dest': 'z' },
{ 'src': /(\u0292)/g, 'dest': 'Z' },
{ 'src': /(\u0294)/g, 'dest': '?' },
// special edits
{ 'src': /(k\'a2n)/g, 'dest': 'k\'@n' },
{ 'src': /(ka2n)/g, 'dest': 'k@n' },
{ 'src': /(gg)/g, 'dest': 'g' },
{ 'src': /(@U)/g, 'dest': 'oU' },
{ 'src': /rr$/g, 'dest': 'r' },
{ 'src': /3r$/g, 'dest': '3:' },
{ 'src': /([iU]|([AO]:))@r$/g, 'dest': '$1@' },
{ 'src': /([^e])@r/g, 'dest': '$1:3' },
{ 'src': /e@r$/g, 'dest': 'e@' },
{ 'src': /e@r([bdDfghklmnNprsStTvwjzZ])/g, 'dest': 'e@$1' },
// edits arising from testing
{ 'src': /(\'k)+/g, 'dest': 'k\'' },
{ 'src': /(\ː)+/g, 'dest': ':' },
{ 'src': /(\:)+/g, 'dest': ':' },
{ 'src': /(ᵻ)/g, 'dest': 'I' },
{ 'src': /(ɜ)/g, 'dest': '3' },
{ 'src': /(ɔ)/g, 'dest': 'O' },
// feedback from formantzero via r/linguistics
{ 'src': /\u0361(.)/g, 'dest': '$1\'' },
{ 'src': /3$/g, 'dest': 'R' }
];
for (var i = 0; i < mappings.length; i++) {
uipa = uipa.replace(mappings[i].src, mappings[i].dest);
//console.log(mappings[i].src + uipa);
}
console.log(uipa);
spoken = meSpeak.speak('[['+uipa+']]', { 'rawdata': 'mime' });
if (spoken == null) {
alert("An error occurred: speaking failed.");
}
document.getElementById("download-button").disabled = false;
meSpeak.play(spoken);
}
</script>
<div id="tool">
<p style="text-align: center;"><em>Convert <abbr title="International Phonetic Alphabet">IPA</abbr> phonetic notation to speech</em></p>
<form onsubmit="process(); return false;">
<input id="ipa-input" onchange="clear_download_button(); return false;" type="text" value="/mʊmˈbaɪ/" />
<input id="submit" onclick="process(); return false;" type="button" value="pronounce" />
<input id="download-button" onclick="download(); return false;" type="button" disabled="disabled" value="download pronunciation" />
<input id="copy-link-button" onclick="copy_link_button(); return false;" type="button" value="copy share url" />
</form>
</div>
<hr />
<div id="about">
<h2>how does it work?</h2>
<p>Using the computational capabilities exposed by your browser's JavaScript processor, the <abbr title="International Phonetic Alphabet">IPA</abbr> phonetic notation is translated into phonemes understood by <a href="http://espeak.sourceforge.net/" rel="nofollow">eSpeak</a> using correspondences and logic found in <a href="http://ssb22.user.srcf.net/gradint/lexconvert.html">lexconvert</a>. The translated phonemes (e.g., [[mUm'baI]] for /mʊmˈbaɪ/) are then provided to <a href="http://www.masswerk.at/mespeak/" rel="nofollow">meSpeak.js</a>, a revised <a href="http://emscripten.org/" rel="nofollow">Emscripten'd</a> version of eSpeak for output. Once the output has been generated, the pronunciation in <abbr title="Waveform Audio File">WAV</abbr> format is downloadable.</p>
<p>As this processing takes place within your browser, this page may be downloaded and used offline. By extension, the requested pronunciations are not logged.</p>
<h2>improve this tool</h2>
<p><strong>Contribute via <a href="https://github.com/itinerarium/phoneme-synthesis/">GitHub</a>.</strong></p>
<p><strong>Report a bug.</strong> Be sure to include the problematic input, your browser version (Help > About), operating system version, and device type. File <a href="https://github.com/itinerarium/phoneme-synthesis/issues">an issue on GitHub</a> or send an e-mail.</p>
<p><strong>Enrich the <abbr title="International Phonetic Alphabet">IPA</abbr>-phoneme correspondence list.</strong> For English, eSpeak recognizes 96 phonemes (<span style="font-family: monospace;">dictsource/dict_phonemes</span>). Currently, only 55 are mapped using lexconvert and there may have been mistakes in interpreting lexconvert's conversion scheme. Additional correspondences should improve pronunciation.</p>
<p><strong>Contribute to the underlying libraries.</strong> eSpeak, meSpeak.js, or the most current Emscripten'd variation to improve performance/size. meSpeak.js is currently over two megabytes.</p>
<h2>about</h2>
<p>This browser-based tool integrates GPL-licensed <a href="http://www.masswerk.at/mespeak/" rel="nofollow">meSpeak.js</a> (a revised <a href="http://emscripten.org/" rel="nofollow">Emscripten'd</a> version of <a href="http://espeak.sourceforge.net/" rel="nofollow">eSpeak</a>) and correspondences from <a href="http://ssb22.user.srcf.net/gradint/lexconvert.html">lexconvert</a> to parse <abbr title="International Phonetic Alphabet">IPA</abbr> phonetic notation and convert it to speech.</p>
<p>This tool is also available at <<a href="https://www.0n0e.com/public/phoneme-synthesis/">https://www.0n0e.com/public/phoneme-synthesis/</a>>.</p>
<h2>contact</h2>
<p style="margin-bottom: 0;">person at 0n0e dot com</p>
</div>
<hr />
<div id="note">
<h2>note</h2>
<p>It was odd that this tool did not exist; the underlying components were free (as in beer and freedom) and readily available for years (eSpeak was Emscripten'd in 2011: <a href="https://github.com/kripken/speak.js/" rel="nofollow">speak.js</a>) alongside clear demand (e.g., in 2013, <a href="https://www.reddit.com/r/linguistics/comments/1qdpoo/ipa_text_to_speech/" rel="nofollow">r/linguistics</a> and <a href="https://linguistics.stackexchange.com/questions/3035/is-it-hard-for-software-speech-synthesisers-to-handle-ipa-if-so-why" rel="nofollow">Linguistics Stack Exchange</a>).</p>
<p>There was a <a href="http://johntantalo.com/blog/ipa-tts-bookmarklet/" rel="nofollow">bookmarket for AT&T's Natural Voices demo</a>, but the endpoint appears to be no longer available.</p>
<p>eSpeak, unfortunately, does not parse <abbr title="International Phonetic Alphabet">IPA</abbr> phonetic notation directly, requiring the conversion assisted by lexconvert. Although the Emscripten'd version of eSpeak is somewhat large, two megabytes has become less noticeable as page sizes have increased over time. As a side effect, with no server-side processing and no automatic processing, privacy and security considerations were simplified (with the assumption that meSpeak.js is trustworthy).</p>
<p style="margin-bottom: 0;">Last updated: 2017-01-04</p>
</div>
</div>
<script type="text/javascript">
function is_valid_input(i) {
return !(i.includes("<") || i.includes(">") || i.length > 200)
}
(function() {
var urlParams = new URLSearchParams(window.location.search);
if(urlParams.has('w')) {
var word = urlParams.get('w');
if(is_valid_input(word)) {
document.getElementById("ipa-input").value = word;
}
}
})();
</script>
</body>
</html>