UncleanCode commited on
Commit
85bc603
·
verified ·
1 Parent(s): a5016b3

Release prep: Phase 3 LM (checkpoints/phase3/p3_lm_full), versions/voices/settings, auto-tagger, Gradio app, CC-BY-SA-4.0 weights + Apache-2.0 code, model card

Browse files
Files changed (37) hide show
  1. .gitattributes +11 -0
  2. LICENSE +427 -201
  3. LICENSE-CODE +202 -0
  4. README.md +205 -105
  5. app.py +140 -0
  6. assets/dynamic_accent_banner.jpg +3 -0
  7. checkpoints/phase2/p2_lm_B/spk2id.json +1 -0
  8. checkpoints/phase3/p3_lm_full/args.json +1 -0
  9. checkpoints/phase3/p3_lm_full/best.pt +3 -0
  10. checkpoints/phase3/p3_lm_full/config.json +7 -0
  11. checkpoints/phase3/p3_lm_full/log.jsonl +211 -0
  12. checkpoints/phase3/p3_lm_full/spk2id.json +0 -0
  13. infer.py +276 -63
  14. prompts/voice_waxal_ibo_5.wav +3 -0
  15. prompts/voice_waxal_yor_5.wav +3 -0
  16. prompts/voices.json +41 -0
  17. requirements.txt +3 -1
  18. samples/phase3/phase2/custom_dynacc.mp3 +0 -0
  19. samples/phase3/phase2/ibo_000.mp3 +0 -0
  20. samples/phase3/phase2/omo_announce.mp3 +0 -0
  21. samples/phase3/phase2/para_hau_eng_pcm.mp3 +3 -0
  22. samples/phase3/phase2/para_ibo_pcm_eng.mp3 +3 -0
  23. samples/phase3/phase2/para_yor_eng.mp3 +3 -0
  24. samples/phase3/phase3/custom_dynacc.mp3 +0 -0
  25. samples/phase3/phase3/ibo_000.mp3 +0 -0
  26. samples/phase3/phase3/omo_announce.mp3 +0 -0
  27. samples/phase3/phase3/para_hau_eng_pcm.mp3 +3 -0
  28. samples/phase3/phase3/para_ibo_pcm_eng.mp3 +3 -0
  29. samples/phase3/phase3/para_yor_eng.mp3 +3 -0
  30. samples/phase3/phase3_female/custom_dynacc__waxal_yor_5.mp3 +0 -0
  31. samples/phase3/phase3_female/ibo_000__waxal_ibo_5.mp3 +0 -0
  32. samples/phase3/phase3_female/omo_announce__waxal_ibo_5.mp3 +0 -0
  33. samples/phase3/phase3_female/omo_announce__waxal_yor_5.mp3 +0 -0
  34. samples/phase3/phase3_female/para_ibo_pcm_eng__waxal_ibo_5.mp3 +3 -0
  35. samples/phase3/phase3_female/para_yor_eng__waxal_yor_5.mp3 +3 -0
  36. settings.json +47 -0
  37. tagger.py +348 -0
.gitattributes CHANGED
@@ -40,3 +40,14 @@ prompts/clip3_waxal_yor_yor_1218.wav filter=lfs diff=lfs merge=lfs -text
40
  samples/prosody_v2/para_hau_eng_pcm.mp3 filter=lfs diff=lfs merge=lfs -text
41
  samples/prosody_v2/para_ibo_pcm_eng.mp3 filter=lfs diff=lfs merge=lfs -text
42
  samples/prosody_v2/para_yor_eng.mp3 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
40
  samples/prosody_v2/para_hau_eng_pcm.mp3 filter=lfs diff=lfs merge=lfs -text
41
  samples/prosody_v2/para_ibo_pcm_eng.mp3 filter=lfs diff=lfs merge=lfs -text
42
  samples/prosody_v2/para_yor_eng.mp3 filter=lfs diff=lfs merge=lfs -text
43
+ assets/dynamic_accent_banner.jpg filter=lfs diff=lfs merge=lfs -text
44
+ prompts/voice_waxal_ibo_5.wav filter=lfs diff=lfs merge=lfs -text
45
+ prompts/voice_waxal_yor_5.wav filter=lfs diff=lfs merge=lfs -text
46
+ samples/phase3/phase2/para_hau_eng_pcm.mp3 filter=lfs diff=lfs merge=lfs -text
47
+ samples/phase3/phase2/para_ibo_pcm_eng.mp3 filter=lfs diff=lfs merge=lfs -text
48
+ samples/phase3/phase2/para_yor_eng.mp3 filter=lfs diff=lfs merge=lfs -text
49
+ samples/phase3/phase3/para_hau_eng_pcm.mp3 filter=lfs diff=lfs merge=lfs -text
50
+ samples/phase3/phase3/para_ibo_pcm_eng.mp3 filter=lfs diff=lfs merge=lfs -text
51
+ samples/phase3/phase3/para_yor_eng.mp3 filter=lfs diff=lfs merge=lfs -text
52
+ samples/phase3/phase3_female/para_ibo_pcm_eng__waxal_ibo_5.mp3 filter=lfs diff=lfs merge=lfs -text
53
+ samples/phase3/phase3_female/para_yor_eng__waxal_yor_5.mp3 filter=lfs diff=lfs merge=lfs -text
LICENSE CHANGED
@@ -1,202 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
- Apache License
3
- Version 2.0, January 2004
4
- http://www.apache.org/licenses/
5
-
6
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
-
8
- 1. Definitions.
9
-
10
- "License" shall mean the terms and conditions for use, reproduction,
11
- and distribution as defined by Sections 1 through 9 of this document.
12
-
13
- "Licensor" shall mean the copyright owner or entity authorized by
14
- the copyright owner that is granting the License.
15
-
16
- "Legal Entity" shall mean the union of the acting entity and all
17
- other entities that control, are controlled by, or are under common
18
- control with that entity. For the purposes of this definition,
19
- "control" means (i) the power, direct or indirect, to cause the
20
- direction or management of such entity, whether by contract or
21
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
- outstanding shares, or (iii) beneficial ownership of such entity.
23
-
24
- "You" (or "Your") shall mean an individual or Legal Entity
25
- exercising permissions granted by this License.
26
-
27
- "Source" form shall mean the preferred form for making modifications,
28
- including but not limited to software source code, documentation
29
- source, and configuration files.
30
-
31
- "Object" form shall mean any form resulting from mechanical
32
- transformation or translation of a Source form, including but
33
- not limited to compiled object code, generated documentation,
34
- and conversions to other media types.
35
-
36
- "Work" shall mean the work of authorship, whether in Source or
37
- Object form, made available under the License, as indicated by a
38
- copyright notice that is included in or attached to the work
39
- (an example is provided in the Appendix below).
40
-
41
- "Derivative Works" shall mean any work, whether in Source or Object
42
- form, that is based on (or derived from) the Work and for which the
43
- editorial revisions, annotations, elaborations, or other modifications
44
- represent, as a whole, an original work of authorship. For the purposes
45
- of this License, Derivative Works shall not include works that remain
46
- separable from, or merely link (or bind by name) to the interfaces of,
47
- the Work and Derivative Works thereof.
48
-
49
- "Contribution" shall mean any work of authorship, including
50
- the original version of the Work and any modifications or additions
51
- to that Work or Derivative Works thereof, that is intentionally
52
- submitted to Licensor for inclusion in the Work by the copyright owner
53
- or by an individual or Legal Entity authorized to submit on behalf of
54
- the copyright owner. For the purposes of this definition, "submitted"
55
- means any form of electronic, verbal, or written communication sent
56
- to the Licensor or its representatives, including but not limited to
57
- communication on electronic mailing lists, source code control systems,
58
- and issue tracking systems that are managed by, or on behalf of, the
59
- Licensor for the purpose of discussing and improving the Work, but
60
- excluding communication that is conspicuously marked or otherwise
61
- designated in writing by the copyright owner as "Not a Contribution."
62
-
63
- "Contributor" shall mean Licensor and any individual or Legal Entity
64
- on behalf of whom a Contribution has been received by Licensor and
65
- subsequently incorporated within the Work.
66
-
67
- 2. Grant of Copyright License. Subject to the terms and conditions of
68
- this License, each Contributor hereby grants to You a perpetual,
69
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
- copyright license to reproduce, prepare Derivative Works of,
71
- publicly display, publicly perform, sublicense, and distribute the
72
- Work and such Derivative Works in Source or Object form.
73
-
74
- 3. Grant of Patent License. Subject to the terms and conditions of
75
- this License, each Contributor hereby grants to You a perpetual,
76
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
- (except as stated in this section) patent license to make, have made,
78
- use, offer to sell, sell, import, and otherwise transfer the Work,
79
- where such license applies only to those patent claims licensable
80
- by such Contributor that are necessarily infringed by their
81
- Contribution(s) alone or by combination of their Contribution(s)
82
- with the Work to which such Contribution(s) was submitted. If You
83
- institute patent litigation against any entity (including a
84
- cross-claim or counterclaim in a lawsuit) alleging that the Work
85
- or a Contribution incorporated within the Work constitutes direct
86
- or contributory patent infringement, then any patent licenses
87
- granted to You under this License for that Work shall terminate
88
- as of the date such litigation is filed.
89
-
90
- 4. Redistribution. You may reproduce and distribute copies of the
91
- Work or Derivative Works thereof in any medium, with or without
92
- modifications, and in Source or Object form, provided that You
93
- meet the following conditions:
94
-
95
- (a) You must give any other recipients of the Work or
96
- Derivative Works a copy of this License; and
97
-
98
- (b) You must cause any modified files to carry prominent notices
99
- stating that You changed the files; and
100
-
101
- (c) You must retain, in the Source form of any Derivative Works
102
- that You distribute, all copyright, patent, trademark, and
103
- attribution notices from the Source form of the Work,
104
- excluding those notices that do not pertain to any part of
105
- the Derivative Works; and
106
-
107
- (d) If the Work includes a "NOTICE" text file as part of its
108
- distribution, then any Derivative Works that You distribute must
109
- include a readable copy of the attribution notices contained
110
- within such NOTICE file, excluding those notices that do not
111
- pertain to any part of the Derivative Works, in at least one
112
- of the following places: within a NOTICE text file distributed
113
- as part of the Derivative Works; within the Source form or
114
- documentation, if provided along with the Derivative Works; or,
115
- within a display generated by the Derivative Works, if and
116
- wherever such third-party notices normally appear. The contents
117
- of the NOTICE file are for informational purposes only and
118
- do not modify the License. You may add Your own attribution
119
- notices within Derivative Works that You distribute, alongside
120
- or as an addendum to the NOTICE text from the Work, provided
121
- that such additional attribution notices cannot be construed
122
- as modifying the License.
123
-
124
- You may add Your own copyright statement to Your modifications and
125
- may provide additional or different license terms and conditions
126
- for use, reproduction, or distribution of Your modifications, or
127
- for any such Derivative Works as a whole, provided Your use,
128
- reproduction, and distribution of the Work otherwise complies with
129
- the conditions stated in this License.
130
-
131
- 5. Submission of Contributions. Unless You explicitly state otherwise,
132
- any Contribution intentionally submitted for inclusion in the Work
133
- by You to the Licensor shall be under the terms and conditions of
134
- this License, without any additional terms or conditions.
135
- Notwithstanding the above, nothing herein shall supersede or modify
136
- the terms of any separate license agreement you may have executed
137
- with Licensor regarding such Contributions.
138
-
139
- 6. Trademarks. This License does not grant permission to use the trade
140
- names, trademarks, service marks, or product names of the Licensor,
141
- except as required for reasonable and customary use in describing the
142
- origin of the Work and reproducing the content of the NOTICE file.
143
-
144
- 7. Disclaimer of Warranty. Unless required by applicable law or
145
- agreed to in writing, Licensor provides the Work (and each
146
- Contributor provides its Contributions) on an "AS IS" BASIS,
147
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
- implied, including, without limitation, any warranties or conditions
149
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
- PARTICULAR PURPOSE. You are solely responsible for determining the
151
- appropriateness of using or redistributing the Work and assume any
152
- risks associated with Your exercise of permissions under this License.
153
-
154
- 8. Limitation of Liability. In no event and under no legal theory,
155
- whether in tort (including negligence), contract, or otherwise,
156
- unless required by applicable law (such as deliberate and grossly
157
- negligent acts) or agreed to in writing, shall any Contributor be
158
- liable to You for damages, including any direct, indirect, special,
159
- incidental, or consequential damages of any character arising as a
160
- result of this License or out of the use or inability to use the
161
- Work (including but not limited to damages for loss of goodwill,
162
- work stoppage, computer failure or malfunction, or any and all
163
- other commercial damages or losses), even if such Contributor
164
- has been advised of the possibility of such damages.
165
-
166
- 9. Accepting Warranty or Additional Liability. While redistributing
167
- the Work or Derivative Works thereof, You may choose to offer,
168
- and charge a fee for, acceptance of support, warranty, indemnity,
169
- or other liability obligations and/or rights consistent with this
170
- License. However, in accepting such obligations, You may act only
171
- on Your own behalf and on Your sole responsibility, not on behalf
172
- of any other Contributor, and only if You agree to indemnify,
173
- defend, and hold each Contributor harmless for any liability
174
- incurred by, or claims asserted against, such Contributor by reason
175
- of your accepting any such warranty or additional liability.
176
-
177
- END OF TERMS AND CONDITIONS
178
-
179
- APPENDIX: How to apply the Apache License to your work.
180
-
181
- To apply the Apache License to your work, attach the following
182
- boilerplate notice, with the fields enclosed by brackets "[]"
183
- replaced with your own identifying information. (Don't include
184
- the brackets!) The text should be enclosed in the appropriate
185
- comment syntax for the file format. We also recommend that a
186
- file or class name and description of purpose be included on the
187
- same "printed page" as the copyright notice for easier
188
- identification within third-party archives.
189
-
190
- Copyright [yyyy] [name of copyright owner]
191
-
192
- Licensed under the Apache License, Version 2.0 (the "License");
193
- you may not use this file except in compliance with the License.
194
- You may obtain a copy of the License at
195
-
196
- http://www.apache.org/licenses/LICENSE-2.0
197
-
198
- Unless required by applicable law or agreed to in writing, software
199
- distributed under the License is distributed on an "AS IS" BASIS,
200
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
- See the License for the specific language governing permissions and
202
- limitations under the License.
 
