@ingglish/phonemes 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +698 -0
- package/dist/index.d.cts +254 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +649 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
// src/arpabet.ts
|
|
2
|
+
var ARPABET_VOWELS = [
|
|
3
|
+
"AA",
|
|
4
|
+
// father, hot
|
|
5
|
+
"AE",
|
|
6
|
+
// cat, bat
|
|
7
|
+
"AH",
|
|
8
|
+
// but, cup (stressed) / schwa (unstressed)
|
|
9
|
+
"AO",
|
|
10
|
+
// thought, law
|
|
11
|
+
"AW",
|
|
12
|
+
// cow, how
|
|
13
|
+
"AY",
|
|
14
|
+
// my, time
|
|
15
|
+
"EH",
|
|
16
|
+
// bed, red
|
|
17
|
+
"ER",
|
|
18
|
+
// bird, her
|
|
19
|
+
"EY",
|
|
20
|
+
// say, day
|
|
21
|
+
"IH",
|
|
22
|
+
// bit, sit
|
|
23
|
+
"IY",
|
|
24
|
+
// bee, see
|
|
25
|
+
"OW",
|
|
26
|
+
// go, show
|
|
27
|
+
"OY",
|
|
28
|
+
// boy, toy
|
|
29
|
+
"UH",
|
|
30
|
+
// book, put
|
|
31
|
+
"UW"
|
|
32
|
+
// too, blue
|
|
33
|
+
];
|
|
34
|
+
var ARPABET_CONSONANTS = [
|
|
35
|
+
// Stops
|
|
36
|
+
"B",
|
|
37
|
+
"D",
|
|
38
|
+
"G",
|
|
39
|
+
"K",
|
|
40
|
+
"P",
|
|
41
|
+
"T",
|
|
42
|
+
// Fricatives
|
|
43
|
+
"DH",
|
|
44
|
+
// the, this
|
|
45
|
+
"F",
|
|
46
|
+
"HH",
|
|
47
|
+
// hat
|
|
48
|
+
"S",
|
|
49
|
+
"SH",
|
|
50
|
+
// ship
|
|
51
|
+
"TH",
|
|
52
|
+
// think
|
|
53
|
+
"V",
|
|
54
|
+
"Z",
|
|
55
|
+
"ZH",
|
|
56
|
+
// measure
|
|
57
|
+
// Affricates
|
|
58
|
+
"CH",
|
|
59
|
+
// chat
|
|
60
|
+
"JH",
|
|
61
|
+
// just
|
|
62
|
+
// Nasals
|
|
63
|
+
"M",
|
|
64
|
+
"N",
|
|
65
|
+
"NG",
|
|
66
|
+
// sing
|
|
67
|
+
// Liquids
|
|
68
|
+
"L",
|
|
69
|
+
"R",
|
|
70
|
+
// Glides
|
|
71
|
+
"W",
|
|
72
|
+
"Y"
|
|
73
|
+
];
|
|
74
|
+
var VOWELS_SET = new Set(ARPABET_VOWELS);
|
|
75
|
+
var CONSONANTS_SET = new Set(ARPABET_CONSONANTS);
|
|
76
|
+
var STRESS_MARKER_REGEX = /[012]$/;
|
|
77
|
+
function getStress(phoneme) {
|
|
78
|
+
const lastChar = phoneme.codePointAt(phoneme.length - 1);
|
|
79
|
+
if (lastChar >= 48 && lastChar <= 50) {
|
|
80
|
+
return lastChar - 48;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function isVowel(phoneme) {
|
|
85
|
+
const base = stripStress(phoneme);
|
|
86
|
+
return VOWELS_SET.has(base);
|
|
87
|
+
}
|
|
88
|
+
function stripStress(phoneme) {
|
|
89
|
+
const lastChar = phoneme.codePointAt(phoneme.length - 1);
|
|
90
|
+
if (lastChar >= 48 && lastChar <= 50) {
|
|
91
|
+
return phoneme.slice(0, -1);
|
|
92
|
+
}
|
|
93
|
+
return phoneme;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/phonotactics.ts
|
|
97
|
+
var VALID_ONSETS = /* @__PURE__ */ new Set([
|
|
98
|
+
// Single consonants (all are valid onsets except NG)
|
|
99
|
+
"B",
|
|
100
|
+
// Two-consonant clusters: consonant + liquid (L, R)
|
|
101
|
+
"B L",
|
|
102
|
+
"B R",
|
|
103
|
+
// Two-consonant clusters: consonant + glide (W, Y)
|
|
104
|
+
"B Y",
|
|
105
|
+
"CH",
|
|
106
|
+
"D",
|
|
107
|
+
"DH",
|
|
108
|
+
"D R",
|
|
109
|
+
"D W",
|
|
110
|
+
"D Y",
|
|
111
|
+
"F",
|
|
112
|
+
"F L",
|
|
113
|
+
"F R",
|
|
114
|
+
"F Y",
|
|
115
|
+
"G",
|
|
116
|
+
"G L",
|
|
117
|
+
"G R",
|
|
118
|
+
"G W",
|
|
119
|
+
"G Y",
|
|
120
|
+
"HH",
|
|
121
|
+
"HH W",
|
|
122
|
+
"HH Y",
|
|
123
|
+
"JH",
|
|
124
|
+
"K",
|
|
125
|
+
"K L",
|
|
126
|
+
"K R",
|
|
127
|
+
"K W",
|
|
128
|
+
"K Y",
|
|
129
|
+
"L",
|
|
130
|
+
"L Y",
|
|
131
|
+
"M",
|
|
132
|
+
"M Y",
|
|
133
|
+
"N",
|
|
134
|
+
"N Y",
|
|
135
|
+
"P",
|
|
136
|
+
"P L",
|
|
137
|
+
"P R",
|
|
138
|
+
"P Y",
|
|
139
|
+
"R",
|
|
140
|
+
"S",
|
|
141
|
+
"SH",
|
|
142
|
+
"SH R",
|
|
143
|
+
// Two-consonant clusters: s + consonant
|
|
144
|
+
"S K",
|
|
145
|
+
// Three-consonant clusters: s + stop + liquid/glide
|
|
146
|
+
"S K R",
|
|
147
|
+
"S K W",
|
|
148
|
+
"S K Y",
|
|
149
|
+
"S L",
|
|
150
|
+
"S M",
|
|
151
|
+
"S N",
|
|
152
|
+
"S P",
|
|
153
|
+
"S P L",
|
|
154
|
+
"S P R",
|
|
155
|
+
"S P Y",
|
|
156
|
+
"S T",
|
|
157
|
+
"S T R",
|
|
158
|
+
"S T Y",
|
|
159
|
+
"S W",
|
|
160
|
+
"S Y",
|
|
161
|
+
"T",
|
|
162
|
+
"TH",
|
|
163
|
+
"TH R",
|
|
164
|
+
"TH W",
|
|
165
|
+
"T R",
|
|
166
|
+
"T W",
|
|
167
|
+
"T Y",
|
|
168
|
+
"V",
|
|
169
|
+
"V Y",
|
|
170
|
+
"W",
|
|
171
|
+
"Y",
|
|
172
|
+
"Z",
|
|
173
|
+
"ZH"
|
|
174
|
+
]);
|
|
175
|
+
function findOnsetStart(consonants) {
|
|
176
|
+
if (consonants.length === 0) {
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
for (let start = 0; start < consonants.length; start++) {
|
|
180
|
+
const candidate = consonants.slice(start);
|
|
181
|
+
if (isValidOnset(candidate)) {
|
|
182
|
+
return start;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return consonants.length - 1;
|
|
186
|
+
}
|
|
187
|
+
function isValidOnset(consonants) {
|
|
188
|
+
if (consonants.length === 0) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
const key = consonants.join(" ");
|
|
192
|
+
return VALID_ONSETS.has(key);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/format-registry.ts
|
|
196
|
+
var registry = /* @__PURE__ */ new Map();
|
|
197
|
+
var isLatinScriptCache = /* @__PURE__ */ new Map();
|
|
198
|
+
var preservesCaseCache = /* @__PURE__ */ new Map();
|
|
199
|
+
function getFormatHandler(name) {
|
|
200
|
+
return registry.get(name);
|
|
201
|
+
}
|
|
202
|
+
function getFormatIsLatinScript(name) {
|
|
203
|
+
return isLatinScriptCache.get(name) ?? true;
|
|
204
|
+
}
|
|
205
|
+
function getFormatJoinSeparator(name) {
|
|
206
|
+
return registry.get(name)?.joinSeparator ?? "";
|
|
207
|
+
}
|
|
208
|
+
function getFormatLabel(name) {
|
|
209
|
+
return registry.get(name)?.label ?? name;
|
|
210
|
+
}
|
|
211
|
+
function getFormatNativeLabel(name) {
|
|
212
|
+
const handler = registry.get(name);
|
|
213
|
+
return handler?.nativeLabel ?? handler?.label ?? name;
|
|
214
|
+
}
|
|
215
|
+
function getFormatPreservesCase(name) {
|
|
216
|
+
return preservesCaseCache.get(name) ?? true;
|
|
217
|
+
}
|
|
218
|
+
function registerFormat(name, handler) {
|
|
219
|
+
const existing = registry.get(name);
|
|
220
|
+
const merged = { ...existing, ...handler };
|
|
221
|
+
registry.set(name, merged);
|
|
222
|
+
isLatinScriptCache.set(name, merged.isLatinScript ?? true);
|
|
223
|
+
preservesCaseCache.set(name, merged.preservesCase ?? merged.isLatinScript ?? true);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/ingglish-maps.ts
|
|
227
|
+
var INGGLISH_VOWEL_MAP = {
|
|
228
|
+
// Monophthongs
|
|
229
|
+
AA: "o",
|
|
230
|
+
// father, hot, rock (but AA+R → 'ar' in star, car)
|
|
231
|
+
AE: "a",
|
|
232
|
+
// cat, bat, had (but AE+R → 'arr' in arrow, barrow)
|
|
233
|
+
AH: "uh",
|
|
234
|
+
// but, cup, son (stressed /ʌ/; unstressed /ə/ AH0 → 'a' in conversion)
|
|
235
|
+
AO: "aw",
|
|
236
|
+
// thought, caught, law (but AO+R → 'or' in store, more)
|
|
237
|
+
// Diphthongs
|
|
238
|
+
AW: "ou",
|
|
239
|
+
// cow, how, out
|
|
240
|
+
AY: "ai",
|
|
241
|
+
// my, eye, time
|
|
242
|
+
EH: "e",
|
|
243
|
+
// bed, red, said (but EH+R → 'air' in air, care, there)
|
|
244
|
+
ER: "er",
|
|
245
|
+
// bird, her, nurse
|
|
246
|
+
EY: "ay",
|
|
247
|
+
// say, day, make
|
|
248
|
+
IH: "i",
|
|
249
|
+
// bit, sit, gym
|
|
250
|
+
IY: "ee",
|
|
251
|
+
// bee, see, machine
|
|
252
|
+
OW: "oh",
|
|
253
|
+
// go, show, coat
|
|
254
|
+
OY: "oi",
|
|
255
|
+
// boy, toy, coin
|
|
256
|
+
UH: "u",
|
|
257
|
+
// book, put, could
|
|
258
|
+
UW: "oo"
|
|
259
|
+
// too, blue, food
|
|
260
|
+
};
|
|
261
|
+
var INGGLISH_CONSONANT_MAP = {
|
|
262
|
+
// Stops (plosives)
|
|
263
|
+
B: "b",
|
|
264
|
+
// bat, cab
|
|
265
|
+
// Affricates
|
|
266
|
+
CH: "ch",
|
|
267
|
+
// chat, batch
|
|
268
|
+
D: "d",
|
|
269
|
+
// dog, bed
|
|
270
|
+
// Fricatives
|
|
271
|
+
DH: "dh",
|
|
272
|
+
// the, this (voiced) - distinguishes from TH
|
|
273
|
+
F: "f",
|
|
274
|
+
// fat, laugh
|
|
275
|
+
G: "g",
|
|
276
|
+
// go, big
|
|
277
|
+
// Aspirate
|
|
278
|
+
HH: "h",
|
|
279
|
+
// hat, ahead
|
|
280
|
+
JH: "j",
|
|
281
|
+
// just, edge
|
|
282
|
+
K: "k",
|
|
283
|
+
// cat, back
|
|
284
|
+
// Liquids
|
|
285
|
+
L: "l",
|
|
286
|
+
// let, well
|
|
287
|
+
// Nasals
|
|
288
|
+
M: "m",
|
|
289
|
+
// man, come
|
|
290
|
+
N: "n",
|
|
291
|
+
// no, pen
|
|
292
|
+
NG: "ng",
|
|
293
|
+
// sing, thing
|
|
294
|
+
P: "p",
|
|
295
|
+
// pat, cup
|
|
296
|
+
R: "r",
|
|
297
|
+
// run, car
|
|
298
|
+
S: "s",
|
|
299
|
+
// sat, miss
|
|
300
|
+
SH: "sh",
|
|
301
|
+
// she, push
|
|
302
|
+
T: "t",
|
|
303
|
+
// top, cat
|
|
304
|
+
TH: "th",
|
|
305
|
+
// think, bath (voiceless)
|
|
306
|
+
V: "v",
|
|
307
|
+
// van, love
|
|
308
|
+
// Semivowels (glides)
|
|
309
|
+
W: "w",
|
|
310
|
+
// wet, away
|
|
311
|
+
Y: "y",
|
|
312
|
+
// yes, you
|
|
313
|
+
Z: "z",
|
|
314
|
+
// zoo, is
|
|
315
|
+
ZH: "zh"
|
|
316
|
+
// measure, beige
|
|
317
|
+
};
|
|
318
|
+
var ARPABET_TO_INGGLISH_MAP = {
|
|
319
|
+
...INGGLISH_VOWEL_MAP,
|
|
320
|
+
...INGGLISH_CONSONANT_MAP
|
|
321
|
+
};
|
|
322
|
+
var INGGLISH_TO_ARPABET_MAP = Object.fromEntries(
|
|
323
|
+
Object.entries(ARPABET_TO_INGGLISH_MAP).map(([arpabet, ingglish]) => [ingglish, arpabet])
|
|
324
|
+
);
|
|
325
|
+
var R_COLORED_VOWELS = [
|
|
326
|
+
{ arpabet: "AA", prefix: "a" },
|
|
327
|
+
// star, car, far → 'ar'
|
|
328
|
+
{ arpabet: "AO", prefix: "o" },
|
|
329
|
+
// store, more, for → 'or'
|
|
330
|
+
{ arpabet: "EH", prefix: "ai" },
|
|
331
|
+
// air, care, there → 'air'
|
|
332
|
+
{ arpabet: "AE", prefix: "ar" },
|
|
333
|
+
// arrow, barrow, carrot → 'arr'
|
|
334
|
+
{ arpabet: "IH", prefix: "ee" },
|
|
335
|
+
// beer, beard, fear → 'eer'
|
|
336
|
+
{ arpabet: "UH", prefix: "u" },
|
|
337
|
+
// tour, cure, pure → 'ur' (CURE vowel, experimentable)
|
|
338
|
+
{ arpabet: "AH", prefix: "uh" }
|
|
339
|
+
// curry, burroughs → 'uhr' (AH=uh, prevents AH0+R collision with 'ar')
|
|
340
|
+
];
|
|
341
|
+
var R_COLORED_FORWARD = new Map(
|
|
342
|
+
R_COLORED_VOWELS.map(({ arpabet, prefix }) => [arpabet, prefix])
|
|
343
|
+
);
|
|
344
|
+
var R_COLORED_REVERSE_3CHAR = Object.fromEntries(
|
|
345
|
+
R_COLORED_VOWELS.filter(({ prefix }) => prefix.length === 2).map(({ arpabet, prefix }) => [
|
|
346
|
+
prefix + "r",
|
|
347
|
+
[arpabet, "R"]
|
|
348
|
+
])
|
|
349
|
+
);
|
|
350
|
+
var R_COLORED_REVERSE_2CHAR = Object.fromEntries(
|
|
351
|
+
R_COLORED_VOWELS.filter(({ prefix }) => prefix.length === 1).map(({ arpabet, prefix }) => [
|
|
352
|
+
prefix + "r",
|
|
353
|
+
[arpabet, "R"]
|
|
354
|
+
])
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// src/to-ingglish.ts
|
|
358
|
+
function arpabetPhonemeToIngglish(phoneme) {
|
|
359
|
+
if (phoneme === "AH0") {
|
|
360
|
+
return "a";
|
|
361
|
+
}
|
|
362
|
+
const base = stripStress(phoneme);
|
|
363
|
+
return ARPABET_TO_INGGLISH_MAP[base] ?? phoneme.toLowerCase();
|
|
364
|
+
}
|
|
365
|
+
function convertArpabet(arpabet, phonemeMap, rColoredMap, stressOverrides) {
|
|
366
|
+
let result = "";
|
|
367
|
+
const len = arpabet.length;
|
|
368
|
+
for (let i = 0; i < len; i++) {
|
|
369
|
+
const phoneme = arpabet[i];
|
|
370
|
+
const base = stripStress(phoneme);
|
|
371
|
+
if (i + 1 < len && arpabet[i + 1] === "R") {
|
|
372
|
+
const rPrefix = rColoredMap.get(base);
|
|
373
|
+
if (rPrefix !== void 0) {
|
|
374
|
+
result += rPrefix;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const stressOverride = stressOverrides.get(phoneme);
|
|
379
|
+
if (stressOverride !== void 0) {
|
|
380
|
+
result += stressOverride;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
result += phonemeMap[base] ?? phoneme.toLowerCase();
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
var INGGLISH_FULL_MAP = {};
|
|
388
|
+
for (const [base, spelling] of Object.entries(ARPABET_TO_INGGLISH_MAP)) {
|
|
389
|
+
INGGLISH_FULL_MAP[base] = spelling;
|
|
390
|
+
INGGLISH_FULL_MAP[base + "0"] = spelling;
|
|
391
|
+
INGGLISH_FULL_MAP[base + "1"] = spelling;
|
|
392
|
+
INGGLISH_FULL_MAP[base + "2"] = spelling;
|
|
393
|
+
}
|
|
394
|
+
INGGLISH_FULL_MAP.AH0 = "a";
|
|
395
|
+
function arpabetToIngglish(arpabet) {
|
|
396
|
+
let result = "";
|
|
397
|
+
const len = arpabet.length;
|
|
398
|
+
for (let i = 0; i < len; i++) {
|
|
399
|
+
const phoneme = arpabet[i];
|
|
400
|
+
if (i + 1 < len && arpabet[i + 1] === "R") {
|
|
401
|
+
const base = stripStress(phoneme);
|
|
402
|
+
const rPrefix = R_COLORED_FORWARD.get(base);
|
|
403
|
+
if (rPrefix !== void 0) {
|
|
404
|
+
result += rPrefix;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
result += INGGLISH_FULL_MAP[phoneme] ?? phoneme.toLowerCase();
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
registerFormat("ingglish", {
|
|
413
|
+
forward: arpabetToIngglish,
|
|
414
|
+
isLatinScript: true,
|
|
415
|
+
label: "Ingglish",
|
|
416
|
+
preservesCase: true
|
|
417
|
+
});
|
|
418
|
+
var EMPTY_R_COLORED = /* @__PURE__ */ new Map();
|
|
419
|
+
var INGGLISH_STRESS_OVERRIDES = /* @__PURE__ */ new Map([["AH0", "a"]]);
|
|
420
|
+
function arpabetToFormat(arpabet, format = "ingglish", options) {
|
|
421
|
+
if (format === "ingglish") {
|
|
422
|
+
if (options?.disableRColoring === true) {
|
|
423
|
+
return convertArpabet(
|
|
424
|
+
arpabet,
|
|
425
|
+
ARPABET_TO_INGGLISH_MAP,
|
|
426
|
+
EMPTY_R_COLORED,
|
|
427
|
+
INGGLISH_STRESS_OVERRIDES
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return arpabetToIngglish(arpabet);
|
|
431
|
+
}
|
|
432
|
+
const handler = getFormatHandler(format);
|
|
433
|
+
if (handler?.forward) {
|
|
434
|
+
return handler.forward(arpabet, options);
|
|
435
|
+
}
|
|
436
|
+
return arpabetToIngglish(arpabet);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/to-pronunciation.ts
|
|
440
|
+
var GUIDE_MAP = {};
|
|
441
|
+
for (const [base, spelling] of Object.entries(ARPABET_TO_INGGLISH_MAP)) {
|
|
442
|
+
GUIDE_MAP[base] = spelling;
|
|
443
|
+
GUIDE_MAP[base + "0"] = spelling;
|
|
444
|
+
GUIDE_MAP[base + "1"] = spelling;
|
|
445
|
+
GUIDE_MAP[base + "2"] = spelling;
|
|
446
|
+
}
|
|
447
|
+
GUIDE_MAP.AH0 = "a";
|
|
448
|
+
function arpabetToPronunciation(arpabet) {
|
|
449
|
+
const syllables = syllabify(arpabet);
|
|
450
|
+
return syllables.map(({ phonemes, stress }) => {
|
|
451
|
+
const spelling = syllableToSpelling(phonemes);
|
|
452
|
+
return stress >= 1 ? spelling.toUpperCase() : spelling;
|
|
453
|
+
}).join("-");
|
|
454
|
+
}
|
|
455
|
+
function registerPronunciation() {
|
|
456
|
+
registerFormat("pronunciation", {
|
|
457
|
+
forward: arpabetToPronunciation,
|
|
458
|
+
isLatinScript: true,
|
|
459
|
+
label: "Guide",
|
|
460
|
+
preservesCase: false
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function syllabify(arpabet) {
|
|
464
|
+
const vowelIndices = [];
|
|
465
|
+
for (const [i, phoneme] of arpabet.entries()) {
|
|
466
|
+
if (isVowel(phoneme)) {
|
|
467
|
+
vowelIndices.push(i);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (vowelIndices.length === 0) {
|
|
471
|
+
return [{ phonemes: arpabet, stress: 0 }];
|
|
472
|
+
}
|
|
473
|
+
const syllables = [];
|
|
474
|
+
let start = 0;
|
|
475
|
+
for (let vi = 0; vi < vowelIndices.length; vi++) {
|
|
476
|
+
const vowelIdx = vowelIndices[vi];
|
|
477
|
+
const stress = getStress(arpabet[vowelIdx]) ?? 0;
|
|
478
|
+
if (vi < vowelIndices.length - 1) {
|
|
479
|
+
const nextVowelIdx = vowelIndices[vi + 1];
|
|
480
|
+
const consonantStart = vowelIdx + 1;
|
|
481
|
+
const consonants = [];
|
|
482
|
+
for (let j = consonantStart; j < nextVowelIdx; j++) {
|
|
483
|
+
consonants.push(stripStress(arpabet[j]));
|
|
484
|
+
}
|
|
485
|
+
if (consonants.length === 0) {
|
|
486
|
+
syllables.push({ phonemes: arpabet.slice(start, consonantStart), stress });
|
|
487
|
+
start = consonantStart;
|
|
488
|
+
} else {
|
|
489
|
+
const onsetIdx = findOnsetStart(consonants);
|
|
490
|
+
const boundary = consonantStart + onsetIdx;
|
|
491
|
+
syllables.push({ phonemes: arpabet.slice(start, boundary), stress });
|
|
492
|
+
start = boundary;
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
syllables.push({ phonemes: arpabet.slice(start), stress });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return syllables;
|
|
499
|
+
}
|
|
500
|
+
function syllableToSpelling(phonemes) {
|
|
501
|
+
let result = "";
|
|
502
|
+
for (let i = 0; i < phonemes.length; i++) {
|
|
503
|
+
const phoneme = phonemes[i];
|
|
504
|
+
if (i + 1 < phonemes.length && phonemes[i + 1] === "R") {
|
|
505
|
+
const base = stripStress(phoneme);
|
|
506
|
+
const rPrefix = R_COLORED_FORWARD.get(base);
|
|
507
|
+
if (rPrefix !== void 0) {
|
|
508
|
+
result += rPrefix;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
result += GUIDE_MAP[phoneme] ?? phoneme.toLowerCase();
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/from-ingglish.ts
|
|
518
|
+
var ARPABET_ALTERNATIVES = {
|
|
519
|
+
AE: [["AH"]],
|
|
520
|
+
// "a" could be AE (cat) or AH (schwa: about, the)
|
|
521
|
+
ER: [["EH", "R"]],
|
|
522
|
+
SH: [["S", "HH"]]
|
|
523
|
+
// "sh" could be SH (ship) or S+HH (exhume)
|
|
524
|
+
};
|
|
525
|
+
var ARPABET_ALTERNATIVES_ENTRIES = Object.entries(ARPABET_ALTERNATIVES);
|
|
526
|
+
function expandArpabetAlternatives(arpabet) {
|
|
527
|
+
const results = [arpabet];
|
|
528
|
+
for (let i = 0; i < arpabet.length; i++) {
|
|
529
|
+
const alternatives = ARPABET_ALTERNATIVES[arpabet[i]];
|
|
530
|
+
if (alternatives !== void 0) {
|
|
531
|
+
for (const alt of alternatives) {
|
|
532
|
+
const expanded = [...arpabet.slice(0, i), ...alt, ...arpabet.slice(i + 1)];
|
|
533
|
+
results.push(expanded);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
for (const [phoneme, alts] of ARPABET_ALTERNATIVES_ENTRIES) {
|
|
538
|
+
for (const alt of alts) {
|
|
539
|
+
if (alt.length === 1) {
|
|
540
|
+
let count = 0;
|
|
541
|
+
for (const p of arpabet) {
|
|
542
|
+
if (p === phoneme) {
|
|
543
|
+
count++;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (count >= 2) {
|
|
547
|
+
results.push(arpabet.map((p) => p === phoneme ? alt[0] : p));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return results;
|
|
553
|
+
}
|
|
554
|
+
var TWO_CHAR_SPELLINGS = new Set(
|
|
555
|
+
Object.keys(INGGLISH_TO_ARPABET_MAP).filter((s) => s.length === 2)
|
|
556
|
+
);
|
|
557
|
+
var ONE_CHAR_SPELLINGS = new Set(
|
|
558
|
+
Object.keys(INGGLISH_TO_ARPABET_MAP).filter((s) => s.length === 1)
|
|
559
|
+
);
|
|
560
|
+
function ingglishToArpabet(ingglish) {
|
|
561
|
+
const result = [];
|
|
562
|
+
const str = ingglish.toLowerCase();
|
|
563
|
+
const len = str.length;
|
|
564
|
+
let pos = 0;
|
|
565
|
+
while (pos < len) {
|
|
566
|
+
if (pos + 3 <= len) {
|
|
567
|
+
const threeChar = str.slice(pos, pos + 3);
|
|
568
|
+
if (threeChar in R_COLORED_REVERSE_3CHAR) {
|
|
569
|
+
result.push(...R_COLORED_REVERSE_3CHAR[threeChar]);
|
|
570
|
+
pos += 3;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (pos + 2 <= len) {
|
|
575
|
+
const twoChar = str.slice(pos, pos + 2);
|
|
576
|
+
if (twoChar in R_COLORED_REVERSE_2CHAR) {
|
|
577
|
+
result.push(...R_COLORED_REVERSE_2CHAR[twoChar]);
|
|
578
|
+
pos += 2;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (TWO_CHAR_SPELLINGS.has(twoChar)) {
|
|
582
|
+
result.push(INGGLISH_TO_ARPABET_MAP[twoChar]);
|
|
583
|
+
pos += 2;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const oneChar = str[pos];
|
|
588
|
+
if (ONE_CHAR_SPELLINGS.has(oneChar)) {
|
|
589
|
+
result.push(INGGLISH_TO_ARPABET_MAP[oneChar]);
|
|
590
|
+
pos += 1;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
pos += 1;
|
|
594
|
+
}
|
|
595
|
+
return result.length > 0 ? result : null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/custom-format.ts
|
|
599
|
+
function createCustomConverter(config) {
|
|
600
|
+
const mergedMap = { ...ARPABET_TO_INGGLISH_MAP, ...config.phonemeMap };
|
|
601
|
+
const mergedRColored = new Map(R_COLORED_FORWARD);
|
|
602
|
+
for (const [vowel] of R_COLORED_FORWARD) {
|
|
603
|
+
if (vowel in config.phonemeMap && !(vowel in config.rColoredPrefixes)) {
|
|
604
|
+
mergedRColored.set(vowel, config.phonemeMap[vowel]);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const [key, value] of Object.entries(config.rColoredPrefixes)) {
|
|
608
|
+
mergedRColored.set(key, value);
|
|
609
|
+
}
|
|
610
|
+
const stressOverrides = /* @__PURE__ */ new Map([["AH0", "a"]]);
|
|
611
|
+
for (const [key, value] of Object.entries(config.phonemeMap)) {
|
|
612
|
+
const lastChar = key.codePointAt(key.length - 1);
|
|
613
|
+
if (lastChar >= 48 && lastChar <= 50) {
|
|
614
|
+
stressOverrides.set(key, value);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const emptyRColored = /* @__PURE__ */ new Map();
|
|
618
|
+
return (arpabet, options) => convertArpabet(
|
|
619
|
+
arpabet,
|
|
620
|
+
mergedMap,
|
|
621
|
+
options?.disableRColoring === true ? emptyRColored : mergedRColored,
|
|
622
|
+
stressOverrides
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
export {
|
|
626
|
+
ARPABET_CONSONANTS,
|
|
627
|
+
ARPABET_TO_INGGLISH_MAP,
|
|
628
|
+
ARPABET_VOWELS,
|
|
629
|
+
R_COLORED_FORWARD,
|
|
630
|
+
STRESS_MARKER_REGEX,
|
|
631
|
+
arpabetPhonemeToIngglish,
|
|
632
|
+
arpabetToFormat,
|
|
633
|
+
arpabetToIngglish,
|
|
634
|
+
createCustomConverter,
|
|
635
|
+
expandArpabetAlternatives,
|
|
636
|
+
findOnsetStart,
|
|
637
|
+
getFormatHandler,
|
|
638
|
+
getFormatIsLatinScript,
|
|
639
|
+
getFormatJoinSeparator,
|
|
640
|
+
getFormatLabel,
|
|
641
|
+
getFormatNativeLabel,
|
|
642
|
+
getFormatPreservesCase,
|
|
643
|
+
getStress,
|
|
644
|
+
ingglishToArpabet,
|
|
645
|
+
isVowel,
|
|
646
|
+
registerFormat,
|
|
647
|
+
registerPronunciation,
|
|
648
|
+
stripStress
|
|
649
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ingglish/phonemes",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Phoneme definitions and ARPAbet/IPA/Ingglish conversion maps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"source": "./src/index.ts",
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"build:fast": "tsup src/index.ts --format esm",
|
|
32
|
+
"lint": "eslint --cache src",
|
|
33
|
+
"test": "vitest run --no-color",
|
|
34
|
+
"bench": "vitest bench --no-color",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"author": "Paul Tarjan",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/ptarjan/ingglish.git",
|
|
42
|
+
"directory": "packages/phonemes"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/ptarjan/ingglish#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/ptarjan/ingglish/issues"
|
|
47
|
+
}
|
|
48
|
+
}
|