oro-g2p 0.1.0__tar.gz
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.
- oro_g2p-0.1.0/LICENSE +21 -0
- oro_g2p-0.1.0/PKG-INFO +108 -0
- oro_g2p-0.1.0/README.md +82 -0
- oro_g2p-0.1.0/oro_g2p/__init__.py +102 -0
- oro_g2p-0.1.0/oro_g2p/kokoro.py +58 -0
- oro_g2p-0.1.0/oro_g2p/normalizer.py +52 -0
- oro_g2p-0.1.0/oro_g2p/tokenizer.py +166 -0
- oro_g2p-0.1.0/oro_g2p.egg-info/PKG-INFO +108 -0
- oro_g2p-0.1.0/oro_g2p.egg-info/SOURCES.txt +12 -0
- oro_g2p-0.1.0/oro_g2p.egg-info/dependency_links.txt +1 -0
- oro_g2p-0.1.0/oro_g2p.egg-info/top_level.txt +1 -0
- oro_g2p-0.1.0/pyproject.toml +47 -0
- oro_g2p-0.1.0/setup.cfg +4 -0
- oro_g2p-0.1.0/tests/test_g2p.py +91 -0
oro_g2p-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oro Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
oro_g2p-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oro-g2p
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Research-grade Yoruba Grapheme-to-Phoneme (G2P) converter and Kokoro TTS frontend
|
|
5
|
+
Author: Oro Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Akintunde102/oro-g2p
|
|
8
|
+
Project-URL: Repository, https://github.com/Akintunde102/oro-g2p
|
|
9
|
+
Project-URL: Issues, https://github.com/Akintunde102/oro-g2p/issues
|
|
10
|
+
Keywords: yoruba,g2p,tts,kokoro,phonemes,african-nlp,speech-synthesis,phonology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# oro-g2p 🗣️
|
|
28
|
+
|
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
|
30
|
+
[](https://www.python.org/)
|
|
31
|
+
|
|
32
|
+
**`oro-g2p`** is a zero-dependency, research-grade Yoruba Grapheme-to-Phoneme (G2P) engine and phonetic tokenizer designed for speech synthesis (TTS), automatic speech recognition (ASR), and forced alignment.
|
|
33
|
+
|
|
34
|
+
It provides out-of-the-box native token mapping for **[Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)** and **StyleTTS2**.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
1. **Deterministic Linguistic Engine:** Pure, rule-based Yoruba phonology — zero hallucinations, reproducible output.
|
|
41
|
+
2. **Post-Nasal Harmony:** Automatically captures unwritten phonetic nasalization on vowels following nasal consonants `m` and `n` (*ọmọ* → `/ɔˉmɔ̃ˉ/`, *inú* → `/iˉnũˊ/`).
|
|
42
|
+
3. **Chameleon Syllabic Nasals:** Dynamically applies homorganic place assimilation to `ń` and `ǹ`:
|
|
43
|
+
- Labials (`b`, `f`): `/m/` (*Mo ń bọ̀* → `moˉ mˊ bɔˋ`)
|
|
44
|
+
- Alveolars (`d`, `t`, `s`, `ṣ`): `/n/` (*Mo ń ṣe* → `moˉ nˊ ʃeˉ`)
|
|
45
|
+
- Velars & Glides (`l`, `k`, `g`): `/ŋ/` (*Mo ń lọ* → `moˉ ŋˊ lɔˉ`)
|
|
46
|
+
4. **Typo Resilience:** Seamlessly normalizes Turkish cedillas (`ş`), combining vertical lines (`\u0329`), and out-of-order diacritic keystrokes into canonical Yoruba underdots (`ẹ`, `ọ`, `ṣ`).
|
|
47
|
+
5. **Contour Glides:** Expands carons (`ǎ` → `àá` Low-High) and circumflexes (`â` → `áà` High-Low).
|
|
48
|
+
6. **Native Kokoro Alignment:** Maps 100% of Yoruba phonemes and tones directly into Kokoro-82M's 178-token embedding table with **0 missing tokens**.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install oro-g2p
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
### 1. Basic G2P Conversion
|
|
63
|
+
```python
|
|
64
|
+
from oro_g2p import g2p
|
|
65
|
+
|
|
66
|
+
# Converts Yoruba text to tone-marked IPA
|
|
67
|
+
phonemes = g2p("Ẹ kú àárọ̀, ọmọ Yorùbá!")
|
|
68
|
+
print(phonemes)
|
|
69
|
+
# Output: 'ɛˉ kuˊ aˋaˊɾɔˋ, ɔˉmɔ̃ˉ joˉɾuˋbaˊ!'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Kokoro-82M Tokenization
|
|
73
|
+
```python
|
|
74
|
+
from oro_g2p import yoruba_to_kokoro
|
|
75
|
+
|
|
76
|
+
text = "Ẹ kú àárọ̀"
|
|
77
|
+
ipa_str, token_ids, missing = yoruba_to_kokoro(text)
|
|
78
|
+
|
|
79
|
+
print(f"IPA: {ipa_str}")
|
|
80
|
+
print(f"Token IDs: {token_ids}")
|
|
81
|
+
print(f"Missing: {missing}") # [] -> 100% coverage!
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Architecture: Kokoro-82M Token Mapping
|
|
87
|
+
|
|
88
|
+
Kokoro has a fixed embedding matrix size of `178` (`0` to `177`). While 114 slots were occupied by Western languages, 64 slots were unassigned.
|
|
89
|
+
|
|
90
|
+
`oro-g2p` resolves Yoruba without modifying any pre-trained weights or embedding tensor dimensions:
|
|
91
|
+
- **High Tone (Mí):** Assigned to Vacant Slot `7` (`ˊ`)
|
|
92
|
+
- **Mid Tone (Ré):** Assigned to Vacant Slot `8` (`ˉ`)
|
|
93
|
+
- **Low Tone (Dò):** Assigned to Vacant Slot `26` (`ˋ`)
|
|
94
|
+
- **Nasal Vowels:** Decomposed into `(vowel + \u0303)`, cleanly utilizing Kokoro's native Room `17`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Running Tests
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m pytest tests/
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT License. Free for academic, personal, and commercial speech technology research.
|
oro_g2p-0.1.0/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# oro-g2p 🗣️
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
|
|
6
|
+
**`oro-g2p`** is a zero-dependency, research-grade Yoruba Grapheme-to-Phoneme (G2P) engine and phonetic tokenizer designed for speech synthesis (TTS), automatic speech recognition (ASR), and forced alignment.
|
|
7
|
+
|
|
8
|
+
It provides out-of-the-box native token mapping for **[Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)** and **StyleTTS2**.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Key Features
|
|
13
|
+
|
|
14
|
+
1. **Deterministic Linguistic Engine:** Pure, rule-based Yoruba phonology — zero hallucinations, reproducible output.
|
|
15
|
+
2. **Post-Nasal Harmony:** Automatically captures unwritten phonetic nasalization on vowels following nasal consonants `m` and `n` (*ọmọ* → `/ɔˉmɔ̃ˉ/`, *inú* → `/iˉnũˊ/`).
|
|
16
|
+
3. **Chameleon Syllabic Nasals:** Dynamically applies homorganic place assimilation to `ń` and `ǹ`:
|
|
17
|
+
- Labials (`b`, `f`): `/m/` (*Mo ń bọ̀* → `moˉ mˊ bɔˋ`)
|
|
18
|
+
- Alveolars (`d`, `t`, `s`, `ṣ`): `/n/` (*Mo ń ṣe* → `moˉ nˊ ʃeˉ`)
|
|
19
|
+
- Velars & Glides (`l`, `k`, `g`): `/ŋ/` (*Mo ń lọ* → `moˉ ŋˊ lɔˉ`)
|
|
20
|
+
4. **Typo Resilience:** Seamlessly normalizes Turkish cedillas (`ş`), combining vertical lines (`\u0329`), and out-of-order diacritic keystrokes into canonical Yoruba underdots (`ẹ`, `ọ`, `ṣ`).
|
|
21
|
+
5. **Contour Glides:** Expands carons (`ǎ` → `àá` Low-High) and circumflexes (`â` → `áà` High-Low).
|
|
22
|
+
6. **Native Kokoro Alignment:** Maps 100% of Yoruba phonemes and tones directly into Kokoro-82M's 178-token embedding table with **0 missing tokens**.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install oro-g2p
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
### 1. Basic G2P Conversion
|
|
37
|
+
```python
|
|
38
|
+
from oro_g2p import g2p
|
|
39
|
+
|
|
40
|
+
# Converts Yoruba text to tone-marked IPA
|
|
41
|
+
phonemes = g2p("Ẹ kú àárọ̀, ọmọ Yorùbá!")
|
|
42
|
+
print(phonemes)
|
|
43
|
+
# Output: 'ɛˉ kuˊ aˋaˊɾɔˋ, ɔˉmɔ̃ˉ joˉɾuˋbaˊ!'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Kokoro-82M Tokenization
|
|
47
|
+
```python
|
|
48
|
+
from oro_g2p import yoruba_to_kokoro
|
|
49
|
+
|
|
50
|
+
text = "Ẹ kú àárọ̀"
|
|
51
|
+
ipa_str, token_ids, missing = yoruba_to_kokoro(text)
|
|
52
|
+
|
|
53
|
+
print(f"IPA: {ipa_str}")
|
|
54
|
+
print(f"Token IDs: {token_ids}")
|
|
55
|
+
print(f"Missing: {missing}") # [] -> 100% coverage!
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Architecture: Kokoro-82M Token Mapping
|
|
61
|
+
|
|
62
|
+
Kokoro has a fixed embedding matrix size of `178` (`0` to `177`). While 114 slots were occupied by Western languages, 64 slots were unassigned.
|
|
63
|
+
|
|
64
|
+
`oro-g2p` resolves Yoruba without modifying any pre-trained weights or embedding tensor dimensions:
|
|
65
|
+
- **High Tone (Mí):** Assigned to Vacant Slot `7` (`ˊ`)
|
|
66
|
+
- **Mid Tone (Ré):** Assigned to Vacant Slot `8` (`ˉ`)
|
|
67
|
+
- **Low Tone (Dò):** Assigned to Vacant Slot `26` (`ˋ`)
|
|
68
|
+
- **Nasal Vowels:** Decomposed into `(vowel + \u0303)`, cleanly utilizing Kokoro's native Room `17`.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Running Tests
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python -m pytest tests/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT License. Free for academic, personal, and commercial speech technology research.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oro_g2p: Research-grade Yoruba Grapheme-to-Phoneme converter and Kokoro TTS frontend.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Tuple, List, Optional
|
|
6
|
+
from .normalizer import normalize_yoruba
|
|
7
|
+
from .tokenizer import tokenize_graphemes
|
|
8
|
+
from .kokoro import to_kokoro_tokens, YORUBA_KOKORO_VOCAB
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
__all__ = [
|
|
12
|
+
"g2p",
|
|
13
|
+
"tokenize_graphemes",
|
|
14
|
+
"normalize_yoruba",
|
|
15
|
+
"to_kokoro_tokens",
|
|
16
|
+
"yoruba_to_kokoro",
|
|
17
|
+
"YORUBA_KOKORO_VOCAB",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
# Standard IPA mappings for Yoruba
|
|
21
|
+
CONSONANT_IPA = {
|
|
22
|
+
"b": "b",
|
|
23
|
+
"d": "d",
|
|
24
|
+
"f": "f",
|
|
25
|
+
"g": "ɡ", # Single-story IPA ɡ (\u0261)
|
|
26
|
+
"gb": "ɡb", # Labial-velar voiced stop
|
|
27
|
+
"h": "h",
|
|
28
|
+
"j": "dʒ", # Voiced palato-alveolar affricate
|
|
29
|
+
"k": "k",
|
|
30
|
+
"l": "l",
|
|
31
|
+
"m": "m",
|
|
32
|
+
"n": "n",
|
|
33
|
+
"ŋ": "ŋ", # Velar nasal
|
|
34
|
+
"p": "kp", # Voiceless labial-velar stop /kp/
|
|
35
|
+
"r": "ɾ", # Alveolar flap/tap
|
|
36
|
+
"s": "s",
|
|
37
|
+
"ṣ": "ʃ", # Voiceless postalveolar fricative
|
|
38
|
+
"t": "t",
|
|
39
|
+
"w": "w",
|
|
40
|
+
"y": "j", # Palatal approximant (IPA 'j')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
VOWEL_IPA = {
|
|
44
|
+
# Oral vowels
|
|
45
|
+
"a": "a",
|
|
46
|
+
"e": "e",
|
|
47
|
+
"ẹ": "ɛ",
|
|
48
|
+
"i": "i",
|
|
49
|
+
"o": "o",
|
|
50
|
+
"ọ": "ɔ",
|
|
51
|
+
"u": "u",
|
|
52
|
+
# Nasal vowels
|
|
53
|
+
"an": "ã",
|
|
54
|
+
"ẹn": "ɛ̃",
|
|
55
|
+
"in": "ĩ",
|
|
56
|
+
"ọn": "ɔ̃",
|
|
57
|
+
"un": "ũ",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
TONE_IPA = {
|
|
61
|
+
"H": "ˊ", # High tone (Mí)
|
|
62
|
+
"M": "ˉ", # Mid tone (Ré)
|
|
63
|
+
"L": "ˋ", # Low tone (Dò)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def g2p(text: str) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Converts Yoruba text into a tone-marked IPA phoneme string.
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
>>> from oro_g2p import g2p
|
|
73
|
+
>>> g2p("Ẹ kú àárọ̀")
|
|
74
|
+
'ɛˉ kuˊ aˋaˊɾɔˋ'
|
|
75
|
+
"""
|
|
76
|
+
tokens = tokenize_graphemes(text)
|
|
77
|
+
phonemes: List[str] = []
|
|
78
|
+
|
|
79
|
+
for grapheme, tone in tokens:
|
|
80
|
+
if grapheme in VOWEL_IPA:
|
|
81
|
+
phonemes.append(VOWEL_IPA[grapheme] + (TONE_IPA[tone] if tone else ""))
|
|
82
|
+
elif grapheme in CONSONANT_IPA:
|
|
83
|
+
sound = CONSONANT_IPA[grapheme]
|
|
84
|
+
if tone is not None:
|
|
85
|
+
sound += TONE_IPA[tone]
|
|
86
|
+
phonemes.append(sound)
|
|
87
|
+
else:
|
|
88
|
+
phonemes.append(grapheme)
|
|
89
|
+
|
|
90
|
+
return "".join(phonemes)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def yoruba_to_kokoro(text: str) -> Tuple[str, List[int], List[str]]:
|
|
94
|
+
"""
|
|
95
|
+
End-to-end pipeline: Raw Yoruba text -> IPA string -> Kokoro token IDs.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
(ipa_string, token_ids, missing_tokens)
|
|
99
|
+
"""
|
|
100
|
+
ipa_str = g2p(text)
|
|
101
|
+
token_ids, missing = to_kokoro_tokens(ipa_str)
|
|
102
|
+
return ipa_str, token_ids, missing
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oro_g2p.kokoro
|
|
3
|
+
~~~~~~~~~~~~~~
|
|
4
|
+
Kokoro-82M vocabulary integration and tokenization.
|
|
5
|
+
Maps Yoruba IPA phones and tones to the native 178-token vocabulary table.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unicodedata
|
|
9
|
+
from typing import Tuple, List
|
|
10
|
+
|
|
11
|
+
# Official Kokoro-82M base vocabulary dictionary (114 occupied slots)
|
|
12
|
+
BASE_KOKORO_VOCAB = {
|
|
13
|
+
";": 1, ":": 2, ",": 3, ".": 4, "!": 5, "?": 6, "—": 9, "…": 10, "\"": 11,
|
|
14
|
+
"(": 12, ")": 13, "“": 14, "”": 15, " ": 16, "\u0303": 17, "ˈ": 18, "ˌ": 19,
|
|
15
|
+
"ː": 20, "ɸ": 21, "β": 22, "θ": 23, "ð": 24, "ʃ": 25, "ɹ": 27, "ɻ": 28,
|
|
16
|
+
"ʋ": 29, "ⱱ": 30, "ç": 32, "ʝ": 33, "ɣ": 35, "χ": 36, "ʁ": 37, "ħ": 38,
|
|
17
|
+
"ʕ": 39, "ɦ": 40, "ɬ": 41, "ɮ": 42, "a": 43, "b": 44, "c": 45, "d": 46,
|
|
18
|
+
"e": 47, "f": 48, "h": 50, "i": 51, "j": 52, "k": 53, "l": 54, "m": 55,
|
|
19
|
+
"n": 56, "o": 57, "p": 58, "q": 59, "r": 60, "s": 61, "t": 62, "u": 63,
|
|
20
|
+
"v": 64, "w": 65, "x": 66, "y": 67, "z": 68, "ɑ": 69, "ɐ": 70, "ɒ": 71,
|
|
21
|
+
"æ": 72, "ɓ": 75, "ɔ": 76, "ɕ": 77, "ɖ": 78, "ɗ": 80, "ɘ": 81, "ə": 82,
|
|
22
|
+
"ɚ": 83, "ɛ": 86, "ɜ": 87, "ɞ": 90, "ɟ": 91, "ɡ": 92, "ɢ": 94, "ɣ": 95,
|
|
23
|
+
"ɤ": 96, "ɥ": 98, "ɦ": 99, "ɧ": 101, "ɨ": 102, "ɪ": 103, "ɫ": 110, "ɬ": 111,
|
|
24
|
+
"ŋ": 112, "ɲ": 113, "ɳ": 114, "ɴ": 115, "ɵ": 116, "œ": 118, "ɶ": 119,
|
|
25
|
+
"ø": 120, "ɸ": 123, "ɹ": 124, "ɾ": 125, "ɺ": 126, "ɻ": 128, "ɽ": 129,
|
|
26
|
+
"s": 130, "ʃ": 131, "ʂ": 132, "θ": 133, "ʈ": 135, "ʉ": 136, "ʊ": 138,
|
|
27
|
+
"ʋ": 139, "ʌ": 140, "ʍ": 142, "w": 143, "x": 145, "χ": 146, "ʒ": 147,
|
|
28
|
+
"ʐ": 148, "ʑ": 150, "ʔ": 156, "ᵻ": 177
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Assign Yoruba's 3 Tone Markers to verified vacant slots in Kokoro's embedding table
|
|
32
|
+
YORUBA_KOKORO_VOCAB = dict(BASE_KOKORO_VOCAB)
|
|
33
|
+
YORUBA_KOKORO_VOCAB["ˊ"] = 7 # High Tone (Mí) -> Vacant Slot 7
|
|
34
|
+
YORUBA_KOKORO_VOCAB["ˉ"] = 8 # Mid Tone (Ré) -> Vacant Slot 8
|
|
35
|
+
YORUBA_KOKORO_VOCAB["ˋ"] = 26 # Low Tone (Dò) -> Vacant Slot 26
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def to_kokoro_tokens(phonemes_str: str) -> Tuple[List[int], List[str]]:
|
|
39
|
+
"""
|
|
40
|
+
Takes an IPA phoneme string, decomposes nasal characters into (vowel + \\u0303),
|
|
41
|
+
and maps each symbol directly to its corresponding Kokoro token ID.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
(token_ids, missing_tokens)
|
|
45
|
+
"""
|
|
46
|
+
# Decompose nasal vowels (e.g. ã -> a + combining tilde \u0303 in Slot 17)
|
|
47
|
+
normalized = unicodedata.normalize("NFD", phonemes_str)
|
|
48
|
+
|
|
49
|
+
token_ids: List[int] = []
|
|
50
|
+
missing_tokens: List[str] = []
|
|
51
|
+
|
|
52
|
+
for ch in normalized:
|
|
53
|
+
if ch in YORUBA_KOKORO_VOCAB:
|
|
54
|
+
token_ids.append(YORUBA_KOKORO_VOCAB[ch])
|
|
55
|
+
else:
|
|
56
|
+
missing_tokens.append(ch)
|
|
57
|
+
|
|
58
|
+
return token_ids, missing_tokens
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oro_g2p.normalizer
|
|
3
|
+
~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Unicode decomposition and diacritic normalization for Yoruba text.
|
|
5
|
+
Handles:
|
|
6
|
+
- Standard combining underdot (\\u0323 on ẹ, ọ, ṣ)
|
|
7
|
+
- Common keyboard/OCR typo substitutions (cedilla \\u0327, vertical line \\u0329)
|
|
8
|
+
- Tone extraction (High, Low, Mid, Rising caron, Falling circumflex)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import unicodedata
|
|
12
|
+
|
|
13
|
+
# 1. Underdot variants found in keyboards, scanned texts, and African corpora
|
|
14
|
+
UNDERDOT = "\u0323" # Standard combining dot below
|
|
15
|
+
CEDILLA = "\u0327" # Combining cedilla (frequently typed on Turkish/Latin keyboards as ş, ȩ)
|
|
16
|
+
VERT_LINE_BELOW = "\u0329" # Combining vertical line below
|
|
17
|
+
ALL_UNDERDOTS = {UNDERDOT, CEDILLA, VERT_LINE_BELOW}
|
|
18
|
+
|
|
19
|
+
# 2. Tone Marks (Simple + Contour Glides)
|
|
20
|
+
LOW_TONE = "\u0300" # Grave (`) -> Low tone (Dò)
|
|
21
|
+
HIGH_TONE = "\u0301" # Acute (´) -> High tone (Mí)
|
|
22
|
+
FALLING_TONE = "\u0302" # Circumflex (ˆ) -> Falling contour (áà)
|
|
23
|
+
MACRON_TONE = "\u0304" # Macron (¯) -> Explicit Mid tone (Ré)
|
|
24
|
+
RISING_TONE = "\u030c" # Caron (ˇ) -> Rising contour (àá)
|
|
25
|
+
ALL_TONES = {LOW_TONE, HIGH_TONE, FALLING_TONE, MACRON_TONE, RISING_TONE}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def normalize_yoruba(text: str) -> list[str]:
|
|
29
|
+
"""
|
|
30
|
+
Decomposes Yoruba text into canonical NFD building blocks.
|
|
31
|
+
|
|
32
|
+
Rules applied:
|
|
33
|
+
1. Un-glues letters, underdots, and tone diacritics via NFD.
|
|
34
|
+
2. Maps any underdot variant (cedilla \\u0327, vertical line \\u0329) on e, o, s
|
|
35
|
+
into the true canonical Yoruba underdot (\\u0323).
|
|
36
|
+
3. Handles out-of-order typing (e.g. tone typed before underdot).
|
|
37
|
+
4. Keeps tone diacritics separated right after their base character for downstream prosody.
|
|
38
|
+
"""
|
|
39
|
+
decomposed = unicodedata.normalize("NFD", text.lower())
|
|
40
|
+
|
|
41
|
+
result: list[str] = []
|
|
42
|
+
for char in decomposed:
|
|
43
|
+
if char in ALL_UNDERDOTS and result:
|
|
44
|
+
# Check if a tone diacritic was typed BEFORE the underdot: [base, tone, underdot]
|
|
45
|
+
if result[-1] in ALL_TONES and len(result) >= 2 and result[-2] in {"e", "o", "s"}:
|
|
46
|
+
result[-2] = unicodedata.normalize("NFC", result[-2] + UNDERDOT)
|
|
47
|
+
elif result[-1] in {"e", "o", "s"}:
|
|
48
|
+
result[-1] = unicodedata.normalize("NFC", result[-1] + UNDERDOT)
|
|
49
|
+
else:
|
|
50
|
+
result.append(char)
|
|
51
|
+
|
|
52
|
+
return result
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oro_g2p.tokenizer
|
|
3
|
+
~~~~~~~~~~~~~~~~~
|
|
4
|
+
Linguistic tokenizer for Standard Yoruba.
|
|
5
|
+
Encodes:
|
|
6
|
+
- Digraph preservation ('gb', 'sh' -> 'ṣ')
|
|
7
|
+
- Strict tone attachment to Tone-Bearing Units (TBUs)
|
|
8
|
+
- Nasal vowel classification (an, ẹn, in, ọn, un)
|
|
9
|
+
- Post-nasal vowel assimilation harmony ('m', 'n' -> 'ọmọ', 'inú', 'mí')
|
|
10
|
+
- Dynamic homorganic chameleon syllabic nasals ('ń', 'ǹ', 'm')
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from .normalizer import (
|
|
15
|
+
normalize_yoruba,
|
|
16
|
+
ALL_TONES,
|
|
17
|
+
HIGH_TONE,
|
|
18
|
+
LOW_TONE,
|
|
19
|
+
MACRON_TONE,
|
|
20
|
+
RISING_TONE,
|
|
21
|
+
FALLING_TONE,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
VOWELS = {"a", "e", "ẹ", "i", "o", "ọ", "u"}
|
|
25
|
+
NASAL_MAP = {
|
|
26
|
+
"a": "an",
|
|
27
|
+
"ẹ": "ẹn",
|
|
28
|
+
"i": "in",
|
|
29
|
+
"ọ": "ọn",
|
|
30
|
+
"u": "un",
|
|
31
|
+
"o": "ọn",
|
|
32
|
+
"e": "ẹn",
|
|
33
|
+
}
|
|
34
|
+
CONSONANTS = {
|
|
35
|
+
"b", "d", "f", "g", "h", "j", "k", "l",
|
|
36
|
+
"m", "n", "p", "r", "s", "ṣ", "t", "w", "y"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_next_consonant(pieces: list[str], start_idx: int) -> Optional[str]:
|
|
41
|
+
"""
|
|
42
|
+
Peeks ahead past spaces to determine the next consonant environment
|
|
43
|
+
for homorganic nasal assimilation.
|
|
44
|
+
"""
|
|
45
|
+
j = start_idx
|
|
46
|
+
while j < len(pieces) and pieces[j] == " ":
|
|
47
|
+
j += 1
|
|
48
|
+
if j < len(pieces):
|
|
49
|
+
if pieces[j] == "g" and (j + 1 < len(pieces)) and pieces[j + 1] == "b":
|
|
50
|
+
return "gb"
|
|
51
|
+
if pieces[j] == "s" and (j + 1 < len(pieces)) and pieces[j + 1] == "h":
|
|
52
|
+
return "ṣ"
|
|
53
|
+
if pieces[j] in CONSONANTS:
|
|
54
|
+
return pieces[j]
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_syllabic_nasal(next_cons: Optional[str]) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Applies homorganic place assimilation to syllabic nasals:
|
|
61
|
+
- Labials (b, f, m) -> 'm' (e.g. ń bọ̀ -> ḿ bọ̀)
|
|
62
|
+
- Alveolars (t, d, s, ṣ, j, n) -> 'n' (e.g. ń ṣe -> ń ṣe)
|
|
63
|
+
- Velars, liquids, glides (k, g, gb, p, w, h, y, l, r) or pause -> 'ŋ' (e.g. Mo ń lọ -> Mo ŋ́ lọ)
|
|
64
|
+
"""
|
|
65
|
+
if next_cons in {"b", "f", "m"}:
|
|
66
|
+
return "m"
|
|
67
|
+
elif next_cons in {"t", "d", "s", "ṣ", "j", "n"}:
|
|
68
|
+
return "n"
|
|
69
|
+
else:
|
|
70
|
+
return "ŋ"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def tokenize_graphemes(text: str) -> list[tuple[str, Optional[str]]]:
|
|
74
|
+
"""
|
|
75
|
+
Tokenizes Yoruba text into a list of (grapheme, tone) tuples.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of tuples: (grapheme_string, tone_symbol)
|
|
79
|
+
Where tone_symbol is 'H' (High), 'M' (Mid), 'L' (Low), or None for non-TBUs.
|
|
80
|
+
"""
|
|
81
|
+
pieces = normalize_yoruba(text)
|
|
82
|
+
tokens: list[tuple[str, Optional[str]]] = []
|
|
83
|
+
i = 0
|
|
84
|
+
n = len(pieces)
|
|
85
|
+
|
|
86
|
+
while i < n:
|
|
87
|
+
ch = pieces[i]
|
|
88
|
+
|
|
89
|
+
# 1. Digraph checks: 'gb' and informal 'sh' -> 'ṣ'
|
|
90
|
+
if ch == "g" and (i + 1 < n) and pieces[i + 1] == "b":
|
|
91
|
+
tokens.append(("gb", None))
|
|
92
|
+
i += 2
|
|
93
|
+
continue
|
|
94
|
+
if ch == "s" and (i + 1 < n) and pieces[i + 1] == "h":
|
|
95
|
+
tokens.append(("ṣ", None))
|
|
96
|
+
i += 2
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
# 2. Vowel handling (oral, nasal, contours, and post-nasal harmony)
|
|
100
|
+
if ch in VOWELS:
|
|
101
|
+
vowel = ch
|
|
102
|
+
tones = ["M"] # Unmarked vowels are canonical Mid tone (Ré)
|
|
103
|
+
had_explicit_tone = False
|
|
104
|
+
i += 1
|
|
105
|
+
|
|
106
|
+
# Extract tone marks (including contour expansions)
|
|
107
|
+
if i < n and pieces[i] in ALL_TONES:
|
|
108
|
+
t_mark = pieces[i]
|
|
109
|
+
had_explicit_tone = True
|
|
110
|
+
if t_mark == HIGH_TONE:
|
|
111
|
+
tones = ["H"]
|
|
112
|
+
elif t_mark == LOW_TONE:
|
|
113
|
+
tones = ["L"]
|
|
114
|
+
elif t_mark == MACRON_TONE:
|
|
115
|
+
tones = ["M"]
|
|
116
|
+
elif t_mark == RISING_TONE:
|
|
117
|
+
tones = ["L", "H"] # Rising caron: àá
|
|
118
|
+
elif t_mark == FALLING_TONE:
|
|
119
|
+
tones = ["H", "L"] # Falling circumflex: áà
|
|
120
|
+
i += 1
|
|
121
|
+
|
|
122
|
+
# Nasal vowel checking (vowel + 'n')
|
|
123
|
+
if i < n and pieces[i] == "n" and vowel in NASAL_MAP:
|
|
124
|
+
next_1 = pieces[i + 1] if (i + 1 < n) else None
|
|
125
|
+
next_2 = pieces[i + 2] if (i + 2 < n) else None
|
|
126
|
+
|
|
127
|
+
# Standard nasal vowel: 'n' not followed by another vowel or tone
|
|
128
|
+
if next_1 not in VOWELS and next_1 not in ALL_TONES:
|
|
129
|
+
vowel = NASAL_MAP[vowel]
|
|
130
|
+
i += 1
|
|
131
|
+
# Catch dataset typos: tone mistakenly placed on word-final 'n' (e.g. ìbọǹ)
|
|
132
|
+
elif (not had_explicit_tone) and (next_1 in {HIGH_TONE, LOW_TONE}) and (next_2 not in VOWELS and next_2 not in CONSONANTS):
|
|
133
|
+
vowel = NASAL_MAP[vowel]
|
|
134
|
+
tones = ["H"] if next_1 == HIGH_TONE else ["L"]
|
|
135
|
+
i += 2
|
|
136
|
+
|
|
137
|
+
# Post-nasal harmony: oral vowels immediately following 'm' or 'n' automatically nasalize
|
|
138
|
+
if tokens and tokens[-1] in {("m", None), ("n", None)} and vowel in {"a", "ẹ", "i", "ọ", "u"}:
|
|
139
|
+
vowel = NASAL_MAP[vowel]
|
|
140
|
+
|
|
141
|
+
for t in tones:
|
|
142
|
+
tokens.append((vowel, t))
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# 3. Syllabic nasals ('n' or 'm' as independent tone-bearing syllables)
|
|
146
|
+
if ch in ("n", "m"):
|
|
147
|
+
if (i + 1 < n) and pieces[i + 1] in ALL_TONES:
|
|
148
|
+
t_mark = pieces[i + 1]
|
|
149
|
+
tone = "H" if t_mark == HIGH_TONE else ("L" if t_mark == LOW_TONE else "M")
|
|
150
|
+
next_cons = get_next_consonant(pieces, i + 2)
|
|
151
|
+
nasal_sound = "m" if ch == "m" else resolve_syllabic_nasal(next_cons)
|
|
152
|
+
tokens.append((nasal_sound, tone))
|
|
153
|
+
i += 2
|
|
154
|
+
continue
|
|
155
|
+
elif (i + 1 < n) and pieces[i + 1] in CONSONANTS:
|
|
156
|
+
next_cons = get_next_consonant(pieces, i + 1)
|
|
157
|
+
nasal_sound = "m" if ch == "m" else resolve_syllabic_nasal(next_cons)
|
|
158
|
+
tokens.append((nasal_sound, "M"))
|
|
159
|
+
i += 1
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# 4. Consonants, punctuation, spaces
|
|
163
|
+
tokens.append((ch, None))
|
|
164
|
+
i += 1
|
|
165
|
+
|
|
166
|
+
return tokens
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oro-g2p
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Research-grade Yoruba Grapheme-to-Phoneme (G2P) converter and Kokoro TTS frontend
|
|
5
|
+
Author: Oro Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Akintunde102/oro-g2p
|
|
8
|
+
Project-URL: Repository, https://github.com/Akintunde102/oro-g2p
|
|
9
|
+
Project-URL: Issues, https://github.com/Akintunde102/oro-g2p/issues
|
|
10
|
+
Keywords: yoruba,g2p,tts,kokoro,phonemes,african-nlp,speech-synthesis,phonology
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# oro-g2p 🗣️
|
|
28
|
+
|
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
|
30
|
+
[](https://www.python.org/)
|
|
31
|
+
|
|
32
|
+
**`oro-g2p`** is a zero-dependency, research-grade Yoruba Grapheme-to-Phoneme (G2P) engine and phonetic tokenizer designed for speech synthesis (TTS), automatic speech recognition (ASR), and forced alignment.
|
|
33
|
+
|
|
34
|
+
It provides out-of-the-box native token mapping for **[Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)** and **StyleTTS2**.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
1. **Deterministic Linguistic Engine:** Pure, rule-based Yoruba phonology — zero hallucinations, reproducible output.
|
|
41
|
+
2. **Post-Nasal Harmony:** Automatically captures unwritten phonetic nasalization on vowels following nasal consonants `m` and `n` (*ọmọ* → `/ɔˉmɔ̃ˉ/`, *inú* → `/iˉnũˊ/`).
|
|
42
|
+
3. **Chameleon Syllabic Nasals:** Dynamically applies homorganic place assimilation to `ń` and `ǹ`:
|
|
43
|
+
- Labials (`b`, `f`): `/m/` (*Mo ń bọ̀* → `moˉ mˊ bɔˋ`)
|
|
44
|
+
- Alveolars (`d`, `t`, `s`, `ṣ`): `/n/` (*Mo ń ṣe* → `moˉ nˊ ʃeˉ`)
|
|
45
|
+
- Velars & Glides (`l`, `k`, `g`): `/ŋ/` (*Mo ń lọ* → `moˉ ŋˊ lɔˉ`)
|
|
46
|
+
4. **Typo Resilience:** Seamlessly normalizes Turkish cedillas (`ş`), combining vertical lines (`\u0329`), and out-of-order diacritic keystrokes into canonical Yoruba underdots (`ẹ`, `ọ`, `ṣ`).
|
|
47
|
+
5. **Contour Glides:** Expands carons (`ǎ` → `àá` Low-High) and circumflexes (`â` → `áà` High-Low).
|
|
48
|
+
6. **Native Kokoro Alignment:** Maps 100% of Yoruba phonemes and tones directly into Kokoro-82M's 178-token embedding table with **0 missing tokens**.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install oro-g2p
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
### 1. Basic G2P Conversion
|
|
63
|
+
```python
|
|
64
|
+
from oro_g2p import g2p
|
|
65
|
+
|
|
66
|
+
# Converts Yoruba text to tone-marked IPA
|
|
67
|
+
phonemes = g2p("Ẹ kú àárọ̀, ọmọ Yorùbá!")
|
|
68
|
+
print(phonemes)
|
|
69
|
+
# Output: 'ɛˉ kuˊ aˋaˊɾɔˋ, ɔˉmɔ̃ˉ joˉɾuˋbaˊ!'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Kokoro-82M Tokenization
|
|
73
|
+
```python
|
|
74
|
+
from oro_g2p import yoruba_to_kokoro
|
|
75
|
+
|
|
76
|
+
text = "Ẹ kú àárọ̀"
|
|
77
|
+
ipa_str, token_ids, missing = yoruba_to_kokoro(text)
|
|
78
|
+
|
|
79
|
+
print(f"IPA: {ipa_str}")
|
|
80
|
+
print(f"Token IDs: {token_ids}")
|
|
81
|
+
print(f"Missing: {missing}") # [] -> 100% coverage!
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Architecture: Kokoro-82M Token Mapping
|
|
87
|
+
|
|
88
|
+
Kokoro has a fixed embedding matrix size of `178` (`0` to `177`). While 114 slots were occupied by Western languages, 64 slots were unassigned.
|
|
89
|
+
|
|
90
|
+
`oro-g2p` resolves Yoruba without modifying any pre-trained weights or embedding tensor dimensions:
|
|
91
|
+
- **High Tone (Mí):** Assigned to Vacant Slot `7` (`ˊ`)
|
|
92
|
+
- **Mid Tone (Ré):** Assigned to Vacant Slot `8` (`ˉ`)
|
|
93
|
+
- **Low Tone (Dò):** Assigned to Vacant Slot `26` (`ˋ`)
|
|
94
|
+
- **Nasal Vowels:** Decomposed into `(vowel + \u0303)`, cleanly utilizing Kokoro's native Room `17`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Running Tests
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m pytest tests/
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT License. Free for academic, personal, and commercial speech technology research.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
oro_g2p/__init__.py
|
|
5
|
+
oro_g2p/kokoro.py
|
|
6
|
+
oro_g2p/normalizer.py
|
|
7
|
+
oro_g2p/tokenizer.py
|
|
8
|
+
oro_g2p.egg-info/PKG-INFO
|
|
9
|
+
oro_g2p.egg-info/SOURCES.txt
|
|
10
|
+
oro_g2p.egg-info/dependency_links.txt
|
|
11
|
+
oro_g2p.egg-info/top_level.txt
|
|
12
|
+
tests/test_g2p.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oro_g2p
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "oro-g2p"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Oro Contributors" }
|
|
10
|
+
]
|
|
11
|
+
description = "Research-grade Yoruba Grapheme-to-Phoneme (G2P) converter and Kokoro TTS frontend"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
keywords = [
|
|
16
|
+
"yoruba",
|
|
17
|
+
"g2p",
|
|
18
|
+
"tts",
|
|
19
|
+
"kokoro",
|
|
20
|
+
"phonemes",
|
|
21
|
+
"african-nlp",
|
|
22
|
+
"speech-synthesis",
|
|
23
|
+
"phonology"
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.9",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
36
|
+
"Topic :: Text Processing :: Linguistic",
|
|
37
|
+
]
|
|
38
|
+
dependencies = []
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/Akintunde102/oro-g2p"
|
|
42
|
+
Repository = "https://github.com/Akintunde102/oro-g2p"
|
|
43
|
+
Issues = "https://github.com/Akintunde102/oro-g2p/issues"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["."]
|
|
47
|
+
include = ["oro_g2p*"]
|
oro_g2p-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automated test suite for oro_g2p covering all 6 Yoruba phonological quirks
|
|
3
|
+
and Kokoro token alignment. (Zero-dependency runner)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from oro_g2p import g2p, tokenize_graphemes, normalize_yoruba, yoruba_to_kokoro
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_basic_sentence():
|
|
10
|
+
"""Verify standard greetings and canonical vowels/tones."""
|
|
11
|
+
text = "Ẹ kú àárọ̀"
|
|
12
|
+
phonemes = g2p(text)
|
|
13
|
+
assert "ɛˉ" in phonemes # 'ẹ' with Mid tone
|
|
14
|
+
assert "kuˊ" in phonemes # 'kú' with High tone
|
|
15
|
+
assert "aˋaˊ" in phonemes # 'àá' with Low then High
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_digraph_gb_and_kp():
|
|
19
|
+
"""Verify 'gb' digraph stays intact and 'p' is pronounced /kp/."""
|
|
20
|
+
text = "igbá àti páńkẹ́rẹ́"
|
|
21
|
+
phonemes = g2p(text)
|
|
22
|
+
assert "ɡb" in phonemes
|
|
23
|
+
assert "kp" in phonemes
|
|
24
|
+
assert " p" not in phonemes # English 'p' should not exist in native Yoruba
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_post_nasal_harmony():
|
|
28
|
+
"""
|
|
29
|
+
Quirk 1: Vowels immediately following consonant 'm' or 'n'
|
|
30
|
+
must automatically nasalize (ọmọ -> ɔ̃, inú -> ũ, mi -> ĩ).
|
|
31
|
+
"""
|
|
32
|
+
text = "ọmọ inú mi"
|
|
33
|
+
phonemes = g2p(text)
|
|
34
|
+
assert "mɔ̃" in phonemes # 'ọ' after 'm' becomes nasal 'ɔ̃'
|
|
35
|
+
assert "nũ" in phonemes # 'ú' after 'n' becomes nasal 'ũ'
|
|
36
|
+
assert "mĩ" in phonemes # 'i' after 'm' becomes nasal 'ĩ'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_chameleon_syllabic_nasal():
|
|
40
|
+
"""
|
|
41
|
+
Quirk 2: Syllabic nasal homorganic assimilation.
|
|
42
|
+
- Before labials (b, f) -> 'm'
|
|
43
|
+
- Before tongue-tip (ṣ, d, t) -> 'n'
|
|
44
|
+
- Before throat/pause/liquids (l, k, g) -> 'ŋ'
|
|
45
|
+
"""
|
|
46
|
+
assert g2p("Mo ń bọ̀").startswith("moˉ mˊ")
|
|
47
|
+
assert "nˊ" in g2p("Mo ń ṣe")
|
|
48
|
+
assert "ŋˊ" in g2p("Mo ń lọ")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_contour_glides():
|
|
52
|
+
"""
|
|
53
|
+
Quirk 3: Rising caron (ˇ) expands to Low-High,
|
|
54
|
+
Falling circumflex (ˆ) expands to High-Low.
|
|
55
|
+
"""
|
|
56
|
+
# ǎ -> àá (Low-High)
|
|
57
|
+
phonemes_rising = g2p("dǎ")
|
|
58
|
+
assert "aˋaˊ" in phonemes_rising
|
|
59
|
+
|
|
60
|
+
# â -> áà (High-Low)
|
|
61
|
+
phonemes_falling = g2p("dâ")
|
|
62
|
+
assert "aˊaˋ" in phonemes_falling
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_keyboard_typo_resilience():
|
|
66
|
+
"""
|
|
67
|
+
Quirk 4: Typo substitutions like Turkish cedilla 'ş'
|
|
68
|
+
must be cleanly normalized to Yoruba 'ṣ' (/ʃ/).
|
|
69
|
+
"""
|
|
70
|
+
typo_text = "Şé o wà?"
|
|
71
|
+
clean_text = "Ṣé o wà?"
|
|
72
|
+
assert g2p(typo_text) == g2p(clean_text)
|
|
73
|
+
assert "ʃ" in g2p(typo_text)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_kokoro_zero_missing_tokens():
|
|
77
|
+
"""
|
|
78
|
+
Verify complete 100% token coverage in Kokoro's embedding table
|
|
79
|
+
with zero missing tokens.
|
|
80
|
+
"""
|
|
81
|
+
test_corpus = [
|
|
82
|
+
"Ẹ kú àárọ̀, gbogbo ọmọ Yorùbá!",
|
|
83
|
+
"Tòmátì àti Ṣòmbọ̀ dídùn.",
|
|
84
|
+
"Mo ń bọ̀ ní ìrọ̀lẹ́ òní.",
|
|
85
|
+
"Máalù jẹ koríko lẹ́gbẹ̀ẹ́ igi.",
|
|
86
|
+
"Báwo ni nǹkan? Şé àlàáfíà ni?",
|
|
87
|
+
]
|
|
88
|
+
for sentence in test_corpus:
|
|
89
|
+
ipa_str, token_ids, missing = yoruba_to_kokoro(sentence)
|
|
90
|
+
assert len(missing) == 0, f"Failed on '{sentence}'! Missing: {missing}"
|
|
91
|
+
assert len(token_ids) > 0
|