1
+ Attribution-ShareAlike 4.0 International
2
+
3
+ =======================================================================
4
+
5
+ Creative Commons Corporation ("Creative Commons") is not a law firm and
6
+ does not provide legal services or legal advice. Distribution of
7
+ Creative Commons public licenses does not create a lawyer-client or
8
+ other relationship. Creative Commons makes its licenses and related
9
+ information available on an "as-is" basis. Creative Commons gives no
10
+ warranties regarding its licenses, any material licensed under their
11
+ terms and conditions, or any related information. Creative Commons
12
+ disclaims all liability for damages resulting from their use to the
13
+ fullest extent possible.
14
+
15
+ Using Creative Commons Public Licenses
16
+
17
+ Creative Commons public licenses provide a standard set of terms and
18
+ conditions that creators and other rights holders may use to share
19
+ original works of authorship and other material subject to copyright
20
+ and certain other rights specified in the public license below. The
21
+ following considerations are for informational purposes only, are not
22
+ exhaustive, and do not form part of our licenses.
23
+
24
+ Considerations for licensors: Our public licenses are
25
+ intended for use by those authorized to give the public
26
+ permission to use material in ways otherwise restricted by
27
+ copyright and certain other rights. Our licenses are
28
+ irrevocable. Licensors should read and understand the terms
29
+ and conditions of the license they choose before applying it.
30
+ Licensors should also secure all rights necessary before
31
+ applying our licenses so that the public can reuse the
32
+ material as expected. Licensors should clearly mark any
33
+ material not subject to the license. This includes other CC-
34
+ licensed material, or material used under an exception or
35
+ limitation to copyright. More considerations for licensors:
36
+ wiki.creativecommons.org/Considerations_for_licensors
37
+
38
+ Considerations for the public: By using one of our public
39
+ licenses, a licensor grants the public permission to use the
40
+ licensed material under specified terms and conditions. If
41
+ the licensor's permission is not necessary for any reason--for
42
+ example, because of any applicable exception or limitation to
43
+ copyright--then that use is not regulated by the license. Our
44
+ licenses grant only permissions under copyright and certain
45
+ other rights that a licensor has authority to grant. Use of
46
+ the licensed material may still be restricted for other
47
+ reasons, including because others have copyright or other
48
+ rights in the material. A licensor may make special requests,
49
+ such as asking that all changes be marked or described.
50
+ Although not required by our licenses, you are encouraged to
51
+ respect those requests where reasonable. More considerations
52
+ for the public:
53
+ wiki.creativecommons.org/Considerations_for_licensees
54
+
55
+ =======================================================================
56
+
57
+ Creative Commons Attribution-ShareAlike 4.0 International Public
58
+ License
59
+
60
+ By exercising the Licensed Rights (defined below), You accept and agree
61
+ to be bound by the terms and conditions of this Creative Commons
62
+ Attribution-ShareAlike 4.0 International Public License ("Public
63
+ License"). To the extent this Public License may be interpreted as a
64
+ contract, You are granted the Licensed Rights in consideration of Your
65
+ acceptance of these terms and conditions, and the Licensor grants You
66
+ such rights in consideration of benefits the Licensor receives from
67
+ making the Licensed Material available under these terms and
68
+ conditions.
69
+
70
+
71
+ Section 1 -- Definitions.
72
+
73
+ a. Adapted Material means material subject to Copyright and Similar
74
+ Rights that is derived from or based upon the Licensed Material
75
+ and in which the Licensed Material is translated, altered,
76
+ arranged, transformed, or otherwise modified in a manner requiring
77
+ permission under the Copyright and Similar Rights held by the
78
+ Licensor. For purposes of this Public License, where the Licensed
79
+ Material is a musical work, performance, or sound recording,
80
+ Adapted Material is always produced where the Licensed Material is
81
+ synched in timed relation with a moving image.
82
+
83
+ b. Adapter's License means the license You apply to Your Copyright
84
+ and Similar Rights in Your contributions to Adapted Material in
85
+ accordance with the terms and conditions of this Public License.
86
+
87
+ c. BY-SA Compatible License means a license listed at
88
+ creativecommons.org/compatiblelicenses, approved by Creative
89
+ Commons as essentially the equivalent of this Public License.
90
+
91
+ d. Copyright and Similar Rights means copyright and/or similar rights
92
+ closely related to copyright including, without limitation,
93
+ performance, broadcast, sound recording, and Sui Generis Database
94
+ Rights, without regard to how the rights are labeled or
95
+ categorized. For purposes of this Public License, the rights
96
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
97
+ Rights.
98
+
99
+ e. Effective Technological Measures means those measures that, in the
100
+ absence of proper authority, may not be circumvented under laws
101
+ fulfilling obligations under Article 11 of the WIPO Copyright
102
+ Treaty adopted on December 20, 1996, and/or similar international
103
+ agreements.
104
+
105
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
106
+ any other exception or limitation to Copyright and Similar Rights
107
+ that applies to Your use of the Licensed Material.
108
+
109
+ g. License Elements means the license attributes listed in the name
110
+ of a Creative Commons Public License. The License Elements of this
111
+ Public License are Attribution and ShareAlike.
112
+
113
+ h. Licensed Material means the artistic or literary work, database,
114
+ or other material to which the Licensor applied this Public
115
+ License.
116
+
117
+ i. Licensed Rights means the rights granted to You subject to the
118
+ terms and conditions of this Public License, which are limited to
119
+ all Copyright and Similar Rights that apply to Your use of the
120
+ Licensed Material and that the Licensor has authority to license.
121
+
122
+ j. Licensor means the individual(s) or entity(ies) granting rights
123
+ under this Public License.
124
+
125
+ k. Share means to provide material to the public by any means or
126
+ process that requires permission under the Licensed Rights, such
127
+ as reproduction, public display, public performance, distribution,
128
+ dissemination, communication, or importation, and to make material
129
+ available to the public including in ways that members of the
130
+ public may access the material from a place and at a time
131
+ individually chosen by them.
132
+
133
+ l. Sui Generis Database Rights means rights other than copyright
134
+ resulting from Directive 96/9/EC of the European Parliament and of
135
+ the Council of 11 March 1996 on the legal protection of databases,
136
+ as amended and/or succeeded, as well as other essentially
137
+ equivalent rights anywhere in the world.
138
+
139
+ m. You means the individual or entity exercising the Licensed Rights
140
+ under this Public License. Your has a corresponding meaning.
141
+
142
+
143
+ Section 2 -- Scope.
144
+
145
+ a. License grant.
146
+
147
+ 1. Subject to the terms and conditions of this Public License,
148
+ the Licensor hereby grants You a worldwide, royalty-free,
149
+ non-sublicensable, non-exclusive, irrevocable license to
150
+ exercise the Licensed Rights in the Licensed Material to:
151
+
152
+ a. reproduce and Share the Licensed Material, in whole or
153
+ in part; and
154
+
155
+ b. produce, reproduce, and Share Adapted Material.
156
+
157
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
158
+ Exceptions and Limitations apply to Your use, this Public
159
+ License does not apply, and You do not need to comply with
160
+ its terms and conditions.
161
+
162
+ 3. Term. The term of this Public License is specified in Section
163
+ 6(a).
164
+
165
+ 4. Media and formats; technical modifications allowed. The
166
+ Licensor authorizes You to exercise the Licensed Rights in
167
+ all media and formats whether now known or hereafter created,
168
+ and to make technical modifications necessary to do so. The
169
+ Licensor waives and/or agrees not to assert any right or
170
+ authority to forbid You from making technical modifications
171
+ necessary to exercise the Licensed Rights, including
172
+ technical modifications necessary to circumvent Effective
173
+ Technological Measures. For purposes of this Public License,
174
+ simply making modifications authorized by this Section 2(a)
175
+ (4) never produces Adapted Material.
176
+
177
+ 5. Downstream recipients.
178
+
179
+ a. Offer from the Licensor -- Licensed Material. Every
180
+ recipient of the Licensed Material automatically
181
+ receives an offer from the Licensor to exercise the
182
+ Licensed Rights under the terms and conditions of this
183
+ Public License.
184
+
185
+ b. Additional offer from the Licensor -- Adapted Material.
186
+ Every recipient of Adapted Material from You
187
+ automatically receives an offer from the Licensor to
188
+ exercise the Licensed Rights in the Adapted Material
189
+ under the conditions of the Adapter's License You apply.
190
+
191
+ c. No downstream restrictions. You may not offer or impose
192
+ any additional or different terms or conditions on, or
193
+ apply any Effective Technological Measures to, the
194
+ Licensed Material if doing so restricts exercise of the
195
+ Licensed Rights by any recipient of the Licensed
196
+ Material.
197
+
198
+ 6. No endorsement. Nothing in this Public License constitutes or
199
+ may be construed as permission to assert or imply that You
200
+ are, or that Your use of the Licensed Material is, connected
201
+ with, or sponsored, endorsed, or granted official status by,
202
+ the Licensor or others designated to receive attribution as
203
+ provided in Section 3(a)(1)(A)(i).
204
+
205
+ b. Other rights.
206
+
207
+ 1. Moral rights, such as the right of integrity, are not
208
+ licensed under this Public License, nor are publicity,
209
+ privacy, and/or other similar personality rights; however, to
210
+ the extent possible, the Licensor waives and/or agrees not to
211
+ assert any such rights held by the Licensor to the limited
212
+ extent necessary to allow You to exercise the Licensed
213
+ Rights, but not otherwise.
214
+
215
+ 2. Patent and trademark rights are not licensed under this
216
+ Public License.
217
+
218
+ 3. To the extent possible, the Licensor waives any right to
219
+ collect royalties from You for the exercise of the Licensed
220
+ Rights, whether directly or through a collecting society
221
+ under any voluntary or waivable statutory or compulsory
222
+ licensing scheme. In all other cases the Licensor expressly
223
+ reserves any right to collect such royalties.
224
+
225
+
226
+ Section 3 -- License Conditions.
227
+
228
+ Your exercise of the Licensed Rights is expressly made subject to the
229
+ following conditions.
230
+
231
+ a. Attribution.
232
+
233
+ 1. If You Share the Licensed Material (including in modified
234
+ form), You must:
235
+
236
+ a. retain the following if it is supplied by the Licensor
237
+ with the Licensed Material:
238
+
239
+ i. identification of the creator(s) of the Licensed
240
+ Material and any others designated to receive
241
+ attribution, in any reasonable manner requested by
242
+ the Licensor (including by pseudonym if
243
+ designated);
244
+
245
+ ii. a copyright notice;
246
+
247
+ iii. a notice that refers to this Public License;
248
+
249
+ iv. a notice that refers to the disclaimer of
250
+ warranties;
251
+
252
+ v. a URI or hyperlink to the Licensed Material to the
253
+ extent reasonably practicable;
254
+
255
+ b. indicate if You modified the Licensed Material and
256
+ retain an indication of any previous modifications; and
257
+
258
+ c. indicate the Licensed Material is licensed under this
259
+ Public License, and include the text of, or the URI or
260
+ hyperlink to, this Public License.
261
+
262
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
263
+ reasonable manner based on the medium, means, and context in
264
+ which You Share the Licensed Material. For example, it may be
265
+ reasonable to satisfy the conditions by providing a URI or
266
+ hyperlink to a resource that includes the required
267
+ information.
268
+
269
+ 3. If requested by the Licensor, You must remove any of the
270
+ information required by Section 3(a)(1)(A) to the extent
271
+ reasonably practicable.
272
+
273
+ b. ShareAlike.
274
+
275
+ In addition to the conditions in Section 3(a), if You Share
276
+ Adapted Material You produce, the following conditions also apply.
277
+
278
+ 1. The Adapter's License You apply must be a Creative Commons
279
+ license with the same License Elements, this version or
280
+ later, or a BY-SA Compatible License.
281
+
282
+ 2. You must include the text of, or the URI or hyperlink to, the
283
+ Adapter's License You apply. You may satisfy this condition
284
+ in any reasonable manner based on the medium, means, and
285
+ context in which You Share Adapted Material.
286
+
287
+ 3. You may not offer or impose any additional or different terms
288
+ or conditions on, or apply any Effective Technological
289
+ Measures to, Adapted Material that restrict exercise of the
290
+ rights granted under the Adapter's License You apply.
291
+
292
+
293
+ Section 4 -- Sui Generis Database Rights.
294
+
295
+ Where the Licensed Rights include Sui Generis Database Rights that
296
+ apply to Your use of the Licensed Material:
297
+
298
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
299
+ to extract, reuse, reproduce, and Share all or a substantial
300
+ portion of the contents of the database;
301
+
302
+ b. if You include all or a substantial portion of the database
303
+ contents in a database in which You have Sui Generis Database
304
+ Rights, then the database in which You have Sui Generis Database
305
+ Rights (but not its individual contents) is Adapted Material,
306
+ including for purposes of Section 3(b); and
307
+
308
+ c. You must comply with the conditions in Section 3(a) if You Share
309
+ all or a substantial portion of the contents of the database.
310
+
311
+ For the avoidance of doubt, this Section 4 supplements and does not
312
+ replace Your obligations under this Public License where the Licensed
313
+ Rights include other Copyright and Similar Rights.
314
+
315
+
316
+ Section 5 -- Disclaimer of Warranties and Limitation of Liability.
317
+
318
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
319
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
320
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
321
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
322
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
323
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
324
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
325
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
326
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
327
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
328
+
329
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
330
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
331
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
332
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
333
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
334
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
335
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
336
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
337
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
338
+
339
+ c. The disclaimer of warranties and limitation of liability provided
340
+ above shall be interpreted in a manner that, to the extent
341
+ possible, most closely approximates an absolute disclaimer and
342
+ waiver of all liability.
343
+
344
+
345
+ Section 6 -- Term and Termination.
346
+
347
+ a. This Public License applies for the term of the Copyright and
348
+ Similar Rights licensed here. However, if You fail to comply with
349
+ this Public License, then Your rights under this Public License
350
+ terminate automatically.
351
+
352
+ b. Where Your right to use the Licensed Material has terminated under
353
+ Section 6(a), it reinstates:
354
+
355
+ 1. automatically as of the date the violation is cured, provided
356
+ it is cured within 30 days of Your discovery of the
357
+ violation; or
358
+
359
+ 2. upon express reinstatement by the Licensor.
360
+
361
+ For the avoidance of doubt, this Section 6(b) does not affect any
362
+ right the Licensor may have to seek remedies for Your violations
363
+ of this Public License.
364
+
365
+ c. For the avoidance of doubt, the Licensor may also offer the
366
+ Licensed Material under separate terms or conditions or stop
367
+ distributing the Licensed Material at any time; however, doing so
368
+ will not terminate this Public License.
369
+
370
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
371
+ License.
372
+
373
+
374
+ Section 7 -- Other Terms and Conditions.
375
+
376
+ a. The Licensor shall not be bound by any additional or different
377
+ terms or conditions communicated by You unless expressly agreed.
378
+
379
+ b. Any arrangements, understandings, or agreements regarding the
380
+ Licensed Material not stated herein are separate from and
381
+ independent of the terms and conditions of this Public License.
382
+
383
+
384
+ Section 8 -- Interpretation.
385
+
386
+ a. For the avoidance of doubt, this Public License does not, and
387
+ shall not be interpreted to, reduce, limit, restrict, or impose
388
+ conditions on any use of the Licensed Material that could lawfully
389
+ be made without permission under this Public License.
390
+
391
+ b. To the extent possible, if any provision of this Public License is
392
+ deemed unenforceable, it shall be automatically reformed to the
393
+ minimum extent necessary to make it enforceable. If the provision
394
+ cannot be reformed, it shall be severed from this Public License
395
+ without affecting the enforceability of the remaining terms and
396
+ conditions.
397
+
398
+ c. No term or condition of this Public License will be waived and no
399
+ failure to comply consented to unless expressly agreed to by the
400
+ Licensor.
401
+
402
+ d. Nothing in this Public License constitutes or may be interpreted
403
+ as a limitation upon, or waiver of, any privileges and immunities
404
+ that apply to the Licensor or You, including from the legal
405
+ processes of any jurisdiction or authority.
406
+
407
+
408
+ =======================================================================
409
+
410
+ Creative Commons is not a party to its public
411
+ licenses. Notwithstanding, Creative Commons may elect to apply one of
412
+ its public licenses to material it publishes and in those instances
413
+ will be considered the “Licensor.” The text of the Creative Commons
414
+ public licenses is dedicated to the public domain under the CC0 Public
415
+ Domain Dedication. Except for the limited purpose of indicating that
416
+ material is shared under a Creative Commons public license or as
417
+ otherwise permitted by the Creative Commons policies published at
418
+ creativecommons.org/policies, Creative Commons does not authorize the
419
+ use of the trademark "Creative Commons" or any other trademark or logo
420
+ of Creative Commons without its prior written consent including,
421
+ without limitation, in connection with any unauthorized modifications
422
+ to any of its public licenses or any other arrangements,
423
+ understandings, or agreements concerning use of licensed material. For
424
+ the avoidance of doubt, this paragraph does not form part of the
425
+ public licenses.
426
+
427
+ Creative Commons may be contacted at creativecommons.org.
428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE-CODE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- license: apache-2.0
3
  language:
4
  - en
5
  - pcm
@@ -8,177 +8,277 @@ language:
8
  - ha
9
  base_model: FunAudioLLM/CosyVoice2-0.5B
10
  pipeline_tag: text-to-speech
 
11
  tags:
12
  - text-to-speech
13
  - code-switching
14
  - nigerian-languages
 
 
 
 
15
  - cosyvoice2
16
- - research-preview
 
 
 
 
 
17
  ---
18
 
19
- # Dynamic Accent (Phase 2, research preview)
20
 
21
- Nigerian multilingual, **code-switching** text-to-speech in one consistent voice: English, Nigerian Pidgin, Yoruba, Igbo and Hausa, mixed freely inside one sentence.
22
 
23
- It is built on [FunAudioLLM/CosyVoice2-0.5B](https://huggingface.co/FunAudioLLM/CosyVoice2-0.5B) and adds:
24
- - per-token **language embeddings**, a **speaker token** and an adversarial (GRL) speaker classifier on the LM, fine-tuned on about 120 h of Nigerian-language and code-switched speech with pause-normalised data (pause profile B);
25
- - in-context **anchor prompts** at the LM and the flow, so prosody and pause style follow the anchor voice (WAXAL Yoruba speaker 4);
26
- - a **flow-matching decoder fine-tuned on the anchor voice** (anchor SFT) for tone and pitch detail.
27
 
28
- This repo is a **private research preview** for testing. It is not a production model.
 
 
 
 
29
 
30
- Inference defaults to the **Phase 2 final behaviour** (`--pause_mode model --speed 1.0`): model-generated pauses. In a listening test the original clearly sounded more natural than prosody v2, so clause mode is kept only as an experimental option.
31
 
32
- ## What is in the repo
33
 
34
- | path | what it is |
35
- |---|---|
36
- | `checkpoints/p2_lm_B/best.pt` | Dynamic Accent LM (CosyVoice2 Qwen2 LM + language/speaker embeddings), pause profile B, 4,000 steps, best validation CE |
37
- | `checkpoints/p2_lm_B/spk2id.json` | speaker-token map (the anchor is `waxal_yor_4`) |
38
- | `checkpoints/p2_flow_sft/best.pt` | anchor-SFT flow-matching decoder (replaces the CosyVoice2 flow weights) |
39
- | `prompts/clip{0..3}_*.wav`, `prompts/prompts.json` | the 4 anchor prompt clips (24 kHz) with their transcripts and S3 speech tokens. The final system uses clip 0 |
40
- | `infer.py` | self-contained inference: the same path as the Phase 2 final samples |
41
- | `code/` | the original training and synthesis code (`train_b.py`, `train_b2.py`, `da/`) for reference |
42
- | `cosyvoice_requirements.txt` | CosyVoice's own requirements file, at the commit used |
43
- | `samples/` | example renders |
 
 
44
 
45
- Everything else comes from the stock base model **`FunAudioLLM/CosyVoice2-0.5B`** (Qwen tokenizer `CosyVoice-BlankEN`, `llm.pt`, `hift.pt` vocoder, `campplus.onnx`, `speech_tokenizer_v2.onnx`, `cosyvoice2.yaml`). Nothing in the base needs changing; `infer.py` downloads it on first run.
46
 
47
- ## Setup (single GPU, e.g. Colab T4 or A100)
48
 
49
  ```bash
50
  # 1. CosyVoice code (the commit the model was built and tested on)
51
  git clone --recursive https://github.com/FunAudioLLM/CosyVoice.git
52
  cd CosyVoice && git checkout 074ca6dc9e80a2f424f1f74b48bdd7d3fea531cc && git submodule update --init --recursive && cd ..
53
 
54
- # 2. This repo (private: log in with a token that can read Modularcomputing)
55
- huggingface-cli login
56
  huggingface-cli download Modularcomputing/dynamic-accent-preview --local-dir dynamic-accent-preview
57
 
58
  # 3. Python deps (Python 3.10; on Colab keep the preinstalled torch/torchaudio)
59
  pip install -r dynamic-accent-preview/requirements.txt
60
  ```
61
 
62
- The base model downloads to `dynamic-accent-preview/pretrained/CosyVoice2-0.5B` the first time you run `infer.py`. You can also point `--model_dir` (or `COSYVOICE2_DIR`) at an existing copy. Point `--cosyvoice_dir` (or `COSYVOICE_REPO`) at your CosyVoice clone if it is not inside the repo folder.
63
-
64
- The 0.5B model should fit comfortably on a 16 GB T4. This build was tested on an H100 only, not yet on Colab.
65
 
66
  ## Usage
67
 
68
  ```bash
69
  cd dynamic-accent-preview
70
- python infer.py --cosyvoice_dir ../CosyVoice \
71
- --text "<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>" \
72
- --seed 0 --out out.wav # defaults: --pause_mode model --speed 1.0
73
- # experimental prosody v2: add --pause_mode clause --speed 1.05
74
- ```
75
 
76
- - **Language tags:** wrap each span in `<eng>`, `<pcm>`, `<yor>`, `<ibo>` or `<hau>`. Untagged text is read as English. Keep punctuation inside the span it belongs to.
77
- - **`--seed`:** the final system is a single sample at seed 0. Try seeds 1 and 2 (or more) and keep the take you like. Best-of-N picking lowered CER by about a third in evaluation.
78
- - **`--pause_mode clause`** (experimental, prosody v2; rejected in listening): the text is cut at `, ; : . ? !` and at language switches. Each chunk is generated separately and its edge silence is trimmed (energy VAD, 30 ms kept). Chunks are then joined with set gaps taken from the real anchor's pause statistics: 0.40 s ± 0.05 at commas and `;:`, 0.60 s at sentence ends, and 0.12 s at an unpunctuated language switch, with 10 ms fades. A one-word chunk is merged into the next chunk of the same language.
79
- - **`--speed`** (default 1.0): a pitch-preserving tempo factor (WSOLA via `audiotsm`) applied to the speech only, so the gaps stay as set. The allowed range is 1.0–1.2.
80
- - **`--pause_mode model --speed 1.0`** reproduces the Phase 2 final system: model-generated pauses, sentence-by-sentence generation, internal silences capped at 0.6 s, and 0.6 s between sentences. Both modes use anchor prompt clip 0 at the LM and flow (`--prompt_clip 0`).
81
- - **`--dtype`:** the LM ran under bf16 autocast. T4 has no bf16, so `auto` uses fp16 there. fp16 has not been tested yet; if a T4 run sounds wrong, compare against an A100 (bf16) run.
82
- - **Determinism:** generation is not bit-reproducible across runs on GPU. Sampling under bf16 can pick a different token on near-ties, so the same seed can give a slightly different take.
83
 
84
- From Python:
 
85
 
86
- ```python
87
- from infer import DynamicAccent
88
- tts = DynamicAccent("pretrained/CosyVoice2-0.5B", "../CosyVoice",
89
- "checkpoints/p2_lm_B/best.pt", "checkpoints/p2_flow_sft/best.pt", "prompts/prompts.json")
90
- wav = tts.say("<hau>Ina kwana, yaya aiki?</hau> <eng>The meeting has been moved to Friday morning.</eng>",
91
- seed=0, pause_mode="model", speed=1.0)
92
- import soundfile as sf; sf.write("hau_eng.wav", wav, tts.sr)
93
  ```
94
 
95
- ### Example code-switched inputs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  ```text
98
  <eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>
 
99
  <yor>Ẹ káàárọ̀ o, ṣé dáadáa ni?</yor> <eng>I will send you the report this afternoon.</eng>
100
- <ibo>Kedu ka ị mere?</ibo> <pcm>Abeg no vex, I go call you for evening.</pcm>
101
  <hau>Ina kwana, yaya aiki?</hau> <eng>The meeting has been moved to Friday morning.</eng>
102
  <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>
103
- <hau>Barka da yamma, yaya kuke?</hau> <eng>The weather report says it will rain tomorrow morning.</eng> <pcm>Abeg no forget o, the rain fit heavy well well.</pcm>
104
  ```
105
 
106
- The Yoruba, Igbo and Hausa example lines are hand-written and have not been checked by native speakers.
107
 
108
- ## Samples
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- - `samples/pcm_eng_dynamic_accent_seed{0,1,2}.mp3`: "this is dynamic accent, e dey sound nice abi?" (English then Pidgin), seeds 0, 1 and 2. These are Phase 2 final renders (`--pause_mode model --speed 1.0`).
111
- - `samples/prosody_v2/*.mp3`: the same custom sentence plus `para_ibo_pcm_eng`, `para_yor_eng`, `para_hau_eng_pcm`, `ibo_000` and `yor_000`, rendered with experimental prosody v2 (`--pause_mode clause --speed 1.05`, seed 0). The user preferred the original.
112
 
113
- ## Prosody v2 (inference-time pause and pace fix)
 
 
 
 
 
 
114
 
115
- **Speaking rate** is articulation rate: syllables per second of speech, with pauses of 0.15 s or more excluded. It uses the same syllable counter for every source. Reference speech is 150 clips per source. The system rate comes from the Phase 2 final renders of the eval set.
 
 
 
 
 
 
 
 
 
 
116
 
117
- | | anchor (yor) | yor | ibo | hau | pcm | eng |
118
- |---|---|---|---|---|---|---|
119
- | reference speech (syl/s) | 4.45 | 4.78 (WAXAL, other speakers) | 4.73 | 5.71 | 3.87 | 4.53 (FLEURS) |
120
- | Phase 2 final system (syl/s) | 4.20 (yor) | 4.20 | 4.50 | 3.98 | 3.63 | 4.77 |
121
- | reference ÷ system | 1.06 | 1.14 | 1.05 | 1.43 | 1.06 | 0.95 |
122
 
123
- The median ratio is 1.06, so the default `--speed` is **1.05** (rounded to 0.05 and clamped to 1.0–1.2). Hausa is the outlier. Its vowel-based syllable count is the roughest of the five.
 
 
 
 
 
 
 
124
 
125
- **Before and after** on the six sample items (custom sentence, 3 paragraphs, ibo_000, yor_000), seed 0. Pauses are measured at forced-aligned word boundaries with the Phase 2 report harness. The anchor row is real WAXAL recordings. With only 6 items, read these as a direction, not a result.
126
 
127
- | | comma pause (s) | sentence pause (s) | unpunctuated pause rate (>0.25 s) | articulation rate (syl/s) | para CER | para CER (no tones) | all-6 CER | SIM |
 
 
 
 
128
  |---|---|---|---|---|---|---|---|---|
129
- | real anchor | 0.417 | 0.543 | 0.137 | 4.45 | – | – | – | ceiling ≈0.84 |
130
- | before: Phase 2 final (`model`, 1.0) | 0.339 | 0.488 | 0.057 | 4.26 | 0.175 | 0.124 | 0.175 | 0.814 |
131
- | clause, speed 1.0 | 0.339 | 0.526 | 0.095 | 3.95 | 0.179 | 0.123 | 0.166 | 0.775 |
132
- | after: clause, speed 1.05 (experimental) | 0.275 (≈0.37 *) | 0.550 | 0.086 | 4.18 | 0.193 | 0.143 | 0.207 | 0.750 |
133
- | clause, speed 1.10 | 0.315 | 0.550 | 0.067 | 4.37 | 0.244 | 0.194 | 0.224 | 0.748 |
134
- | model pauses, speed 1.05 | 0.235 | 0.441 | 0.029 | 4.35 | 0.189 | 0.149 | 0.188 | 0.788 |
135
 
136
- \* At two commas that follow the one-letter Pidgin particle "o," the aligner moves the inserted gap to the boundary before "o". This reads as a 0.0 s comma plus one unpunctuated pause. The other comma gaps in clause mode measure 0.37–0.39 s, against the 0.40 s target.
137
 
138
- - **Pauses:** clause mode moves comma and sentence pauses and the phrase-break rate toward the anchor's numbers. Sentence pauses go from 0.49 to 0.55 s against the anchor's 0.54 s.
139
- - **Pace:** generating short chunks one at a time makes speech about 7% slower (4.26 to 3.95 syl/s). So 1.05 only brings the rate back to about where the final system was, and it is still below the anchor.
140
- - **Cost:** WSOLA speed-up and chunking each lower ECAPA similarity (0.81 to 0.75–0.79). At 1.10, paragraph CER rises clearly (0.175 to 0.244). At 1.05 the rise is small (0.175 to 0.193), and mixed across paragraphs: Hausa got better, Igbo and Yoruba got worse. A blind listening check should decide between 1.0 and 1.05.
 
 
141
 
142
- ## Evaluation (Phase 2 report)
143
 
144
- CER is ASR round-trip: MMS-1b-all for ibo/hau/yor, Whisper-large-v3-turbo for eng and pcm, and min(MMS, Whisper) for code-switched (CS) items and paragraphs. "No tones" strips the diacritics. SIM is ECAPA cosine to the anchor centroid; the ceiling is about 0.84 on real held-out anchor clips. Pidgin CER is limited by the judge: real Pidgin recordings score 0.43. Full eval set: 30 items per language + 9 CS sentences + 3 paragraphs = 162 items, single sample (seed 0, anchor prompt).
 
 
 
 
145
 
146
- | system | ibo CER | hau CER | yor CER (no tones) | pcm CER | eng CER | CS CER | para CER | SIM to anchor (mean of 5 langs) | SIM spread | CS half-vs-half SIM |
147
- |---|---|---|---|---|---|---|---|---|---|---|
148
- | Phase 1 Arm B (smoke) | 0.212 | 0.244 | 0.407 (0.294) | 0.412 | 0.035 | 0.201 | 0.195 | 0.804 | 0.030 | 0.649 |
149
- | p2_lm (profile A) + SFT flow | 0.199 | 0.183 | 0.223 (0.140) | 0.190 | 0.038 | 0.184 | 0.176 | 0.769 | 0.069 | 0.634 |
150
- | **p2_lm_B (profile B) + SFT flow (this release)** | 0.188 | 0.188 | 0.224 (0.137) | 0.174 | 0.034 | 0.167 | 0.173 | 0.782 | 0.039 | 0.642 |
151
 
152
- Pause and pitch (energy VAD on forced-aligned word boundaries):
153
 
154
- | system | int. pause p90 (s) | pauses / s | spurious pause rate | comma pause (s) | pause-length W1 vs anchor | para: sentence pause (s) | para: pause p90 (s) | F0 std (st) | F0 range (st) | yor tone-direction acc (pairs) |
155
- |---|---|---|---|---|---|---|---|---|---|---|
156
- | **real anchor recordings** (WAXAL val+test, raw) | 1.071 | 0.624 | 0.137 | 0.417 | 0 | 0.543 | – | 3.152 | 10.173 | 0.750 (2990) |
157
- | Phase 1 Arm B (smoke) | 1.334 | 0.584 | 0.044 | 0.245 | 0.147 | 0.315 | 1.035 | 3.058 | 9.999 | 0.777 (251) |
158
- | p2_lm (profile A) + SFT flow | 0.414 | 0.328 | 0.008 | 0.263 | 0.330 | 0.509 | 0.547 | 3.038 | 10.069 | 0.752 (254) |
159
- | **p2_lm_B (profile B) + SFT flow (this release)** | 0.460 | 0.288 | 0.087 | 0.251 | 0.262 | 0.488 | 0.531 | 3.057 | 10.134 | 0.749 (255) |
160
 
161
- Zero-shot baselines on the 62-item stage subset (for context): CosyVoice2 zero-shot ibo/hau/yor CER 0.689 / 0.281 / 0.852, and CosyVoice3 zero-shot 0.629 / 0.197 / 0.717, against 0.173 / 0.220 / 0.226 for Phase 2 (p2_lm + SFT flow).
 
 
 
 
 
162
 
163
- ## Known limitations
164
 
165
- - In `model` mode, comma pauses are short (0.25 s on the full eval set, against the anchor's 0.42 s). `clause` mode sets them directly (see Prosody v2).
166
- - Speaker similarity is 0.78 against Phase 1's 0.80. English and Pidgin drift the most from the anchor.
 
 
 
167
  - English after a Nigerian-language span keeps a strong local accent.
168
- - Pidgin has only about 6 h of training data and no reliable ASR judge.
169
- - Yoruba tone quality needs human listening; the automatic tone metric is saturated.
170
- - Naturalness has not been rated by native speakers yet (no MOS / A/B).
 
171
 
172
- ## Training data
173
 
174
- The training data includes CC-BY-SA sources. Datasets used:
175
- - **WAXAL** (Yoruba, Igbo, Hausa, Nigerian Pidgin; includes the anchor voice, Yoruba speaker 4), CC-BY-SA-4.0
176
- - **BibleTTS** (Yoruba and Hausa), CC-BY-SA
177
- - **FLEURS** (English, Hausa, Igbo, Yoruba)
178
- - **OpenSLR SLR70** (Nigerian English)
179
- - **OpenSLR SLR86** (Yoruba)
180
- - 4,000 code-switched pairs built from the sets above
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  ## Licence
183
 
184
- Apache-2.0 (see `LICENSE`). The base model, CosyVoice2-0.5B, is Apache-2.0.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ license: cc-by-sa-4.0
3
  language:
4
  - en
5
  - pcm
 
8
  - ha
9
  base_model: FunAudioLLM/CosyVoice2-0.5B
10
  pipeline_tag: text-to-speech
11
+ library_name: pytorch
12
  tags:
13
  - text-to-speech
14
  - code-switching
15
  - nigerian-languages
16
+ - igbo
17
+ - yoruba
18
+ - hausa
19
+ - nigerian-pidgin
20
  - cosyvoice2
21
+ datasets:
22
+ - google/WaxalNLP
23
+ - McGill-NLP/NaijaS2ST
24
+ - AfriSpeech/open-bible-speech-african
25
+ - nkowaokwu/ibo-dict
26
+ - google/fleurs
27
  ---
28
 
29
+ <p align="center"><img src="assets/dynamic_accent_banner.jpg" width="600"/></p>
30
 
31
+ # Dynamic Accent: Code-switching TTS
32
 
33
+ **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.
 
 
 
34
 
35
+ Dynamic Accent is a fine-tune of [FunAudioLLM/CosyVoice2-0.5B](https://huggingface.co/FunAudioLLM/CosyVoice2-0.5B) by Modular. It adds:
36
+ - 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*);
37
+ - training on about 178 h of Nigerian-language and code-switched speech, including about 102 h of Igbo;
38
+ - learned `[pause]`/`[breath]` events and a speaking-rate instruction (Phase 3);
39
+ - a **flow-matching decoder fine-tuned on the anchor voice** for tone and pitch detail.
40
 
41
+ It is a research release, not a production system. Read **Limitations** before you use it.
42
 
43
+ ## Versions and voices
44
 
45
+ Two model versions ship side by side. Pick one with `--version`, or render both with `--compare` and A/B before use.
46
+
47
+ | version | LM | flow | decoding | what changed |
48
+ |---|---|---|---|---|
49
+ | `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% |
50
+ | `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 |
51
+
52
+ | voice (`--voice`) | who | notes |
53
+ |---|---|---|
54
+ | `anchor_male` (default) | WAXAL Yoruba speaker 4, male | the voice both versions were trained around |
55
+ | `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 |
56
+ | `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 |
57
 
58
+ **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`.
59
 
60
+ ## Setup (single GPU)
61
 
62
  ```bash
63
  # 1. CosyVoice code (the commit the model was built and tested on)
64
  git clone --recursive https://github.com/FunAudioLLM/CosyVoice.git
65
  cd CosyVoice && git checkout 074ca6dc9e80a2f424f1f74b48bdd7d3fea531cc && git submodule update --init --recursive && cd ..
66
 
67
+ # 2. This repo
 
68
  huggingface-cli download Modularcomputing/dynamic-accent-preview --local-dir dynamic-accent-preview
69
 
70
  # 3. Python deps (Python 3.10; on Colab keep the preinstalled torch/torchaudio)
71
  pip install -r dynamic-accent-preview/requirements.txt
72
  ```
73
 
74
+ 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.
 
 
75
 
76
  ## Usage
77
 
78
  ```bash
79
  cd dynamic-accent-preview
80
+ # default: --version phase3 --voice anchor_male
81
+ python infer.py --cosyvoice_dir ../CosyVoice --text "<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>" --out out.wav
 
 
 
82
 
83
+ # recommended pairing
84
+ python infer.py --cosyvoice_dir ../CosyVoice --version phase3 --voice waxal_ibo_5_female \
85
+ --text "<ibo>Kedu ka ị mere?</ibo> <pcm>Abeg no vex, I go call you for evening.</pcm>" --out ibo_f.wav
 
 
 
 
86
 
87
+ # A/B: the same text, seed and voice through both versions -> cmp_phase2.wav, cmp_phase3.wav
88
+ python infer.py --cosyvoice_dir ../CosyVoice --compare --voice anchor_male --text "..." --out cmp.wav
89
 
90
+ python infer.py --list_voices # voices, versions, recommended pairing
 
 
 
 
 
 
91
  ```
92
 
93
+ | flag | what it does |
94
+ |---|---|
95
+ | `--text` | the text: tagged (below) or plain with `--auto_tag` |
96
+ | `--version phase2\|phase3` | model version (default `phase3`) |
97
+ | `--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 |
98
+ | `--list_voices` | list voices, versions and the recommended pairing |
99
+ | `--compare` | render with both versions → `<out>_phase2.wav`, `<out>_phase3.wav` |
100
+ | `--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 |
101
+ | `--auto_tag` | tag untagged mixed text automatically (see below), print the tagged text, then synthesise |
102
+ | `--tag_only` | with `--auto_tag`: print the tagged text and stop, so you can edit it and pass it back as `--text` |
103
+ | `--force_lang eng\|pcm\|ibo\|yor\|hau` | read the whole text in one language (tags are ignored) |
104
+ | `--lang_map "omo=pcm,sha=pcm"` | per-word language overrides for the tagger |
105
+ | `--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`) |
106
+ | `--seed N` | sampling seed (default 0). Try 2–3 seeds and keep the take you like |
107
+ | `--out file.wav` | output path (24 kHz WAV) |
108
+ | `--dtype auto\|bf16\|fp16\|fp32` | LM autocast dtype on GPU (samples: bf16). CPU always runs fp32 |
109
+ | `--stitch` | `phase3`: sentence by sentence instead of one pass (not recommended) |
110
+ | `--prompt_clip 0-3` | which anchor prompt clip (released systems: 0) |
111
+ | `--model_dir`, `--cosyvoice_dir`, `--lm`, `--flow`, `--prompts`, `--voices` | paths and checkpoint overrides |
112
+ | `--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 |
113
+
114
+ 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.
115
+
116
+ ### Controlling code-switching
117
+
118
+ **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.
119
 
120
  ```text
121
  <eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>
122
+ <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>
123
  <yor>Ẹ káàárọ̀ o, ṣé dáadáa ni?</yor> <eng>I will send you the report this afternoon.</eng>
 
124
  <hau>Ina kwana, yaya aiki?</hau> <eng>The meeting has been moved to Friday morning.</eng>
125
  <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>
126
+ <eng>Please tell everyone that</eng> <yor>a ó pàdé ní ọ̀la.</yor>
127
  ```
128
 
129
+ 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.
130
 
131
+ **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:
132
+
133
+ ```bash
134
+ 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"
135
+ # [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>
136
+ python infer.py --auto_tag --text "..." --out out.wav # tag, print, synthesise
137
+ python tagger.py "Kedu ka ị mere? Abeg no vex, I go call you for evening." # tagger on its own
138
+ ```
139
+
140
+ Write Yoruba and Igbo with their diacritics where you can; text without diacritics is much harder to identify. See the tagger accuracy below.
141
+
142
+ **3. `--force_lang`** reads everything in one language, e.g. `--force_lang ibo`.
143
+
144
+ **4. `--lang_map`** fixes words the tagger gets wrong, e.g. `--lang_map "omo=pcm,sha=pcm,oga=pcm"`.
145
 
146
+ ### Gradio app (local)
 
147
 
148
+ ```bash
149
+ python app.py --cosyvoice_dir ../CosyVoice # http://127.0.0.1:7860 (--share for a public link)
150
+ ```
151
+
152
+ 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.
153
+
154
+ ### From Python
155
 
156
+ ```python
157
+ import types, soundfile as sf, infer
158
+ a = types.SimpleNamespace(model_dir="pretrained/CosyVoice2-0.5B", cosyvoice_dir="../CosyVoice", lm="", flow="",
159
+ prompts="prompts/prompts.json", voices="prompts/voices.json", dtype="auto", stitch=False,
160
+ rate="default", voice="waxal_ibo_5_female")
161
+ tts = infer.build("phase3", a)
162
+ wav = tts.say("<ibo>Kedu ka ị mere?</ibo> <eng>The meeting has been moved to Friday morning.</eng>", seed=0)
163
+ sf.write("out.wav", wav, tts.sr)
164
+ ```
165
+
166
+ ## Samples
167
 
168
+ 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").
 
 
 
 
169
 
170
+ | input | Phase 2, anchor_male | Phase 3, anchor_male | Phase 3, female |
171
+ |---|---|---|---|
172
+ | 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) |
173
+ | 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) |
174
+ | para_hau_eng_pcm | [mp3](samples/phase3/phase2/para_hau_eng_pcm.mp3) | [mp3](samples/phase3/phase3/para_hau_eng_pcm.mp3) | – |
175
+ | 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) |
176
+ | 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) |
177
+ | 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) |
178
 
179
+ 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).
180
 
181
+ ## Evaluation: Phase 2 vs Phase 3
182
+
183
+ 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.
184
+
185
+ | system | ibo | hau | yor | pcm | eng | CS | para (hand / native) | SIM |
186
  |---|---|---|---|---|---|---|---|---|
187
+ | `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 |
188
+ | 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 |
189
+ | **`phase3`** | **0.219 (0.173)** | **0.151** | **0.233 (0.145)** | 0.202 | 0.043 | **0.151** | **0.170** / 0.155 | **0.788** |
 
 
 
190
 
191
+ Same decoding, Phase 3 LM vs Phase 2 LM: lower CER in every language except Pidgin, same speaker similarity.
192
 
193
+ | 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 |
194
+ |---|---|---|---|---|---|---|---|
195
+ | real anchor recordings | 0.417 | 0.543 | 0.624 | 0 | 0.16 | 15% | 4.43 |
196
+ | Phase 2 LM, Phase 3 decoding | 0.403 | 0.389 | 0.329 | 0.194 | 0.08 | 63% | 4.36 |
197
+ | **`phase3`** | **0.422** | **0.453** | 0.319 | **0.161** | **0.17** | **11%** | 4.44 |
198
 
199
+ Female voices (29-item subset, `phase3`):
200
 
201
+ | voice | CER | no-tone CER | SIM to the speaker's own real clips | clipped endings |
202
+ |---|---|---|---|---|
203
+ | `anchor_male` (same items) | 0.182 | 0.141 | 0.12–0.13 (it is a different speaker) | 17% |
204
+ | `waxal_ibo_5_female` | **0.149** | **0.101** | 0.64 | 21% |
205
+ | `waxal_yor_5_female` | 0.171 | 0.134 | **0.76** | 62% |
206
 
207
+ Naturalness has not been rated by native speakers (no MOS or blind A/B yet). The numbers above are automatic proxies.
 
 
 
 
208
 
209
+ ### Auto-tagger accuracy
210
 
211
+ Word-level accuracy of `tagger.py` against hand-tagged text (no GlotLID / with `--lid glotlid`):
 
 
 
 
 
212
 
213
+ | test set | words | rule-based | + GlotLID | exact sentences (rule-based) |
214
+ |---|---|---|---|---|
215
+ | the 6 sample inputs above | 145 | 1.000 | 1.000 | 6/6 |
216
+ | 10 new mixed sentences, written before the lexicons | 113 | 0.991 | 0.991 | 9/10 |
217
+ | 12 code-switched eval items (overlaps the samples) | 206 | 0.995 | 1.000 | 11/12 |
218
+ | 150 single-language eval sentences (real WAXAL/FLEURS text, never looked at while building the lexicons) | 1,697 | **0.898** | **0.915** | 115/150 |
219
 
220
+ 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.
221
 
222
+ ## Limitations
223
+
224
+ - **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.
225
+ - **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).
226
+ - **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.
227
  - English after a Nigerian-language span keeps a strong local accent.
228
+ - Yoruba tone quality needs native listening; the automatic tone metric is saturated.
229
+ - Female voices are zero-shot; `waxal_yor_5_female` copies its short prompt (short pauses, clipped endings).
230
+ - Not evaluated for numbers, abbreviations or URLs; write numbers as words.
231
+ - 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.
232
 
233
+ ## What is in the repo
234
 
235
+ | path | what it is | licence |
236
+ |---|---|---|
237
+ | `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 |
238
+ | `checkpoints/phase2/p2_lm_B/` | Phase 2 LM (`best.pt`) and `spk2id.json` | CC-BY-SA-4.0 |
239
+ | `checkpoints/phase2/p2_flow_sft/best.pt` | anchor-SFT flow-matching decoder, used by both versions | CC-BY-SA-4.0 |
240
+ | `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 |
241
+ | `samples/` | example renders | CC-BY-SA-4.0 |
242
+ | `settings.json` | versions, voices, recommended pairing | Apache-2.0 |
243
+ | `infer.py`, `tagger.py`, `app.py` | inference CLI, auto-tagger, Gradio app | Apache-2.0 |
244
+ | `code/` | the original training and synthesis code, for reference | Apache-2.0 |
245
+ | `requirements.txt`, `cosyvoice_requirements.txt` | dependencies (`cosyvoice_requirements.txt` is CosyVoice's own file at the commit used) | – |
246
+ | `assets/` | banner | – |
247
+
248
+ ## Training data and attribution
249
+
250
+ 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).
251
+
252
+ | dataset | used for | licence | attribution |
253
+ |---|---|---|---|
254
+ | [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) |
255
+ | [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. |
256
+ | [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) |
257
+ | [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 |
258
+ | [IgboSynCorp](https://doi.org/10.7910/dvn/yb9fwk) | natural Igbo speech (Phase 3) | CC0-1.0 | University of Ibadan and Afe Babalola University (Lacuna Fund) |
259
+ | [FLEURS](https://huggingface.co/datasets/google/fleurs) (English, Hausa, Igbo, Yoruba) | multi-speaker data | CC-BY-4.0 | Google, FLEURS (Conneau et al., 2022) |
260
+ | [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 |
261
+ | [OpenSLR SLR70](https://openslr.org/70/) (Nigerian English) | English | CC-BY-SA-4.0 | Google, crowdsourced Nigerian English |
262
+ | [OpenSLR SLR86](https://openslr.org/86/) (Yoruba) | Yoruba | CC-BY-SA-4.0 | Google, crowdsourced Yoruba |
263
+ | 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 |
264
+ | 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) |
265
+ | 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) |
266
+
267
+ Excluded on licence grounds: WaZoBiaSpeech (bans TTS use), NaijaVoices (non-commercial), and non-commercial TTS weights.
268
 
269
  ## Licence
270
 
271
+ - **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.
272
+ - **Code** (`infer.py`, `tagger.py`, `app.py`, `code/`, `settings.json`): **[Apache-2.0](LICENSE-CODE)**.
273
+ - The base model, CosyVoice2-0.5B, is Apache-2.0; its notices still apply to the parts it contributes.
274
+
275
+ ## Citation
276
+
277
+ ```bibtex
278
+ @misc{dynamicaccent2026,
279
+ title = {Dynamic Accent: Code-switching TTS for Nigerian languages},
280
+ author = {Modular},
281
+ year = {2026},
282
+ url = {https://huggingface.co/Modularcomputing/dynamic-accent-preview}
283
+ }
284
+ ```
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Dynamic Accent: local Gradio demo (single GPU).
3
+
4
+ pip install -r requirements.txt # includes gradio
5
+ python app.py --cosyvoice_dir ../CosyVoice # then open http://127.0.0.1:7860
6
+
7
+ Text box -> optional auto-tag (editable preview of the tagged text) -> version + voice + rate -> Synthesize, or
8
+ Compare to hear Phase 2 and Phase 3 on the same text, seed and voice. Both versions stay loaded (one shared CosyVoice2
9
+ base; each version has its own LM copy), so a GPU with >= 16 GB is recommended. CPU works but is slow.
10
+ """
11
+ import argparse, json, os, sys, types
12
+
13
+ HERE = os.path.dirname(os.path.abspath(__file__))
14
+ sys.path.insert(0, HERE)
15
+ import infer # noqa: E402
16
+ from tagger import Tagger, parse_map, strip_tags, TAGGED, LANGS # noqa: E402
17
+
18
+ EXAMPLES = [
19
+ "Omo, I'm very excited to announce this new model, ọ na-eme m obi ụtọ, I no go lie give you",
20
+ "Ẹ káàbọ̀ sí ìpàdé wa òní. Today we will talk about the new school calendar.",
21
+ "Barka da yamma, yaya kuke? The weather report says it will rain tomorrow morning.",
22
+ "Kedu ka ị mere? Abeg no vex, I go call you for evening.",
23
+ ]
24
+
25
+
26
+ class Engine:
27
+ def __init__(self, a):
28
+ self.a = a
29
+ self.models = {}
30
+ self.cosy = self.tok = None
31
+ self.taggers = {}
32
+
33
+ def get(self, version, voice):
34
+ if version not in self.models:
35
+ ns = types.SimpleNamespace(**vars(self.a))
36
+ ns.voice = voice
37
+ S = infer.build(version, ns, self.cosy, self.tok, copy_llm=self.cosy is not None)
38
+ self.cosy, self.tok = S.cosy, S.tok
39
+ S._rate0 = S.rate
40
+ self.models[version] = S
41
+ S = self.models[version]
42
+ if getattr(S, "_voice_name", None) != voice:
43
+ S.set_voice(voice, self.a.voices); S._voice_name = voice
44
+ return S
45
+
46
+ def tag(self, text, lang_map, use_lid):
47
+ key = (lang_map, use_lid)
48
+ if key not in self.taggers:
49
+ self.taggers[key] = Tagger("glotlid" if use_lid else None, parse_map(lang_map))
50
+ return self.taggers[key].tag(strip_tags(text))
51
+
52
+ def say(self, text, version, voice, rate, seed):
53
+ S = self.get(version, voice)
54
+ S.rate = S._rate0 if rate == "default" else (infer.RATES.index(rate) if rate in infer.RATES else 0)
55
+ w = S.say(text, seed=int(seed))
56
+ return S.sr, w
57
+
58
+
59
+ def main():
60
+ ap = argparse.ArgumentParser()
61
+ ap.add_argument("--cosyvoice_dir", default=os.environ.get("COSYVOICE_REPO", os.path.join(HERE, "CosyVoice")))
62
+ ap.add_argument("--model_dir", default=os.environ.get("COSYVOICE2_DIR", os.path.join(HERE, "pretrained", "CosyVoice2-0.5B")))
63
+ ap.add_argument("--host", default="127.0.0.1")
64
+ ap.add_argument("--port", type=int, default=7860)
65
+ ap.add_argument("--share", action="store_true")
66
+ a = ap.parse_args()
67
+ for k, v in dict(lm="", flow="", prompts=os.path.join(HERE, "prompts", "prompts.json"),
68
+ voices=os.path.join(HERE, "prompts", "voices.json"), dtype="auto", stitch=False, rate="default").items():
69
+ setattr(a, k, v)
70
+ if not os.path.isdir(a.model_dir):
71
+ from huggingface_hub import snapshot_download
72
+ snapshot_download("FunAudioLLM/CosyVoice2-0.5B", local_dir=a.model_dir)
73
+
74
+ import gradio as gr
75
+ E = Engine(a)
76
+ S = infer.SETTINGS
77
+ voices = S.get("voices", ["anchor_male", "waxal_ibo_5_female", "waxal_yor_5_female"])
78
+ rec = S.get("recommended", {})
79
+ rates = ["default"] + S.get("rates", ["none", "very slow", "slow", "normal", "fast", "very fast"])
80
+
81
+ def prepare(text, auto, lang_map, use_lid, force):
82
+ if not text.strip():
83
+ return ""
84
+ if force and force != "off":
85
+ return f"<{force}>{strip_tags(text)}</{force}>"
86
+ if auto and not TAGGED.search(text):
87
+ return E.tag(text, lang_map or "", bool(use_lid))
88
+ return text
89
+
90
+ def hint(version, voice):
91
+ r = rec.get(version, [])
92
+ return "" if voice in r else f"Tip: {version} is recommended with {', '.join(r)}. {rec.get('note', '')}"
93
+
94
+ def synth(text, tagged, auto, lang_map, use_lid, force, version, voice, rate, seed):
95
+ t = tagged.strip() if tagged.strip() else prepare(text, auto, lang_map, use_lid, force)
96
+ sr, w = E.say(t, version, voice, rate, seed)
97
+ return t, (sr, w), hint(version, voice)
98
+
99
+ def compare(text, tagged, auto, lang_map, use_lid, force, voice, rate, seed):
100
+ t = tagged.strip() if tagged.strip() else prepare(text, auto, lang_map, use_lid, force)
101
+ a2 = E.say(t, "phase2", voice, rate, seed)
102
+ a3 = E.say(t, "phase3", voice, rate, seed)
103
+ return t, a2, a3
104
+
105
+ with gr.Blocks(title="Dynamic Accent: code-switching TTS") as demo:
106
+ gr.Markdown("## Dynamic Accent: code-switching TTS\nEnglish, Nigerian Pidgin, Igbo, Yoruba and Hausa, mixed in one "
107
+ "sentence. Tag spans with `<eng> <pcm> <ibo> <yor> <hau>`, or turn on auto-tag and edit its preview.")
108
+ text = gr.Textbox(label="Text", lines=3, value=EXAMPLES[0])
109
+ with gr.Row():
110
+ auto = gr.Checkbox(label="Auto-tag languages", value=True)
111
+ use_lid = gr.Checkbox(label="Use GlotLID (1.7 GB download, needs fasttext)", value=False)
112
+ force = gr.Dropdown(["off"] + LANGS, value="off", label="Force one language")
113
+ lang_map = gr.Textbox(label="Word overrides (e.g. omo=pcm,sha=pcm)", value="")
114
+ tag_btn = gr.Button("Preview tags")
115
+ tagged = gr.Textbox(label="Tagged text (edit before synthesis; leave empty to tag automatically)", lines=3)
116
+ with gr.Row():
117
+ version = gr.Dropdown(sorted(infer.VERSIONS), value=S.get("default_version", "phase3"), label="Version")
118
+ voice = gr.Dropdown(voices, value=S.get("default_voice", "anchor_male"), label="Voice")
119
+ rate = gr.Dropdown(rates, value="default", label="Rate (phase3)")
120
+ seed = gr.Number(value=0, precision=0, label="Seed")
121
+ tip = gr.Markdown()
122
+ with gr.Row():
123
+ go = gr.Button("Synthesize", variant="primary")
124
+ cmp_btn = gr.Button("Compare Phase 2 vs Phase 3")
125
+ out = gr.Audio(label="Output", type="numpy")
126
+ with gr.Row():
127
+ out2 = gr.Audio(label="Phase 2", type="numpy")
128
+ out3 = gr.Audio(label="Phase 3", type="numpy")
129
+ gr.Examples(EXAMPLES, inputs=text)
130
+
131
+ tag_btn.click(prepare, [text, auto, lang_map, use_lid, force], tagged)
132
+ version.change(hint, [version, voice], tip)
133
+ voice.change(hint, [version, voice], tip)
134
+ go.click(synth, [text, tagged, auto, lang_map, use_lid, force, version, voice, rate, seed], [tagged, out, tip])
135
+ cmp_btn.click(compare, [text, tagged, auto, lang_map, use_lid, force, voice, rate, seed], [tagged, out2, out3])
136
+ demo.queue(max_size=8).launch(server_name=a.host, server_port=a.port, share=a.share)
137
+
138
+
139
+ if __name__ == "__main__":
140
+ main()
assets/dynamic_accent_banner.jpg ADDED

Git LFS Details

  • SHA256: 0b51f3df37d126969b4f4b5e6737e9d92d6e91e5db78bac0cc47bd6d16ca55be
  • Pointer size: 131 Bytes
  • Size of remote file: 305 kB
checkpoints/phase2/p2_lm_B/spk2id.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"bible_hau": 0, "bible_yor": 1, "fleurs_eng_FEMALE_c000": 2, "fleurs_eng_FEMALE_c001": 3, "fleurs_eng_FEMALE_c002": 4, "fleurs_eng_FEMALE_c003": 5, "fleurs_eng_FEMALE_c004": 6, "fleurs_eng_FEMALE_c005": 7, "fleurs_eng_FEMALE_c006": 8, "fleurs_eng_FEMALE_c007": 9, "fleurs_eng_FEMALE_c008": 10, "fleurs_eng_FEMALE_c009": 11, "fleurs_eng_FEMALE_c010": 12, "fleurs_eng_FEMALE_c012": 13, "fleurs_eng_MALE_c000": 14, "fleurs_eng_MALE_c001": 15, "fleurs_eng_MALE_c002": 16, "fleurs_eng_MALE_c003": 17, "fleurs_eng_MALE_c004": 18, "fleurs_eng_MALE_c005": 19, "fleurs_eng_MALE_c006": 20, "fleurs_eng_MALE_c007": 21, "fleurs_hau_FEMALE_c000": 22, "fleurs_hau_FEMALE_c001": 23, "fleurs_hau_FEMALE_c003": 24, "fleurs_hau_FEMALE_misc": 25, "fleurs_hau_MALE_c000": 26, "fleurs_hau_MALE_c002": 27, "fleurs_hau_MALE_c003": 28, "fleurs_hau_MALE_c004": 29, "fleurs_hau_MALE_misc": 30, "fleurs_ibo_FEMALE_c000": 31, "fleurs_ibo_FEMALE_c001": 32, "fleurs_ibo_FEMALE_c002": 33, "fleurs_ibo_FEMALE_c003": 34, "fleurs_ibo_FEMALE_c004": 35, "fleurs_ibo_FEMALE_misc": 36, "fleurs_ibo_MALE_c000": 37, "fleurs_ibo_MALE_c001": 38, "fleurs_ibo_MALE_c002": 39, "fleurs_ibo_MALE_c004": 40, "fleurs_ibo_MALE_c005": 41, "fleurs_ibo_MALE_misc": 42, "fleurs_yor_FEMALE_c000": 43, "fleurs_yor_FEMALE_c001": 44, "fleurs_yor_FEMALE_c002": 45, "fleurs_yor_MALE_c000": 46, "fleurs_yor_MALE_c001": 47, "fleurs_yor_MALE_misc": 48, "slr70_ngf_00295": 49, "slr70_ngf_00610": 50, "slr70_ngf_01208": 51, "slr70_ngf_01523": 52, "slr70_ngf_02121": 53, "slr70_ngf_02436": 54, "slr70_ngf_02484": 55, "slr70_ngf_03349": 56, "slr70_ngf_03397": 57, "slr70_ngf_04310": 58, "slr70_ngf_05223": 59, "slr70_ngf_06136": 60, "slr70_ngf_07049": 61, "slr70_ngf_07508": 62, "slr70_ngf_08421": 63, "slr70_ngf_09334": 64, "slr70_ngf_12484": 65, "slr70_ngf_13397": 66, "slr70_ngf_15223": 67, "slr70_ngm_01523": 68, "slr70_ngm_02436": 69, "slr70_ngm_02484": 70, "slr70_ngm_03349": 71, "slr70_ngm_03397": 72, "slr70_ngm_07049": 73, "slr70_ngm_07508": 74, "slr70_ngm_08784": 75, "slr70_ngm_09334": 76, "slr70_ngm_09697": 77, "slr70_ngm_12484": 78, "slr70_ngm_14310": 79, "slr86_yof_00295": 80, "slr86_yof_00610": 81, "slr86_yof_01208": 82, "slr86_yof_02121": 83, "slr86_yof_02436": 84, "slr86_yof_02484": 85, "slr86_yof_03034": 86, "slr86_yof_03349": 87, "slr86_yof_03397": 88, "slr86_yof_04310": 89, "slr86_yof_05223": 90, "slr86_yof_06136": 91, "slr86_yof_07049": 92, "slr86_yof_07505": 93, "slr86_yof_07508": 94, "slr86_yof_08421": 95, "slr86_yof_08784": 96, "slr86_yof_09334": 97, "slr86_yof_09697": 98, "slr86_yom_00295": 99, "slr86_yom_00610": 100, "slr86_yom_01208": 101, "slr86_yom_01523": 102, "slr86_yom_02121": 103, "slr86_yom_02436": 104, "slr86_yom_02484": 105, "slr86_yom_03034": 106, "slr86_yom_03397": 107, "slr86_yom_04310": 108, "slr86_yom_06136": 109, "slr86_yom_07049": 110, "slr86_yom_07505": 111, "slr86_yom_07508": 112, "slr86_yom_08421": 113, "slr86_yom_08784": 114, "slr86_yom_09334": 115, "waxal_hau_1": 116, "waxal_hau_2": 117, "waxal_hau_3": 118, "waxal_hau_4": 119, "waxal_hau_5": 120, "waxal_hau_6": 121, "waxal_hau_7": 122, "waxal_hau_8": 123, "waxal_ibo_1": 124, "waxal_ibo_2": 125, "waxal_ibo_3": 126, "waxal_ibo_4": 127, "waxal_ibo_5": 128, "waxal_ibo_6": 129, "waxal_ibo_7": 130, "waxal_ibo_8": 131, "waxal_pcm_F1": 132, "waxal_pcm_F2": 133, "waxal_pcm_F3": 134, "waxal_pcm_F4": 135, "waxal_pcm_M1": 136, "waxal_pcm_M2": 137, "waxal_pcm_M3": 138, "waxal_pcm_M4": 139, "waxal_yor_1": 140, "waxal_yor_2": 141, "waxal_yor_3": 142, "waxal_yor_4": 143, "waxal_yor_5": 144, "waxal_yor_6": 145, "waxal_yor_7": 146, "waxal_yor_8": 147}
checkpoints/phase3/p3_lm_full/args.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"cmd": "lm", "arm": "b", "out": "/home/dev/runs/p3_lm_full", "init": "/home/dev/runs/p2_lm_B/best.pt", "flow_init": "/home/dev/runs/p2_flow_sft/best.pt", "minutes": 60.0, "max_steps": 5000, "lr": 1e-05, "lr_new": 0.0005, "warmup": 200, "tokens": 14000, "max_tok": 750, "adv_w": 0.02, "p_prompt": 0.5, "tag_drop": 0.5, "rate_drop": 0.5, "anchor_w": 1.0, "val_every": 500, "patience": 4, "hours_cap": 0, "quad": 600.0}
checkpoints/phase3/p3_lm_full/best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1fa9986713767e80b3d6a6b72cbb3d4e182e37179554d34bdb4dc7a86cd52b2c
3
+ size 2046917388
checkpoints/phase3/p3_lm_full/config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "rate": "normal",
3
+ "flow": "checkpoints/phase2/p2_flow_sft/best.pt",
4
+ "sampler": "silexcap",
5
+ "one_pass": true,
6
+ "note": "Phase 3 LM (p3_lm_full, best val step 4500). Uses the shared Phase 2 anchor-SFT flow; a Phase 3 flow SFT gave no measurable gain and is not shipped."
7
+ }
checkpoints/phase3/p3_lm_full/log.jsonl ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"step": 0, "val": {"np": 3.563712294792896, "p": 3.571646510972262, "anchor_p": 3.2171030044555664, "mean": 3.567679402882579}}
2
+ {"step": 25, "ce": 3.7304, "acc": 0.187, "adv": 8.175, "adv_acc": 0.129, "gn": 2.12, "B": 18, "lr": 1.25e-06, "skips": 0, "oom": 0, "min": 0.24}
3
+ {"step": 50, "ce": 4.0527, "acc": 0.1759, "adv": 7.922, "adv_acc": 0.0, "gn": 3.5, "B": 10, "lr": 2.499e-06, "skips": 0, "oom": 0, "min": 0.36}
4
+ {"step": 75, "ce": 4.1871, "acc": 0.1463, "adv": 8.59, "adv_acc": 0.009, "gn": 2.74, "B": 53, "lr": 3.748e-06, "skips": 0, "oom": 0, "min": 0.49}
5
+ {"step": 100, "ce": 3.4694, "acc": 0.2184, "adv": 4.838, "adv_acc": 0.0, "gn": 2.66, "B": 6, "lr": 4.996e-06, "skips": 1, "oom": 0, "min": 0.61}
6
+ {"step": 125, "ce": 3.7797, "acc": 0.1892, "adv": 5.106, "adv_acc": 0.029, "gn": 2.78, "B": 29, "lr": 6.241e-06, "skips": 1, "oom": 0, "min": 0.73}
7
+ {"step": 150, "ce": 3.6674, "acc": 0.2085, "adv": 4.72, "adv_acc": 0.001, "gn": 2.17, "B": 14, "lr": 7.485e-06, "skips": 2, "oom": 0, "min": 0.85}
8
+ {"step": 175, "ce": 3.8224, "acc": 0.178, "adv": 6.07, "adv_acc": 0.007, "gn": 2.08, "B": 34, "lr": 8.726e-06, "skips": 2, "oom": 0, "min": 0.97}
9
+ {"step": 200, "ce": 3.4966, "acc": 0.231, "adv": 5.114, "adv_acc": 0.18, "gn": 2.23, "B": 13, "lr": 9.965e-06, "skips": 2, "oom": 0, "min": 1.09}
10
+ {"step": 225, "ce": 3.6002, "acc": 0.2083, "adv": 4.485, "adv_acc": 0.172, "gn": 2.06, "B": 28, "lr": 9.956e-06, "skips": 2, "oom": 0, "min": 1.22}
11
+ {"step": 250, "ce": 3.831, "acc": 0.175, "adv": 5.128, "adv_acc": 0.004, "gn": 2.1, "B": 13, "lr": 9.945e-06, "skips": 2, "oom": 0, "min": 1.35}
12
+ {"step": 275, "ce": 3.5115, "acc": 0.2121, "adv": 5.416, "adv_acc": 0.0, "gn": 2.1, "B": 17, "lr": 9.933e-06, "skips": 2, "oom": 0, "min": 1.49}
13
+ {"step": 300, "ce": 3.6931, "acc": 0.1905, "adv": 5.637, "adv_acc": 0.011, "gn": 1.87, "B": 14, "lr": 9.921e-06, "skips": 2, "oom": 0, "min": 1.61}
14
+ {"step": 325, "ce": 3.933, "acc": 0.1594, "adv": 6.282, "adv_acc": 0.022, "gn": 2.38, "B": 41, "lr": 9.907e-06, "skips": 2, "oom": 0, "min": 1.73}
15
+ {"step": 350, "ce": 3.3591, "acc": 0.2514, "adv": 4.969, "adv_acc": 0.001, "gn": 2.06, "B": 10, "lr": 9.892e-06, "skips": 2, "oom": 0, "min": 1.85}
16
+ {"step": 375, "ce": 3.6679, "acc": 0.1897, "adv": 5.176, "adv_acc": 0.12, "gn": 1.9, "B": 15, "lr": 9.876e-06, "skips": 2, "oom": 0, "min": 1.97}
17
+ {"step": 400, "ce": 3.8537, "acc": 0.1769, "adv": 5.109, "adv_acc": 0.024, "gn": 1.81, "B": 23, "lr": 9.859e-06, "skips": 2, "oom": 0, "min": 2.07}
18
+ {"step": 425, "ce": 3.6942, "acc": 0.1925, "adv": 4.691, "adv_acc": 0.125, "gn": 2.17, "B": 26, "lr": 9.841e-06, "skips": 2, "oom": 0, "min": 2.2}
19
+ {"step": 450, "ce": 3.9306, "acc": 0.1615, "adv": 6.244, "adv_acc": 0.022, "gn": 2.23, "B": 44, "lr": 9.822e-06, "skips": 2, "oom": 0, "min": 2.33}
20
+ {"step": 475, "ce": 3.343, "acc": 0.2304, "adv": 4.348, "adv_acc": 0.115, "gn": 2.17, "B": 16, "lr": 9.802e-06, "skips": 2, "oom": 0, "min": 2.45}
21
+ {"step": 500, "ce": 3.6741, "acc": 0.1936, "adv": 4.489, "adv_acc": 0.025, "gn": 1.83, "B": 21, "lr": 9.781e-06, "skips": 3, "oom": 0, "min": 2.57}
22
+ {"step": 500, "val": {"np": 3.507689257786406, "p": 3.512645748616818, "anchor_p": 3.1165452003479004, "mean": 3.510167503201612}, "best": true}
23
+ {"step": 525, "ce": 3.7693, "acc": 0.1915, "adv": 8.908, "adv_acc": 0.0, "gn": 2.37, "B": 59, "lr": 9.758e-06, "skips": 3, "oom": 0, "min": 2.77}
24
+ {"step": 550, "ce": 3.5181, "acc": 0.2223, "adv": 4.695, "adv_acc": 0.103, "gn": 2.16, "B": 14, "lr": 9.735e-06, "skips": 4, "oom": 0, "min": 2.89}
25
+ {"step": 575, "ce": 3.5077, "acc": 0.2158, "adv": 4.792, "adv_acc": 0.0, "gn": 3.13, "B": 7, "lr": 9.71e-06, "skips": 4, "oom": 0, "min": 3.01}
26
+ {"step": 600, "ce": 3.4617, "acc": 0.2337, "adv": 4.699, "adv_acc": 0.032, "gn": 2.14, "B": 10, "lr": 9.685e-06, "skips": 4, "oom": 0, "min": 3.14}
27
+ {"step": 625, "ce": 3.5601, "acc": 0.1991, "adv": 4.77, "adv_acc": 0.021, "gn": 2.13, "B": 24, "lr": 9.659e-06, "skips": 5, "oom": 0, "min": 3.25}
28
+ {"step": 650, "ce": 3.3011, "acc": 0.2631, "adv": 4.122, "adv_acc": 0.011, "gn": 1.85, "B": 14, "lr": 9.631e-06, "skips": 5, "oom": 0, "min": 3.38}
29
+ {"step": 675, "ce": 3.5935, "acc": 0.2031, "adv": 4.653, "adv_acc": 0.033, "gn": 1.88, "B": 19, "lr": 9.602e-06, "skips": 5, "oom": 0, "min": 3.5}
30
+ {"step": 700, "ce": 3.2415, "acc": 0.2494, "adv": 4.693, "adv_acc": 0.003, "gn": 2.14, "B": 7, "lr": 9.573e-06, "skips": 5, "oom": 0, "min": 3.63}
31
+ {"step": 725, "ce": 3.2985, "acc": 0.2415, "adv": 4.372, "adv_acc": 0.129, "gn": 1.99, "B": 11, "lr": 9.542e-06, "skips": 5, "oom": 0, "min": 3.76}
32
+ {"step": 750, "ce": 3.5693, "acc": 0.1964, "adv": 4.211, "adv_acc": 0.047, "gn": 2.05, "B": 27, "lr": 9.511e-06, "skips": 5, "oom": 0, "min": 3.88}
33
+ {"step": 775, "ce": 3.26, "acc": 0.2431, "adv": 4.574, "adv_acc": 0.007, "gn": 2.29, "B": 8, "lr": 9.478e-06, "skips": 5, "oom": 0, "min": 4.01}
34
+ {"step": 800, "ce": 3.3265, "acc": 0.2363, "adv": 4.495, "adv_acc": 0.047, "gn": 2.32, "B": 9, "lr": 9.445e-06, "skips": 5, "oom": 0, "min": 4.13}
35
+ {"step": 825, "ce": 3.7204, "acc": 0.1812, "adv": 7.24, "adv_acc": 0.002, "gn": 2.3, "B": 29, "lr": 9.41e-06, "skips": 5, "oom": 0, "min": 4.27}
36
+ {"step": 850, "ce": 3.5532, "acc": 0.2079, "adv": 15.118, "adv_acc": 0.006, "gn": 3.38, "B": 11, "lr": 9.375e-06, "skips": 5, "oom": 0, "min": 4.38}
37
+ {"step": 875, "ce": 3.9259, "acc": 0.1713, "adv": 40.286, "adv_acc": 0.017, "gn": 4.35, "B": 41, "lr": 9.338e-06, "skips": 5, "oom": 0, "min": 4.51}
38
+ {"step": 900, "ce": 3.9721, "acc": 0.1658, "adv": 43.79, "adv_acc": 0.02, "gn": 6.45, "B": 51, "lr": 9.301e-06, "skips": 5, "oom": 0, "min": 4.62}
39
+ {"step": 925, "ce": 3.4603, "acc": 0.2349, "adv": 10.369, "adv_acc": 0.0, "gn": 2.68, "B": 14, "lr": 9.263e-06, "skips": 5, "oom": 0, "min": 4.74}
40
+ {"step": 950, "ce": 3.5721, "acc": 0.2092, "adv": 9.951, "adv_acc": 0.032, "gn": 2.45, "B": 20, "lr": 9.223e-06, "skips": 5, "oom": 0, "min": 4.85}
41
+ {"step": 975, "ce": 3.7512, "acc": 0.1921, "adv": 38.525, "adv_acc": 0.0, "gn": 5.27, "B": 65, "lr": 9.183e-06, "skips": 5, "oom": 0, "min": 4.97}
42
+ {"step": 1000, "ce": 3.4875, "acc": 0.2139, "adv": 9.408, "adv_acc": 0.104, "gn": 2.57, "B": 19, "lr": 9.142e-06, "skips": 5, "oom": 0, "min": 5.1}
43
+ {"step": 1000, "val": {"np": 3.4977280627632763, "p": 3.5005001353906886, "anchor_p": 3.1229519844055176, "mean": 3.4991140990769827}, "best": true}
44
+ {"step": 1025, "ce": 3.4877, "acc": 0.2195, "adv": 15.951, "adv_acc": 0.0, "gn": 3.29, "B": 16, "lr": 9.1e-06, "skips": 6, "oom": 0, "min": 5.31}
45
+ {"step": 1050, "ce": 3.4113, "acc": 0.2332, "adv": 19.778, "adv_acc": 0.08, "gn": 3.95, "B": 9, "lr": 9.057e-06, "skips": 6, "oom": 0, "min": 5.43}
46
+ {"step": 1075, "ce": 3.1604, "acc": 0.2612, "adv": 10.481, "adv_acc": 0.001, "gn": 3.24, "B": 7, "lr": 9.014e-06, "skips": 6, "oom": 0, "min": 5.54}
47
+ {"step": 1100, "ce": 3.9686, "acc": 0.1677, "adv": 33.668, "adv_acc": 0.0, "gn": 4.65, "B": 58, "lr": 8.969e-06, "skips": 6, "oom": 0, "min": 5.66}
48
+ {"step": 1125, "ce": 3.8179, "acc": 0.1783, "adv": 16.777, "adv_acc": 0.002, "gn": 3.08, "B": 36, "lr": 8.924e-06, "skips": 6, "oom": 0, "min": 5.78}
49
+ {"step": 1150, "ce": 3.6925, "acc": 0.1956, "adv": 10.705, "adv_acc": 0.012, "gn": 2.96, "B": 14, "lr": 8.877e-06, "skips": 6, "oom": 0, "min": 5.89}
50
+ {"step": 1175, "ce": 3.746, "acc": 0.1831, "adv": 11.042, "adv_acc": 0.001, "gn": 2.63, "B": 18, "lr": 8.83e-06, "skips": 6, "oom": 0, "min": 6.01}
51
+ {"step": 1200, "ce": 3.6621, "acc": 0.195, "adv": 8.882, "adv_acc": 0.0, "gn": 2.38, "B": 10, "lr": 8.782e-06, "skips": 6, "oom": 0, "min": 6.14}
52
+ {"step": 1225, "ce": 3.4611, "acc": 0.2234, "adv": 12.372, "adv_acc": 0.047, "gn": 2.56, "B": 18, "lr": 8.734e-06, "skips": 6, "oom": 0, "min": 6.26}
53
+ {"step": 1250, "ce": 3.7915, "acc": 0.1761, "adv": 9.404, "adv_acc": 0.045, "gn": 2.54, "B": 13, "lr": 8.684e-06, "skips": 6, "oom": 0, "min": 6.37}
54
+ {"step": 1275, "ce": 3.766, "acc": 0.1962, "adv": 22.222, "adv_acc": 0.0, "gn": 3.98, "B": 57, "lr": 8.634e-06, "skips": 6, "oom": 0, "min": 6.49}
55
+ {"step": 1300, "ce": 3.772, "acc": 0.1855, "adv": 9.375, "adv_acc": 0.001, "gn": 2.33, "B": 22, "lr": 8.583e-06, "skips": 6, "oom": 0, "min": 6.61}
56
+ {"step": 1325, "ce": 3.2919, "acc": 0.2332, "adv": 4.917, "adv_acc": 0.053, "gn": 2.88, "B": 7, "lr": 8.531e-06, "skips": 6, "oom": 0, "min": 6.73}
57
+ {"step": 1350, "ce": 3.3606, "acc": 0.2269, "adv": 8.513, "adv_acc": 0.001, "gn": 2.57, "B": 13, "lr": 8.478e-06, "skips": 6, "oom": 0, "min": 6.85}
58
+ {"step": 1375, "ce": 3.4161, "acc": 0.2352, "adv": 6.922, "adv_acc": 0.013, "gn": 2.26, "B": 14, "lr": 8.425e-06, "skips": 6, "oom": 0, "min": 6.96}
59
+ {"step": 1400, "ce": 3.325, "acc": 0.2363, "adv": 8.211, "adv_acc": 0.007, "gn": 2.91, "B": 9, "lr": 8.371e-06, "skips": 6, "oom": 0, "min": 7.08}
60
+ {"step": 1425, "ce": 3.7772, "acc": 0.1893, "adv": 22.674, "adv_acc": 0.0, "gn": 4.1, "B": 61, "lr": 8.316e-06, "skips": 6, "oom": 0, "min": 7.2}
61
+ {"step": 1450, "ce": 3.5065, "acc": 0.2128, "adv": 7.335, "adv_acc": 0.032, "gn": 2.11, "B": 29, "lr": 8.26e-06, "skips": 6, "oom": 0, "min": 7.33}
62
+ {"step": 1475, "ce": 3.5474, "acc": 0.2084, "adv": 6.431, "adv_acc": 0.009, "gn": 2.18, "B": 26, "lr": 8.204e-06, "skips": 6, "oom": 0, "min": 7.44}
63
+ {"step": 1500, "ce": 3.7764, "acc": 0.1759, "adv": 11.695, "adv_acc": 0.041, "gn": 2.27, "B": 43, "lr": 8.147e-06, "skips": 7, "oom": 0, "min": 7.56}
64
+ {"step": 1500, "val": {"np": 3.483489041219705, "p": 3.4874038346815577, "anchor_p": 3.090611457824707, "mean": 3.4854464379506314}, "best": true}
65
+ {"step": 1525, "ce": 3.5611, "acc": 0.2128, "adv": 6.366, "adv_acc": 0.017, "gn": 2.16, "B": 12, "lr": 8.09e-06, "skips": 7, "oom": 0, "min": 7.76}
66
+ {"step": 1550, "ce": 3.571, "acc": 0.2182, "adv": 11.321, "adv_acc": 0.004, "gn": 3.66, "B": 76, "lr": 8.032e-06, "skips": 7, "oom": 0, "min": 7.89}
67
+ {"step": 1575, "ce": 3.4323, "acc": 0.2329, "adv": 6.34, "adv_acc": 0.015, "gn": 2.26, "B": 10, "lr": 7.973e-06, "skips": 7, "oom": 0, "min": 7.99}
68
+ {"step": 1600, "ce": 3.7183, "acc": 0.1903, "adv": 11.932, "adv_acc": 0.005, "gn": 3.07, "B": 51, "lr": 7.914e-06, "skips": 7, "oom": 0, "min": 8.1}
69
+ {"step": 1625, "ce": 3.4348, "acc": 0.2274, "adv": 6.191, "adv_acc": 0.005, "gn": 2.24, "B": 12, "lr": 7.854e-06, "skips": 7, "oom": 0, "min": 8.23}
70
+ {"step": 1650, "ce": 3.5439, "acc": 0.1908, "adv": 7.033, "adv_acc": 0.0, "gn": 2.77, "B": 6, "lr": 7.793e-06, "skips": 7, "oom": 0, "min": 8.35}
71
+ {"step": 1675, "ce": 3.7204, "acc": 0.1895, "adv": 6.672, "adv_acc": 0.041, "gn": 1.98, "B": 27, "lr": 7.732e-06, "skips": 7, "oom": 0, "min": 8.47}
72
+ {"step": 1700, "ce": 3.6902, "acc": 0.1898, "adv": 6.503, "adv_acc": 0.031, "gn": 2.17, "B": 29, "lr": 7.67e-06, "skips": 8, "oom": 0, "min": 8.6}
73
+ {"step": 1725, "ce": 3.5412, "acc": 0.2078, "adv": 6.138, "adv_acc": 0.022, "gn": 2.09, "B": 21, "lr": 7.608e-06, "skips": 8, "oom": 0, "min": 8.73}
74
+ {"step": 1750, "ce": 3.5848, "acc": 0.208, "adv": 6.601, "adv_acc": 0.056, "gn": 2.2, "B": 10, "lr": 7.545e-06, "skips": 8, "oom": 0, "min": 8.84}
75
+ {"step": 1775, "ce": 3.4334, "acc": 0.2558, "adv": 18.173, "adv_acc": 0.0, "gn": 3.74, "B": 69, "lr": 7.482e-06, "skips": 8, "oom": 0, "min": 8.95}
76
+ {"step": 1800, "ce": 3.3952, "acc": 0.2212, "adv": 4.707, "adv_acc": 0.064, "gn": 2.3, "B": 9, "lr": 7.419e-06, "skips": 8, "oom": 0, "min": 9.07}
77
+ {"step": 1825, "ce": 3.6546, "acc": 0.2024, "adv": 6.154, "adv_acc": 0.044, "gn": 2.32, "B": 33, "lr": 7.354e-06, "skips": 8, "oom": 0, "min": 9.19}
78
+ {"step": 1850, "ce": 3.5126, "acc": 0.2026, "adv": 6.185, "adv_acc": 0.01, "gn": 2.16, "B": 26, "lr": 7.29e-06, "skips": 8, "oom": 0, "min": 9.3}
79
+ {"step": 1875, "ce": 3.0871, "acc": 0.2778, "adv": 4.731, "adv_acc": 0.098, "gn": 2.17, "B": 10, "lr": 7.225e-06, "skips": 8, "oom": 0, "min": 9.41}
80
+ {"step": 1900, "ce": 3.1692, "acc": 0.2457, "adv": 5.498, "adv_acc": 0.015, "gn": 2.32, "B": 6, "lr": 7.159e-06, "skips": 9, "oom": 0, "min": 9.53}
81
+ {"step": 1925, "ce": 3.6654, "acc": 0.1816, "adv": 11.98, "adv_acc": 0.001, "gn": 2.36, "B": 41, "lr": 7.093e-06, "skips": 9, "oom": 0, "min": 9.65}
82
+ {"step": 1950, "ce": 3.691, "acc": 0.1926, "adv": 15.862, "adv_acc": 0.0, "gn": 3.8, "B": 67, "lr": 7.027e-06, "skips": 9, "oom": 0, "min": 9.76}
83
+ {"step": 1975, "ce": 3.569, "acc": 0.2165, "adv": 5.811, "adv_acc": 0.034, "gn": 2.0, "B": 16, "lr": 6.96e-06, "skips": 9, "oom": 0, "min": 9.89}
84
+ {"step": 2000, "ce": 3.6569, "acc": 0.2025, "adv": 5.672, "adv_acc": 0.011, "gn": 2.62, "B": 24, "lr": 6.893e-06, "skips": 9, "oom": 0, "min": 10.01}
85
+ {"step": 2000, "val": {"np": 3.4673069890236623, "p": 3.473822727265498, "anchor_p": 3.0952401161193848, "mean": 3.47056485814458}, "best": true}
86
+ {"step": 2025, "ce": 3.5417, "acc": 0.2055, "adv": 6.049, "adv_acc": 0.031, "gn": 2.45, "B": 10, "lr": 6.826e-06, "skips": 9, "oom": 0, "min": 10.2}
87
+ {"step": 2050, "ce": 3.3296, "acc": 0.2364, "adv": 5.457, "adv_acc": 0.018, "gn": 2.29, "B": 8, "lr": 6.758e-06, "skips": 9, "oom": 0, "min": 10.32}
88
+ {"step": 2075, "ce": 3.39, "acc": 0.2286, "adv": 5.628, "adv_acc": 0.001, "gn": 2.08, "B": 13, "lr": 6.69e-06, "skips": 9, "oom": 0, "min": 10.43}
89
+ {"step": 2100, "ce": 3.7423, "acc": 0.1898, "adv": 10.36, "adv_acc": 0.003, "gn": 2.33, "B": 43, "lr": 6.622e-06, "skips": 9, "oom": 0, "min": 10.56}
90
+ {"step": 2125, "ce": 3.6225, "acc": 0.2003, "adv": 6.223, "adv_acc": 0.005, "gn": 1.99, "B": 13, "lr": 6.553e-06, "skips": 9, "oom": 0, "min": 10.68}
91
+ {"step": 2150, "ce": 3.4632, "acc": 0.2213, "adv": 5.662, "adv_acc": 0.0, "gn": 1.88, "B": 16, "lr": 6.484e-06, "skips": 9, "oom": 0, "min": 10.8}
92
+ {"step": 2175, "ce": 3.2873, "acc": 0.2562, "adv": 5.891, "adv_acc": 0.004, "gn": 2.14, "B": 10, "lr": 6.415e-06, "skips": 9, "oom": 0, "min": 10.92}
93
+ {"step": 2200, "ce": 3.9016, "acc": 0.1671, "adv": 8.882, "adv_acc": 0.005, "gn": 2.36, "B": 41, "lr": 6.346e-06, "skips": 9, "oom": 0, "min": 11.03}
94
+ {"step": 2225, "ce": 2.9077, "acc": 0.3194, "adv": 5.976, "adv_acc": 0.013, "gn": 2.83, "B": 7, "lr": 6.276e-06, "skips": 9, "oom": 0, "min": 11.15}
95
+ {"step": 2250, "ce": 3.6433, "acc": 0.2093, "adv": 13.079, "adv_acc": 0.001, "gn": 3.92, "B": 54, "lr": 6.207e-06, "skips": 9, "oom": 0, "min": 11.26}
96
+ {"step": 2275, "ce": 3.4893, "acc": 0.2168, "adv": 5.574, "adv_acc": 0.06, "gn": 1.87, "B": 14, "lr": 6.137e-06, "skips": 10, "oom": 0, "min": 11.38}
97
+ {"step": 2300, "ce": 3.704, "acc": 0.1807, "adv": 10.287, "adv_acc": 0.0, "gn": 2.77, "B": 50, "lr": 6.067e-06, "skips": 10, "oom": 0, "min": 11.5}
98
+ {"step": 2325, "ce": 3.8156, "acc": 0.1893, "adv": 13.466, "adv_acc": 0.0, "gn": 3.07, "B": 55, "lr": 5.997e-06, "skips": 10, "oom": 0, "min": 11.61}
99
+ {"step": 2350, "ce": 3.5805, "acc": 0.2117, "adv": 14.332, "adv_acc": 0.005, "gn": 3.27, "B": 68, "lr": 5.926e-06, "skips": 10, "oom": 0, "min": 11.74}
100
+ {"step": 2375, "ce": 3.3738, "acc": 0.2044, "adv": 6.482, "adv_acc": 0.0, "gn": 2.93, "B": 6, "lr": 5.856e-06, "skips": 10, "oom": 0, "min": 11.87}
101
+ {"step": 2400, "ce": 3.6756, "acc": 0.1828, "adv": 6.471, "adv_acc": 0.005, "gn": 1.93, "B": 12, "lr": 5.785e-06, "skips": 10, "oom": 0, "min": 11.99}
102
+ {"step": 2425, "ce": 3.3128, "acc": 0.2365, "adv": 6.476, "adv_acc": 0.013, "gn": 1.89, "B": 10, "lr": 5.715e-06, "skips": 10, "oom": 0, "min": 12.1}
103
+ {"step": 2450, "ce": 3.4673, "acc": 0.2181, "adv": 6.946, "adv_acc": 0.015, "gn": 2.02, "B": 33, "lr": 5.644e-06, "skips": 10, "oom": 0, "min": 12.22}
104
+ {"step": 2475, "ce": 3.5298, "acc": 0.2094, "adv": 6.09, "adv_acc": 0.039, "gn": 1.71, "B": 19, "lr": 5.574e-06, "skips": 10, "oom": 0, "min": 12.32}
105
+ {"step": 2500, "ce": 3.2715, "acc": 0.2515, "adv": 6.632, "adv_acc": 0.121, "gn": 1.87, "B": 17, "lr": 5.503e-06, "skips": 10, "oom": 0, "min": 12.43}
106
+ {"step": 2500, "val": {"np": 3.455619011329129, "p": 3.4601697774197455, "anchor_p": 3.0822691917419434, "mean": 3.4578943943744376}, "best": true}
107
+ {"step": 2525, "ce": 3.7506, "acc": 0.183, "adv": 15.727, "adv_acc": 0.002, "gn": 2.97, "B": 55, "lr": 5.432e-06, "skips": 10, "oom": 0, "min": 12.63}
108
+ {"step": 2550, "ce": 3.2503, "acc": 0.2422, "adv": 6.394, "adv_acc": 0.012, "gn": 2.1, "B": 10, "lr": 5.361e-06, "skips": 11, "oom": 0, "min": 12.75}
109
+ {"step": 2575, "ce": 3.3457, "acc": 0.2401, "adv": 6.05, "adv_acc": 0.119, "gn": 1.93, "B": 17, "lr": 5.291e-06, "skips": 11, "oom": 0, "min": 12.86}
110
+ {"step": 2600, "ce": 3.6437, "acc": 0.1846, "adv": 6.671, "adv_acc": 0.061, "gn": 1.87, "B": 29, "lr": 5.22e-06, "skips": 11, "oom": 0, "min": 12.97}
111
+ {"step": 2625, "ce": 3.5463, "acc": 0.2367, "adv": 14.848, "adv_acc": 0.003, "gn": 3.72, "B": 74, "lr": 5.15e-06, "skips": 11, "oom": 0, "min": 13.09}
112
+ {"step": 2650, "ce": 3.3847, "acc": 0.2314, "adv": 6.816, "adv_acc": 0.016, "gn": 2.39, "B": 9, "lr": 5.079e-06, "skips": 11, "oom": 0, "min": 13.2}
113
+ {"step": 2675, "ce": 3.5453, "acc": 0.2041, "adv": 6.815, "adv_acc": 0.003, "gn": 2.07, "B": 23, "lr": 5.009e-06, "skips": 11, "oom": 0, "min": 13.31}
114
+ {"step": 2700, "ce": 3.3028, "acc": 0.2437, "adv": 7.003, "adv_acc": 0.128, "gn": 1.87, "B": 15, "lr": 4.939e-06, "skips": 11, "oom": 0, "min": 13.43}
115
+ {"step": 2725, "ce": 3.8053, "acc": 0.1819, "adv": 9.202, "adv_acc": 0.03, "gn": 2.73, "B": 42, "lr": 4.869e-06, "skips": 11, "oom": 0, "min": 13.54}
116
+ {"step": 2750, "ce": 3.586, "acc": 0.2259, "adv": 10.705, "adv_acc": 0.038, "gn": 3.32, "B": 76, "lr": 4.799e-06, "skips": 12, "oom": 0, "min": 13.66}
117
+ {"step": 2775, "ce": 3.242, "acc": 0.2543, "adv": 7.569, "adv_acc": 0.147, "gn": 1.83, "B": 14, "lr": 4.729e-06, "skips": 12, "oom": 0, "min": 13.77}
118
+ {"step": 2800, "ce": 3.5247, "acc": 0.2002, "adv": 7.495, "adv_acc": 0.0, "gn": 1.98, "B": 12, "lr": 4.66e-06, "skips": 12, "oom": 0, "min": 13.88}
119
+ {"step": 2825, "ce": 3.5414, "acc": 0.2004, "adv": 7.73, "adv_acc": 0.06, "gn": 2.6, "B": 40, "lr": 4.59e-06, "skips": 12, "oom": 0, "min": 13.99}
120
+ {"step": 2850, "ce": 3.2731, "acc": 0.2393, "adv": 7.595, "adv_acc": 0.051, "gn": 1.9, "B": 21, "lr": 4.521e-06, "skips": 12, "oom": 0, "min": 14.1}
121
+ {"step": 2875, "ce": 3.427, "acc": 0.2152, "adv": 7.72, "adv_acc": 0.0, "gn": 1.96, "B": 10, "lr": 4.452e-06, "skips": 13, "oom": 0, "min": 14.23}
122
+ {"step": 2900, "ce": 3.4472, "acc": 0.2124, "adv": 7.726, "adv_acc": 0.0, "gn": 1.9, "B": 14, "lr": 4.384e-06, "skips": 13, "oom": 0, "min": 14.34}
123
+ {"step": 2925, "ce": 3.3561, "acc": 0.228, "adv": 7.77, "adv_acc": 0.139, "gn": 1.95, "B": 17, "lr": 4.315e-06, "skips": 13, "oom": 0, "min": 14.45}
124
+ {"step": 2950, "ce": 3.6581, "acc": 0.2103, "adv": 8.242, "adv_acc": 0.001, "gn": 2.15, "B": 53, "lr": 4.247e-06, "skips": 13, "oom": 0, "min": 14.58}
125
+ {"step": 2975, "ce": 3.5687, "acc": 0.2064, "adv": 7.748, "adv_acc": 0.0, "gn": 2.09, "B": 11, "lr": 4.18e-06, "skips": 13, "oom": 0, "min": 14.69}
126
+ {"step": 3000, "ce": 3.8188, "acc": 0.1662, "adv": 8.003, "adv_acc": 0.01, "gn": 11.11, "B": 52, "lr": 4.112e-06, "skips": 13, "oom": 0, "min": 14.81}
127
+ {"step": 3000, "val": {"np": 3.458494051271619, "p": 3.4631192987050605, "anchor_p": 3.0834555625915527, "mean": 3.4608066749883397}, "best": false}
128
+ {"step": 3025, "ce": 3.8608, "acc": 0.1688, "adv": 7.726, "adv_acc": 0.001, "gn": 2.45, "B": 47, "lr": 4.045e-06, "skips": 14, "oom": 0, "min": 14.96}
129
+ {"step": 3050, "ce": 3.5531, "acc": 0.2074, "adv": 7.65, "adv_acc": 0.193, "gn": 2.03, "B": 13, "lr": 3.978e-06, "skips": 14, "oom": 0, "min": 15.07}
130
+ {"step": 3075, "ce": 3.6485, "acc": 0.2358, "adv": 8.172, "adv_acc": 0.0, "gn": 2.18, "B": 54, "lr": 3.912e-06, "skips": 14, "oom": 0, "min": 15.19}
131
+ {"step": 3100, "ce": 3.6049, "acc": 0.1975, "adv": 7.879, "adv_acc": 0.015, "gn": 1.97, "B": 43, "lr": 3.846e-06, "skips": 14, "oom": 0, "min": 15.3}
132
+ {"step": 3125, "ce": 3.4728, "acc": 0.2281, "adv": 7.707, "adv_acc": 0.002, "gn": 1.97, "B": 15, "lr": 3.781e-06, "skips": 14, "oom": 0, "min": 15.4}
133
+ {"step": 3150, "ce": 3.1627, "acc": 0.248, "adv": 7.661, "adv_acc": 0.001, "gn": 2.15, "B": 7, "lr": 3.715e-06, "skips": 14, "oom": 0, "min": 15.51}
134
+ {"step": 3175, "ce": 3.632, "acc": 0.1925, "adv": 8.086, "adv_acc": 0.0, "gn": 2.32, "B": 61, "lr": 3.651e-06, "skips": 15, "oom": 0, "min": 15.64}
135
+ {"step": 3200, "ce": 3.1466, "acc": 0.2697, "adv": 7.568, "adv_acc": 0.001, "gn": 2.21, "B": 8, "lr": 3.587e-06, "skips": 15, "oom": 0, "min": 15.76}
136
+ {"step": 3225, "ce": 3.4873, "acc": 0.2071, "adv": 7.532, "adv_acc": 0.0, "gn": 2.03, "B": 15, "lr": 3.523e-06, "skips": 15, "oom": 0, "min": 15.87}
137
+ {"step": 3250, "ce": 3.4786, "acc": 0.2021, "adv": 7.501, "adv_acc": 0.078, "gn": 1.92, "B": 24, "lr": 3.46e-06, "skips": 15, "oom": 0, "min": 15.97}
138
+ {"step": 3275, "ce": 3.8398, "acc": 0.1564, "adv": 7.779, "adv_acc": 0.0, "gn": 2.0, "B": 45, "lr": 3.397e-06, "skips": 15, "oom": 0, "min": 16.09}
139
+ {"step": 3300, "ce": 3.4721, "acc": 0.1948, "adv": 7.401, "adv_acc": 0.0, "gn": 2.6, "B": 6, "lr": 3.335e-06, "skips": 15, "oom": 0, "min": 16.2}
140
+ {"step": 3325, "ce": 3.4505, "acc": 0.2151, "adv": 7.363, "adv_acc": 0.037, "gn": 2.08, "B": 19, "lr": 3.273e-06, "skips": 17, "oom": 0, "min": 16.32}
141
+ {"step": 3350, "ce": 3.6933, "acc": 0.1805, "adv": 8.033, "adv_acc": 0.0, "gn": 2.17, "B": 54, "lr": 3.212e-06, "skips": 17, "oom": 0, "min": 16.43}
142
+ {"step": 3375, "ce": 3.6515, "acc": 0.1935, "adv": 7.364, "adv_acc": 0.001, "gn": 1.94, "B": 26, "lr": 3.151e-06, "skips": 17, "oom": 0, "min": 16.54}
143
+ {"step": 3400, "ce": 3.3099, "acc": 0.2384, "adv": 7.2, "adv_acc": 0.241, "gn": 1.99, "B": 18, "lr": 3.091e-06, "skips": 18, "oom": 0, "min": 16.66}
144
+ {"step": 3425, "ce": 3.4313, "acc": 0.2122, "adv": 7.262, "adv_acc": 0.0, "gn": 2.29, "B": 11, "lr": 3.032e-06, "skips": 18, "oom": 0, "min": 16.77}
145
+ {"step": 3450, "ce": 3.1878, "acc": 0.2464, "adv": 7.173, "adv_acc": 0.109, "gn": 1.89, "B": 18, "lr": 2.973e-06, "skips": 18, "oom": 0, "min": 16.88}
146
+ {"step": 3475, "ce": 3.1116, "acc": 0.2702, "adv": 7.052, "adv_acc": 0.115, "gn": 2.0, "B": 12, "lr": 2.915e-06, "skips": 18, "oom": 0, "min": 16.98}
147
+ {"step": 3500, "ce": 3.69, "acc": 0.183, "adv": 7.387, "adv_acc": 0.001, "gn": 2.04, "B": 44, "lr": 2.857e-06, "skips": 18, "oom": 0, "min": 17.1}
148
+ {"step": 3500, "val": {"np": 3.450007374201225, "p": 3.453779433371578, "anchor_p": 3.084826946258545, "mean": 3.4518934037864017}, "best": true}
149
+ {"step": 3525, "ce": 3.8386, "acc": 0.1708, "adv": 7.872, "adv_acc": 0.0, "gn": 2.22, "B": 59, "lr": 2.8e-06, "skips": 18, "oom": 0, "min": 17.29}
150
+ {"step": 3550, "ce": 3.3008, "acc": 0.2418, "adv": 6.87, "adv_acc": 0.256, "gn": 2.36, "B": 18, "lr": 2.744e-06, "skips": 18, "oom": 0, "min": 17.39}
151
+ {"step": 3575, "ce": 3.2114, "acc": 0.2499, "adv": 6.952, "adv_acc": 0.143, "gn": 2.01, "B": 25, "lr": 2.689e-06, "skips": 18, "oom": 0, "min": 17.49}
152
+ {"step": 3600, "ce": 3.631, "acc": 0.2007, "adv": 7.465, "adv_acc": 0.0, "gn": 2.28, "B": 48, "lr": 2.634e-06, "skips": 18, "oom": 0, "min": 17.59}
153
+ {"step": 3625, "ce": 3.0395, "acc": 0.2746, "adv": 6.853, "adv_acc": 0.365, "gn": 2.04, "B": 14, "lr": 2.58e-06, "skips": 19, "oom": 0, "min": 17.71}
154
+ {"step": 3650, "ce": 3.2907, "acc": 0.2407, "adv": 6.957, "adv_acc": 0.059, "gn": 2.04, "B": 19, "lr": 2.526e-06, "skips": 19, "oom": 0, "min": 17.82}
155
+ {"step": 3675, "ce": 3.4421, "acc": 0.219, "adv": 6.903, "adv_acc": 0.078, "gn": 2.07, "B": 18, "lr": 2.474e-06, "skips": 20, "oom": 0, "min": 17.94}
156
+ {"step": 3700, "ce": 3.3729, "acc": 0.2236, "adv": 6.87, "adv_acc": 0.087, "gn": 2.08, "B": 11, "lr": 2.422e-06, "skips": 21, "oom": 0, "min": 18.05}
157
+ {"step": 3725, "ce": 3.8656, "acc": 0.1712, "adv": 7.638, "adv_acc": 0.001, "gn": 2.12, "B": 48, "lr": 2.37e-06, "skips": 21, "oom": 0, "min": 18.16}
158
+ {"step": 3750, "ce": 3.1599, "acc": 0.251, "adv": 6.81, "adv_acc": 0.002, "gn": 2.31, "B": 9, "lr": 2.32e-06, "skips": 21, "oom": 0, "min": 18.26}
159
+ {"step": 3775, "ce": 3.597, "acc": 0.2018, "adv": 7.29, "adv_acc": 0.0, "gn": 2.19, "B": 45, "lr": 2.27e-06, "skips": 21, "oom": 0, "min": 18.35}
160
+ {"step": 3800, "ce": 3.4677, "acc": 0.2096, "adv": 7.013, "adv_acc": 0.021, "gn": 2.12, "B": 38, "lr": 2.222e-06, "skips": 21, "oom": 0, "min": 18.46}
161
+ {"step": 3825, "ce": 3.8072, "acc": 0.162, "adv": 7.709, "adv_acc": 0.0, "gn": 2.54, "B": 56, "lr": 2.174e-06, "skips": 21, "oom": 0, "min": 18.57}
162
+ {"step": 3850, "ce": 3.3768, "acc": 0.2393, "adv": 6.691, "adv_acc": 0.07, "gn": 2.16, "B": 15, "lr": 2.126e-06, "skips": 22, "oom": 0, "min": 18.68}
163
+ {"step": 3875, "ce": 3.4887, "acc": 0.2255, "adv": 8.748, "adv_acc": 0.001, "gn": 2.49, "B": 57, "lr": 2.08e-06, "skips": 22, "oom": 0, "min": 18.78}
164
+ {"step": 3900, "ce": 3.3054, "acc": 0.2282, "adv": 6.594, "adv_acc": 0.169, "gn": 1.96, "B": 19, "lr": 2.034e-06, "skips": 23, "oom": 0, "min": 18.89}
165
+ {"step": 3925, "ce": 3.366, "acc": 0.2343, "adv": 6.587, "adv_acc": 0.001, "gn": 1.94, "B": 25, "lr": 1.99e-06, "skips": 24, "oom": 0, "min": 19.01}
166
+ {"step": 3950, "ce": 3.3814, "acc": 0.2161, "adv": 6.627, "adv_acc": 0.0, "gn": 2.39, "B": 8, "lr": 1.946e-06, "skips": 24, "oom": 0, "min": 19.13}
167
+ {"step": 3975, "ce": 3.8303, "acc": 0.1731, "adv": 7.142, "adv_acc": 0.001, "gn": 2.05, "B": 40, "lr": 1.903e-06, "skips": 24, "oom": 0, "min": 19.25}
168
+ {"step": 4000, "ce": 3.3377, "acc": 0.2298, "adv": 6.495, "adv_acc": 0.094, "gn": 2.11, "B": 14, "lr": 1.861e-06, "skips": 24, "oom": 0, "min": 19.34}
169
+ {"step": 4000, "val": {"np": 3.4431558952269414, "p": 3.446689893057758, "anchor_p": 3.0781209468841553, "mean": 3.4449228941423495}, "best": true}
170
+ {"step": 4025, "ce": 3.6726, "acc": 0.2001, "adv": 7.533, "adv_acc": 0.0, "gn": 2.45, "B": 48, "lr": 1.82e-06, "skips": 25, "oom": 0, "min": 19.52}
171
+ {"step": 4050, "ce": 3.3085, "acc": 0.241, "adv": 6.482, "adv_acc": 0.087, "gn": 1.9, "B": 19, "lr": 1.78e-06, "skips": 26, "oom": 0, "min": 19.63}
172
+ {"step": 4075, "ce": 3.3112, "acc": 0.2284, "adv": 6.608, "adv_acc": 0.0, "gn": 2.34, "B": 7, "lr": 1.74e-06, "skips": 26, "oom": 0, "min": 19.73}
173
+ {"step": 4100, "ce": 3.2196, "acc": 0.2551, "adv": 6.462, "adv_acc": 0.062, "gn": 2.0, "B": 18, "lr": 1.702e-06, "skips": 27, "oom": 0, "min": 19.83}
174
+ {"step": 4125, "ce": 3.3942, "acc": 0.2193, "adv": 6.398, "adv_acc": 0.072, "gn": 2.13, "B": 24, "lr": 1.665e-06, "skips": 27, "oom": 0, "min": 19.93}
175
+ {"step": 4150, "ce": 3.5767, "acc": 0.1964, "adv": 6.64, "adv_acc": 0.015, "gn": 2.21, "B": 37, "lr": 1.628e-06, "skips": 27, "oom": 0, "min": 20.05}
176
+ {"step": 4175, "ce": 3.1631, "acc": 0.2804, "adv": 8.278, "adv_acc": 0.0, "gn": 2.84, "B": 70, "lr": 1.593e-06, "skips": 27, "oom": 0, "min": 20.15}
177
+ {"step": 4200, "ce": 3.5769, "acc": 0.2021, "adv": 6.396, "adv_acc": 0.045, "gn": 3.32, "B": 29, "lr": 1.558e-06, "skips": 27, "oom": 0, "min": 20.25}
178
+ {"step": 4225, "ce": 3.4182, "acc": 0.228, "adv": 6.34, "adv_acc": 0.026, "gn": 1.91, "B": 23, "lr": 1.524e-06, "skips": 27, "oom": 0, "min": 20.34}
179
+ {"step": 4250, "ce": 3.4323, "acc": 0.1862, "adv": 6.416, "adv_acc": 0.0, "gn": 4.24, "B": 4, "lr": 1.492e-06, "skips": 27, "oom": 0, "min": 20.46}
180
+ {"step": 4275, "ce": 3.2959, "acc": 0.2454, "adv": 6.336, "adv_acc": 0.074, "gn": 2.14, "B": 10, "lr": 1.46e-06, "skips": 27, "oom": 0, "min": 20.56}
181
+ {"step": 4300, "ce": 3.3964, "acc": 0.2241, "adv": 6.433, "adv_acc": 0.0, "gn": 2.44, "B": 9, "lr": 1.429e-06, "skips": 27, "oom": 0, "min": 20.67}
182
+ {"step": 4325, "ce": 3.1359, "acc": 0.2704, "adv": 6.4, "adv_acc": 0.0, "gn": 2.44, "B": 8, "lr": 1.4e-06, "skips": 27, "oom": 0, "min": 20.78}
183
+ {"step": 4350, "ce": 3.3125, "acc": 0.2263, "adv": 6.296, "adv_acc": 0.024, "gn": 2.0, "B": 21, "lr": 1.371e-06, "skips": 27, "oom": 0, "min": 20.87}
184
+ {"step": 4375, "ce": 3.7765, "acc": 0.1722, "adv": 6.958, "adv_acc": 0.003, "gn": 2.27, "B": 44, "lr": 1.344e-06, "skips": 28, "oom": 0, "min": 20.98}
185
+ {"step": 4400, "ce": 3.802, "acc": 0.1807, "adv": 6.978, "adv_acc": 0.048, "gn": 2.29, "B": 44, "lr": 1.317e-06, "skips": 28, "oom": 0, "min": 21.1}
186
+ {"step": 4425, "ce": 3.9622, "acc": 0.1576, "adv": 7.175, "adv_acc": 0.001, "gn": 2.23, "B": 53, "lr": 1.292e-06, "skips": 28, "oom": 0, "min": 21.22}
187
+ {"step": 4450, "ce": 3.5589, "acc": 0.1894, "adv": 6.487, "adv_acc": 0.121, "gn": 2.24, "B": 38, "lr": 1.267e-06, "skips": 28, "oom": 0, "min": 21.33}
188
+ {"step": 4475, "ce": 3.5869, "acc": 0.1958, "adv": 6.216, "adv_acc": 0.091, "gn": 2.13, "B": 21, "lr": 1.244e-06, "skips": 28, "oom": 0, "min": 21.44}
189
+ {"step": 4500, "ce": 3.5748, "acc": 0.2104, "adv": 8.296, "adv_acc": 0.001, "gn": 2.57, "B": 62, "lr": 1.221e-06, "skips": 28, "oom": 0, "min": 21.54}
190
+ {"step": 4500, "val": {"np": 3.4411249983970817, "p": 3.4445827232509947, "anchor_p": 3.079256296157837, "mean": 3.442853860824038}, "best": true}
191
+ {"step": 4525, "ce": 3.6671, "acc": 0.1935, "adv": 7.579, "adv_acc": 0.0, "gn": 2.41, "B": 61, "lr": 1.2e-06, "skips": 28, "oom": 0, "min": 21.73}
192
+ {"step": 4550, "ce": 3.5396, "acc": 0.2035, "adv": 6.112, "adv_acc": 0.033, "gn": 2.28, "B": 28, "lr": 1.179e-06, "skips": 28, "oom": 0, "min": 21.84}
193
+ {"step": 4575, "ce": 3.0832, "acc": 0.2803, "adv": 6.234, "adv_acc": 0.0, "gn": 2.13, "B": 8, "lr": 1.16e-06, "skips": 29, "oom": 0, "min": 21.94}
194
+ {"step": 4600, "ce": 3.5924, "acc": 0.1956, "adv": 6.252, "adv_acc": 0.001, "gn": 2.17, "B": 29, "lr": 1.142e-06, "skips": 29, "oom": 0, "min": 22.05}
195
+ {"step": 4625, "ce": 3.4295, "acc": 0.2224, "adv": 6.193, "adv_acc": 0.043, "gn": 2.09, "B": 20, "lr": 1.125e-06, "skips": 30, "oom": 0, "min": 22.17}
196
+ {"step": 4650, "ce": 3.3532, "acc": 0.2302, "adv": 6.142, "adv_acc": 0.181, "gn": 2.09, "B": 22, "lr": 1.109e-06, "skips": 30, "oom": 0, "min": 22.29}
197
+ {"step": 4675, "ce": 3.6147, "acc": 0.2032, "adv": 6.351, "adv_acc": 0.031, "gn": 2.01, "B": 35, "lr": 1.094e-06, "skips": 30, "oom": 0, "min": 22.39}
198
+ {"step": 4700, "ce": 3.5387, "acc": 0.2016, "adv": 6.802, "adv_acc": 0.0, "gn": 2.13, "B": 43, "lr": 1.08e-06, "skips": 30, "oom": 0, "min": 22.51}
199
+ {"step": 4725, "ce": 3.5325, "acc": 0.2115, "adv": 6.492, "adv_acc": 0.001, "gn": 2.37, "B": 41, "lr": 1.067e-06, "skips": 30, "oom": 0, "min": 22.62}
200
+ {"step": 4750, "ce": 3.3788, "acc": 0.2414, "adv": 6.013, "adv_acc": 0.256, "gn": 1.93, "B": 16, "lr": 1.056e-06, "skips": 30, "oom": 0, "min": 22.73}
201
+ {"step": 4775, "ce": 3.4268, "acc": 0.228, "adv": 6.181, "adv_acc": 0.0, "gn": 2.18, "B": 13, "lr": 1.045e-06, "skips": 30, "oom": 0, "min": 22.83}
202
+ {"step": 4800, "ce": 3.4523, "acc": 0.2268, "adv": 6.0, "adv_acc": 0.077, "gn": 2.02, "B": 17, "lr": 1.036e-06, "skips": 30, "oom": 0, "min": 22.94}
203
+ {"step": 4825, "ce": 3.5242, "acc": 0.2296, "adv": 7.628, "adv_acc": 0.044, "gn": 2.09, "B": 51, "lr": 1.027e-06, "skips": 30, "oom": 0, "min": 23.05}
204
+ {"step": 4850, "ce": 3.1302, "acc": 0.2662, "adv": 5.944, "adv_acc": 0.324, "gn": 2.04, "B": 12, "lr": 1.02e-06, "skips": 30, "oom": 0, "min": 23.14}
205
+ {"step": 4875, "ce": 3.4428, "acc": 0.2185, "adv": 6.03, "adv_acc": 0.165, "gn": 2.1, "B": 15, "lr": 1.014e-06, "skips": 30, "oom": 0, "min": 23.25}
206
+ {"step": 4900, "ce": 3.1318, "acc": 0.2708, "adv": 6.019, "adv_acc": 0.0, "gn": 2.6, "B": 6, "lr": 1.009e-06, "skips": 30, "oom": 0, "min": 23.35}
207
+ {"step": 4925, "ce": 3.5066, "acc": 0.2196, "adv": 6.043, "adv_acc": 0.039, "gn": 1.91, "B": 24, "lr": 1.005e-06, "skips": 30, "oom": 0, "min": 23.44}
208
+ {"step": 4950, "ce": 3.4736, "acc": 0.2202, "adv": 7.73, "adv_acc": 0.0, "gn": 3.54, "B": 69, "lr": 1.002e-06, "skips": 30, "oom": 0, "min": 23.54}
209
+ {"step": 4975, "ce": 3.0291, "acc": 0.2834, "adv": 5.771, "adv_acc": 0.001, "gn": 2.29, "B": 9, "lr": 1.001e-06, "skips": 30, "oom": 0, "min": 23.64}
210
+ {"step": 5000, "ce": 3.4741, "acc": 0.2143, "adv": 5.99, "adv_acc": 0.0, "gn": 1.92, "B": 15, "lr": 1e-06, "skips": 30, "oom": 0, "min": 23.74}
211
+ {"step": 5000, "val": {"np": 3.4417521021653465, "p": 3.445160358658831, "anchor_p": 3.078298807144165, "mean": 3.443456230412089}, "best": false}
checkpoints/phase3/p3_lm_full/spk2id.json ADDED
The diff for this file is too large to render. See raw diff
 
infer.py CHANGED
@@ -1,35 +1,58 @@
1
  #!/usr/bin/env python
2
- """Dynamic Accent (Phase 2 preview): Nigerian multilingual, code-switching TTS on CosyVoice2-0.5B.
3
-
4
- Same inference path as the Phase 2 final samples: LM `p2_lm_B` (pause profile B) + anchor-SFT flow,
5
- anchor prompt clip 0 at the LM and the flow, single sample, sentence-level stitching, edge trim and
6
- internal-silence cap (0.6 s), 0.6 s between sentences.
7
-
8
- python infer.py --text "<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>" --seed 0 --out out.wav
9
-
10
- Prosody v2 (experimental, `--pause_mode clause`; default is `model`): the text is cut at , ; : . ? ! and at language-switch points; each chunk
11
- is generated with the same LM/flow/prompt, its edge silence is trimmed (energy VAD, 30 ms kept), its speech is sped
12
- up pitch-preserving by `--speed` (WSOLA, default 1.0), and chunks are joined with set gaps from the real anchor's pause statistics:
13
- comma/;/: 0.40 s (+-0.05 jitter), sentence end 0.60 s, unpunctuated language switch 0.12 s, with 10 ms fades.
14
- `--pause_mode model --speed 1.0` reproduces the Phase 2 final samples (model-generated pauses, sentence stitching).
15
-
16
- Text: wrap each language span in <eng>, <pcm>, <yor>, <ibo> or <hau> tags. Untagged text is treated as English.
 
 
 
 
 
 
 
 
17
  """
18
- import argparse, inspect, json, os, re, sys, unicodedata
 
19
 
20
  import numpy as np
21
  import soundfile as sf
22
  import librosa
23
  import torch
24
  import torch.nn as nn
 
25
 
26
  HERE = os.path.dirname(os.path.abspath(__file__))
27
  LANGS = ["eng", "ibo", "hau", "yor", "pcm"]
28
  L2I = {l: i for i, l in enumerate(LANGS)}
29
  ANCHOR = "waxal_yor_4"
 
30
  DEFAULT_SPEED = 1.0 # user listening test 2026-10-21: original model pauses preferred; clause/1.05 kept as experimental
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  # ------------------------------------------------------------------ text front-end (tags -> spans)
34
  _TAG = re.compile(r"<(eng|ibo|hau|yor|pcm)>(.*?)</\1>", re.S)
35
  _SENT = re.compile(r"(?<=[.?!])\s+")
@@ -214,6 +237,11 @@ def _sf_load_wav(wav, target_sr, min_sr=16000):
214
 
215
  def load_cosy(model_dir, cosyvoice_dir):
216
  sys.path[:0] = [cosyvoice_dir, os.path.join(cosyvoice_dir, "third_party", "Matcha-TTS")]
 
 
 
 
 
217
  import cosyvoice.utils.file_utils as fu
218
  fu.load_wav = _sf_load_wav
219
  import cosyvoice.cli.frontend as cf
@@ -231,16 +259,16 @@ def prompt_feats(cosy, wav_path):
231
  n = min(feat.shape[1] // 2, tokn.shape[1])
232
  feat, tokn = feat[:, :2 * n], tokn[:, :n]
233
  emb = fe._extract_spk_embedding(wav_path)
234
- return tokn.cuda(), feat.cuda(), emb.cuda()
235
 
236
 
237
  @torch.no_grad()
238
  def token2wav(cosy, tokens, ptok, pfeat, emb):
239
  flow = cosy.model.flow
240
- t = torch.tensor([tokens], dtype=torch.int32).cuda()
241
- kw = dict(token=t, token_len=torch.tensor([t.shape[1]], dtype=torch.int32).cuda(),
242
- prompt_token=ptok, prompt_token_len=torch.tensor([ptok.shape[1]], dtype=torch.int32).cuda(),
243
- prompt_feat=pfeat, prompt_feat_len=torch.tensor([pfeat.shape[1]], dtype=torch.int32).cuda(),
244
  embedding=emb)
245
  sig = inspect.signature(flow.inference).parameters
246
  if "streaming" in sig:
@@ -248,43 +276,55 @@ def token2wav(cosy, tokens, ptok, pfeat, emb):
248
  if "finalize" in sig:
249
  kw["finalize"] = True
250
  if "flow_cache" in sig:
251
- kw["flow_cache"] = torch.zeros(1, 80, 0, 2).cuda()
252
  mel = flow.inference(**kw)
253
  mel = mel[0] if isinstance(mel, tuple) else mel
254
  hs = inspect.signature(cosy.model.hift.inference).parameters
255
- w = cosy.model.hift.inference(speech_feat=mel, **({"cache_source": torch.zeros(1, 1, 0).cuda()} if "cache_source" in hs else {}))
256
  w = w[0] if isinstance(w, tuple) else w
257
  return w.squeeze().float().cpu().numpy()
258
 
259
 
260
  # ------------------------------------------------------------------ Dynamic Accent LM (Qwen2LM + language + speaker embeddings)
 
 
 
 
 
261
  class DALM2(nn.Module):
262
  def __init__(self, qlm, n_spk, d=896):
263
  super().__init__()
264
  self.q = qlm
265
  self.lang_emb = nn.Embedding(len(LANGS), d)
266
  self.spk_emb = nn.Embedding(n_spk, d)
 
 
267
  self.adv = nn.Sequential(nn.Linear(d, 256), nn.ReLU(), nn.Linear(256, n_spk)) # training-only head
268
  self.V = qlm.speech_token_size
269
 
270
  def embed_text(self, ids, langs):
271
  return self.q.llm.model.model.embed_tokens(ids) + self.lang_emb(langs)
272
 
273
- def build2(self, ids, langs, spk, pids=None, plangs=None, psp=None):
274
  sos = self.q.llm_embedding.weight[self.q.sos][None]
275
  task = self.q.llm_embedding.weight[self.q.task_id][None]
276
  if pids is not None:
277
  ids = torch.cat([pids, ids]); langs = torch.cat([plangs, langs])
278
- parts = [sos, self.embed_text(ids, langs), self.spk_emb(spk)[None], task]
 
 
 
279
  if psp is not None:
280
  parts.append(self.q.speech_embedding(psp))
281
  return torch.cat(parts, 0)
282
 
283
  @torch.no_grad()
284
  def generate2(self, ids, langs, spk, prompt=None, top_p=0.8, top_k=25, win=10, tau_r=0.1, seed=0,
285
- min_ratio=2.0, max_ratio=20.0):
 
 
286
  g = torch.Generator(device=ids.device).manual_seed(seed)
287
- x = self.build2(ids, langs, spk, *(prompt if prompt else (None, None, None)))[None]
288
  n = len(ids)
289
  min_len, max_len = int(n * min_ratio), int(n * max_ratio)
290
  past, out, inp = None, [], x
@@ -301,7 +341,15 @@ class DALM2(nn.Module):
301
  keep[top_k:] = False
302
  cp, ci = sp[keep], si[keep]
303
  tokid = int(ci[torch.multinomial(cp / cp.sum(), 1, generator=g)])
304
- if out and sum(1 for t in out[-win:] if t == tokid) >= win * tau_r and tokid != self.V: # RAS
 
 
 
 
 
 
 
 
305
  tokid = int(torch.multinomial(p, 1, generator=g))
306
  if tokid >= self.V:
307
  break
@@ -310,40 +358,128 @@ class DALM2(nn.Module):
310
  return out
311
 
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  class DynamicAccent:
314
- def __init__(self, model_dir, cosyvoice_dir, lm_ckpt, flow_ckpt, prompts_json, dtype="auto", sent_pause=0.6, cap=0.6):
 
315
  from transformers import AutoTokenizer
316
- self.tok = AutoTokenizer.from_pretrained(os.path.join(model_dir, "CosyVoice-BlankEN"))
317
- self.cosy = load_cosy(model_dir, cosyvoice_dir)
318
  self.sr = self.cosy.sample_rate
319
- self.cosy.model.flow.load_state_dict(torch.load(flow_ckpt, map_location="cuda")["flow"])
320
- ck = torch.load(lm_ckpt, map_location="cuda")
 
321
  self.spk2id = json.load(open(os.path.join(os.path.dirname(lm_ckpt), "spk2id.json")))
322
- self.m = DALM2(self.cosy.model.llm, ck["n_spk"]).cuda().eval()
323
- self.m.load_state_dict(ck["model"])
324
- if dtype == "auto":
 
 
 
 
 
 
 
325
  dtype = "bf16" if torch.cuda.is_bf16_supported() else "fp16"
326
- self.dtype = torch.bfloat16 if dtype == "bf16" else torch.float16
327
  self.sent_pause, self.cap = sent_pause, cap
 
 
328
  meta = json.load(open(prompts_json, encoding="utf-8"))
329
  root = os.path.dirname(os.path.dirname(os.path.abspath(prompts_json)))
330
  self.prompts = []
331
  for p in meta["prompts"]:
332
  ids, langs = text_tokens(self.tok, p["tagged"])
333
- lmp = (torch.tensor(ids).cuda(), torch.tensor(langs).cuda(), torch.tensor(p["s3_tokens"], dtype=torch.int64).cuda())
334
- self.prompts.append(dict(id=p["id"], lm=lmp, flow=prompt_feats(self.cosy, os.path.join(root, p["wav"]))))
335
-
336
- def one(self, tagged_text, seed, prompt_k, use_lm_prompt=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  ids, langs = text_tokens(self.tok, tagged_text)
338
- P = self.prompts[prompt_k % len(self.prompts)]
339
- with torch.autocast("cuda", dtype=self.dtype):
340
- sp = self.m.generate2(torch.tensor(ids).cuda(), torch.tensor(langs).cuda(),
341
- torch.tensor(self.spk2id[ANCHOR]).cuda(), prompt=P["lm"] if use_lm_prompt else None, seed=seed)
 
342
  if len(sp) < 3:
343
  sp = sp + [0] * 3
 
344
  return token2wav(self.cosy, sp, *P["flow"])
345
 
346
  def say(self, tagged_text, seed=0, prompt_k=0, stitch=True, pause_mode="model", speed=1.0, return_info=False):
 
 
 
 
 
 
 
 
 
 
 
347
  if pause_mode == "clause":
348
  return self.say_clause(tagged_text, seed, prompt_k, speed, return_info)
349
  w = self.say_model(tagged_text, seed, prompt_k, stitch)
@@ -359,11 +495,11 @@ class DynamicAccent:
359
  w = cap_internal(trim_tight(w, self.sr), self.sr, self.cap)
360
  w = fade(time_stretch(w, self.sr, speed), self.sr)
361
  if i == 0:
362
- out.append(np.zeros(int(0.12 * self.sr), np.float32)) # same 0.12 s lead as model mode
363
  out.append(w)
364
  if b is not None:
365
  g = GAPS[b] + rng.uniform(-JITTER[b], JITTER[b])
366
- out.append(np.zeros(max(0, int((g - 2 * lead) * self.sr)), np.float32)) # 30 ms kept each side
367
  else:
368
  out.append(np.zeros(int(0.15 * self.sr), np.float32))
369
  info.append(dict(lang=l, text=t, boundary=b))
@@ -384,34 +520,111 @@ class DynamicAccent:
384
  return np.concatenate(out)
385
 
386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  def main():
388
  ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
389
- ap.add_argument("--text", required=True, help="tagged text, e.g. '<eng>Good morning,</eng> <pcm>how body?</pcm>'")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  ap.add_argument("--seed", type=int, default=0)
391
  ap.add_argument("--out", default="out.wav")
392
- ap.add_argument("--prompt_clip", type=int, default=0, help="anchor prompt clip 0-3 (final system: 0)")
393
  ap.add_argument("--model_dir", default=os.environ.get("COSYVOICE2_DIR", os.path.join(HERE, "pretrained", "CosyVoice2-0.5B")),
394
- help="stock FunAudioLLM/CosyVoice2-0.5B snapshot")
395
  ap.add_argument("--cosyvoice_dir", default=os.environ.get("COSYVOICE_REPO", os.path.join(HERE, "CosyVoice")),
396
  help="clone of github.com/FunAudioLLM/CosyVoice (with submodules)")
397
- ap.add_argument("--lm", default=os.path.join(HERE, "checkpoints", "p2_lm_B", "best.pt"))
398
- ap.add_argument("--flow", default=os.path.join(HERE, "checkpoints", "p2_flow_sft", "best.pt"))
399
  ap.add_argument("--prompts", default=os.path.join(HERE, "prompts", "prompts.json"))
400
- ap.add_argument("--dtype", default="auto", choices=["auto", "bf16", "fp16"],
401
- help="LM autocast dtype. Samples were made with bf16; T4 has no bf16, so auto picks fp16 there")
402
- ap.add_argument("--no_stitch", action="store_true", help="generate multi-sentence text in one pass")
403
- ap.add_argument("--pause_mode", default="model", choices=["clause", "model"],
404
- help="clause: clause-level chunks joined with anchor-matched gaps (default); model: Phase 2 final behaviour")
405
- ap.add_argument("--speed", type=float, default=DEFAULT_SPEED,
406
- help="pitch-preserving tempo factor applied to speech (gaps are kept as set). 1.0 = Phase 2 final pace")
407
  a = ap.parse_args()
 
 
 
 
 
 
 
 
 
408
  if not os.path.isdir(a.model_dir):
409
  from huggingface_hub import snapshot_download
410
  snapshot_download("FunAudioLLM/CosyVoice2-0.5B", local_dir=a.model_dir)
411
- S = DynamicAccent(a.model_dir, a.cosyvoice_dir, a.lm, a.flow, a.prompts, a.dtype)
412
- w = S.say(a.text, seed=a.seed, prompt_k=a.prompt_clip, stitch=not a.no_stitch, pause_mode=a.pause_mode, speed=a.speed)
413
- sf.write(a.out, w, S.sr)
414
- print(f"wrote {a.out} ({len(w) / S.sr:.2f} s, {S.sr} Hz)")
 
 
 
 
 
 
 
 
 
 
 
415
 
416
 
417
  if __name__ == "__main__":
 
1
  #!/usr/bin/env python
2
+ """Dynamic Accent: Nigerian multilingual, code-switching TTS on CosyVoice2-0.5B, with selectable model versions.
3
+
4
+ Versions (`--version`):
5
+ phase2 LM `p2_lm_B` + anchor-SFT flow `p2_flow_sft`. Phase 2 sampler, sentence stitching, edge trim, internal
6
+ silences capped at 0.6 s, 0.6 s between sentences (exactly the Phase 2 final samples).
7
+ phase3 LM `p3_lm_full` (re-cut long-form data with natural pauses, ~102 h of Igbo) + the shared Phase 2 flow (see
8
+ settings.json and checkpoints/phase3/p3_lm_full/config.json). Silence-aware sampler (silence tokens exempt from the repetition check up
9
+ to a 1 s run), the whole paragraph in ONE pass (up to ~60 words per pass), no audio post-processing: every
10
+ pause and the pace come from the model.
11
+
12
+ Voices (`--voice`): `anchor_male` (default; WAXAL Yoruba speaker 4), `waxal_ibo_5_female`, `waxal_yor_5_female`
13
+ (see --list_voices; recommended: phase3 with a female voice, phase2 with anchor_male). A non-anchor voice is zero-shot: its clip is the flow prompt and the LM prompt, and its
14
+ speaker token is used when the LM was trained on that speaker.
15
+
16
+ python infer.py --text "<eng>this is dynamic accent,</eng> <pcm>e dey sound nice abi?</pcm>" --version phase3 --out out.wav
17
+ python infer.py --text "..." --compare --out cmp.wav # -> cmp_phase2.wav and cmp_phase3.wav, same text, seed, voice
18
+ python infer.py --list_voices
19
+ python infer.py --auto_tag --tag_only --text "Omo, this traffic no be here o. Ẹ ṣé púpọ̀." # inspect / edit the tags
20
+
21
+ Text: wrap each language span in <eng>, <pcm>, <yor>, <ibo> or <hau> tags. Untagged text is treated as English, unless
22
+ --auto_tag (automatic word/span language ID, see tagger.py), --force_lang or --lang_map is given.
23
+ `--pause_mode clause` / `--speed` (prosody v2: fixed gaps + WSOLA) are kept for phase2 only as an experiment; a
24
+ listening test preferred the model's own pauses.
25
  """
26
+
27
+ import argparse, copy, inspect, json, os, re, sys, unicodedata
28
 
29
  import numpy as np
30
  import soundfile as sf
31
  import librosa
32
  import torch
33
  import torch.nn as nn
34
+ import contextlib
35
 
36
  HERE = os.path.dirname(os.path.abspath(__file__))
37
  LANGS = ["eng", "ibo", "hau", "yor", "pcm"]
38
  L2I = {l: i for i, l in enumerate(LANGS)}
39
  ANCHOR = "waxal_yor_4"
40
+ DEV = torch.device("cuda" if torch.cuda.is_available() else "cpu") # set_device() / --device to override
41
  DEFAULT_SPEED = 1.0 # user listening test 2026-10-21: original model pauses preferred; clause/1.05 kept as experimental
42
 
43
 
44
+ def set_device(d):
45
+ global DEV
46
+ DEV = torch.device(d if d != "auto" else ("cuda" if torch.cuda.is_available() else "cpu"))
47
+ return DEV
48
+
49
+
50
+ def autocast(dtype):
51
+ if DEV.type == "cuda" and dtype is not None:
52
+ return torch.autocast("cuda", dtype=dtype)
53
+ return contextlib.nullcontext() # CPU: plain fp32
54
+
55
+
56
  # ------------------------------------------------------------------ text front-end (tags -> spans)
57
  _TAG = re.compile(r"<(eng|ibo|hau|yor|pcm)>(.*?)</\1>", re.S)
58
  _SENT = re.compile(r"(?<=[.?!])\s+")
 
237
 
238
  def load_cosy(model_dir, cosyvoice_dir):
239
  sys.path[:0] = [cosyvoice_dir, os.path.join(cosyvoice_dir, "third_party", "Matcha-TTS")]
240
+ try: # pyworld is only needed for CosyVoice training (F0 features); it often fails
241
+ import pyworld # noqa: F401 # to build, so inference runs without it
242
+ except ImportError:
243
+ import types
244
+ sys.modules["pyworld"] = types.ModuleType("pyworld")
245
  import cosyvoice.utils.file_utils as fu
246
  fu.load_wav = _sf_load_wav
247
  import cosyvoice.cli.frontend as cf
 
259
  n = min(feat.shape[1] // 2, tokn.shape[1])
260
  feat, tokn = feat[:, :2 * n], tokn[:, :n]
261
  emb = fe._extract_spk_embedding(wav_path)
262
+ return tokn.to(DEV), feat.to(DEV), emb.to(DEV)
263
 
264
 
265
  @torch.no_grad()
266
  def token2wav(cosy, tokens, ptok, pfeat, emb):
267
  flow = cosy.model.flow
268
+ t = torch.tensor([tokens], dtype=torch.int32).to(DEV)
269
+ kw = dict(token=t, token_len=torch.tensor([t.shape[1]], dtype=torch.int32).to(DEV),
270
+ prompt_token=ptok, prompt_token_len=torch.tensor([ptok.shape[1]], dtype=torch.int32).to(DEV),
271
+ prompt_feat=pfeat, prompt_feat_len=torch.tensor([pfeat.shape[1]], dtype=torch.int32).to(DEV),
272
  embedding=emb)
273
  sig = inspect.signature(flow.inference).parameters
274
  if "streaming" in sig:
 
276
  if "finalize" in sig:
277
  kw["finalize"] = True
278
  if "flow_cache" in sig:
279
+ kw["flow_cache"] = torch.zeros(1, 80, 0, 2).to(DEV)
280
  mel = flow.inference(**kw)
281
  mel = mel[0] if isinstance(mel, tuple) else mel
282
  hs = inspect.signature(cosy.model.hift.inference).parameters
283
+ w = cosy.model.hift.inference(speech_feat=mel, **({"cache_source": torch.zeros(1, 1, 0).to(DEV)} if "cache_source" in hs else {}))
284
  w = w[0] if isinstance(w, tuple) else w
285
  return w.squeeze().float().cpu().numpy()
286
 
287
 
288
  # ------------------------------------------------------------------ Dynamic Accent LM (Qwen2LM + language + speaker embeddings)
289
+ # S3 tokens that fill silent frames in the anchor's recordings (Phase 3 Stage 0); used by the silence-aware sampler
290
+ SIL_TOKENS = {4137, 4218, 1950, 2031, 3894, 6324, 1707, 3677, 1701, 6405, 1947, 3645, 1944, 1704, 3891, 4134, 3680, 5838, 3651}
291
+ RATES = [None, "very slow", "slow", "normal", "fast", "very fast"]
292
+
293
+
294
  class DALM2(nn.Module):
295
  def __init__(self, qlm, n_spk, d=896):
296
  super().__init__()
297
  self.q = qlm
298
  self.lang_emb = nn.Embedding(len(LANGS), d)
299
  self.spk_emb = nn.Embedding(n_spk, d)
300
+ self.rate_emb = nn.Embedding(len(RATES), d, padding_idx=0) # phase3 speed instruct; zero = none (phase2)
301
+ nn.init.zeros_(self.rate_emb.weight)
302
  self.adv = nn.Sequential(nn.Linear(d, 256), nn.ReLU(), nn.Linear(256, n_spk)) # training-only head
303
  self.V = qlm.speech_token_size
304
 
305
  def embed_text(self, ids, langs):
306
  return self.q.llm.model.model.embed_tokens(ids) + self.lang_emb(langs)
307
 
308
+ def build2(self, ids, langs, spk, rate=0, pids=None, plangs=None, psp=None):
309
  sos = self.q.llm_embedding.weight[self.q.sos][None]
310
  task = self.q.llm_embedding.weight[self.q.task_id][None]
311
  if pids is not None:
312
  ids = torch.cat([pids, ids]); langs = torch.cat([plangs, langs])
313
+ sv = self.spk_emb(spk)
314
+ if rate:
315
+ sv = sv + self.rate_emb(torch.tensor(rate, device=sv.device))
316
+ parts = [sos, self.embed_text(ids, langs), sv[None], task]
317
  if psp is not None:
318
  parts.append(self.q.speech_embedding(psp))
319
  return torch.cat(parts, 0)
320
 
321
  @torch.no_grad()
322
  def generate2(self, ids, langs, spk, prompt=None, top_p=0.8, top_k=25, win=10, tau_r=0.1, seed=0,
323
+ min_ratio=2.0, max_ratio=20.0, sampler="ras", rate=0, sil_cap=25):
324
+ """sampler 'ras' = Phase 2 repetition-aware sampling; 'silexcap' = silence tokens exempt from the repetition
325
+ check while the current silence run is shorter than sil_cap frames (25 = 1.0 s), then the normal check."""
326
  g = torch.Generator(device=ids.device).manual_seed(seed)
327
+ x = self.build2(ids, langs, spk, rate, *(prompt if prompt else (None, None, None)))[None]
328
  n = len(ids)
329
  min_len, max_len = int(n * min_ratio), int(n * max_ratio)
330
  past, out, inp = None, [], x
 
341
  keep[top_k:] = False
342
  cp, ci = sp[keep], si[keep]
343
  tokid = int(ci[torch.multinomial(cp / cp.sum(), 1, generator=g)])
344
+ exempt = False
345
+ if sampler == "silexcap" and tokid in SIL_TOKENS:
346
+ run = 0
347
+ for t in reversed(out):
348
+ if t not in SIL_TOKENS:
349
+ break
350
+ run += 1
351
+ exempt = run < sil_cap
352
+ if not exempt and out and sum(1 for t in out[-win:] if t == tokid) >= win * tau_r and tokid != self.V: # RAS
353
  tokid = int(torch.multinomial(p, 1, generator=g))
354
  if tokid >= self.V:
355
  break
 
358
  return out
359
 
360
 
361
+ SETTINGS_PATH = os.path.join(HERE, "settings.json")
362
+ SETTINGS = json.load(open(SETTINGS_PATH)) if os.path.exists(SETTINGS_PATH) else {}
363
+ VERSIONS = {k: {kk: vv for kk, vv in v.items() if kk != "desc"} for k, v in SETTINGS.get("versions", {}).items()} or {
364
+ "phase2": dict(lm="checkpoints/phase2/p2_lm_B/best.pt", flow="checkpoints/phase2/p2_flow_sft/best.pt", sampler="ras",
365
+ one_pass=False, postprocess=True, rate=None),
366
+ "phase3": dict(lm="checkpoints/phase3/p3_lm_full/best.pt", flow="checkpoints/phase2/p2_flow_sft/best.pt",
367
+ sampler="silexcap", one_pass=True, postprocess=False, rate="normal"),
368
+ }
369
+ RECOMMENDED = SETTINGS.get("recommended", {})
370
+
371
+
372
+ def recommendation(version, voice):
373
+ """one-line hint when a version/voice pairing is not the recommended one (None when it is)."""
374
+ rec = RECOMMENDED.get(version)
375
+ if rec and voice not in rec:
376
+ return f"note: {version} is recommended with {' or '.join(rec)} ({RECOMMENDED.get('note', '')})"
377
+ return None
378
+
379
+
380
+ def version_cfg(name):
381
+ cfg = dict(VERSIONS[name])
382
+ cj = os.path.join(HERE, os.path.dirname(cfg["lm"]), "config.json")
383
+ if os.path.exists(cj): # a checkpoint may override flow / rate / sampler
384
+ cfg.update({k: v for k, v in json.load(open(cj)).items() if k in cfg})
385
+ cfg["lm"] = os.path.join(HERE, cfg["lm"]); cfg["flow"] = os.path.join(HERE, cfg["flow"])
386
+ return cfg
387
+
388
+
389
+ def word_chunks(text, max_words=60):
390
+ """one pass per <= max_words, split only at sentence ends (a whole paragraph usually fits in one pass)."""
391
+ sents = split_sentences(text)
392
+ out, cur, n = [], [], 0
393
+ for s in sents:
394
+ w = len(re.sub(r"<[^>]+>", " ", s).split())
395
+ if cur and n + w > max_words:
396
+ out.append(" ".join(cur)); cur, n = [], 0
397
+ cur.append(s); n += w
398
+ if cur:
399
+ out.append(" ".join(cur))
400
+ return out or [text]
401
+
402
+
403
  class DynamicAccent:
404
+ def __init__(self, model_dir, cosyvoice_dir, lm_ckpt, flow_ckpt, prompts_json, dtype="auto", sent_pause=0.6, cap=0.6,
405
+ sampler="ras", one_pass=False, postprocess=True, rate=None, cosy=None, tok=None, copy_llm=False):
406
  from transformers import AutoTokenizer
407
+ self.tok = tok or AutoTokenizer.from_pretrained(os.path.join(model_dir, "CosyVoice-BlankEN"))
408
+ self.cosy = cosy or load_cosy(model_dir, cosyvoice_dir)
409
  self.sr = self.cosy.sample_rate
410
+ self.flow_sd = torch.load(flow_ckpt, map_location=DEV, weights_only=False)["flow"]
411
+ self.cosy.model.flow.load_state_dict(self.flow_sd)
412
+ ck = torch.load(lm_ckpt, map_location=DEV, weights_only=False)
413
  self.spk2id = json.load(open(os.path.join(os.path.dirname(lm_ckpt), "spk2id.json")))
414
+ # two versions resident at once (app.py) must not share LM weights: copy_llm=True gives each its own copy.
415
+ # The CLI runs versions one after the other and reloads the full LM state in place (saves ~2 GB RAM).
416
+ qlm = copy.deepcopy(self.cosy.model.llm) if copy_llm else self.cosy.model.llm
417
+ self.m = DALM2(qlm, ck["n_spk"]).to(DEV).eval()
418
+ miss = self.m.load_state_dict(ck["model"], strict=False)
419
+ assert not [k for k in miss.missing_keys if not k.startswith("rate_emb")], miss
420
+ del ck
421
+ if DEV.type != "cuda":
422
+ dtype = "fp32"
423
+ elif dtype == "auto":
424
  dtype = "bf16" if torch.cuda.is_bf16_supported() else "fp16"
425
+ self.dtype = {"bf16": torch.bfloat16, "fp16": torch.float16, "fp32": None}[dtype]
426
  self.sent_pause, self.cap = sent_pause, cap
427
+ self.sampler, self.one_pass, self.postprocess = sampler, one_pass, postprocess
428
+ self.rate = RATES.index(rate) if rate in RATES else 0
429
  meta = json.load(open(prompts_json, encoding="utf-8"))
430
  root = os.path.dirname(os.path.dirname(os.path.abspath(prompts_json)))
431
  self.prompts = []
432
  for p in meta["prompts"]:
433
  ids, langs = text_tokens(self.tok, p["tagged"])
434
+ lmp = (torch.tensor(ids).to(DEV), torch.tensor(langs).to(DEV), torch.tensor(p["s3_tokens"], dtype=torch.int64).to(DEV))
435
+ self.prompts.append(dict(id=p["id"], lm=lmp, flow=prompt_feats(self.cosy, os.path.join(root, p["wav"])), spk=ANCHOR))
436
+ self.voice = None
437
+
438
+ def use_flow(self):
439
+ self.cosy.model.flow.load_state_dict(self.flow_sd) # two versions may share one CosyVoice2 instance
440
+
441
+ def set_voice(self, voice, voices_json=None):
442
+ """'anchor' or an id from voices.json: its clip is the flow + LM prompt (zero-shot)."""
443
+ meta = json.load(open(voices_json, encoding="utf-8")) if voices_json else {"voices": {}}
444
+ voice = meta.get("aliases", {}).get(voice, voice)
445
+ if voice in (None, "anchor", "anchor_male"):
446
+ self.voice = None; return
447
+ if voice not in meta["voices"]:
448
+ raise SystemExit(f"unknown voice '{voice}'; available: {', '.join(meta['voices'])}")
449
+ V = meta["voices"][voice]
450
+ root = os.path.dirname(os.path.dirname(os.path.abspath(voices_json)))
451
+ ptok, pfeat, pemb = prompt_feats(self.cosy, os.path.join(root, V["wav"]))
452
+ ids, langs = text_tokens(self.tok, V["tagged"])
453
+ lmp = (torch.tensor(ids).to(DEV), torch.tensor(langs).to(DEV), ptok[0].long())
454
+ self.voice = dict(id=voice, lm=lmp, flow=(ptok, pfeat, pemb), spk=V["spk"] if V["spk"] in self.spk2id else ANCHOR)
455
+
456
+ def _prompt(self, prompt_k):
457
+ return self.voice or self.prompts[prompt_k % len(self.prompts)]
458
+
459
+ def one(self, tagged_text, seed, prompt_k, use_lm_prompt=True, lm_prompt=None):
460
  ids, langs = text_tokens(self.tok, tagged_text)
461
+ P = self._prompt(prompt_k)
462
+ with autocast(self.dtype):
463
+ sp = self.m.generate2(torch.tensor(ids).to(DEV), torch.tensor(langs).to(DEV), torch.tensor(self.spk2id[P["spk"]]).to(DEV),
464
+ prompt=(lm_prompt or P["lm"]) if use_lm_prompt else None, seed=seed, sampler=self.sampler,
465
+ rate=self.rate, max_ratio=30.0 if self.one_pass else 20.0)
466
  if len(sp) < 3:
467
  sp = sp + [0] * 3
468
+ self.last_tokens = sp
469
  return token2wav(self.cosy, sp, *P["flow"])
470
 
471
  def say(self, tagged_text, seed=0, prompt_k=0, stitch=True, pause_mode="model", speed=1.0, return_info=False):
472
+ self.use_flow()
473
+ if self.one_pass: # phase3: the model makes every pause; nothing is trimmed or inserted
474
+ ws, prev = [], None
475
+ for i, ch in enumerate(word_chunks(tagged_text)):
476
+ w = self.one(ch, seed + 1000 * i, prompt_k, lm_prompt=prev)
477
+ ws.append(w)
478
+ ids, langs = text_tokens(self.tok, ch) # next chunk continues from this one (model sets the gap)
479
+ if len(self.last_tokens) <= 400:
480
+ prev = (torch.tensor(ids).to(DEV), torch.tensor(langs).to(DEV), torch.tensor(self.last_tokens).to(DEV))
481
+ w = np.concatenate(ws)
482
+ return (w, None) if return_info else w
483
  if pause_mode == "clause":
484
  return self.say_clause(tagged_text, seed, prompt_k, speed, return_info)
485
  w = self.say_model(tagged_text, seed, prompt_k, stitch)
 
495
  w = cap_internal(trim_tight(w, self.sr), self.sr, self.cap)
496
  w = fade(time_stretch(w, self.sr, speed), self.sr)
497
  if i == 0:
498
+ out.append(np.zeros(int(0.12 * self.sr), np.float32))
499
  out.append(w)
500
  if b is not None:
501
  g = GAPS[b] + rng.uniform(-JITTER[b], JITTER[b])
502
+ out.append(np.zeros(max(0, int((g - 2 * lead) * self.sr)), np.float32))
503
  else:
504
  out.append(np.zeros(int(0.15 * self.sr), np.float32))
505
  info.append(dict(lang=l, text=t, boundary=b))
 
520
  return np.concatenate(out)
521
 
522
 
523
+ def build(version, a, cosy=None, tok=None, copy_llm=False):
524
+ cfg = version_cfg(version)
525
+ lm = a.lm or cfg["lm"]; flow = a.flow or cfg["flow"]
526
+ S = DynamicAccent(a.model_dir, a.cosyvoice_dir, lm, flow, a.prompts, a.dtype, sampler=cfg["sampler"],
527
+ one_pass=cfg["one_pass"] and not a.stitch, postprocess=cfg["postprocess"],
528
+ rate=a.rate if a.rate != "default" else cfg["rate"], cosy=cosy, tok=tok, copy_llm=copy_llm)
529
+ S.set_voice(a.voice, a.voices)
530
+ return S
531
+
532
+
533
+ def list_voices(voices_json):
534
+ V = json.load(open(voices_json, encoding="utf-8"))["voices"] if os.path.exists(voices_json) else {}
535
+ print("voices (--voice):")
536
+ for k, v in V.items():
537
+ print(f" {k:20s} {v.get('desc', '')}")
538
+ print("\nversions (--version):")
539
+ for k, v in SETTINGS.get("versions", {}).items():
540
+ print(f" {k:20s} {v.get('desc', '')}")
541
+ if RECOMMENDED:
542
+ print("\nrecommended pairing: phase3 -> " + ", ".join(RECOMMENDED.get("phase3", [])) +
543
+ "; phase2 -> " + ", ".join(RECOMMENDED.get("phase2", [])))
544
+ print(" " + RECOMMENDED.get("note", ""))
545
+
546
+
547
+ def prepare_text(a):
548
+ """apply --auto_tag / --force_lang / --lang_map; returns the tagged text that will be synthesised."""
549
+ from tagger import Tagger, parse_map, TAGGED
550
+ text = a.text
551
+ if a.force_lang:
552
+ from tagger import strip_tags
553
+ return f"<{a.force_lang}>{strip_tags(text)}</{a.force_lang}>"
554
+ if a.auto_tag or a.lang_map:
555
+ if TAGGED.search(text) and not a.auto_tag:
556
+ return text
557
+ from tagger import strip_tags
558
+ T = Tagger(None if a.lid == "none" else a.lid, parse_map(a.lang_map))
559
+ return T.tag(strip_tags(text))
560
+ return text
561
+
562
+
563
  def main():
564
  ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
565
+ ap.add_argument("--text", help="text; tagged ('<eng>Good morning,</eng> <pcm>how body?</pcm>') or plain with --auto_tag")
566
+ ap.add_argument("--version", default=SETTINGS.get("default_version", "phase3"), choices=sorted(VERSIONS),
567
+ help="model version (default phase3)")
568
+ ap.add_argument("--compare", action="store_true",
569
+ help="render the same text, seed and voice with phase2 AND phase3 -> <out>_phase2.wav, <out>_phase3.wav")
570
+ ap.add_argument("--voice", default=SETTINGS.get("default_voice", "anchor_male"),
571
+ help="anchor_male | waxal_ibo_5_female | waxal_yor_5_female (see --list_voices)")
572
+ ap.add_argument("--list_voices", action="store_true", help="list voices, versions and the recommended pairing")
573
+ ap.add_argument("--rate", default="default", choices=["default", "none", "very slow", "slow", "normal", "fast", "very fast"],
574
+ help="phase3 speed instruction to the LM (default: the version's setting, 'normal'; 'none' = no instruction)")
575
+ # code-switching control
576
+ ap.add_argument("--auto_tag", action="store_true",
577
+ help="tag untagged mixed text automatically (word/span language ID), print it, then synthesise")
578
+ ap.add_argument("--tag_only", action="store_true", help="with --auto_tag: print the tagged text and exit (edit it, then pass it back as --text)")
579
+ ap.add_argument("--force_lang", choices=LANGS, help="read the whole text in one language (tags are ignored)")
580
+ ap.add_argument("--lang_map", default="", help='per-word language overrides for the tagger, e.g. "omo=pcm,sha=pcm"')
581
+ ap.add_argument("--lid", default="none", choices=["none", "glotlid"],
582
+ help="optional LID model for --auto_tag (glotlid: cis-lmu/glotlid, Apache-2.0, 1.7 GB, pip install fasttext)")
583
  ap.add_argument("--seed", type=int, default=0)
584
  ap.add_argument("--out", default="out.wav")
585
+ ap.add_argument("--prompt_clip", type=int, default=0, help="anchor prompt clip 0-3 (final systems: 0)")
586
  ap.add_argument("--model_dir", default=os.environ.get("COSYVOICE2_DIR", os.path.join(HERE, "pretrained", "CosyVoice2-0.5B")),
587
+ help="stock FunAudioLLM/CosyVoice2-0.5B snapshot (downloaded on first run)")
588
  ap.add_argument("--cosyvoice_dir", default=os.environ.get("COSYVOICE_REPO", os.path.join(HERE, "CosyVoice")),
589
  help="clone of github.com/FunAudioLLM/CosyVoice (with submodules)")
590
+ ap.add_argument("--lm", default="", help="override the version's LM checkpoint")
591
+ ap.add_argument("--flow", default="", help="override the version's flow checkpoint")
592
  ap.add_argument("--prompts", default=os.path.join(HERE, "prompts", "prompts.json"))
593
+ ap.add_argument("--voices", default=os.path.join(HERE, "prompts", "voices.json"))
594
+ ap.add_argument("--dtype", default="auto", choices=["auto", "bf16", "fp16", "fp32"],
595
+ help="LM autocast dtype on GPU (samples: bf16; auto picks fp16 on GPUs without bf16). CPU always runs fp32")
596
+ ap.add_argument("--stitch", action="store_true", help="phase3: generate sentence by sentence instead of one pass")
597
+ ap.add_argument("--no_stitch", action="store_true", help="phase2: generate multi-sentence text in one pass")
598
+ ap.add_argument("--pause_mode", default="model", choices=["clause", "model"], help="phase2 only; clause = experimental prosody v2 (not recommended)")
599
+ ap.add_argument("--speed", type=float, default=DEFAULT_SPEED, help="phase2 only; pitch-preserving tempo factor (1.0 = as trained; not recommended)")
600
  a = ap.parse_args()
601
+ if a.list_voices:
602
+ list_voices(a.voices); return
603
+ if not a.text:
604
+ ap.error("--text is required")
605
+ text = prepare_text(a)
606
+ if text != a.text or a.auto_tag:
607
+ print(f"[tagged] {text}")
608
+ if a.tag_only:
609
+ return
610
  if not os.path.isdir(a.model_dir):
611
  from huggingface_hub import snapshot_download
612
  snapshot_download("FunAudioLLM/CosyVoice2-0.5B", local_dir=a.model_dir)
613
+ versions = ["phase2", "phase3"] if a.compare else [a.version]
614
+ cosy = tok = None
615
+ base, ext = os.path.splitext(a.out)
616
+ for v in versions:
617
+ hint = recommendation(v, json.load(open(a.voices)).get("aliases", {}).get(a.voice, a.voice) if os.path.exists(a.voices) else a.voice)
618
+ if hint:
619
+ print(hint)
620
+ S = build(v, a, cosy, tok)
621
+ cosy, tok = S.cosy, S.tok
622
+ w = S.say(text, seed=a.seed, prompt_k=a.prompt_clip, stitch=not a.no_stitch, pause_mode=a.pause_mode,
623
+ speed=a.speed if v == "phase2" else 1.0)
624
+ out = f"{base}_{v}{ext or '.wav'}" if a.compare else a.out
625
+ sf.write(out, w, S.sr)
626
+ print(f"[{v}] wrote {out} ({len(w) / S.sr:.2f} s, {S.sr} Hz, voice={a.voice}, device={DEV.type})")
627
+ del S.m; torch.cuda.empty_cache()
628
 
629
 
630
  if __name__ == "__main__":
prompts/voice_waxal_ibo_5.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d537c83dbe167ccf0b43f03304e456fb05e59dfda6b4e359d941900ef629924f
3
+ size 318044
prompts/voice_waxal_yor_5.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce75b93cf5166d0dc2f53cc943330b4d304f2e060f831d38deb2e664fff49ca5
3
+ size 182924
prompts/voices.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "note": "zero-shot voices: the clip is the flow + LM prompt; the speaker token is used when the LM saw that speaker",
3
+ "voices": {
4
+ "anchor_male": {
5
+ "wav": null,
6
+ "lang": "yor",
7
+ "spk": "waxal_yor_4",
8
+ "gender": "male",
9
+ "source": "WAXAL Media Trust TTS, Yoruba speaker 4 (prompts/clip0..3)",
10
+ "licence": "CC-BY-SA-4.0 (Media Trust TTS subset)",
11
+ "desc": "male, WAXAL Yoruba speaker 4: the anchor voice both versions were trained around (uses prompts/prompts.json clip 0)"
12
+ },
13
+ "waxal_ibo_5_female": {
14
+ "wav": "prompts/voice_waxal_ibo_5.wav",
15
+ "tagged": "<ibo>I nwere ike itinye mmemme ọbụla n'izu a ma gị atụla ụjọ maka oké mmiri ozuzo.</ibo>",
16
+ "text": "I nwere ike itinye mmemme ọbụla n'izu a ma gị atụla ụjọ maka oké mmiri ozuzo.",
17
+ "lang": "ibo",
18
+ "spk": "waxal_ibo_5",
19
+ "gender": "female",
20
+ "source_clip": "waxal_ibo_test_ibo_502_c003909",
21
+ "licence": "CC-BY-SA-4.0 (Media Trust TTS subset)",
22
+ "desc": "female native Igbo speaker (WAXAL ibo speaker 5); prompt clip waxal_ibo_test_ibo_502_c003909 (6.625 s)"
23
+ },
24
+ "waxal_yor_5_female": {
25
+ "wav": "prompts/voice_waxal_yor_5.wav",
26
+ "tagged": "<yor>Nínú oṣù Ọpẹ́ ni ó ra mọ́tò tí wọ́n jí gbé.</yor>",
27
+ "text": "Nínú oṣù Ọpẹ́ ni ó ra mọ́tò tí wọ́n jí gbé.",
28
+ "lang": "yor",
29
+ "spk": "waxal_yor_5",
30
+ "gender": "female",
31
+ "source_clip": "waxal_yor_test_yor_686",
32
+ "licence": "CC-BY-SA-4.0 (Media Trust TTS subset)",
33
+ "desc": "female native Yoruba speaker (WAXAL yor speaker 5); prompt clip waxal_yor_test_yor_686 (3.81 s)"
34
+ }
35
+ },
36
+ "aliases": {
37
+ "anchor": "anchor_male",
38
+ "waxal_ibo_5": "waxal_ibo_5_female",
39
+ "waxal_yor_5": "waxal_yor_5_female"
40
+ }
41
+ }
requirements.txt CHANGED
@@ -21,7 +21,7 @@ inflect>=7.3
21
  x-transformers>=2.11
22
  openai-whisper>=20231117
23
  einops>=0.8
24
- pyworld>=0.3.4
25
  pyarrow>=18
26
  wget==3.2
27
  gdown>=5.1
@@ -31,3 +31,5 @@ protobuf>=4.25
31
  pydantic>=2.7
32
  wetext==0.0.4
33
  audiotsm==0.1.2 # WSOLA time-stretch for --speed (falls back to librosa if missing)
 
 
 
21
  x-transformers>=2.11
22
  openai-whisper>=20231117
23
  einops>=0.8
24
+ # pyworld>=0.3.4 # CosyVoice training only; infer.py runs without it
25
  pyarrow>=18
26
  wget==3.2
27
  gdown>=5.1
 
31
  pydantic>=2.7
32
  wetext==0.0.4
33
  audiotsm==0.1.2 # WSOLA time-stretch for --speed (falls back to librosa if missing)
34
+ gradio>=4.44,<6 # app.py
35
+ # optional, for --lid glotlid / the GlotLID checkbox: fasttext (or fasttext-wheel)
samples/phase3/phase2/custom_dynacc.mp3 ADDED
Binary file (33.6 kB). View file
 
samples/phase3/phase2/ibo_000.mp3 ADDED
Binary file (42.4 kB). View file
 
samples/phase3/phase2/omo_announce.mp3 ADDED
Binary file (73.5 kB). View file
 
samples/phase3/phase2/para_hau_eng_pcm.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd687cc31eab7f893afd9e8ab1e64a3ce2093c7331ef6a70cd0a5f9a3f33bd72
3
+ size 143876
samples/phase3/phase2/para_ibo_pcm_eng.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:afae973f5a0fcbd50931b8dfc8f42d48c91b17f18a584a6558c8609af0e37f41
3
+ size 122636
samples/phase3/phase2/para_yor_eng.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:03d0f2bce66da138cbe6e0fe2d38392e638122b82be82dfcb1ba83b3f140d811
3
+ size 123380
samples/phase3/phase3/custom_dynacc.mp3 ADDED
Binary file (35.7 kB). View file
 
samples/phase3/phase3/ibo_000.mp3 ADDED
Binary file (36.1 kB). View file
 
samples/phase3/phase3/omo_announce.mp3 ADDED
Binary file (66.4 kB). View file
 
samples/phase3/phase3/para_hau_eng_pcm.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee8e7b3b1011ef0e6fd6900acae4d7ddc62cbb830b0401492f6a5e40a84b19fa
3
+ size 132620
samples/phase3/phase3/para_ibo_pcm_eng.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:be778e8be5554b933e8fcaa5d9deac5150b2defd5fca9351bc3ae483697ed5b4
3
+ size 128036
samples/phase3/phase3/para_yor_eng.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2546d9ddde91b773447148cf767f19252bad7efc0914b494e04ee1cf8a23cd99
3
+ size 123884
samples/phase3/phase3_female/custom_dynacc__waxal_yor_5.mp3 ADDED
Binary file (42.7 kB). View file
 
samples/phase3/phase3_female/ibo_000__waxal_ibo_5.mp3 ADDED
Binary file (28.1 kB). View file
 
samples/phase3/phase3_female/omo_announce__waxal_ibo_5.mp3 ADDED
Binary file (55.5 kB). View file
 
samples/phase3/phase3_female/omo_announce__waxal_yor_5.mp3 ADDED
Binary file (61.3 kB). View file
 
samples/phase3/phase3_female/para_ibo_pcm_eng__waxal_ibo_5.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eacca63c857ff58afcce75297eeb4c7e7f9deeb90ebc2dd212e57a026a53121a
3
+ size 119228
samples/phase3/phase3_female/para_yor_eng__waxal_yor_5.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0f6b90c343f255c8ffaab0ea92811928dee7b8b985fcd14973c2cbec40f80429
3
+ size 119060
settings.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "default_version": "phase3",
3
+ "default_voice": "anchor_male",
4
+ "versions": {
5
+ "phase2": {
6
+ "lm": "checkpoints/phase2/p2_lm_B/best.pt",
7
+ "flow": "checkpoints/phase2/p2_flow_sft/best.pt",
8
+ "sampler": "ras",
9
+ "one_pass": false,
10
+ "postprocess": true,
11
+ "rate": null,
12
+ "desc": "Phase 2 final system: LM p2_lm_B (~120 h, pause profile B) + anchor-SFT flow; sentence by sentence, internal silences capped at 0.6 s"
13
+ },
14
+ "phase3": {
15
+ "lm": "checkpoints/phase3/p3_lm_full/best.pt",
16
+ "flow": "checkpoints/phase2/p2_flow_sft/best.pt",
17
+ "sampler": "silexcap",
18
+ "one_pass": true,
19
+ "postprocess": false,
20
+ "rate": "normal",
21
+ "desc": "Phase 3: LM p3_lm_full (178 h re-cut, ~102 h Igbo, [pause]/[breath] + speed instruction) + the shared Phase 2 flow; one pass per paragraph, model-made pauses"
22
+ }
23
+ },
24
+ "voices": [
25
+ "anchor_male",
26
+ "waxal_ibo_5_female",
27
+ "waxal_yor_5_female"
28
+ ],
29
+ "recommended": {
30
+ "phase3": [
31
+ "waxal_ibo_5_female",
32
+ "waxal_yor_5_female"
33
+ ],
34
+ "phase2": [
35
+ "anchor_male"
36
+ ],
37
+ "note": "Listening verdict (model author, 2026-10-21): Phase 3 sounds best with the female voices; Phase 2 is fine for the male anchor. Use --compare to A/B before use."
38
+ },
39
+ "rates": [
40
+ "none",
41
+ "very slow",
42
+ "slow",
43
+ "normal",
44
+ "fast",
45
+ "very fast"
46
+ ]
47
+ }
tagger.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Dynamic Accent auto-tagger: word/span-level language ID for untagged mixed Nigerian text.
3
+
4
+ Labels: eng (English), pcm (Nigerian Pidgin), ibo (Igbo), yor (Yoruba), hau (Hausa).
5
+ Output is the tagged text infer.py expects, e.g.
6
+ "omo, I'm very excited" -> "<pcm>omo,</pcm> <eng>I'm very excited</eng>"
7
+
8
+ How it decides (rule-based, no model needed):
9
+ 1. letters: Yoruba-only (ẹ ṣ, tone-marked n/m), Igbo-only (ị ụ ṅ), Hausa-only (ɓ ɗ ƙ ƴ), shared ọ;
10
+ 2. lexicons of frequent function words / greetings per language (diacritics folded for lookup);
11
+ 3. Pidgin markers ("dey", "abi", "omo", "wetin", "abeg", "una", "no go", "e don", "know say", pronoun + "go" + verb ...):
12
+ a clause of plain-ASCII words that contains a Pidgin marker becomes Pidgin as a whole;
13
+ 4. clause context: ambiguous words ("na", "o", "ka", "ya", "ma", "ni" ...) and unknown words take the language of the
14
+ clause they sit in, then of the neighbouring clause; plain ASCII with no evidence falls back to English.
15
+ 5. optional: --lid glotlid adds clause-level scores from GlotLID v3 (cis-lmu/glotlid, fastText, Apache-2.0 + notices,
16
+ 1.7 GB download, needs `pip install fasttext`), mainly to separate untagged Igbo/Yoruba/Hausa written without
17
+ diacritics from English.
18
+
19
+ Overrides: lang_map {"omo": "pcm"} forces a word; force_lang renders everything in one language.
20
+ Always print the tagged text and let the user fix it: the tagger is a convenience, not ground truth.
21
+
22
+ python tagger.py "Omo, this traffic no be here o. I go call you later."
23
+ python tagger.py --lang_map "omo=pcm,sha=pcm" "..."
24
+ """
25
+ import argparse, json, re, sys, unicodedata
26
+
27
+ LANGS = ["eng", "pcm", "ibo", "yor", "hau"]
28
+ NG = ("ibo", "yor", "hau")
29
+ TAGGED = re.compile(r"<(eng|ibo|hau|yor|pcm)>")
30
+
31
+
32
+ def fold(w):
33
+ """lowercase, NFD, drop every combining mark (tones, dot below, dot above), map hooked letters."""
34
+ w = unicodedata.normalize("NFD", w.lower())
35
+ w = "".join(c for c in w if not unicodedata.combining(c))
36
+ return w.translate(str.maketrans({"ɓ": "b", "ɗ": "d", "ƙ": "k", "ƴ": "y", "’": "'", "ʼ": "'", "‘": "'"}))
37
+
38
+
39
+ def _set(s):
40
+ return {fold(x) for x in s.split()}
41
+
42
+
43
+ def _exact(s):
44
+ return {unicodedata.normalize("NFC", x.lower()) for x in s.split()}
45
+
46
+
47
+ # --- lexicons (frequent, high-precision words; folded). Words shared across languages are listed in each.
48
+ _RAW = {
49
+ "ibo": """
50
+ bụ nke ndị ihe ụlọ mmiri kedu ndewo nụ ụmụ ụmụnna nna nne anyị unu gị ha ọ ya ka ma
51
+ nwere ga ji dị ọma biko daalụ dalu ụtọ obi ike nri ego akwụkwọ ọrụ maka ebe otu ọzọ ugbu
52
+ a m n' na mere meela kwa kwesịrị ịga ịbịa bịa gaa nyere aka mmadụ obodo nwanne nwa nwoke nwanyị
53
+ ụbọchị echi taa ụnyaahụ abalị ututu ehihie mgbede oge afọ ọnwa izu ukwuu nnukwu ntakịrị
54
+ mana ọbụna ọbụla ruo site gbasara ebee onye gịnị olee ole kpọmkwem eziokwu nso anọ abụọ atọ
55
+ ise isii asaa asatọ itoolu iri mbụ zuru ezu ahụ ahụla ịhụ hụ chere chọrọ chọ mara amaghị
56
+ nọ nọrọ bịara gara kwuru kwuo sị siri esi na-esi na-eme eme mee eji ogologo ọkụ ụzọ ahịa ụka
57
+ ọnụ ụtụtụ ka ọ dị mma mma nnọọ kpọọ zụta re ree rie riri ụmụaka ezinụlọ chi chukwu ndụ
58
+ """,
59
+ "yor": """
60
+ ni ti si sí àti àwọn mo o wa wọn kò ṣé jẹ́ ọjọ́ ẹ káàbọ̀ ilé ìlú ọmọ púpọ̀ dáadáa bẹ́ẹ̀ ni
61
+ náà yìí kan mi wa yín rẹ̀ rẹ tirẹ̀ fún láti pé ń máa yóò ó a jẹ ṣe lọ wá rí fẹ́ mọ̀ sọ gbọ́
62
+ ẹ̀kọ́ iṣẹ́ owó ọjà òní ọ̀la àná ìpàdé padà ilé-ìwé ọjọ́ ajé ìsinmi alẹ́ àárọ̀ káàárọ̀ káàsán
63
+ káalẹ́ ẹ kú ṣé gan an pàdé ìyá bàbá ẹ̀gbọ́n àbúrò ọ̀rẹ́ ènìyàn èèyàn àgbà ọba ìjọba
64
+ nítorí ṣùgbọ́n tàbí bí kí kì ìgbà ibi ibo kílódé báwo kíni ta tani mélòó ọ̀kan méjì mẹ́ta
65
+ mẹ́rin márùn-ún ẹ̀ẹ̀kan dúpẹ́ ẹ ṣeun ẹ̀ jọ̀ọ́ jọ̀ọ́ oúnjẹ omi ilẹ̀ ojú ọwọ́ ẹsẹ̀
66
+ """,
67
+ "hau": """
68
+ da ba ya ta ka ki su mu na ne ce ina yaya barka kuke don haka za idan mai wannan wancan yana
69
+ tana suna muna kuna sannu lafiya kwana aiki gobe yau jiya ruwa sai amma kuma shi ita nan
70
+ wani wata abin zan zai zata zamu mutane gida kudi gode sosai tafi fita zuwa laima yamma safe
71
+ rana dare mako shekara wata yanzu kai ke ku mun sun ban bai bata sani so iya yi yin
72
+ farashin tumatur tashi kasuwa abinci ruwan sha babu akwai kamar saboda domin tare har tun
73
+ daga cikin waje gaba baya sama kasa me wane wace ina yaushe nawa daya biyu uku hudu biyar
74
+ """,
75
+ "pcm": """
76
+ dey abi omo wetin abeg una wahala sabi oga sef sha oya jare ehn ehen shey kpele pikin
77
+ chop comot waka belle palava wan wey dem don am siddon gist gbam yawa kampe sharp sharp
78
+ nawa wahala kolo japa ginger vex dash ogbonge padi paddy shine eye
79
+ """,
80
+ "eng": """
81
+ the a an and or but if then so because of to in on at for with from by about as into over
82
+ after before between through during without under again further here there when where why how
83
+ all any both each few more most other some such no nor not only own same than too very can
84
+ will just should now i me my we our you your he him his she her it its they them their what
85
+ which who whom this that these those am is are was were be been being have has had having do
86
+ does did doing would could might must shall may please thank thanks yes hello hi good morning
87
+ evening afternoon night today tomorrow yesterday week year time day people man woman child
88
+ children team meeting report school market road weather rain safe everyone everybody
89
+ new old big small great right left back home work job money food water house car family
90
+ friend friends know think say said tell told make made take took get got give gave go went
91
+ come came see saw look want need like love help call send sent finish start stop keep let put
92
+ read write talk speak discuss three two one four five six seven eight nine ten first second
93
+ last next soon late early ready sure also still really very much many lot thing things way
94
+ out up down off well done going coming doing nothing something everything anything model
95
+ """,
96
+ }
97
+ LEX = {l: _set(v) for l, v in _RAW.items()} # folded (diacritics dropped) for lookup
98
+ LEX_EXACT = {l: _exact(v) for l, v in _RAW.items()} # as written
99
+ _N_LEX = {}
100
+ for _l, _v in LEX.items():
101
+ for _w in _v:
102
+ _N_LEX[_w] = _N_LEX.get(_w, 0) + 1 # >1 = word shared by several languages (na, o, ka, ya ...)
103
+ # words that are Pidgin markers on their own (strong evidence)
104
+ PCM_STRONG = _set("dey abi omo wetin abeg una wahala sabi sef oya jare shey pikin chop comot waka palava wan wey dem siddon vex japa ehn ehen")
105
+ PCM_BIGRAMS = {("no", "go"), ("no", "be"), ("no", "fit"), ("no", "dey"), ("e", "don"), ("e", "dey"), ("e", "no"),
106
+ ("know", "say"), ("tell", "am"), ("well", "well"), ("how", "far"), ("how", "body"), ("na", "so"),
107
+ ("make", "we"), ("make", "i"), ("make", "una"), ("make", "dem"), ("i", "no"), ("we", "no"),
108
+ ("you", "no"), ("dem", "no"), ("no", "easy"), ("no", "forget"), ("no", "vex"), ("fit", "heavy"),
109
+ ("don", "come"), ("don", "go"), ("for", "inside"), ("una", "well"), ("e", "go"), ("na", "you"),
110
+ ("na", "me"), ("na", "im"), ("na", "dem"), ("na", "una"), ("give", "you"), ("lie", "give")}
111
+ PRON = _set("i we you dem una e im him she he they")
112
+ NOT_VERB_AFTER_GO = _set("to home there back out away on in up down off ahead")
113
+ PCM_AMBIG_END = _set("o") # clause-final particle "o" in an ASCII clause -> Pidgin evidence
114
+
115
+ YOR_CH = set("ẹṣ") # after NFC lowercase; also e/s + U+0323
116
+ IBO_CH = set("ịụṅ")
117
+ HAU_CH = set("ɓɗƙƴ")
118
+ WORD = re.compile(r"[^\W\d_](?:[^\W\d_]|['’ʼ\-\u0300-\u036f])*", re.U)
119
+ CLAUSE_END = re.compile(r"[,;:.?!]")
120
+
121
+
122
+ def char_scores(w):
123
+ s = dict.fromkeys(LANGS, 0.0)
124
+ nfd = unicodedata.normalize("NFD", w.lower())
125
+ base = None
126
+ for c in nfd:
127
+ if unicodedata.combining(c):
128
+ if c == "\u0323": # dot below
129
+ if base in "es":
130
+ s["yor"] += 3
131
+ elif base in "iu":
132
+ s["ibo"] += 3
133
+ elif base == "o":
134
+ s["yor"] += 1; s["ibo"] += 1
135
+ elif c == "\u0307" and base == "n":
136
+ s["ibo"] += 3
137
+ elif c in "\u0300\u0301\u0304": # tones
138
+ if base in "nm":
139
+ s["yor"] += 2
140
+ else:
141
+ s["yor"] += 0.8; s["ibo"] += 0.3
142
+ continue
143
+ base = c
144
+ if c in HAU_CH:
145
+ s["hau"] += 4
146
+ if not any(s.values()) and re.fullmatch(r"[a-z'\-]+", fold(w)):
147
+ s["ascii"] = 1
148
+ return s
149
+
150
+
151
+ def word_scores(w):
152
+ s = char_scores(w)
153
+ ascii_ = s.pop("ascii", 0)
154
+ f = fold(w)
155
+ parts = [f] + (f.split("-") if "-" in f else [])
156
+ if f.startswith("n'") or re.match(r"^na-[a-z]", f):
157
+ s["ibo"] += 3
158
+ ex = unicodedata.normalize("NFC", w.lower())
159
+ for p in parts:
160
+ amb = 0.5 if _N_LEX.get(p, 0) > 1 else 1.0 # shared words: let the clause decide
161
+ for l in LANGS:
162
+ if p in LEX[l]:
163
+ w_ = 1.5 if (p == f and ex in LEX_EXACT[l]) or ascii_ and p in LEX_EXACT[l] else 0.7
164
+ s[l] += amb * (w_ if l != "pcm" else 1.0)
165
+ if f in PCM_STRONG and ascii_: # Pidgin markers are plain ASCII ("ọmọ" is Yoruba)
166
+ s["pcm"] += 4
167
+ # orthography hints (weak)
168
+ if ascii_:
169
+ if re.search(r"(th|wh|ck|tion|ing$|ould$|ght)", f):
170
+ s["eng"] += 1
171
+ if re.match(r"^(nw|kw|gw|mm|nn|nk|ng[a-z])", f) and f not in LEX["eng"]:
172
+ s["ibo"] += 0.7
173
+ if re.search(r"(ts|^ky|^'y|aa|ii|uu)", f) and f not in LEX["eng"]:
174
+ s["hau"] += 0.5
175
+ return s, bool(ascii_)
176
+
177
+
178
+ def tokenize(text):
179
+ """-> list of (start, end, word) for words; everything else is glue."""
180
+ return [(m.start(), m.end(), m.group(0)) for m in WORD.finditer(text)]
181
+
182
+
183
+ def clauses(text, toks):
184
+ """clause index for each word: a new clause starts after , ; : . ? !"""
185
+ ends = [m.start() for m in CLAUSE_END.finditer(text)]
186
+ out, k = [], 0
187
+ for a, b, w in toks:
188
+ while k < len(ends) and ends[k] < a:
189
+ k += 1
190
+ out.append(k)
191
+ return out
192
+
193
+
194
+ def _norm(d):
195
+ t = sum(max(0.0, v) for v in d.values())
196
+ return {k: (max(0.0, v) / t if t else 0.0) for k, v in d.items()}
197
+
198
+
199
+ class Tagger:
200
+ def __init__(self, lid=None, lang_map=None):
201
+ self.lang_map = {fold(k): v for k, v in (lang_map or {}).items()}
202
+ self.lid = None
203
+ if lid == "glotlid":
204
+ import fasttext
205
+ from huggingface_hub import hf_hub_download
206
+ self.lid = fasttext.load_model(hf_hub_download("cis-lmu/glotlid", "model.bin"))
207
+
208
+ def _lid_scores(self, text):
209
+ labs, ps = self.lid.predict(text.replace("\n", " "), k=20)
210
+ m = {"__label__eng_Latn": "eng", "__label__pcm_Latn": "pcm", "__label__ibo_Latn": "ibo",
211
+ "__label__yor_Latn": "yor", "__label__hau_Latn": "hau"}
212
+ s = dict.fromkeys(LANGS, 0.0)
213
+ for l, p in zip(labs, ps):
214
+ if l in m:
215
+ s[m[l]] += float(p)
216
+ return s
217
+
218
+ def word_langs(self, text):
219
+ toks = tokenize(text)
220
+ if not toks:
221
+ return toks, []
222
+ cl = clauses(text, toks)
223
+ n = len(toks)
224
+ sc, asc = zip(*[word_scores(w) for _, _, w in toks])
225
+ sc = [dict(s) for s in sc]
226
+ f = [fold(w) for _, _, w in toks]
227
+ # Pidgin bigrams and "pronoun go verb"
228
+ for i in range(n - 1):
229
+ if cl[i] != cl[i + 1]:
230
+ continue
231
+ if (f[i], f[i + 1]) in PCM_BIGRAMS:
232
+ sc[i]["pcm"] += 2.5; sc[i + 1]["pcm"] += 2.5
233
+ if i + 2 < n and f[i] in PRON and f[i + 1] == "go" and cl[i + 2] == cl[i] and f[i + 2] not in NOT_VERB_AFTER_GO \
234
+ and f[i + 2] in LEX["eng"] | LEX["pcm"]:
235
+ for j in (i, i + 1, i + 2):
236
+ sc[j]["pcm"] += 2.5
237
+ # clause-level evidence
238
+ C = sorted(set(cl))
239
+ cs = {c: dict.fromkeys(LANGS, 0.0) for c in C}
240
+ for i in range(n):
241
+ for l in LANGS:
242
+ cs[cl[i]][l] += sc[i][l]
243
+ # clause-final "o" in an ASCII clause = Pidgin particle (Yoruba "o" clauses carry other Yoruba evidence)
244
+ for i in range(n):
245
+ last = i == n - 1 or cl[i + 1] != cl[i]
246
+ if last and f[i] in PCM_AMBIG_END and asc[i]:
247
+ others = [j for j in range(n) if cl[j] == cl[i] and j != i]
248
+ if others and all(asc[j] for j in others) and cs[cl[i]]["yor"] < 2:
249
+ sc[i]["pcm"] += 3; cs[cl[i]]["pcm"] += 3
250
+ if self.lid is not None:
251
+ for c in C:
252
+ ws = [toks[i][2] for i in range(n) if cl[i] == c]
253
+ for l, v in self._lid_scores(" ".join(ws)).items():
254
+ cs[c][l] += 2.0 * v
255
+ out = []
256
+ for i in range(n):
257
+ c = cl[i]
258
+ ctx = _norm(cs[c])
259
+ nb = dict.fromkeys(LANGS, 0.0) # neighbouring clauses, weaker
260
+ for d in (-1, 1):
261
+ if c + d in cs:
262
+ for l, v in _norm(cs[c + d]).items():
263
+ nb[l] += 0.3 * v
264
+ tot = {l: sc[i][l] + 1.5 * ctx[l] + nb[l] for l in LANGS}
265
+ # plain-ASCII words in a clause without Nigerian-language evidence: English or Pidgin
266
+ if asc[i] and max(sc[i][l] for l in NG) < 1.0:
267
+ ng_ctx = max(cs[c][l] for l in NG)
268
+ if ng_ctx < 1.5 * max(cs[c]["eng"], cs[c]["pcm"], 0.1):
269
+ pc = cs[c]["pcm"] >= 2.5
270
+ out.append("pcm" if pc else ("eng" if sc[i]["pcm"] < 4 else "pcm"))
271
+ continue
272
+ best = max(tot, key=tot.get)
273
+ if tot[best] <= 0:
274
+ best = "eng"
275
+ out.append(best)
276
+ for i in range(n): # user overrides
277
+ if f[i] in self.lang_map:
278
+ out[i] = self.lang_map[f[i]]
279
+ return toks, out
280
+
281
+ def tag(self, text, force_lang=None):
282
+ text = unicodedata.normalize("NFC", text.strip())
283
+ if TAGGED.search(text):
284
+ return text # already tagged: leave alone
285
+ if force_lang:
286
+ return f"<{force_lang}>{text}</{force_lang}>"
287
+ toks, langs = self.word_langs(text)
288
+ if not toks:
289
+ return f"<eng>{text}</eng>"
290
+ # spans: consecutive same-language words; glue (spaces/punct) stays with the preceding word
291
+ spans, cur_l, a = [], langs[0], 0
292
+ for i in range(1, len(toks)):
293
+ if langs[i] != cur_l:
294
+ b = toks[i][0]
295
+ seg = text[a:b]
296
+ spans.append((cur_l, seg))
297
+ cur_l, a = langs[i], b
298
+ spans.append((cur_l, text[a:]))
299
+ lead = text[:toks[0][0]]
300
+ out = []
301
+ for k, (l, s) in enumerate(spans):
302
+ s2 = (lead + s) if k == 0 else s
303
+ core = s2.rstrip()
304
+ out.append(f"<{l}>{core}</{l}>")
305
+ return " ".join(out)
306
+
307
+
308
+ def parse_map(s):
309
+ m = {}
310
+ for kv in (s or "").split(","):
311
+ if "=" in kv:
312
+ k, v = kv.split("=", 1)
313
+ v = v.strip()
314
+ if v not in LANGS:
315
+ raise ValueError(f"--lang_map: unknown language '{v}' (use {LANGS})")
316
+ m[k.strip()] = v
317
+ return m
318
+
319
+
320
+ def spans_of(tagged):
321
+ return [(m.group(1), m.group(2)) for m in re.finditer(r"<(eng|ibo|hau|yor|pcm)>(.*?)</\1>", tagged, re.S)]
322
+
323
+
324
+ def word_labels(tagged):
325
+ """gold/pred word-level labels from a tagged string."""
326
+ out = []
327
+ for l, s in spans_of(tagged):
328
+ out += [(fold(w), l) for _, _, w in tokenize(s)]
329
+ return out
330
+
331
+
332
+ def strip_tags(tagged):
333
+ return re.sub(r"\s+", " ", re.sub(r"</?(eng|ibo|hau|yor|pcm)>", "", tagged)).strip()
334
+
335
+
336
+ def main():
337
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
338
+ ap.add_argument("text")
339
+ ap.add_argument("--lang_map", default="", help='per-word overrides, e.g. "omo=pcm,sha=pcm"')
340
+ ap.add_argument("--force_lang", choices=LANGS)
341
+ ap.add_argument("--lid", choices=["none", "glotlid"], default="none")
342
+ a = ap.parse_args()
343
+ t = Tagger(None if a.lid == "none" else a.lid, parse_map(a.lang_map))
344
+ print(t.tag(a.text, a.force_lang))
345
+
346
+
347
+ if __name__ == "__main__":
348
+ main()