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.

Dynamic Accent is a fine-tune of 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

Two model versions ship side by side. Pick one with --version, or render both with --compare and A/B before use.

version LM flow decoding what changed
phase3 (default) 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 pairing (author's listening verdict): Phase 3 sounds best with the female voices; Phase 2 is fine for the male anchor. infer.py prints a note when you pick another pairing. The female voices were evaluated with phase3 only; they run with phase2 but are untested there. Settings live in settings.json.

Setup (single GPU)

# 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 model FunAudioLLM/CosyVoice2-0.5B (tokenizer, vocoder, speech tokenizer, speaker encoder) downloads to pretrained/CosyVoice2-0.5B on first run; nothing in it needs changing. 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

cd dynamic-accent-preview
# default: --version phase3 --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

# recommended pairing
python infer.py --cosyvoice_dir ../CosyVoice --version phase3 --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

# A/B: the same text, seed and voice through both versions -> cmp_phase2.wav, cmp_phase3.wav
python infer.py --cosyvoice_dir ../CosyVoice --compare --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 phase2|phase3 model version (default phase3)
--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 render with both versions → <out>_phase2.wav, <out>_phase3.wav
--rate default|none|very slow|slow|normal|fast|very fast Phase 3 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 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.

<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:

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)

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 2 and Phase 3 side by side.

From 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)
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

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 mp3 waxal_ibo_5_female
para_yor_eng mp3 mp3 waxal_yor_5_female
para_hau_eng_pcm mp3 mp3 –
ibo_000 mp3 mp3 waxal_ibo_5_female
custom_dynacc mp3 mp3 waxal_yor_5_female
omo_announce mp3 mp3 waxal_ibo_5_female, waxal_yor_5_female

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).

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

  • Pace: 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/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 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
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, 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 (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 (Igbo) Igbo, many native speakers (Phase 3) CC-BY-4.0 McGill-NLP, NaijaS2ST (arXiv:2604.16287)
IgboAPI (ibo-dict, ibo-dict-expansion) Igbo sentences (Phase 3) CC-BY-4.0 Nkọwa okwu / IgboAPI contributors
IgboSynCorp natural Igbo speech (Phase 3) CC0-1.0 University of Ibadan and Afe Babalola University (Lacuna Fund)
FLEURS (English, Hausa, Igbo, Yoruba) multi-speaker data CC-BY-4.0 Google, FLEURS (Conneau et al., 2022)
BibleTTS (Yoruba, Hausa) Yoruba and Hausa CC-BY-SA-4.0 Meyer et al., BibleTTS (2022); Biblica / open.bible audio
OpenSLR SLR70 (Nigerian English) English CC-BY-SA-4.0 Google, crowdsourced Nigerian English
OpenSLR SLR86 (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/CosyVoice2-0.5B LM initialisation, flow, vocoder, tokenizers Apache-2.0 FunAudioLLM / Alibaba Tongyi Lab, CosyVoice 2 (Du et al., 2024)
optional GlotLID (not bundled) --lid glotlid in the tagger Apache-2.0 plus notices Kargaran et al., GlotLID (EMNLP 2023)

Excluded on licence grounds: WaZoBiaSpeech (bans TTS use), NaijaVoices (non-commercial), and non-commercial TTS weights.

Licence

  • Model weights, voice prompts and samples (checkpoints/, prompts/, samples/): CC-BY-SA-4.0. 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, code/, settings.json): Apache-2.0.
  • The base model, CosyVoice2-0.5B, is Apache-2.0; its notices still apply to the parts it contributes.

Citation

@misc{dynamicaccent2026,
  title  = {Dynamic Accent: Code-switching TTS for Nigerian languages},
  author = {Modular},
  year   = {2026},
  url    = {https://huggingface.co/Modularcomputing/dynamic-accent-preview}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Modularcomputing/dynamic-accent-preview

Finetuned
(14)
this model

Datasets used to train Modularcomputing/dynamic-accent-preview

Papers for Modularcomputing/dynamic-accent-preview