File size: 31,481 Bytes
256a3b6 85bc603 256a3b6 e4ae2bb 256a3b6 85bc603 256a3b6 85bc603 256a3b6 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 e4ae2bb 85bc603 e4ae2bb 85bc603 256a3b6 e4ae2bb 256a3b6 85bc603 256a3b6 85bc603 256a3b6 e4ae2bb 256a3b6 e4ae2bb 85bc603 256a3b6 e4ae2bb 85bc603 256a3b6 e4ae2bb 256a3b6 85bc603 256a3b6 85bc603 e4ae2bb 85bc603 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 e4ae2bb 85bc603 256a3b6 85bc603 e4ae2bb 85bc603 256a3b6 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 e4ae2bb 85bc603 256a3b6 85bc603 256a3b6 85bc603 256a3b6 85bc603 e4ae2bb 85bc603 e4ae2bb 85bc603 e4ae2bb 85bc603 e4ae2bb 85bc603 e4ae2bb 85bc603 e4ae2bb 256a3b6 85bc603 e4ae2bb 85bc603 | 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 334 335 336 337 338 339 340 341 342 343 344 345 346 | ---
license: cc-by-sa-4.0
language:
- en
- pcm
- yo
- ig
- ha
base_model:
- FunAudioLLM/Fun-CosyVoice3-0.5B-2512
- FunAudioLLM/CosyVoice2-0.5B
pipeline_tag: text-to-speech
library_name: pytorch
tags:
- text-to-speech
- code-switching
- nigerian-languages
- igbo
- yoruba
- hausa
- nigerian-pidgin
- cosyvoice2
- cosyvoice3
- dpo
datasets:
- google/WaxalNLP
- McGill-NLP/NaijaS2ST
- AfriSpeech/open-bible-speech-african
- nkowaokwu/ibo-dict
- google/fleurs
---
<p align="center"><img src="assets/dynamic_accent_banner.jpg" width="600"/></p>
# Dynamic Accent: Code-switching TTS
**Code-switching TTS for Nigerian languages.** English, Nigerian Pidgin, Igbo, Yoruba and Hausa, mixed freely inside one sentence and spoken by one consistent voice, with no model swapping between languages.
**New: Dynamic Accent Flash** (`--version flash`, now the default) is the same recipe ported to [FunAudioLLM/Fun-CosyVoice3-0.5B-2512](https://huggingface.co/FunAudioLLM/Fun-CosyVoice3-0.5B-2512), then preference-tuned with DPO for naturalness, intelligibility, pace and pauses, with a text front-end that restores Yoruba/Igbo diacritics. It is trained on licensed data only. See **Flash results** below.
Dynamic Accent (Phase 2/3) is a fine-tune of [FunAudioLLM/CosyVoice2-0.5B](https://huggingface.co/FunAudioLLM/CosyVoice2-0.5B) by Modular. It adds:
- per-token **language embeddings**, a **speaker token** and an adversarial (gradient-reversal) speaker classifier on the LM, so the voice stays the same across a language switch (after Zhang et al., Google, 2019, *Learning to Speak Fluently in a Foreign Language*);
- training on about 178 h of Nigerian-language and code-switched speech, including about 102 h of Igbo;
- learned `[pause]`/`[breath]` events and a speaking-rate instruction (Phase 3);
- a **flow-matching decoder fine-tuned on the anchor voice** for tone and pitch detail.
It is a research release, not a production system. Read **Limitations** before you use it.
## Versions and voices
Three model versions ship side by side. Pick one with `--version`, or render several with `--compare` and A/B before use.
| version | LM | flow | decoding | what changed |
|---|---|---|---|---|
| `flash` (**default**) | `checkpoints/flash/flash_dpo` on **Fun-CosyVoice3-0.5B-2512**: Flash SFT (CV3 LM, the Phase 3 data re-weighted towards natural speech, plus IgboSynCorp pseudo-transcripts) + DPO (1,206 preference pairs; reward = naturalness + CER + articulation rate + pause rate) | stock CV3 flow + vocoder, zero-shot from the voice clip | text front-end (numbers verbalised, missing Yoruba/Igbo diacritics restored by `checkpoints/flash/diacritics_byt5`), one pass per paragraph, silence-aware repetition check, no audio post-processing | pace about native, fewest clipped endings for every voice, lower anchor CER (see **Flash results**) |
| `phase3` | `checkpoints/phase3/p3_lm_full` (Phase 2 LM + 5,000 steps on a 178 h re-cut with ~102 h Igbo, `[pause]`/`[breath]` tags and a speed instruction, rate `normal`) | shared `checkpoints/phase2/p2_flow_sft` | one pass per paragraph, silence-aware repetition check, no audio post-processing | lower CER in Igbo/Hausa/Yoruba/code-switch/paragraphs, comma pauses at the anchor's length, clipped endings 63% → 11% |
| `phase2` | `checkpoints/phase2/p2_lm_B` (~120 h, pause profile B) | `checkpoints/phase2/p2_flow_sft` | sentence by sentence, internal silences capped at 0.6 s | the Phase 2 final system |
| voice (`--voice`) | who | notes |
|---|---|---|
| `anchor_male` (default) | WAXAL Yoruba speaker 4, male | the voice both versions were trained around |
| `waxal_ibo_5_female` | WAXAL Igbo speaker 5, female, native Igbo | zero-shot (prompt clip, no fine-tune); best CER of the three on the female test subset |
| `waxal_yor_5_female` | WAXAL Yoruba speaker 5, female, native Yoruba | zero-shot; strongest identity match, but its 3.8 s prompt has no pauses, so pauses are short and endings often clipped |
**Recommended:** `flash` with any of the three voices; it scores best overall (see **Flash results**), with one known weakness: with `anchor_male` it under-pauses in Igbo and Yoruba. Earlier listening verdict (before Flash): Phase 3 sounds best with the female voices, Phase 2 is fine for the male anchor. `infer.py` prints a note when you pick a pairing outside these. The female voices run with `phase2` but are untested there. Settings live in `settings.json`.
## Setup (single GPU)
```bash
# 1. CosyVoice code (the commit the model was built and tested on)
git clone --recursive https://github.com/FunAudioLLM/CosyVoice.git
cd CosyVoice && git checkout 074ca6dc9e80a2f424f1f74b48bdd7d3fea531cc && git submodule update --init --recursive && cd ..
# 2. This repo
huggingface-cli download Modularcomputing/dynamic-accent-preview --local-dir dynamic-accent-preview
# 3. Python deps (Python 3.10; on Colab keep the preinstalled torch/torchaudio)
pip install -r dynamic-accent-preview/requirements.txt
```
The stock base models download on first run and need no changes: `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` to `pretrained/Fun-CosyVoice3-0.5B-2512` for `flash` (`--cv3_dir` / `CV3_DIR` to reuse a copy), and `FunAudioLLM/CosyVoice2-0.5B` to `pretrained/CosyVoice2-0.5B` for `phase2`/`phase3`. The same CosyVoice commit runs both (it has `CosyVoice3`). **`flash` needs a CUDA GPU** (tested on an H100, bf16). Point `--model_dir` (or `COSYVOICE2_DIR`) at an existing copy, and `--cosyvoice_dir` (or `COSYVOICE_REPO`) at your CosyVoice clone. A 16 GB GPU is enough for the CLI; the Gradio app keeps both versions loaded. Tested on an H100 (bf16). `infer.py` also runs on CPU in fp32, which is slow. T4-class GPUs fall back to fp16, which has not been tested.
## Usage
```bash
cd dynamic-accent-preview
# default: --version flash --voice anchor_male
python infer.py --cosyvoice_dir ../CosyVoice --text "<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>" --out out.wav
# Flash with the female Igbo voice
python infer.py --cosyvoice_dir ../CosyVoice --version flash --voice waxal_ibo_5_female \
--text "<ibo>Kedu ka ị mere?</ibo> <pcm>Abeg no vex, I go call you for evening.</pcm>" --out ibo_f.wav
# plain Yoruba without tone marks: Flash's front-end restores them before synthesis
python infer.py --cosyvoice_dir ../CosyVoice --version flash --voice waxal_yor_5_female --frontend native --frontend_lang yor \
--text "E kaaaro, se daadaa ni gbogbo yin wa?" --out yor.wav
# A/B: the same text, seed and voice through several versions -> cmp_phase2.wav, cmp_phase3.wav, cmp_flash.wav
python infer.py --cosyvoice_dir ../CosyVoice --compare --compare_versions phase2,phase3,flash --voice anchor_male --text "..." --out cmp.wav
python infer.py --list_voices # voices, versions, recommended pairing
```
| flag | what it does |
|---|---|
| `--text` | the text: tagged (below) or plain with `--auto_tag` |
| `--version flash\|phase3\|phase2` | model version (default `flash`) |
| `--voice anchor_male\|waxal_ibo_5_female\|waxal_yor_5_female` | voice (default `anchor_male`); old ids `anchor`, `waxal_ibo_5`, `waxal_yor_5` still work |
| `--list_voices` | list voices, versions and the recommended pairing |
| `--compare`, `--compare_versions phase2,phase3,flash` | render with several versions → `<out>_<version>.wav` |
| `--frontend codeswitch\|native\|accent` | `flash` text front-end: `codeswitch` (default; tagged text, or untagged text in `--frontend_lang`), `native` (whole text in `--frontend_lang`, diacritics restored), `accent` (words as written, read with the `--frontend_lang` accent) |
| `--frontend_lang eng\|pcm\|ibo\|yor\|hau` | language for untagged text / `native` / `accent` |
| `--no_diacritics` | `flash`: do not restore missing Yoruba/Igbo tone marks and underdots |
| `--frontend translate --experimental_translate` | **experimental, off by default**: translate English/Pidgin into `--frontend_lang` (ibo/yor/hau) with MADLAD-400-3B-MT (Apache-2.0, ~12 GB download) before synthesis. MADLAD often loops (repeated words or dots) on longer inputs and translates Pidgin poorly, so check the printed text |
| `--cv3_dir` | `flash`: path of the Fun-CosyVoice3-0.5B-2512 snapshot |
| `--rate default\|none\|very slow\|slow\|normal\|fast\|very fast` | Phase 3 / Flash speaking-rate instruction to the LM (default: the version's setting, `normal`). It is a learned instruction, not time-stretching. `phase2` ignores it |
| `--auto_tag` | tag untagged mixed text automatically (see below), print the tagged text, then synthesise |
| `--tag_only` | with `--auto_tag`: print the tagged text and stop, so you can edit it and pass it back as `--text` |
| `--force_lang eng\|pcm\|ibo\|yor\|hau` | read the whole text in one language (tags are ignored) |
| `--lang_map "omo=pcm,sha=pcm"` | per-word language overrides for the tagger |
| `--lid none\|glotlid` | with `--auto_tag`: add [GlotLID v3](https://huggingface.co/cis-lmu/glotlid) clause scores (Apache-2.0 plus notices, 1.7 GB, `pip install fasttext`) |
| `--seed N` | sampling seed (default 0). Try 2–3 seeds and keep the take you like |
| `--out file.wav` | output path (24 kHz WAV) |
| `--dtype auto\|bf16\|fp16\|fp32` | LM autocast dtype on GPU (samples: bf16). CPU always runs fp32 |
| `--stitch` | `phase3`: sentence by sentence instead of one pass (not recommended) |
| `--prompt_clip 0-3` | which anchor prompt clip (released systems: 0) |
| `--model_dir`, `--cosyvoice_dir`, `--lm`, `--flow`, `--prompts`, `--voices` | paths and checkpoint overrides |
| `--pause_mode clause`, `--speed` | `phase2` only: the experimental "prosody v2" (fixed gaps plus WSOLA speed-up). A listening test rejected it; the defaults (`model`, `1.0`) are the real Phase 2 system |
Generation on GPU is not bit-reproducible: bf16 sampling can pick a different token on near-ties, so the same seed can give a slightly different take.
### Controlling code-switching
**1. Explicit tags (most control).** Wrap each span in `<eng>`, `<pcm>`, `<ibo>`, `<yor>` or `<hau>`. Keep punctuation inside the span it belongs to and put a space between spans. Untagged text is read as English.
```text
<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>
<pcm>omo,</pcm> <eng>I'm very excited to announce this new model,</eng> <ibo>ọ na-eme m obi ụtọ,</ibo> <pcm>I no go lie give you</pcm>
<yor>Ẹ káàárọ̀ o, ṣé dáadáa ni?</yor> <eng>I will send you the report this afternoon.</eng>
<hau>Ina kwana, yaya aiki?</hau> <eng>The meeting has been moved to Friday morning.</eng>
<ibo>Ndewo nụ, ụmụnna m.</ibo> <pcm>Una well done o, I know say the road no easy.</pcm> <eng>We have three things to discuss this evening.</eng>
<eng>Please tell everyone that</eng> <yor>a ó pàdé ní ọ̀la.</yor>
```
A switch can fall mid-sentence (`<eng>Please tell everyone that</eng> <yor>a ó pàdé ní ọ̀la.</yor>`). The tag sets the pronunciation and accent of the words inside it; the voice stays the same.
**2. `--auto_tag` for untagged text.** `tagger.py` does word- and span-level language ID: Yoruba/Igbo/Hausa letters (ẹ ṣ / ị ụ ṅ / ɓ ɗ ƙ ƴ), small lexicons of frequent words, Pidgin markers ("dey", "abi", "omo", "wetin", "abeg", "una", "no go", "e don", "know say", pronoun + "go" + verb …) and clause context for ambiguous words ("na", "o", "ka", "ya"). It prints the tagged text so you can check and edit it:
```bash
python infer.py --auto_tag --tag_only --text "omo, I'm very excited to announce this new model, ọ na-eme m obi ụtọ, I no go lie give you"
# [tagged] <pcm>omo,</pcm> <eng>I'm very excited to announce this new model,</eng> <ibo>ọ na-eme m obi ụtọ,</ibo> <pcm>I no go lie give you</pcm>
python infer.py --auto_tag --text "..." --out out.wav # tag, print, synthesise
python tagger.py "Kedu ka ị mere? Abeg no vex, I go call you for evening." # tagger on its own
```
Write Yoruba and Igbo with their diacritics where you can; text without diacritics is much harder to identify. See the tagger accuracy below.
**3. `--force_lang`** reads everything in one language, e.g. `--force_lang ibo`.
**4. `--lang_map`** fixes words the tagger gets wrong, e.g. `--lang_map "omo=pcm,sha=pcm,oga=pcm"`.
### Gradio app (local)
```bash
python app.py --cosyvoice_dir ../CosyVoice # http://127.0.0.1:7860 (--share for a public link)
```
It has a text box, an auto-tag toggle with an editable tagged-text preview, word overrides, a force-language option, version, voice and rate dropdowns, a seed, and a **Compare** button that plays Phase 3 and Flash side by side. The app launch has not been smoke-tested yet.
### From Python
```python
import types, soundfile as sf, infer
a = types.SimpleNamespace(model_dir="pretrained/CosyVoice2-0.5B", cosyvoice_dir="../CosyVoice", lm="", flow="",
prompts="prompts/prompts.json", voices="prompts/voices.json", dtype="auto", stitch=False,
rate="default", voice="waxal_ibo_5_female")
tts = infer.build("phase3", a) # for "flash" also set frontend="codeswitch", frontend_lang="eng", no_diacritics=False,
# experimental_translate=False, cv3_dir="pretrained/Fun-CosyVoice3-0.5B-2512"
wav = tts.say("<ibo>Kedu ka ị mere?</ibo> <eng>The meeting has been moved to Friday morning.</eng>", seed=0)
sf.write("out.wav", wav, tts.sr)
```
## Samples
### Flash (`--version flash`, Flash-DPO)
Seed 0, model pauses, speed 1.0, no post-processing. Inputs are written with diacritics unless noted.
| sample | voice | text |
|---|---|---|
| [igbo_pure_female](samples/flash/igbo_pure_female.mp3) | waxal_ibo_5_female | Chei, obi dị m ụtọ nke ukwuu ịkpọsa ụdị ọhụrụ a, ọ na-eme m obi ụtọ, agaghị m agha gị ụgha. |
| [igbo_para_female](samples/flash/igbo_para_female.mp3) | waxal_ibo_5_female | Ndewo nu, ụmụnna m. Taa bụ ụbọchị pụrụ iche n'obodo anyị. Anyị ga-ezukọ n'ama egwuregwu, rie nri, ma kparịta ụka maka ọdịnihu ụmụaka anyị. |
| [yoruba_female](samples/flash/yoruba_female.mp3) / [yoruba_male](samples/flash/yoruba_male.mp3) | waxal_yor_5_female / anchor_male | Ẹ káàsán o, ẹ̀yin ará. Inú mi dùn láti rí gbogbo yín lónìí. Ẹ jẹ́ ká jọ ṣiṣẹ́ pọ̀ fún ìlọsíwájú ìlú wa. |
| [hausa_male](samples/flash/hausa_male.mp3) | anchor_male | Barka da yamma, 'yan uwa. Muna farin cikin ganin ku a yau. Za mu fara taron nan ba da daɗewa ba, don haka ku zauna lafiya. |
| [pidgin_female](samples/flash/pidgin_female.mp3) | waxal_ibo_5_female | Omo, this traffic no be here o. Abeg make una no vex, I go reach there small time. Wetin dey happen for your side? |
| [codeswitch_ibo_pcm_eng](samples/flash/codeswitch_ibo_pcm_eng.mp3) | waxal_ibo_5_female | `<ibo>Nne m, ọ dị mma, ka anyị gaa ahịa taa.</ibo> <pcm>Abeg make we no late o,</pcm> <eng>the shop closes at 6pm.</eng>` |
| [codeswitch_yor_eng](samples/flash/codeswitch_yor_eng.mp3) | waxal_yor_5_female | `<yor>Ẹ jọ̀ọ́, ẹ dúró díẹ̀,</yor> <eng>I will send you the report this afternoon.</eng> <yor>Ẹ ṣé púpọ̀.</yor>` |
| [codeswitch_dynacc](samples/flash/codeswitch_dynacc.mp3) | waxal_ibo_5_female | `<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>` |
| [numbers_igbo](samples/flash/numbers_igbo.mp3) | waxal_ibo_5_female | Ọnụ ahịa ya bụ ₦2,500, anyị ga-ahụ gị na 21/10/2026 n'elekere 4:30. (numbers verbalised by the front-end) |
| [diacritics_yoruba_from_plain](samples/flash/diacritics_yoruba_from_plain.mp3) | waxal_yor_5_female | input without marks: "E kaaaro, se daadaa ni gbogbo yin wa? Oju ojo dara pupo loni." → front-end: Ẹ káàárọ̀, ṣé dáadáa ni gbogbo yín wà? Ojú ọjọ́ dára púpọ̀ lónì. |
### Phase 2 and Phase 3
Seed 0, rendered with `infer.py`. Inputs: `para_ibo_pcm_eng`, `para_yor_eng`, `para_hau_eng_pcm` (code-switched paragraphs), `ibo_000` (Igbo), `custom_dynacc` ("this is dynamic accent, e dey sound nice abi?") and `omo_announce` ("omo, I'm very excited to announce this new model, ọ na-eme m obi ụtọ, I no go lie give you").
| input | Phase 2, anchor_male | Phase 3, anchor_male | Phase 3, female |
|---|---|---|---|
| para_ibo_pcm_eng | [mp3](samples/phase3/phase2/para_ibo_pcm_eng.mp3) | [mp3](samples/phase3/phase3/para_ibo_pcm_eng.mp3) | [waxal_ibo_5_female](samples/phase3/phase3_female/para_ibo_pcm_eng__waxal_ibo_5.mp3) |
| para_yor_eng | [mp3](samples/phase3/phase2/para_yor_eng.mp3) | [mp3](samples/phase3/phase3/para_yor_eng.mp3) | [waxal_yor_5_female](samples/phase3/phase3_female/para_yor_eng__waxal_yor_5.mp3) |
| para_hau_eng_pcm | [mp3](samples/phase3/phase2/para_hau_eng_pcm.mp3) | [mp3](samples/phase3/phase3/para_hau_eng_pcm.mp3) | – |
| ibo_000 | [mp3](samples/phase3/phase2/ibo_000.mp3) | [mp3](samples/phase3/phase3/ibo_000.mp3) | [waxal_ibo_5_female](samples/phase3/phase3_female/ibo_000__waxal_ibo_5.mp3) |
| custom_dynacc | [mp3](samples/phase3/phase2/custom_dynacc.mp3) | [mp3](samples/phase3/phase3/custom_dynacc.mp3) | [waxal_yor_5_female](samples/phase3/phase3_female/custom_dynacc__waxal_yor_5.mp3) |
| omo_announce | [mp3](samples/phase3/phase2/omo_announce.mp3) | [mp3](samples/phase3/phase3/omo_announce.mp3) | [waxal_ibo_5_female](samples/phase3/phase3_female/omo_announce__waxal_ibo_5.mp3), [waxal_yor_5_female](samples/phase3/phase3_female/omo_announce__waxal_yor_5.mp3) |
Older renders: `samples/pcm_eng_dynamic_accent_seed{0,1,2}.mp3` (Phase 2, three seeds) and `samples/prosody_v2/` (the rejected prosody-v2 post-processing, kept for reference).
## Flash results
Same eval set and judges as the Phase 2 vs Phase 3 tables below (183 items plus code-switched and paragraph groups), seed 0, all groups pooled. **Pace** and **pauses** are ratios to native speakers reading the same texts (1.00 = native). **Clipped endings** = share of renders whose trailing silence is under 0.1 s (native recordings: 23.8%). SIM is ECAPA cosine to the voice's own real clips.
| voice | system | CER | CER no tones | SIM | pace × native | pauses/s × native | clipped endings |
|---|---|---|---|---|---|---|---|
| anchor_male | Phase 3 | 0.174 | 0.146 | 0.815 | 0.92 | 1.11 | 9.1% |
| anchor_male | **Flash** | **0.162** | **0.135** | 0.784 | **0.99** | 0.75 | **7.1%** |
| waxal_ibo_5_female | Phase 3 | 0.164 | 0.132 | 0.683 | 1.00 | 0.74 | 22.8% |
| waxal_ibo_5_female | **Flash** | 0.165 | 0.134 | **0.700** | 1.03 | **0.95** | **10.7%** |
- Flash lowers the anchor's CER (0.174 → 0.162) and clipped endings (9.1% → 7.1%), brings its pace to about native (0.92× → 0.99×), and more than halves clipped endings on the female Igbo voice (22.8% → 10.7%) at about native pace and pause rate.
- Per language with the anchor, Flash CER: Igbo 0.189 (Phase 3: 0.210), Yoruba 0.230 (0.243), Hausa 0.123 (0.146), English 0.026 (0.039), paragraphs 0.131 (0.168), code-switch 0.208 (0.212); Pidgin 0.227 (0.209) is worse, and the Pidgin judge is weak anyway.
- **Known weakness:** with `anchor_male`, Flash **under-pauses in Igbo (0.40× native pauses per second) and Yoruba (0.30×)**. Long Igbo or Yoruba passages with the male voice can sound rushed; use a female voice or `--compare` with `phase3` there.
- Speaker similarity of the anchor drops slightly (0.815 → 0.784), because Flash uses the stock CV3 flow zero-shot instead of the anchor-SFT flow.
- **UTMOS caveat:** UTMOS22 rates real native speech (3.53) below every synthetic system (3.8–4.3), so it rewards clean studio-like synthesis rather than nativeness. It was not used to choose the model (Phase 3 has the highest UTMOS22: 4.15 anchor / 4.27 female; Flash 3.89 / 4.16; Flash UTMOSv2 3.17 / 3.51). The choice rests on CER, pace and pauses relative to native speech, and clipped endings. Native-speaker MOS / blind A/B is still pending.
- Text front-end: diacritic restoration word accuracy Yoruba 0.862 (stripped input: 0.171), Igbo 0.968 (0.640).
## Evaluation: Phase 2 vs Phase 3
183-item eval set (30 per language, 10 code-switched, 3 hand-written + 20 native long-form paragraphs), anchor voice, seed 0. CER is ASR round-trip: MMS-1b-all for Igbo/Hausa/Yoruba, Whisper-large-v3-turbo for English and Pidgin, the lower of the two for code-switched items and paragraphs. "No tones" (in brackets) strips diacritics. SIM is ECAPA cosine to the anchor (ceiling about 0.84 on real held-out anchor clips). The Pidgin judge is weak: real Pidgin recordings score about 0.43.
| system | ibo | hau | yor | pcm | eng | CS | para (hand / native) | SIM |
|---|---|---|---|---|---|---|---|---|
| `phase2` as shipped (sentence stitching) | 0.215 (0.171) | 0.194 | 0.234 (0.149) | 0.196 | 0.035 | 0.163 | 0.192 / 0.154 | 0.785 |
| Phase 2 LM, Phase 3 decoding | 0.243 (0.189) | 0.169 | 0.275 (0.175) | 0.195 | 0.046 | 0.185 | 0.195 / 0.158 | 0.787 |
| **`phase3`** | **0.219 (0.173)** | **0.151** | **0.233 (0.145)** | 0.202 | 0.043 | **0.151** | **0.170** / 0.155 | **0.788** |
Same decoding, Phase 3 LM vs Phase 2 LM: lower CER in every language except Pidgin, same speaker similarity.
| system | comma pause s | sentence pause s | pauses / s | pause-length W1 vs anchor | trailing silence s | clipped endings (< 0.1 s) | articulation syl/s |
|---|---|---|---|---|---|---|---|
| real anchor recordings | 0.417 | 0.543 | 0.624 | 0 | 0.16 | 15% | 4.43 |
| Phase 2 LM, Phase 3 decoding | 0.403 | 0.389 | 0.329 | 0.194 | 0.08 | 63% | 4.36 |
| **`phase3`** | **0.422** | **0.453** | 0.319 | **0.161** | **0.17** | **11%** | 4.44 |
Female voices (29-item subset, `phase3`):
| voice | CER | no-tone CER | SIM to the speaker's own real clips | clipped endings |
|---|---|---|---|---|
| `anchor_male` (same items) | 0.182 | 0.141 | 0.12–0.13 (it is a different speaker) | 17% |
| `waxal_ibo_5_female` | **0.149** | **0.101** | 0.64 | 21% |
| `waxal_yor_5_female` | 0.171 | 0.134 | **0.76** | 62% |
Naturalness has not been rated by native speakers (no MOS or blind A/B yet). The numbers above are automatic proxies.
### Auto-tagger accuracy
Word-level accuracy of `tagger.py` against hand-tagged text (no GlotLID / with `--lid glotlid`):
| test set | words | rule-based | + GlotLID | exact sentences (rule-based) |
|---|---|---|---|---|
| the 6 sample inputs above | 145 | 1.000 | 1.000 | 6/6 |
| 10 new mixed sentences, written before the lexicons | 113 | 0.991 | 0.991 | 9/10 |
| 12 code-switched eval items (overlaps the samples) | 206 | 0.995 | 1.000 | 11/12 |
| 150 single-language eval sentences (real WAXAL/FLEURS text, never looked at while building the lexicons) | 1,697 | **0.898** | **0.915** | 115/150 |
On the held-out single-language set, per language (rule-based / + GlotLID): English 0.99 / 1.00, Igbo 1.00 / 1.00, Yoruba 1.00 / 1.00, Hausa 0.89 / 0.94, **Pidgin 0.63 / 0.65**. Pidgin written without markers looks like English and gets tagged `eng`. The mixed test sentences were written and gold-tagged by the tagger's author, not native speakers, so their scores are optimistic. Check the printed tags, and use explicit tags or `--lang_map` when it matters.
## Limitations
- **Flash** (`flash`): the male anchor under-pauses in Igbo and Yoruba (0.40× / 0.30× native pause rate); Pidgin CER is slightly worse than Phase 3; CUDA only; the translate-first mode is experimental and off by default (MADLAD-400 loops on long inputs and is weak on Pidgin); diacritic restoration can still pick the wrong tone on ambiguous words, so write marks yourself where you can. The limitations below for Phase 3 (no laughter, Pidgin data, accent after a switch) apply to Flash too; the pace gap is largely closed for Flash.
- **Pace (`phase2`/`phase3`)**: speech is still 13–28% slower than native speakers (Igbo 4.60 vs 5.30 syl/s, Hausa 3.77 vs 5.26 syl/s), and there are about half as many pauses per second as in real speech. It can sound careful rather than fully native.
- **Pidgin is the weakest language**: only about 5 h of training data, no reliable ASR judge, and the auto-tagger's Pidgin/English split is its weakest point (~0.63 word accuracy on plain Pidgin).
- **No laughter or other expressive events**: the training data has effectively no laughter, so tags like `[laughter]` do nothing useful. `[pause]`/`[breath]` were learned from data, but there is no reliable user control over them yet.
- English after a Nigerian-language span keeps a strong local accent.
- Yoruba tone quality needs native listening; the automatic tone metric is saturated.
- Female voices are zero-shot; `waxal_yor_5_female` copies its short prompt (short pauses, clipped endings).
- Not evaluated for numbers, abbreviations or URLs; write numbers as words.
- Do not use it to impersonate real people. The voices are WAXAL Media Trust speakers, released under CC-BY-SA-4.0 for speech technology.
## What is in the repo
| path | what it is | licence |
|---|---|---|
| `checkpoints/flash/flash_dpo/` | Flash LM (`best.pt`, sha256 `f59a6fc0…7e3a307`, CosyVoice3 LM + language/speaker/rate embeddings), `config.json`, `spk2id.json`, DPO `args.json`, `log.jsonl` | CC-BY-SA-4.0 |
| `checkpoints/flash/diacritics_byt5/` | Yoruba/Igbo diacritic-restoration model (fine-tune of [google/byt5-small](https://huggingface.co/google/byt5-small), Apache-2.0) used by the Flash front-end | CC-BY-SA-4.0 |
| `checkpoints/phase3/p3_lm_full/` | Phase 3 LM (`best.pt`, val step 4,500), `config.json` (rate, sampler, one-pass, points at the shared Phase 2 flow), `spk2id.json`, `args.json`, `log.jsonl` | CC-BY-SA-4.0 |
| `checkpoints/phase2/p2_lm_B/` | Phase 2 LM (`best.pt`) and `spk2id.json` | CC-BY-SA-4.0 |
| `checkpoints/phase2/p2_flow_sft/best.pt` | anchor-SFT flow-matching decoder, used by both versions | CC-BY-SA-4.0 |
| `prompts/` | anchor prompt clips (`clip0..3`, `prompts.json` with transcripts and S3 tokens) and female voice prompts (`voice_*.wav`, `voices.json`), all from WAXAL | CC-BY-SA-4.0 |
| `samples/` | example renders (`samples/flash/` for Flash) | CC-BY-SA-4.0 |
| `settings.json` | versions, voices, recommended pairing | Apache-2.0 |
| `infer.py`, `tagger.py`, `app.py` | inference CLI, auto-tagger, Gradio app | Apache-2.0 |
| `flash_lm.py`, `frontend/` | Flash model and synthesis code (training code for reference; the data manifest is not shipped), Flash text front-end (normalisation, diacritics, experimental MT) | Apache-2.0 |
| `prompts/flash_voices_text.json` | transcripts of the voice clips, used as Flash LM prompts | CC-BY-SA-4.0 |
| `code/` | the original training and synthesis code, for reference | Apache-2.0 |
| `requirements.txt`, `cosyvoice_requirements.txt` | dependencies (`cosyvoice_requirements.txt` is CosyVoice's own file at the commit used) | – |
| `assets/` | banner | – |
## Training data and attribution
The model weights are trained on, and the voice prompts are taken from, the datasets below. Several are CC-BY-SA-4.0, so the weights, voice prompts and samples are released under **CC-BY-SA-4.0** (share-alike).
| dataset | used for | licence | attribution |
|---|---|---|---|
| [WAXAL](https://huggingface.co/datasets/google/WaxalNLP), Media Trust TTS subsets (Igbo, Yoruba, Hausa, Nigerian Pidgin) | main training data; the **anchor voice** (Yoruba speaker 4) and the **female prompt voices** (Igbo speaker 5, Yoruba speaker 5) | CC-BY-SA-4.0 (as listed on the dataset card for the Media Trust subsets) | Google and Media Trust, WAXAL (arXiv:2602.02734) |
| [Open.Bible Igbo](https://huggingface.co/datasets/AfriSpeech/open-bible-speech-african) (Biblica® Open Igbo Contemporary Bible™, Audio Edition) | Igbo (Phase 3) | CC-BY-SA-4.0 | Biblica, Inc. via open.bible; Biblica® and Open Igbo Contemporary Bible™ are trademarks of Biblica, Inc. |
| [NaijaS2ST](https://huggingface.co/datasets/McGill-NLP/NaijaS2ST) (Igbo) | Igbo, many native speakers (Phase 3) | CC-BY-4.0 | McGill-NLP, NaijaS2ST (arXiv:2604.16287) |
| [IgboAPI](https://huggingface.co/datasets/nkowaokwu/ibo-dict) (ibo-dict, ibo-dict-expansion) | Igbo sentences (Phase 3) | CC-BY-4.0 | Nkọwa okwu / IgboAPI contributors |
| [IgboSynCorp](https://doi.org/10.7910/dvn/yb9fwk) | natural Igbo speech (Phase 3); Flash adds pseudo-transcripts of its untranscribed audio | CC0-1.0 | University of Ibadan and Afe Babalola University (Lacuna Fund) |
| [FLEURS](https://huggingface.co/datasets/google/fleurs) (English, Hausa, Igbo, Yoruba) | multi-speaker data | CC-BY-4.0 | Google, FLEURS (Conneau et al., 2022) |
| [BibleTTS](https://masakhane-io.github.io/bibleTTS/) (Yoruba, Hausa) | Yoruba and Hausa | CC-BY-SA-4.0 | Meyer et al., BibleTTS (2022); Biblica / open.bible audio |
| [OpenSLR SLR70](https://openslr.org/70/) (Nigerian English) | English | CC-BY-SA-4.0 | Google, crowdsourced Nigerian English |
| [OpenSLR SLR86](https://openslr.org/86/) (Yoruba) | Yoruba | CC-BY-SA-4.0 | Google, crowdsourced Yoruba |
| code-switched pairs | about 9 h of synthetic code-switch training pairs | follows the source clips (CC-BY-SA-4.0 / CC-BY-4.0) | built by concatenating clips from the sets above, following the Google 2019 code-switching recipe (Zhang et al., 2019); no separate Google dataset |
| base model [FunAudioLLM/Fun-CosyVoice3-0.5B-2512](https://huggingface.co/FunAudioLLM/Fun-CosyVoice3-0.5B-2512) | Flash: LM initialisation, flow, vocoder, tokenizers | Apache-2.0 | FunAudioLLM / Alibaba Tongyi Lab, CosyVoice 3 |
| [google/byt5-small](https://huggingface.co/google/byt5-small) | base of the Flash diacritic-restoration model (fine-tuned on transcripts of the datasets above) | Apache-2.0 | Google, ByT5 (Xue et al., 2022) |
| base model [FunAudioLLM/CosyVoice2-0.5B](https://huggingface.co/FunAudioLLM/CosyVoice2-0.5B) | LM initialisation, flow, vocoder, tokenizers | Apache-2.0 | FunAudioLLM / Alibaba Tongyi Lab, CosyVoice 2 (Du et al., 2024) |
| optional [GlotLID](https://huggingface.co/cis-lmu/glotlid) (not bundled) | `--lid glotlid` in the tagger | Apache-2.0 plus notices | Kargaran et al., GlotLID (EMNLP 2023) |
Flash's training data is the licensed set above (the same sources as Phase 3, re-weighted with less Bible narration and slow read speech, plus IgboSynCorp pseudo-transcripts); its DPO pairs are the model's own renders of eval-style prompts, scored automatically. Excluded on licence grounds from every released checkpoint, Flash included: African Voices / WaZoBiaSpeech (its cards ban TTS use), NaijaVoices (non-commercial), and non-commercial TTS weights. The optional MADLAD-400-3B-MT translator (Apache-2.0) is not bundled; NLLB was avoided because it is non-commercial.
## Licence
- **Model weights, voice prompts and samples** (`checkpoints/`, `prompts/`, `samples/`): **[CC-BY-SA-4.0](LICENSE)**. You may use them commercially. You must credit Modular and the datasets listed above, and anything you derive from the weights (fine-tunes, merges) must be shared under CC-BY-SA-4.0 too.
- **Code** (`infer.py`, `tagger.py`, `app.py`, `flash_lm.py`, `frontend/`, `code/`, `settings.json`): **[Apache-2.0](LICENSE-CODE)**.
- The base models, CosyVoice2-0.5B and Fun-CosyVoice3-0.5B-2512 (and byt5-small for the diacritic model), are Apache-2.0; their notices still apply to the parts they contribute.
## Citation
```bibtex
@misc{dynamicaccent2026,
title = {Dynamic Accent: Code-switching TTS for Nigerian languages},
author = {Modular},
year = {2026},
url = {https://huggingface.co/Modularcomputing/dynamic-accent-preview}
}
```
|