yangrongzhao commited on
Commit
fa7a22e
·
1 Parent(s): 7606a76

Update cpp, jieba still buggy

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +6 -0
  2. cpp/.gitattributes +3 -0
  3. cpp/CMakeLists.txt +65 -0
  4. cpp/build.sh +8 -0
  5. cpp/cmake/msp_dependencies.cmake +24 -0
  6. cpp/dict/cmudict-0.7b/LICENSE +33 -0
  7. cpp/dict/cmudict-0.7b/README +32 -0
  8. cpp/dict/cmudict-0.7b/README.developer +66 -0
  9. cpp/dict/cmudict-0.7b/cmudict.dict +0 -0
  10. cpp/dict/cmudict-0.7b/cmudict.phones +39 -0
  11. cpp/dict/cmudict-0.7b/cmudict.symbols +84 -0
  12. cpp/dict/cmudict-0.7b/cmudict.vp +54 -0
  13. cpp/dict/cmudict-0.7b/make_ps_dict.py +188 -0
  14. cpp/dict/hmm_model.utf8 +0 -0
  15. cpp/dict/idf.utf8 +0 -0
  16. cpp/dict/jieba.dict.utf8 +0 -0
  17. cpp/dict/pinyin.txt +0 -0
  18. cpp/dict/pinyin_phrase.txt +0 -0
  19. cpp/dict/pos_dict/char_state_tab.utf8 +0 -0
  20. cpp/dict/pos_dict/prob_emit.utf8 +0 -0
  21. cpp/dict/pos_dict/prob_start.utf8 +259 -0
  22. cpp/dict/pos_dict/prob_trans.utf8 +0 -0
  23. cpp/dict/stop_words.utf8 +1534 -0
  24. cpp/dict/user.dict.utf8 +4 -0
  25. cpp/dict/vocab.txt +114 -0
  26. cpp/download_bsp.sh +5 -0
  27. cpp/main.cpp +115 -0
  28. cpp/scripts/compare.py +14 -0
  29. cpp/scripts/convert_dict.py +122 -0
  30. cpp/scripts/export_vocab.py +38 -0
  31. cpp/scripts/export_voices.py +18 -0
  32. cpp/src/AudioFile.h +1293 -0
  33. cpp/src/EnG2P.h +138 -0
  34. cpp/src/JiebaProcessor.h +103 -0
  35. cpp/src/Kokoro.cpp +1216 -0
  36. cpp/src/Kokoro.h +185 -0
  37. cpp/src/PinyinFinder.cpp +194 -0
  38. cpp/src/PinyinFinder.h +23 -0
  39. cpp/src/Tokenizer.cpp +69 -0
  40. cpp/src/Tokenizer.h +34 -0
  41. cpp/src/ToneSandhi.cpp +322 -0
  42. cpp/src/ToneSandhi.h +46 -0
  43. cpp/src/Utils.h +290 -0
  44. cpp/src/ZHFrontend.cpp +182 -0
  45. cpp/src/ZHFrontend.h +40 -0
  46. cpp/src/ZHG2P.cpp +428 -0
  47. cpp/src/ZHG2P.h +48 -0
  48. cpp/src/ax_model_runner/ax_model_runner.cpp +442 -0
  49. cpp/src/ax_model_runner/ax_model_runner.hpp +84 -0
  50. cpp/src/cppjieba/DictTrie.hpp +280 -0
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /cpp/ax650n_bsp_sdk
2
+ /cpp/install
3
+ __pycache__
4
+ .vscode
5
+ /cpp/build
6
+ *.wav
cpp/.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ third_party/onnxruntime/lib/libonnxruntime_providers_shared.so filter=lfs diff=lfs merge=lfs -text
2
+ third_party/onnxruntime/lib/libonnxruntime.so filter=lfs diff=lfs merge=lfs -text
3
+ third_party/onnxruntime/lib/libonnxruntime.so.1.14.0 filter=lfs diff=lfs merge=lfs -text
cpp/CMakeLists.txt ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
2
+ project(kokoro)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+
6
+ if (CMAKE_BUILD_TYPE MATCHES Debug)
7
+ set(CMAKE_CXX_FLAGS "-fvisibility=hidden -g -O0")
8
+ elseif (CMAKE_BUILD_TYPE MATCHES Release)
9
+ set(CMAKE_CXX_FLAGS "-fvisibility=hidden -O2")
10
+ endif()
11
+
12
+ include(cmake/msp_dependencies.cmake)
13
+ add_definitions(-DENV_HAS_STD_FILESYSTEM)
14
+ add_definitions(-DENV_HAS_POSIX_FILE_STAT)
15
+
16
+ include_directories(${MSP_INC_DIR})
17
+ link_directories(${MSP_LIB_DIR})
18
+
19
+ # onnxruntime
20
+ include_directories(third_party/onnxruntime/include)
21
+ include_directories(third_party/onnxruntime/include/onnxruntime/core/session)
22
+ link_directories(third_party/onnxruntime/lib)
23
+ list(APPEND ORT_LIBS onnxruntime onnxruntime_providers_shared)
24
+
25
+ # limonp
26
+ include_directories(src/limonp/include)
27
+
28
+ # Eigen
29
+ include_directories(src/librosa/eigen3)
30
+
31
+ # librosa
32
+ include_directories(src/librosa/)
33
+
34
+ include_directories(src)
35
+ aux_source_directory(src SRC)
36
+ aux_source_directory(src/ax_model_runner SRC)
37
+
38
+ add_executable(kokoro main.cpp ${SRC})
39
+ target_link_libraries(kokoro ${MSP_LIBS} ${ORT_LIBS})
40
+
41
+ add_executable(test_ax_model tests/test_ax_model.cpp ${SRC})
42
+ target_link_libraries(test_ax_model ${MSP_LIBS} ${ORT_LIBS})
43
+
44
+ add_executable(test_jieba tests/test_jieba.cpp ${SRC})
45
+ target_link_libraries(test_jieba ${MSP_LIBS} ${ORT_LIBS})
46
+
47
+ install(TARGETS
48
+ kokoro
49
+ test_ax_model
50
+ test_jieba
51
+ RUNTIME
52
+ DESTINATION .)
53
+
54
+ set_target_properties(kokoro
55
+ PROPERTIES
56
+ INSTALL_RPATH "$ORIGIN/../third_party/onnxruntime/lib"
57
+ )
58
+ set_target_properties(test_ax_model
59
+ PROPERTIES
60
+ INSTALL_RPATH "$ORIGIN/../third_party/onnxruntime/lib"
61
+ )
62
+ set_target_properties(test_jieba
63
+ PROPERTIES
64
+ INSTALL_RPATH "$ORIGIN/../third_party/onnxruntime/lib"
65
+ )
cpp/build.sh ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ mkdir -p build && cd build
2
+ cmake .. \
3
+ -DCHIP_AX650=ON \
4
+ -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-none-linux-gnu.toolchain.cmake \
5
+ -DCMAKE_INSTALL_PREFIX=../install \
6
+ -DCMAKE_BUILD_TYPE=Release
7
+ make -j4
8
+ make install
cpp/cmake/msp_dependencies.cmake ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # bsp
2
+ if(NOT BSP_MSP_DIR)
3
+ if (CHIP_AX650)
4
+ add_definitions(-DCHIP_AX650)
5
+ set(BSP_MSP_DIR ${CMAKE_SOURCE_DIR}/ax650n_bsp_sdk/msp/out)
6
+ else()
7
+ add_definitions(-DCHIP_AX630C)
8
+ set(BSP_MSP_DIR ${CMAKE_SOURCE_DIR}/ax620e_bsp_sdk/msp/out/arm64_glibc)
9
+ endif()
10
+ endif()
11
+ message(STATUS "BSP_MSP_DIR = ${BSP_MSP_DIR}")
12
+
13
+ # check bsp exist
14
+ if(NOT EXISTS ${BSP_MSP_DIR})
15
+ message(FATAL_ERROR "FATAL: BSP_MSP_DIR ${BSP_MSP_DIR} not exist")
16
+ endif()
17
+
18
+ set(MSP_INC_DIR ${BSP_MSP_DIR}/include)
19
+ set(MSP_LIB_DIR ${BSP_MSP_DIR}/lib)
20
+
21
+ list(APPEND MSP_LIBS
22
+ ax_sys
23
+ ax_engine
24
+ ax_interpreter)
cpp/dict/cmudict-0.7b/LICENSE ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (C) 1993-2015 Carnegie Mellon University. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ The contents of this file are deemed to be source code.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in
13
+ the documentation and/or other materials provided with the
14
+ distribution.
15
+
16
+ This work was supported in part by funding from the Defense Advanced
17
+ Research Projects Agency, the Office of Naval Research and the National
18
+ Science Foundation of the United States of America, and by member
19
+ companies of the Carnegie Mellon Sphinx Speech Consortium. We acknowledge
20
+ the contributions of many volunteers to the expansion and improvement of
21
+ this dictionary.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
24
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
27
+ NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cpp/dict/cmudict-0.7b/README ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ CMUdict
3
+ -------
4
+
5
+ CMUdict (the Carnegie Mellon Pronouncing Dictionary) is a free
6
+ pronouncing dictionary of English, suitable for uses in speech
7
+ technology and is maintained by the Speech Group in the School of
8
+ Computer Science at Carnegie Mellon University.
9
+
10
+ The Carnegie Mellon Speech Group does not guarantee the accuracy of
11
+ this dictionary, nor its suitability for any specific purpose. In
12
+ fact, we expect a number of errors, omissions and inconsistencies to
13
+ remain in the dictionary. We intend to continually update the
14
+ dictionary by correction existing entries and by adding new ones. From
15
+ time to time a new major version will be released.
16
+
17
+ We welcome input from users: Please send email to Alex Rudnicky
18
+ (air+cmudict@cs.cmu.edu).
19
+
20
+ The Carnegie Mellon Pronouncing Dictionary, in its current and
21
+ previous versions is Copyright (C) 1993-2014 by Carnegie Mellon
22
+ University. Use of this dictionary for any research or commercial
23
+ purpose is completely unrestricted. If you make use of or
24
+ redistribute this material we request that you acknowledge its
25
+ origin in your descriptions.
26
+
27
+ If you add words to or correct words in your version of this
28
+ dictionary, we would appreciate it if you could send these additions
29
+ and corrections to us (air+cmudict@cs.cmu.edu) for consideration in a
30
+ subsequent version. All submissions will be reviewed and approved by
31
+ the current maintainer, Alex Rudnicky at Carnegie Mellon.
32
+
cpp/dict/cmudict-0.7b/README.developer ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Development and maintenance for cmudict
2
+ ---------------------------------------
3
+ [20100118] (air)
4
+
5
+ The maintainer is responsible for acquiring and vetting new entries,
6
+ and for fixing errors that they otherwise encounter.
7
+
8
+ At this point, the cmudict project has been incrementally
9
+ re-organized; maintenance has been simplified and several aspects have
10
+ been automated. The scripts/ folder contains instructions and scripts
11
+ for routine maintenance. It has everything you should need to get
12
+ started.
13
+
14
+ Version numbers and files
15
+ -------------------------
16
+ There is no particular rule for incrementing the version number. To
17
+ date the minor version (letter suffix) has been incremented to reflect
18
+ changes in maintainers. Major version increments (right now, the
19
+ decimal) are incurred when some (subjectively) large change
20
+ occurs. For example, the 0.6-->0.7 increment was marked by a large
21
+ number of new entries and by the removal of many incorrect entries
22
+ from the preceeding 0.6e version.
23
+
24
+ The cmudict.*.phones file lists all legal phones, plus their phonetic class.
25
+ The cmudict.*.symbols file lists all legal phonetic symbols (the only
26
+ substantive difference is that stress combinations are explicitly noted).
27
+
28
+ Projects for the ambitious
29
+ --------------------------
30
+
31
+ 1. Change the current flat-file version to a database format. This
32
+ should still allow producing a flat file, but it will simplify adding
33
+ useful information to the dictionary. Some possible data includes:
34
+
35
+ a. part-of-speech information
36
+ b. domain information (e.g., location, medical, non-english, etc)
37
+ c. spelling variants
38
+ d. source information (who, when, ...)
39
+ e. probabilities for pronunciation variants
40
+
41
+ There's additional stuff that can be done but the above bits seem the
42
+ most useful ones. I also have ideas on how to do it, so feel free to
43
+ get in touch (air at cs cmu edu).
44
+
45
+ 2. Create an OS independent GUI for managing the database. This should
46
+ allow the maintainer to view and modify entries, while dealing with
47
+ bookkeeping. It would be nice if the GUI included a synthesizer so
48
+ that entries can be checked by listening.
49
+
50
+ Reductions
51
+ ----------
52
+
53
+ CMUDict tries to be phonetic dictionary and account for possible phonetic
54
+ reductions like
55
+
56
+ N T -> N intersomething countersomething
57
+ IH -> AH lots of examples
58
+ EH -> AH lots of examples
59
+ AE -> EH happens sometimes
60
+
61
+ However, this is not done in a consistent way yet. So sometimes both
62
+ reduced and original version is present in the dictionary, sometimes
63
+ only original or only reduced based on perceived frequency in read speech.
64
+
65
+ This situation might improve in the future
66
+
cpp/dict/cmudict-0.7b/cmudict.dict ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/cmudict-0.7b/cmudict.phones ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AA vowel
2
+ AE vowel
3
+ AH vowel
4
+ AO vowel
5
+ AW vowel
6
+ AY vowel
7
+ B stop
8
+ CH affricate
9
+ D stop
10
+ DH fricative
11
+ EH vowel
12
+ ER vowel
13
+ EY vowel
14
+ F fricative
15
+ G stop
16
+ HH aspirate
17
+ IH vowel
18
+ IY vowel
19
+ JH affricate
20
+ K stop
21
+ L liquid
22
+ M nasal
23
+ N nasal
24
+ NG nasal
25
+ OW vowel
26
+ OY vowel
27
+ P stop
28
+ R liquid
29
+ S fricative
30
+ SH fricative
31
+ T stop
32
+ TH fricative
33
+ UH vowel
34
+ UW vowel
35
+ V fricative
36
+ W semivowel
37
+ Y semivowel
38
+ Z fricative
39
+ ZH fricative
cpp/dict/cmudict-0.7b/cmudict.symbols ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AA
2
+ AA0
3
+ AA1
4
+ AA2
5
+ AE
6
+ AE0
7
+ AE1
8
+ AE2
9
+ AH
10
+ AH0
11
+ AH1
12
+ AH2
13
+ AO
14
+ AO0
15
+ AO1
16
+ AO2
17
+ AW
18
+ AW0
19
+ AW1
20
+ AW2
21
+ AY
22
+ AY0
23
+ AY1
24
+ AY2
25
+ B
26
+ CH
27
+ D
28
+ DH
29
+ EH
30
+ EH0
31
+ EH1
32
+ EH2
33
+ ER
34
+ ER0
35
+ ER1
36
+ ER2
37
+ EY
38
+ EY0
39
+ EY1
40
+ EY2
41
+ F
42
+ G
43
+ HH
44
+ IH
45
+ IH0
46
+ IH1
47
+ IH2
48
+ IY
49
+ IY0
50
+ IY1
51
+ IY2
52
+ JH
53
+ K
54
+ L
55
+ M
56
+ N
57
+ NG
58
+ OW
59
+ OW0
60
+ OW1
61
+ OW2
62
+ OY
63
+ OY0
64
+ OY1
65
+ OY2
66
+ P
67
+ R
68
+ S
69
+ SH
70
+ T
71
+ TH
72
+ UH
73
+ UH0
74
+ UH1
75
+ UH2
76
+ UW
77
+ UW0
78
+ UW1
79
+ UW2
80
+ V
81
+ W
82
+ Y
83
+ Z
84
+ ZH
cpp/dict/cmudict-0.7b/cmudict.vp ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ !exclamation-point EH2 K S K L AH0 M EY1 SH AH0 N P OY2 N T
2
+ "close-quote K L OW1 Z K W OW1 T
3
+ "double-quote D AH1 B AH0 L K W OW1 T
4
+ "end-of-quote EH1 N D AH0 V K W OW1 T
5
+ "end-quote EH1 N D K W OW1 T
6
+ "in-quotes IH1 N K W OW1 T S
7
+ "quote K W OW1 T
8
+ "unquote AH1 N K W OW1 T
9
+ #sharp-sign SH AA1 R P S AY1 N
10
+ %percent P ER0 S EH1 N T
11
+ &ampersand AE1 M P ER0 S AE2 N D
12
+ (begin-parens B IH0 G IH1 N P ER0 EH1 N Z
13
+ (in-parentheses IH1 N P ER0 EH1 N TH AH0 S IY2 Z
14
+ (left-paren L EH1 F T P ER0 EH1 N
15
+ (open-parentheses OW1 P AH0 N P ER0 EH1 N TH AH0 S IY2 Z
16
+ (paren P ER0 EH1 N
17
+ (parens P ER0 EH1 N Z
18
+ (parentheses P ER0 EH1 N TH AH0 S IY2 Z
19
+ )close-paren K L OW1 Z P ER0 EH1 N
20
+ )close-parentheses K L OW1 Z P ER0 EH1 N TH AH0 S IY2 Z
21
+ )end-paren EH1 N D P ER0 EH1 N
22
+ )end-parens EH1 N D P ER0 EH1 N Z
23
+ )end-parentheses EH1 N D P ER0 EH1 N TH AH0 S IY2 Z
24
+ )end-the-paren EH1 N D DH AH0 P ER0 EH1 N
25
+ )paren P ER0 EH1 N
26
+ )parens P ER0 EH1 N Z
27
+ )right-paren R AY1 T P ER0 EH1 N
28
+ )right-paren(1) R AY1 T P EH1 R AH0 N
29
+ )un-parentheses AH1 N P ER0 EH1 N TH AH0 S IY1 Z
30
+ ,comma K AA1 M AH0
31
+ -dash D AE1 SH
32
+ -hyphen HH AY1 F AH0 N
33
+ ...ellipsis IH0 L IH1 P S IH0 S
34
+ .decimal D EH1 S AH0 M AH0 L
35
+ .dot D AA1 T
36
+ .full-stop F UH1 L S T AA1 P
37
+ .period P IH1 R IY0 AH0 D
38
+ .point P OY1 N T
39
+ /slash S L AE1 SH
40
+ :colon K OW1 L AH0 N
41
+ ;semi-colon S EH1 M IY0 K OW1 L AH0 N
42
+ ;semi-colon(1) S EH1 M IH0 K OW2 L AH0 N
43
+ ?question-mark K W EH1 S CH AH0 N M AA1 R K
44
+ {brace B R EY1 S
45
+ {left-brace L EH1 F T B R EY1 S
46
+ {open-brace OW1 P EH0 N B R EY1 S
47
+ }close-brace K L OW1 Z B R EY1 S
48
+ }right-brace R AY1 T B R EY1 S
49
+ 'end-inner-quote EH1 N D IH1 N ER0 K W OW1 T
50
+ 'end-quote EH1 N D K W OW1 T
51
+ 'inner-quote IH1 N ER0 K W OW1 T
52
+ 'single-quote S IH1 NG G AH0 L K W OW1 T
53
+ 'quote K W OW1 T
54
+ 'apostrophe AH0 P AA1 S T R AH0 F IY0
cpp/dict/cmudict-0.7b/make_ps_dict.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Convert CMU Pronouncing Dictionary to PocketSphinx format.
4
+
5
+ This script strips stress markers (0, 1, 2) from phonemes and deduplicates
6
+ pronunciations that become identical after stress removal.
7
+
8
+ Example:
9
+ interest IH1 N T R AH0 S T -> interest IH N T R AH S T
10
+ interest(2) IH1 N T R IH0 S T -> interest(2) IH N T R IH S T
11
+ """
12
+
13
+ import argparse
14
+ import re
15
+ import sys
16
+ from collections import defaultdict
17
+ from typing import TextIO, Dict, List
18
+
19
+
20
+ def strip_stress(phoneme: str) -> str:
21
+ """
22
+ Remove stress markers from a phoneme.
23
+
24
+ CMUdict uses digits 0, 1, 2 at the end of vowel phonemes to indicate stress:
25
+ 0 = no stress
26
+ 1 = primary stress
27
+ 2 = secondary stress
28
+
29
+ Args:
30
+ phoneme: A phoneme string (e.g., 'AH0', 'T', 'IH1')
31
+
32
+ Returns:
33
+ The phoneme without stress markers (e.g., 'AH', 'T', 'IH')
34
+ """
35
+ return re.sub(r'[012]$', '', phoneme)
36
+
37
+
38
+ def parse_cmudict_line(line: str) -> tuple[str, List[str]] | None:
39
+ """
40
+ Parse a single line from CMUdict.
41
+
42
+ Args:
43
+ line: A line from the dictionary file
44
+
45
+ Returns:
46
+ Tuple of (base_word, phonemes) or None if line should be skipped
47
+
48
+ Examples:
49
+ "hello HH AH0 L OW1" -> ("hello", ["HH", "AH0", "L", "OW1"])
50
+ "world(2) W ER1 L D" -> ("world", ["W", "ER1", "L", "D"])
51
+ ";;; comment" -> None
52
+ """
53
+ line = line.strip()
54
+
55
+ # Skip empty lines and comments
56
+ if not line or line.startswith(';;;'):
57
+ return None
58
+
59
+ # Remove inline comments
60
+ if '#' in line:
61
+ line = line.split('#')[0].strip()
62
+
63
+ # Split into word and phonemes
64
+ parts = line.split()
65
+ if len(parts) < 2:
66
+ return None
67
+
68
+ word_with_variant = parts[0]
69
+ phonemes = parts[1:]
70
+
71
+ # Extract base word by removing variant marker like (2), (3), etc.
72
+ base_word = re.sub(r'\(\d+\)$', '', word_with_variant)
73
+
74
+ return base_word, phonemes
75
+
76
+
77
+ def convert_dict(infile: TextIO, outfile: TextIO) -> int:
78
+ """
79
+ Convert CMUdict to PocketSphinx format by stripping stress markers.
80
+
81
+ The function:
82
+ 1. Reads all pronunciations from the input
83
+ 2. Strips stress markers from phonemes
84
+ 3. Groups pronunciations by base word
85
+ 4. Removes duplicate pronunciations (same after stress removal)
86
+ 5. Writes sorted output with proper variant numbering
87
+
88
+ Args:
89
+ infile: Input file stream (CMUdict format)
90
+ outfile: Output file stream (PocketSphinx format)
91
+
92
+ Returns:
93
+ Number of entries written
94
+ """
95
+ # Dictionary to store unique pronunciations for each word
96
+ # Key: base word, Value: list of unique stress-free pronunciations
97
+ word_pronunciations: Dict[str, List[str]] = defaultdict(list)
98
+
99
+ # Read and process all lines
100
+ for line in infile:
101
+ result = parse_cmudict_line(line)
102
+ if result is None:
103
+ continue
104
+
105
+ base_word, phonemes = result
106
+
107
+ # Strip stress from all phonemes
108
+ phonemes_no_stress = [strip_stress(p) for p in phonemes]
109
+ pronunciation = ' '.join(phonemes_no_stress)
110
+
111
+ # Only keep unique pronunciations (deduplication)
112
+ if pronunciation not in word_pronunciations[base_word]:
113
+ word_pronunciations[base_word].append(pronunciation)
114
+
115
+ # Write output in sorted order
116
+ entries_written = 0
117
+ for base_word in sorted(word_pronunciations):
118
+ pronunciations = word_pronunciations[base_word]
119
+
120
+ for idx, pronunciation in enumerate(pronunciations):
121
+ # First pronunciation has no suffix, subsequent ones get (2), (3), etc.
122
+ if idx == 0:
123
+ word_variant = base_word
124
+ else:
125
+ word_variant = f"{base_word}({idx + 1})"
126
+
127
+ print(f"{word_variant} {pronunciation}", file=outfile)
128
+ entries_written += 1
129
+
130
+ return entries_written
131
+
132
+
133
+ def main() -> None:
134
+ """
135
+ Main entry point for the script.
136
+
137
+ Parses command-line arguments and orchestrates the conversion process.
138
+ """
139
+ parser = argparse.ArgumentParser(
140
+ description="Convert CMUdict to PocketSphinx format (strip stress markers)",
141
+ epilog="Example: python make_ps_dict.py cmudict.dict -o pocketsphinx.dict"
142
+ )
143
+ parser.add_argument(
144
+ "input",
145
+ nargs="?",
146
+ default="cmudict.dict",
147
+ help="input CMUdict file (default: cmudict.dict)"
148
+ )
149
+ parser.add_argument(
150
+ "-o", "--output",
151
+ type=str,
152
+ help="output file (default: stdout)"
153
+ )
154
+ parser.add_argument(
155
+ "-v", "--verbose",
156
+ action="store_true",
157
+ help="print progress information to stderr"
158
+ )
159
+
160
+ args = parser.parse_args()
161
+
162
+ # Log input file if verbose
163
+ if args.verbose:
164
+ print(f"Reading from: {args.input}", file=sys.stderr)
165
+
166
+ # Read input and write output
167
+ try:
168
+ with open(args.input, 'r', encoding='latin-1') as infile:
169
+ if args.output:
170
+ with open(args.output, 'w', encoding='utf-8') as outfile:
171
+ count = convert_dict(infile, outfile)
172
+ else:
173
+ count = convert_dict(infile, sys.stdout)
174
+
175
+ # Log success if verbose
176
+ if args.verbose:
177
+ print(f"Successfully wrote {count:,} entries", file=sys.stderr)
178
+
179
+ except FileNotFoundError:
180
+ print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
181
+ sys.exit(1)
182
+ except IOError as e:
183
+ print(f"Error: {e}", file=sys.stderr)
184
+ sys.exit(1)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
cpp/dict/hmm_model.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/idf.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/jieba.dict.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/pinyin.txt ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/pinyin_phrase.txt ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/pos_dict/char_state_tab.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/pos_dict/prob_emit.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/pos_dict/prob_start.utf8 ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #初始状态的概率
2
+ #格式
3
+ #状态:概率
4
+ B,a:-4.7623052146
5
+ B,ad:-6.68006603678
6
+ B,ag:-3.14e+100
7
+ B,an:-8.69708322302
8
+ B,b:-5.01837436211
9
+ B,bg:-3.14e+100
10
+ B,c:-3.42388018495
11
+ B,d:-3.97504752976
12
+ B,df:-8.88897423083
13
+ B,dg:-3.14e+100
14
+ B,e:-8.56355183039
15
+ B,en:-3.14e+100
16
+ B,f:-5.49163041848
17
+ B,g:-3.14e+100
18
+ B,h:-13.53336513
19
+ B,i:-6.11578472756
20
+ B,in:-3.14e+100
21
+ B,j:-5.05761912847
22
+ B,jn:-3.14e+100
23
+ B,k:-3.14e+100
24
+ B,l:-4.90588358466
25
+ B,ln:-3.14e+100
26
+ B,m:-3.6524299819
27
+ B,mg:-3.14e+100
28
+ B,mq:-6.7869530014
29
+ B,n:-1.69662577975
30
+ B,ng:-3.14e+100
31
+ B,nr:-2.23104959138
32
+ B,nrfg:-5.87372217541
33
+ B,nrt:-4.98564273352
34
+ B,ns:-2.8228438315
35
+ B,nt:-4.84609166818
36
+ B,nz:-3.94698846058
37
+ B,o:-8.43349870215
38
+ B,p:-4.20098413209
39
+ B,q:-6.99812385896
40
+ B,qe:-3.14e+100
41
+ B,qg:-3.14e+100
42
+ B,r:-3.40981877908
43
+ B,rg:-3.14e+100
44
+ B,rr:-12.4347528413
45
+ B,rz:-7.94611647157
46
+ B,s:-5.52267359084
47
+ B,t:-3.36474790945
48
+ B,tg:-3.14e+100
49
+ B,u:-9.1639172775
50
+ B,ud:-3.14e+100
51
+ B,ug:-3.14e+100
52
+ B,uj:-3.14e+100
53
+ B,ul:-3.14e+100
54
+ B,uv:-3.14e+100
55
+ B,uz:-3.14e+100
56
+ B,v:-2.67405848743
57
+ B,vd:-9.04472876024
58
+ B,vg:-3.14e+100
59
+ B,vi:-12.4347528413
60
+ B,vn:-4.33156108902
61
+ B,vq:-12.1470707689
62
+ B,w:-3.14e+100
63
+ B,x:-3.14e+100
64
+ B,y:-9.84448567586
65
+ B,yg:-3.14e+100
66
+ B,z:-7.04568111149
67
+ B,zg:-3.14e+100
68
+ E,a:-3.14e+100
69
+ E,ad:-3.14e+100
70
+ E,ag:-3.14e+100
71
+ E,an:-3.14e+100
72
+ E,b:-3.14e+100
73
+ E,bg:-3.14e+100
74
+ E,c:-3.14e+100
75
+ E,d:-3.14e+100
76
+ E,df:-3.14e+100
77
+ E,dg:-3.14e+100
78
+ E,e:-3.14e+100
79
+ E,en:-3.14e+100
80
+ E,f:-3.14e+100
81
+ E,g:-3.14e+100
82
+ E,h:-3.14e+100
83
+ E,i:-3.14e+100
84
+ E,in:-3.14e+100
85
+ E,j:-3.14e+100
86
+ E,jn:-3.14e+100
87
+ E,k:-3.14e+100
88
+ E,l:-3.14e+100
89
+ E,ln:-3.14e+100
90
+ E,m:-3.14e+100
91
+ E,mg:-3.14e+100
92
+ E,mq:-3.14e+100
93
+ E,n:-3.14e+100
94
+ E,ng:-3.14e+100
95
+ E,nr:-3.14e+100
96
+ E,nrfg:-3.14e+100
97
+ E,nrt:-3.14e+100
98
+ E,ns:-3.14e+100
99
+ E,nt:-3.14e+100
100
+ E,nz:-3.14e+100
101
+ E,o:-3.14e+100
102
+ E,p:-3.14e+100
103
+ E,q:-3.14e+100
104
+ E,qe:-3.14e+100
105
+ E,qg:-3.14e+100
106
+ E,r:-3.14e+100
107
+ E,rg:-3.14e+100
108
+ E,rr:-3.14e+100
109
+ E,rz:-3.14e+100
110
+ E,s:-3.14e+100
111
+ E,t:-3.14e+100
112
+ E,tg:-3.14e+100
113
+ E,u:-3.14e+100
114
+ E,ud:-3.14e+100
115
+ E,ug:-3.14e+100
116
+ E,uj:-3.14e+100
117
+ E,ul:-3.14e+100
118
+ E,uv:-3.14e+100
119
+ E,uz:-3.14e+100
120
+ E,v:-3.14e+100
121
+ E,vd:-3.14e+100
122
+ E,vg:-3.14e+100
123
+ E,vi:-3.14e+100
124
+ E,vn:-3.14e+100
125
+ E,vq:-3.14e+100
126
+ E,w:-3.14e+100
127
+ E,x:-3.14e+100
128
+ E,y:-3.14e+100
129
+ E,yg:-3.14e+100
130
+ E,z:-3.14e+100
131
+ E,zg:-3.14e+100
132
+ M,a:-3.14e+100
133
+ M,ad:-3.14e+100
134
+ M,ag:-3.14e+100
135
+ M,an:-3.14e+100
136
+ M,b:-3.14e+100
137
+ M,bg:-3.14e+100
138
+ M,c:-3.14e+100
139
+ M,d:-3.14e+100
140
+ M,df:-3.14e+100
141
+ M,dg:-3.14e+100
142
+ M,e:-3.14e+100
143
+ M,en:-3.14e+100
144
+ M,f:-3.14e+100
145
+ M,g:-3.14e+100
146
+ M,h:-3.14e+100
147
+ M,i:-3.14e+100
148
+ M,in:-3.14e+100
149
+ M,j:-3.14e+100
150
+ M,jn:-3.14e+100
151
+ M,k:-3.14e+100
152
+ M,l:-3.14e+100
153
+ M,ln:-3.14e+100
154
+ M,m:-3.14e+100
155
+ M,mg:-3.14e+100
156
+ M,mq:-3.14e+100
157
+ M,n:-3.14e+100
158
+ M,ng:-3.14e+100
159
+ M,nr:-3.14e+100
160
+ M,nrfg:-3.14e+100
161
+ M,nrt:-3.14e+100
162
+ M,ns:-3.14e+100
163
+ M,nt:-3.14e+100
164
+ M,nz:-3.14e+100
165
+ M,o:-3.14e+100
166
+ M,p:-3.14e+100
167
+ M,q:-3.14e+100
168
+ M,qe:-3.14e+100
169
+ M,qg:-3.14e+100
170
+ M,r:-3.14e+100
171
+ M,rg:-3.14e+100
172
+ M,rr:-3.14e+100
173
+ M,rz:-3.14e+100
174
+ M,s:-3.14e+100
175
+ M,t:-3.14e+100
176
+ M,tg:-3.14e+100
177
+ M,u:-3.14e+100
178
+ M,ud:-3.14e+100
179
+ M,ug:-3.14e+100
180
+ M,uj:-3.14e+100
181
+ M,ul:-3.14e+100
182
+ M,uv:-3.14e+100
183
+ M,uz:-3.14e+100
184
+ M,v:-3.14e+100
185
+ M,vd:-3.14e+100
186
+ M,vg:-3.14e+100
187
+ M,vi:-3.14e+100
188
+ M,vn:-3.14e+100
189
+ M,vq:-3.14e+100
190
+ M,w:-3.14e+100
191
+ M,x:-3.14e+100
192
+ M,y:-3.14e+100
193
+ M,yg:-3.14e+100
194
+ M,z:-3.14e+100
195
+ M,zg:-3.14e+100
196
+ S,a:-3.90253968313
197
+ S,ad:-11.0484584802
198
+ S,ag:-6.95411391796
199
+ S,an:-12.8402179494
200
+ S,b:-6.47288876397
201
+ S,bg:-3.14e+100
202
+ S,c:-4.78696679586
203
+ S,d:-3.90391976418
204
+ S,df:-3.14e+100
205
+ S,dg:-8.9483976513
206
+ S,e:-5.94251300628
207
+ S,en:-3.14e+100
208
+ S,f:-5.19482024998
209
+ S,g:-6.50782681533
210
+ S,h:-8.65056320738
211
+ S,i:-3.14e+100
212
+ S,in:-3.14e+100
213
+ S,j:-4.91199211964
214
+ S,jn:-3.14e+100
215
+ S,k:-6.94032059583
216
+ S,l:-3.14e+100
217
+ S,ln:-3.14e+100
218
+ S,m:-3.26920065212
219
+ S,mg:-10.8253149289
220
+ S,mq:-3.14e+100
221
+ S,n:-3.85514838976
222
+ S,ng:-4.9134348611
223
+ S,nr:-4.48366310396
224
+ S,nrfg:-3.14e+100
225
+ S,nrt:-3.14e+100
226
+ S,ns:-3.14e+100
227
+ S,nt:-12.1470707689
228
+ S,nz:-3.14e+100
229
+ S,o:-8.46446092775
230
+ S,p:-2.98684018136
231
+ S,q:-4.88865861826
232
+ S,qe:-3.14e+100
233
+ S,qg:-3.14e+100
234
+ S,r:-2.76353367841
235
+ S,rg:-10.2752685919
236
+ S,rr:-3.14e+100
237
+ S,rz:-3.14e+100
238
+ S,s:-3.14e+100
239
+ S,t:-3.14e+100
240
+ S,tg:-6.27284253188
241
+ S,u:-6.94032059583
242
+ S,ud:-7.72823016105
243
+ S,ug:-7.53940370266
244
+ S,uj:-6.85251045118
245
+ S,ul:-8.41537131755
246
+ S,uv:-8.15808672229
247
+ S,uz:-9.29925862537
248
+ S,v:-3.05329230341
249
+ S,vd:-3.14e+100
250
+ S,vg:-5.94301818437
251
+ S,vi:-3.14e+100
252
+ S,vn:-11.4539235883
253
+ S,vq:-3.14e+100
254
+ S,w:-3.14e+100
255
+ S,x:-8.42741965607
256
+ S,y:-6.19707946995
257
+ S,yg:-13.53336513
258
+ S,z:-3.14e+100
259
+ S,zg:-3.14e+100
cpp/dict/pos_dict/prob_trans.utf8 ADDED
The diff for this file is too large to render. See raw diff
 
cpp/dict/stop_words.utf8 ADDED
@@ -0,0 +1,1534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "
2
+ .
3
+
4
+ ,
5
+
6
+
7
+
8
+
9
+
10
+ `
11
+
12
+
13
+
14
+ ^
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ ~
23
+ \
24
+
25
+ |
26
+ ¦
27
+
28
+ — 
29
+ (
30
+ )
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+ »
44
+ «
45
+
46
+
47
+
48
+
49
+
50
+
51
+ }
52
+ {
53
+ ]
54
+ [
55
+
56
+ ¸
57
+
58
+
59
+
60
+ ;
61
+
62
+ ¡
63
+
64
+ ¿
65
+
66
+
67
+
68
+
69
+
70
+ ´
71
+ ˊ
72
+ ˋ
73
+ -
74
+
75
+
76
+ @
77
+
78
+
79
+ _
80
+ ¯
81
+ _
82
+
83
+
84
+ +
85
+
86
+ =
87
+
88
+
89
+ <
90
+ ­
91
+ ˜
92
+ ~
93
+
94
+ #
95
+
96
+ $
97
+
98
+ &
99
+
100
+ %
101
+
102
+ *
103
+
104
+ \
105
+
106
+
107
+
108
+
109
+ ˇ
110
+
111
+
112
+
113
+
114
+
115
+ ︿
116
+
117
+
118
+
119
+
120
+ _
121
+ ˉ
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ the
131
+ a
132
+ an
133
+ that
134
+ those
135
+ this
136
+ that
137
+ $
138
+ 0
139
+ 1
140
+ 2
141
+ 3
142
+ 4
143
+ 5
144
+ 6
145
+ 7
146
+ 8
147
+ 9
148
+ ?
149
+ _
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+ 一些
158
+ 一何
159
+ 一切
160
+ 一则
161
+ 一方面
162
+ 一旦
163
+ 一来
164
+ 一样
165
+ 一般
166
+ 一转眼
167
+ 万一
168
+
169
+ 上下
170
+
171
+
172
+ 不仅
173
+ 不但
174
+ 不光
175
+ 不单
176
+ 不只
177
+ 不外乎
178
+ 不如
179
+ 不妨
180
+ 不尽
181
+ 不尽然
182
+ 不得
183
+ 不怕
184
+ 不惟
185
+ 不成
186
+ 不拘
187
+ 不料
188
+ 不是
189
+ 不比
190
+ 不然
191
+ 不特
192
+ 不独
193
+ 不管
194
+ 不至于
195
+ 不若
196
+ 不论
197
+ 不过
198
+ 不问
199
+
200
+ 与其
201
+ 与其说
202
+ 与否
203
+ 与此同时
204
+
205
+ 且不说
206
+ 且说
207
+ 两者
208
+
209
+ 个别
210
+
211
+
212
+ 为了
213
+ 为什么
214
+ 为何
215
+ 为止
216
+ 为此
217
+ 为着
218
+
219
+ 乃至
220
+ 乃至于
221
+
222
+
223
+ 之一
224
+ 之所以
225
+ 之类
226
+ 乌乎
227
+
228
+
229
+
230
+ 也好
231
+ 也罢
232
+
233
+ 二来
234
+
235
+ 于是
236
+ 于是乎
237
+ 云云
238
+ 云尔
239
+
240
+
241
+
242
+ 人们
243
+ 人家
244
+ 什么
245
+ 什么样
246
+
247
+ 介于
248
+
249
+ 仍旧
250
+
251
+ 从此
252
+ 从而
253
+
254
+ 他人
255
+ 他们
256
+
257
+ 以上
258
+ 以为
259
+ 以便
260
+ 以免
261
+ 以及
262
+ 以故
263
+ 以期
264
+ 以来
265
+ 以至
266
+ 以至于
267
+ 以致
268
+
269
+
270
+ 任何
271
+ 任凭
272
+ 似的
273
+
274
+ 但凡
275
+ 但是
276
+
277
+ 何以
278
+ 何况
279
+ 何处
280
+ 何时
281
+ 余外
282
+ 作为
283
+
284
+ 你们
285
+ 使
286
+ 使得
287
+ 例如
288
+
289
+ 依据
290
+ 依照
291
+ 便于
292
+
293
+ 俺们
294
+
295
+ 倘使
296
+ 倘或
297
+ 倘然
298
+ 倘若
299
+
300
+ 假使
301
+ 假如
302
+ 假若
303
+ 傥然
304
+
305
+
306
+ 先不先
307
+ 光是
308
+ 全体
309
+ 全部
310
+
311
+ 关于
312
+
313
+ 其一
314
+ 其中
315
+ 其二
316
+ 其他
317
+ 其余
318
+ 其它
319
+ 其次
320
+ 具体地说
321
+ 具体说来
322
+ 兼之
323
+
324
+
325
+ 再其次
326
+ 再则
327
+ 再有
328
+ 再者
329
+ 再者说
330
+ 再说
331
+
332
+
333
+ 况且
334
+
335
+ 几时
336
+
337
+ 凡是
338
+
339
+ 凭借
340
+ 出于
341
+ 出来
342
+ 分别
343
+
344
+ 则甚
345
+
346
+ 别人
347
+ 别处
348
+ 别是
349
+ 别的
350
+ 别管
351
+ 别说
352
+
353
+ 前后
354
+ 前此
355
+ 前者
356
+ 加之
357
+ 加以
358
+
359
+ 即令
360
+ 即使
361
+ 即便
362
+ 即如
363
+ 即或
364
+ 即若
365
+
366
+
367
+
368
+ 又及
369
+
370
+ 及其
371
+ 及至
372
+ 反之
373
+ 反而
374
+ 反过来
375
+ 反过来说
376
+ 受到
377
+
378
+ 另一方面
379
+ 另外
380
+ 另悉
381
+
382
+ 只当
383
+ 只怕
384
+ 只是
385
+ 只有
386
+ 只消
387
+ 只要
388
+ 只限
389
+
390
+ 叮咚
391
+
392
+ 可以
393
+ 可是
394
+ 可见
395
+
396
+ 各个
397
+ 各位
398
+ 各种
399
+ 各自
400
+
401
+ 同时
402
+
403
+ 后者
404
+
405
+ 向使
406
+ 向着
407
+
408
+
409
+ 否则
410
+
411
+ 吧哒
412
+
413
+
414
+
415
+
416
+
417
+
418
+ 呜呼
419
+
420
+
421
+ 呵呵
422
+
423
+ 呼哧
424
+
425
+
426
+
427
+
428
+
429
+
430
+ 咱们
431
+
432
+
433
+
434
+ 哈哈
435
+
436
+
437
+ 哎呀
438
+ 哎哟
439
+
440
+
441
+
442
+
443
+
444
+ 哪个
445
+ 哪些
446
+ 哪儿
447
+ 哪天
448
+ 哪年
449
+ 哪怕
450
+ 哪样
451
+ 哪边
452
+ 哪里
453
+
454
+ 哼唷
455
+
456
+ 唯有
457
+
458
+
459
+
460
+
461
+ 啪达
462
+ 啷当
463
+
464
+
465
+ 喔唷
466
+
467
+
468
+ 嗡嗡
469
+
470
+
471
+
472
+
473
+ 嘎登
474
+
475
+
476
+
477
+
478
+ 嘿嘿
479
+
480
+ 因为
481
+ 因了
482
+ 因此
483
+ 因着
484
+ 因而
485
+ 固然
486
+
487
+ 在下
488
+ 在于
489
+
490
+ 基于
491
+ 处在
492
+
493
+ 多么
494
+ 多少
495
+
496
+ 大家
497
+
498
+ 她们
499
+
500
+
501
+ 如上
502
+ 如上所述
503
+ 如下
504
+ 如何
505
+ 如其
506
+ 如同
507
+ 如是
508
+ 如果
509
+ 如此
510
+ 如若
511
+ 始而
512
+ 孰料
513
+ 孰知
514
+
515
+ 宁可
516
+ 宁愿
517
+ 宁肯
518
+
519
+ 它们
520
+
521
+ 对于
522
+ 对待
523
+ 对方
524
+ 对比
525
+
526
+
527
+
528
+ 尔后
529
+ 尔尔
530
+ 尚且
531
+
532
+ 就是
533
+ 就是了
534
+ 就是说
535
+ 就算
536
+ 就要
537
+
538
+ 尽管
539
+ 尽管如此
540
+ 岂但
541
+
542
+
543
+ 已矣
544
+
545
+ 巴巴
546
+
547
+ 并且
548
+ 并非
549
+ 庶乎
550
+ 庶几
551
+ 开外
552
+ 开始
553
+
554
+ 归齐
555
+
556
+ 当地
557
+ 当然
558
+ 当着
559
+
560
+ 彼时
561
+ 彼此
562
+
563
+
564
+
565
+
566
+ 得了
567
+
568
+ 怎么
569
+ 怎么办
570
+ 怎么样
571
+ 怎奈
572
+ 怎样
573
+ 总之
574
+ 总的来看
575
+ 总的来说
576
+ 总的说来
577
+ 总而言之
578
+ 恰恰相反
579
+
580
+ 惟其
581
+ 慢说
582
+
583
+ 我们
584
+
585
+ 或则
586
+ 或是
587
+ 或曰
588
+ 或者
589
+ 截至
590
+
591
+ 所以
592
+ 所在
593
+ 所幸
594
+ 所有
595
+
596
+ 才能
597
+
598
+ 打从
599
+
600
+ 抑或
601
+
602
+
603
+ 按照
604
+ 换句话说
605
+ 换言之
606
+
607
+ 据此
608
+ 接着
609
+
610
+ 故此
611
+ 故而
612
+ 旁人
613
+
614
+ 无宁
615
+ 无论
616
+
617
+ 既往
618
+ 既是
619
+ 既然
620
+ 时候
621
+
622
+ 是以
623
+ 是的
624
+
625
+
626
+ 替代
627
+
628
+
629
+ 有些
630
+ 有关
631
+ 有及
632
+ 有时
633
+ 有的
634
+
635
+
636
+ 朝着
637
+
638
+ 本人
639
+ 本地
640
+ 本着
641
+ 本身
642
+
643
+ 来着
644
+ 来自
645
+ 来说
646
+ 极了
647
+ 果然
648
+ 果真
649
+
650
+ 某个
651
+ 某些
652
+ 某某
653
+ 根据
654
+
655
+ 正值
656
+ 正如
657
+ 正巧
658
+ 正是
659
+
660
+ 此地
661
+ 此处
662
+ 此外
663
+ 此时
664
+ 此次
665
+ 此间
666
+ 毋宁
667
+
668
+ 每当
669
+
670
+ 比及
671
+ 比如
672
+ 比方
673
+ 没奈何
674
+ 沿
675
+ 沿着
676
+ 漫说
677
+
678
+ 然则
679
+ 然后
680
+ 然而
681
+
682
+ 照着
683
+ 犹且
684
+ 犹自
685
+ 甚且
686
+ 甚么
687
+ 甚或
688
+ 甚而
689
+ 甚至
690
+ 甚至于
691
+
692
+ 用来
693
+
694
+ 由于
695
+ 由是
696
+ 由此
697
+ 由此可见
698
+
699
+ 的确
700
+ 的话
701
+ 直到
702
+ 相对而言
703
+ 省得
704
+
705
+ 眨眼
706
+
707
+ 着呢
708
+
709
+ 矣乎
710
+ 矣哉
711
+
712
+ 竟而
713
+
714
+
715
+ 等到
716
+ 等等
717
+ 简言之
718
+
719
+ 类如
720
+ 紧接着
721
+
722
+ 纵令
723
+ 纵使
724
+ 纵然
725
+
726
+ 经过
727
+ 结果
728
+
729
+ 继之
730
+ 继后
731
+ 继而
732
+ 综上所述
733
+ 罢了
734
+
735
+
736
+ 而且
737
+ 而况
738
+ 而后
739
+ 而外
740
+ 而已
741
+ 而是
742
+ 而言
743
+
744
+ 能否
745
+
746
+
747
+ 自个儿
748
+ 自从
749
+ 自各儿
750
+ 自后
751
+ 自家
752
+ 自己
753
+ 自打
754
+ 自身
755
+
756
+ 至于
757
+ 至今
758
+ 至若
759
+
760
+ 般的
761
+
762
+ 若夫
763
+ 若是
764
+ 若果
765
+ 若非
766
+ 莫不然
767
+ 莫如
768
+ 莫若
769
+
770
+ 虽则
771
+ 虽然
772
+ 虽说
773
+
774
+
775
+ 要不
776
+ 要不是
777
+ 要不然
778
+ 要么
779
+ 要是
780
+ 譬喻
781
+ 譬如
782
+
783
+ 许多
784
+
785
+ 设使
786
+ 设或
787
+ 设若
788
+ 诚如
789
+ 诚然
790
+
791
+ 说来
792
+
793
+ 诸位
794
+ 诸如
795
+
796
+ 谁人
797
+ 谁料
798
+ 谁知
799
+ 贼死
800
+ 赖以
801
+
802
+
803
+ 起见
804
+
805
+ 趁着
806
+ 越是
807
+
808
+
809
+
810
+ 较之
811
+
812
+
813
+
814
+ 还是
815
+ 还有
816
+ 还要
817
+
818
+ 这一来
819
+ 这个
820
+ 这么
821
+ 这么些
822
+ 这么样
823
+ 这么点儿
824
+ 这些
825
+ 这会儿
826
+ 这儿
827
+ 这就是说
828
+ 这时
829
+ 这样
830
+ 这次
831
+ 这般
832
+ 这边
833
+ 这里
834
+ 进而
835
+
836
+ 连同
837
+ 逐步
838
+ 通过
839
+ 遵循
840
+ 遵照
841
+
842
+ 那个
843
+ 那么
844
+ 那么些
845
+ 那么样
846
+ 那些
847
+ 那会儿
848
+ 那儿
849
+ 那时
850
+ 那样
851
+ 那般
852
+ 那边
853
+ 那里
854
+
855
+ 鄙人
856
+ 鉴于
857
+ 针对
858
+
859
+
860
+ 除了
861
+ 除外
862
+ 除开
863
+ 除此之外
864
+ 除非
865
+
866
+ 随后
867
+ 随时
868
+ 随着
869
+ 难道说
870
+ 非但
871
+ 非徒
872
+ 非特
873
+ 非独
874
+
875
+
876
+ 顺着
877
+ 首先
878
+
879
+
880
+
881
+
882
+
883
+ to
884
+ can
885
+ could
886
+ dare
887
+ do
888
+ did
889
+ does
890
+ may
891
+ might
892
+ would
893
+ should
894
+ must
895
+ will
896
+ ought
897
+ shall
898
+ need
899
+ is
900
+ a
901
+ am
902
+ are
903
+ about
904
+ according
905
+ after
906
+ against
907
+ all
908
+ almost
909
+ also
910
+ although
911
+ among
912
+ an
913
+ and
914
+ another
915
+ any
916
+ anything
917
+ approximately
918
+ as
919
+ asked
920
+ at
921
+ back
922
+ because
923
+ before
924
+ besides
925
+ between
926
+ both
927
+ but
928
+ by
929
+ call
930
+ called
931
+ currently
932
+ despite
933
+ did
934
+ do
935
+ dr
936
+ during
937
+ each
938
+ earlier
939
+ eight
940
+ even
941
+ eventually
942
+ every
943
+ everything
944
+ five
945
+ for
946
+ four
947
+ from
948
+ he
949
+ her
950
+ here
951
+ his
952
+ how
953
+ however
954
+ i
955
+ if
956
+ in
957
+ indeed
958
+ instead
959
+ it
960
+ its
961
+ just
962
+ last
963
+ like
964
+ major
965
+ many
966
+ may
967
+ maybe
968
+ meanwhile
969
+ more
970
+ moreover
971
+ most
972
+ mr
973
+ mrs
974
+ ms
975
+ much
976
+ my
977
+ neither
978
+ net
979
+ never
980
+ nevertheless
981
+ nine
982
+ no
983
+ none
984
+ not
985
+ nothing
986
+ now
987
+ of
988
+ on
989
+ once
990
+ one
991
+ only
992
+ or
993
+ other
994
+ our
995
+ over
996
+ partly
997
+ perhaps
998
+ prior
999
+ regarding
1000
+ separately
1001
+ seven
1002
+ several
1003
+ she
1004
+ should
1005
+ similarly
1006
+ since
1007
+ six
1008
+ so
1009
+ some
1010
+ somehow
1011
+ still
1012
+ such
1013
+ ten
1014
+ that
1015
+ the
1016
+ their
1017
+ then
1018
+ there
1019
+ therefore
1020
+ these
1021
+ they
1022
+ this
1023
+ those
1024
+ though
1025
+ three
1026
+ to
1027
+ two
1028
+ under
1029
+ unless
1030
+ unlike
1031
+ until
1032
+ volume
1033
+ we
1034
+ what
1035
+ whatever
1036
+ whats
1037
+ when
1038
+ where
1039
+ which
1040
+ while
1041
+ why
1042
+ with
1043
+ without
1044
+ yesterday
1045
+ yet
1046
+ you
1047
+ your
1048
+ aboard
1049
+ about
1050
+ above
1051
+ according to
1052
+ across
1053
+ afore
1054
+ after
1055
+ against
1056
+ agin
1057
+ along
1058
+ alongside
1059
+ amid
1060
+ amidst
1061
+ among
1062
+ amongst
1063
+ anent
1064
+ around
1065
+ as
1066
+ aslant
1067
+ astride
1068
+ at
1069
+ athwart
1070
+ bar
1071
+ because of
1072
+ before
1073
+ behind
1074
+ below
1075
+ beneath
1076
+ beside
1077
+ besides
1078
+ between
1079
+ betwixt
1080
+ beyond
1081
+ but
1082
+ by
1083
+ circa
1084
+ despite
1085
+ down
1086
+ during
1087
+ due to
1088
+ ere
1089
+ except
1090
+ for
1091
+ from
1092
+ in
1093
+ inside
1094
+ into
1095
+ less
1096
+ like
1097
+ mid
1098
+ midst
1099
+ minus
1100
+ near
1101
+ next
1102
+ nigh
1103
+ nigher
1104
+ nighest
1105
+ notwithstanding
1106
+ of
1107
+ off
1108
+ on
1109
+ on to
1110
+ onto
1111
+ out
1112
+ out of
1113
+ outside
1114
+ over
1115
+ past
1116
+ pending
1117
+ per
1118
+ plus
1119
+ qua
1120
+ re
1121
+ round
1122
+ sans
1123
+ save
1124
+ since
1125
+ through
1126
+ throughout
1127
+ thru
1128
+ till
1129
+ to
1130
+ toward
1131
+ towards
1132
+ under
1133
+ underneath
1134
+ unlike
1135
+ until
1136
+ unto
1137
+ up
1138
+ upon
1139
+ versus
1140
+ via
1141
+ vice
1142
+ with
1143
+ within
1144
+ without
1145
+ he
1146
+ her
1147
+ herself
1148
+ hers
1149
+ him
1150
+ himself
1151
+ his
1152
+ I
1153
+ it
1154
+ its
1155
+ itself
1156
+ me
1157
+ mine
1158
+ my
1159
+ myself
1160
+ ours
1161
+ she
1162
+ their
1163
+ theirs
1164
+ them
1165
+ themselves
1166
+ they
1167
+ us
1168
+ we
1169
+ our
1170
+ ourselves
1171
+ you
1172
+ your
1173
+ yours
1174
+ yourselves
1175
+ yourself
1176
+ this
1177
+ that
1178
+ these
1179
+ those
1180
+ "
1181
+ '
1182
+ ''
1183
+ (
1184
+ )
1185
+ *LRB*
1186
+ *RRB*
1187
+ <dquote>
1188
+ <ldquo>
1189
+ <lsquo>
1190
+ <rdquo>
1191
+ <rsquo>
1192
+ @
1193
+ &
1194
+ [
1195
+ ]
1196
+ `
1197
+ ``
1198
+ e.g.,
1199
+ {
1200
+ }
1201
+ &quot;
1202
+ &ldquo;
1203
+ &rdquo;
1204
+ -RRB-
1205
+ -LRB-
1206
+ --
1207
+ a
1208
+ about
1209
+ above
1210
+ across
1211
+ after
1212
+ afterwards
1213
+ again
1214
+ against
1215
+ all
1216
+ almost
1217
+ alone
1218
+ along
1219
+ already
1220
+ also
1221
+ although
1222
+ always
1223
+ am
1224
+ among
1225
+ amongst
1226
+ amoungst
1227
+ amount
1228
+ an
1229
+ and
1230
+ another
1231
+ any
1232
+ anyhow
1233
+ anyone
1234
+ anything
1235
+ anyway
1236
+ anywhere
1237
+ are
1238
+ around
1239
+ as
1240
+ at
1241
+ back
1242
+ be
1243
+ became
1244
+ because
1245
+ become
1246
+ becomes
1247
+ becoming
1248
+ been
1249
+ before
1250
+ beforehand
1251
+ behind
1252
+ being
1253
+ below
1254
+ beside
1255
+ besides
1256
+ between
1257
+ beyond
1258
+ bill
1259
+ both
1260
+ bottom
1261
+ but
1262
+ by
1263
+ call
1264
+ can
1265
+ cannot
1266
+ cant
1267
+ co
1268
+ computer
1269
+ con
1270
+ could
1271
+ couldnt
1272
+ cry
1273
+ de
1274
+ describe
1275
+ detail
1276
+ do
1277
+ done
1278
+ down
1279
+ due
1280
+ during
1281
+ each
1282
+ eg
1283
+ eight
1284
+ either
1285
+ eleven
1286
+ else
1287
+ elsewhere
1288
+ empty
1289
+ enough
1290
+ etc
1291
+ even
1292
+ ever
1293
+ every
1294
+ everyone
1295
+ everything
1296
+ everywhere
1297
+ except
1298
+ few
1299
+ fifteen
1300
+ fify
1301
+ fill
1302
+ find
1303
+ fire
1304
+ first
1305
+ five
1306
+ for
1307
+ former
1308
+ formerly
1309
+ forty
1310
+ found
1311
+ four
1312
+ from
1313
+ front
1314
+ full
1315
+ further
1316
+ get
1317
+ give
1318
+ go
1319
+ had
1320
+ has
1321
+ hasnt
1322
+ have
1323
+ he
1324
+ hence
1325
+ her
1326
+ here
1327
+ hereafter
1328
+ hereby
1329
+ herein
1330
+ hereupon
1331
+ hers
1332
+ herself
1333
+ him
1334
+ himself
1335
+ his
1336
+ how
1337
+ however
1338
+ hundred
1339
+ i
1340
+ ie
1341
+ if
1342
+ in
1343
+ inc
1344
+ indeed
1345
+ interest
1346
+ into
1347
+ is
1348
+ it
1349
+ its
1350
+ itself
1351
+ keep
1352
+ last
1353
+ latter
1354
+ latterly
1355
+ least
1356
+ less
1357
+ ltd
1358
+ made
1359
+ many
1360
+ may
1361
+ me
1362
+ meanwhile
1363
+ might
1364
+ mill
1365
+ mine
1366
+ more
1367
+ moreover
1368
+ most
1369
+ mostly
1370
+ move
1371
+ much
1372
+ must
1373
+ my
1374
+ myself
1375
+ name
1376
+ namely
1377
+ neither
1378
+ never
1379
+ nevertheless
1380
+ next
1381
+ nine
1382
+ no
1383
+ nobody
1384
+ none
1385
+ noone
1386
+ nor
1387
+ not
1388
+ nothing
1389
+ now
1390
+ nowhere
1391
+ of
1392
+ off
1393
+ often
1394
+ on
1395
+ once
1396
+ one
1397
+ only
1398
+ onto
1399
+ or
1400
+ other
1401
+ others
1402
+ otherwise
1403
+ our
1404
+ ours
1405
+ ourselves
1406
+ out
1407
+ over
1408
+ own
1409
+ part
1410
+ per
1411
+ perhaps
1412
+ please
1413
+ put
1414
+ rather
1415
+ re
1416
+ same
1417
+ see
1418
+ seem
1419
+ seemed
1420
+ seeming
1421
+ seems
1422
+ serious
1423
+ several
1424
+ she
1425
+ should
1426
+ show
1427
+ side
1428
+ since
1429
+ sincere
1430
+ six
1431
+ sixty
1432
+ so
1433
+ some
1434
+ somehow
1435
+ someone
1436
+ something
1437
+ sometime
1438
+ sometimes
1439
+ somewhere
1440
+ still
1441
+ such
1442
+ system
1443
+ take
1444
+ ten
1445
+ than
1446
+ that
1447
+ the
1448
+ their
1449
+ them
1450
+ themselves
1451
+ then
1452
+ thence
1453
+ there
1454
+ thereafter
1455
+ thereby
1456
+ therefore
1457
+ therein
1458
+ thereupon
1459
+ these
1460
+ they
1461
+ thick
1462
+ thin
1463
+ third
1464
+ this
1465
+ those
1466
+ though
1467
+ three
1468
+ through
1469
+ throughout
1470
+ thru
1471
+ thus
1472
+ to
1473
+ together
1474
+ too
1475
+ top
1476
+ toward
1477
+ towards
1478
+ twelve
1479
+ twenty
1480
+ two
1481
+ un
1482
+ under
1483
+ until
1484
+ up
1485
+ upon
1486
+ us
1487
+ very
1488
+ via
1489
+ was
1490
+ we
1491
+ well
1492
+ were
1493
+ what
1494
+ whatever
1495
+ when
1496
+ whence
1497
+ whenever
1498
+ where
1499
+ whereafter
1500
+ whereas
1501
+ whereby
1502
+ wherein
1503
+ whereupon
1504
+ wherever
1505
+ whether
1506
+ which
1507
+ while
1508
+ whither
1509
+ who
1510
+ whoever
1511
+ whole
1512
+ whom
1513
+ whose
1514
+ why
1515
+ will
1516
+ with
1517
+ within
1518
+ without
1519
+ would
1520
+ yet
1521
+ you
1522
+ your
1523
+ yours
1524
+ yourself
1525
+ yourselves
1526
+
1527
+
1528
+ :
1529
+ /
1530
+
1531
+ >
1532
+
1533
+ <
1534
+ !
cpp/dict/user.dict.utf8 ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ 云计算
2
+ 韩玉鉴赏
3
+ 蓝翔 nz
4
+ 区块链 10 nz
cpp/dict/vocab.txt ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ; 1
2
+ : 2
3
+ , 3
4
+ . 4
5
+ ! 5
6
+ ? 6
7
+ — 9
8
+ … 10
9
+ " 11
10
+ ( 12
11
+ ) 13
12
+ “ 14
13
+ ” 15
14
+ 16
15
+ ̃ 17
16
+ ʣ 18
17
+ ʥ 19
18
+ ʦ 20
19
+ ʨ 21
20
+ ᵝ 22
21
+ ꭧ 23
22
+ A 24
23
+ I 25
24
+ O 31
25
+ Q 33
26
+ S 35
27
+ T 36
28
+ W 39
29
+ Y 41
30
+ ᵊ 42
31
+ a 43
32
+ b 44
33
+ c 45
34
+ d 46
35
+ e 47
36
+ f 48
37
+ h 50
38
+ i 51
39
+ j 52
40
+ k 53
41
+ l 54
42
+ m 55
43
+ n 56
44
+ o 57
45
+ p 58
46
+ q 59
47
+ r 60
48
+ s 61
49
+ t 62
50
+ u 63
51
+ v 64
52
+ w 65
53
+ x 66
54
+ y 67
55
+ z 68
56
+ ɑ 69
57
+ ɐ 70
58
+ ɒ 71
59
+ æ 72
60
+ β 75
61
+ ɔ 76
62
+ ɕ 77
63
+ ç 78
64
+ ɖ 80
65
+ ð 81
66
+ ʤ 82
67
+ ə 83
68
+ ɚ 85
69
+ ɛ 86
70
+ ɜ 87
71
+ ɟ 90
72
+ ɡ 92
73
+ ɥ 99
74
+ ɨ 101
75
+ ɪ 102
76
+ ʝ 103
77
+ ɯ 110
78
+ ɰ 111
79
+ ŋ 112
80
+ ɳ 113
81
+ ɲ 114
82
+ ɴ 115
83
+ ø 116
84
+ ɸ 118
85
+ θ 119
86
+ œ 120
87
+ ɹ 123
88
+ ɾ 125
89
+ ɻ 126
90
+ ʁ 128
91
+ ɽ 129
92
+ ʂ 130
93
+ ʃ 131
94
+ ʈ 132
95
+ ʧ 133
96
+ ʊ 135
97
+ ʋ 136
98
+ ʌ 138
99
+ ɣ 139
100
+ ɤ 140
101
+ χ 142
102
+ ʎ 143
103
+ ʒ 147
104
+ ʔ 148
105
+ ˈ 156
106
+ ˌ 157
107
+ ː 158
108
+ ʰ 162
109
+ ʲ 164
110
+ ↓ 169
111
+ → 171
112
+ ↗ 172
113
+ ↘ 173
114
+ ᵻ 177
cpp/download_bsp.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ if [ ! -d ax650n_bsp_sdk ]; then
3
+ echo "clone ax650 bsp to ax650n_bsp_sdk, please wait..."
4
+ git clone https://github.com/AXERA-TECH/ax650n_bsp_sdk.git
5
+ fi
cpp/main.cpp ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <stdio.h>
2
+ #include <vector>
3
+ #include <fstream>
4
+ #include <ax_sys_api.h>
5
+ #include <ctime>
6
+ #include <sys/time.h>
7
+ #include <locale>
8
+
9
+ #include "utils/cmdline.hpp"
10
+ #include "AudioFile.h"
11
+ #include "utils/timer.hpp"
12
+ #include "utils/logger.hpp"
13
+ #include "Kokoro.h"
14
+
15
+
16
+ int main(int argc, char** argv) {
17
+ // 设置locale为UTF-8
18
+ std::setlocale(LC_ALL, "en_US.UTF-8");
19
+
20
+ cmdline::parser cmd;
21
+ cmd.add<std::string>("axmodel_dir", 0, "model path", false, "../models");
22
+ cmd.add<std::string>("text", 't', "Text to be generated", false, "我想留在大家身边,从过去,一同迈向明天");
23
+ cmd.add<std::string>("lang", 'l', "Language code, Only support a(American English) or z(Chinese) currently", false, "z");
24
+ cmd.add<std::string>("voice_path", 0, "Binary voices store path", false, "./voices");
25
+ cmd.add<std::string>("voice_name", 'v', "Speaker voice name, check possible choices from voices/", false, "zf_xiaoxiao");
26
+ cmd.add<std::string>("output", 'o', "Output file path", false, "output.wav");
27
+ cmd.add<float>("fade_out", 'f', "Fade out ratio between sentences", false, 0.0f);
28
+ cmd.add<int>("max_len", 'm', "Max input token num, fixed by model, no need to change usually", false, 96);
29
+ cmd.parse_check(argc, argv);
30
+
31
+ // 0. get app args, can be removed from user's app
32
+ auto axmodel_dir = cmd.get<std::string>("axmodel_dir");
33
+ auto text = cmd.get<std::string>("text");
34
+ auto lang = cmd.get<std::string>("lang");
35
+ auto voice_path = cmd.get<std::string>("voice_path");
36
+ auto voice_name = cmd.get<std::string>("voice_name");
37
+ auto output = cmd.get<std::string>("output");
38
+ auto fade_out = cmd.get<float>("fade_out");
39
+ auto max_len = cmd.get<int>("max_len");
40
+
41
+ ALOGI("Args:");
42
+ ALOGI("axmodel_dir: %s", axmodel_dir.c_str());
43
+ ALOGI("text: %s", text.c_str());
44
+ ALOGI("lang: %s", lang.c_str());
45
+ ALOGI("voice_path: %s", voice_path.c_str());
46
+ ALOGI("voice_name: %s", voice_name.c_str());
47
+ ALOGI("output: %s", output.c_str());
48
+ ALOGI("fade_out: %.2f", fade_out);
49
+ ALOGI("max_len: %d", max_len);
50
+
51
+ const float SPEED = 1.0f;
52
+ const float PAUSE = 0.0f;
53
+ const int sample_rate = 24000;
54
+
55
+ int ret = AX_SYS_Init();
56
+ if (0 != ret) {
57
+ fprintf(stderr, "AX_SYS_Init failed! ret = 0x%x\n", ret);
58
+ return -1;
59
+ }
60
+
61
+ #if defined(CHIP_AX650)
62
+ AX_ENGINE_NPU_ATTR_T npu_attr;
63
+ memset(&npu_attr, 0, sizeof(npu_attr));
64
+ npu_attr.eHardMode = static_cast<AX_ENGINE_NPU_MODE_T>(0);
65
+ ret = AX_ENGINE_Init(&npu_attr);
66
+ if (0 != ret) {
67
+ fprintf(stderr, "Init ax-engine failed{0x%8x}.\n", ret);
68
+ return -1;
69
+ }
70
+ #else
71
+ AX_ENGINE_NPU_ATTR_T npu_attr;
72
+ memset(&npu_attr, 0, sizeof(npu_attr));
73
+ npu_attr.eHardMode = AX_ENGINE_VIRTUAL_NPU_DISABLE;
74
+ ret = AX_ENGINE_Init(&npu_attr);
75
+ if (0 != ret) {
76
+ fprintf(stderr, "Init ax-engine failed{0x%8x}.\n", ret);
77
+ return -1;
78
+ }
79
+ #endif
80
+
81
+ Timer timer;
82
+
83
+ timer.start();
84
+ Kokoro kokoro;
85
+ if (!kokoro.init(axmodel_dir, max_len, voice_path, voice_name)) {
86
+ ALOGE("Init kokoro failed!");
87
+ return -1;
88
+ }
89
+ timer.stop();
90
+ ALOGI("Init kokoro take %.4f seconds", timer.elapsed<std::chrono::seconds>());
91
+
92
+ timer.start();
93
+ std::vector<float> audio;
94
+ if (!kokoro.tts(text, lang, voice_name, SPEED, sample_rate, fade_out, PAUSE, audio)) {
95
+ printf("run whisper failed!\n");
96
+ return -1;
97
+ }
98
+ timer.stop();
99
+
100
+ AudioFile<float> audio_file;
101
+ std::vector<std::vector<float> > audio_samples{audio};
102
+ audio_file.setAudioBuffer(audio_samples);
103
+ audio_file.setSampleRate(sample_rate);
104
+ if (!audio_file.save(output)) {
105
+ ALOGE("Save audio file failed!\n");
106
+ return -1;
107
+ }
108
+
109
+ ALOGI("Audio save to %s", output.c_str());
110
+
111
+ float elapsed = timer.elapsed<std::chrono::seconds>();
112
+ float duration = audio.size() * 1.f / sample_rate;
113
+ ALOGI("RTF: %.4f, process_time: %.4f seconds, audio duration: %.2f seconds\n", elapsed / duration, elapsed, duration);
114
+ return 0;
115
+ }
cpp/scripts/compare.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import sys
3
+
4
+ def cos_sim(A, B):
5
+ dot_product = np.dot(A, B)
6
+ norm_A = np.linalg.norm(A)
7
+ norm_B = np.linalg.norm(B)
8
+ cosine_sim = dot_product / (norm_A * norm_B)
9
+ return cosine_sim
10
+
11
+ A = np.fromfile(sys.argv[1], dtype=np.int32)
12
+ B = np.fromfile(sys.argv[2], dtype=np.int32)
13
+ np.testing.assert_allclose(A, B)
14
+ print(cos_sim(A, B))
cpp/scripts/convert_dict.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Tone mapping dictionary
4
+ TONE_MAP = {
5
+ 'ā': ('a', 1), 'á': ('a', 2), 'ǎ': ('a', 3), 'à': ('a', 4),
6
+ 'ē': ('e', 1), 'é': ('e', 2), 'ě': ('e', 3), 'è': ('e', 4),
7
+ 'ī': ('i', 1), 'í': ('i', 2), 'ǐ': ('i', 3), 'ì': ('i', 4),
8
+ 'ō': ('o', 1), 'ó': ('o', 2), 'ǒ': ('o', 3), 'ò': ('o', 4),
9
+ 'ū': ('u', 1), 'ú': ('u', 2), 'ǔ': ('u', 3), 'ù': ('u', 4),
10
+ 'ü': ('v', 0), 'ǖ': ('v', 1), 'ǘ': ('v', 2), 'ǚ': ('v', 3), 'ǜ': ('v', 4),
11
+ 'ń': ('n', 2), 'ň': ('n', 3), 'ǹ': ('n', 4), 'ḿ': ('m', 2),
12
+ 'm̀': ('m', 4)
13
+ }
14
+
15
+ def convert_tone(pinyin):
16
+ if not pinyin: return ""
17
+ tone = 5 # Default neutral tone
18
+ base = ""
19
+
20
+ for char in pinyin:
21
+ if char in TONE_MAP:
22
+ base_char, t = TONE_MAP[char]
23
+ base += base_char
24
+ tone = t
25
+ else:
26
+ base += char
27
+
28
+ if tone != 5:
29
+ return f"{base}{tone}"
30
+ else:
31
+ return f"{base}5"
32
+
33
+ def process_char_dict(input_file, out_file):
34
+ print(f"Processing char dict: {input_file} -> {out_file}")
35
+ with open(input_file, 'r', encoding='utf-8') as f:
36
+ lines = f.readlines()
37
+
38
+ new_lines = []
39
+ for line in lines:
40
+ line = line.strip()
41
+ if not line or line.startswith('#'):
42
+ new_lines.append(line)
43
+ continue
44
+
45
+ # Expected format: U+XXXX: pinyin,pinyin # char
46
+ if ':' not in line:
47
+ new_lines.append(line)
48
+ continue
49
+
50
+ part1, part2 = line.split(':', 1) # U+XXXX, " pinyin,pinyin # char"
51
+
52
+ char_comment = ""
53
+ if '#' in part2:
54
+ pinyins_part, char_comment = part2.split('#', 1)
55
+ char_comment = " #" + char_comment
56
+ else:
57
+ pinyins_part = part2
58
+
59
+ # Split by comma for polyphones
60
+ pinyins = [p.strip() for p in pinyins_part.split(',')]
61
+ numeric_pinyins = [convert_tone(p) for p in pinyins if p]
62
+
63
+ new_pinyins_str = ",".join(numeric_pinyins)
64
+
65
+ # Reconstruct line
66
+ new_line = f"{part1}: {new_pinyins_str}{char_comment}"
67
+ new_lines.append(new_line)
68
+
69
+ with open(out_file, 'w', encoding='utf-8') as f:
70
+ f.write("\n".join(new_lines))
71
+ print(f"Done. Wrote {len(new_lines)} lines.")
72
+
73
+ def process_phrase_dict(input_file, out_file):
74
+ print(f"Processing phrase dict: {input_file} -> {out_file}")
75
+ with open(input_file, 'r', encoding='utf-8') as f:
76
+ lines = f.readlines()
77
+
78
+ phrase_entries = []
79
+
80
+ for line in lines:
81
+ line = line.strip()
82
+ if not line or ':' not in line:
83
+ continue
84
+
85
+ word, pinyins_str = line.split(':', 1)
86
+ word = word.strip()
87
+ pinyins = [p.strip() for p in pinyins_str.split()]
88
+
89
+ # Skip single char entries in phrase dict if any
90
+ if len(word) == 1:
91
+ continue
92
+
93
+ numeric_pinyins = [convert_tone(p) for p in pinyins]
94
+ pinyins_joined = " ".join(numeric_pinyins)
95
+
96
+ phrase_entries.append(f"{word}: {pinyins_joined}")
97
+
98
+ with open(out_file, 'w', encoding='utf-8') as f:
99
+ f.write("\n".join(phrase_entries))
100
+ print(f"Done. Wrote {len(phrase_entries)} phrases.")
101
+
102
+ if __name__ == "__main__":
103
+ # Input files
104
+ input_char_path = r"g:\work\misaki\cpp\dict2\pinyin.txt"
105
+ input_phrase_path = r"g:\work\misaki\cpp\dict2\large_pinyin.txt"
106
+
107
+ # Output directory
108
+ output_dir = r"g:\work\misaki\cpp\dict"
109
+
110
+ if not os.path.exists(output_dir):
111
+ os.makedirs(output_dir)
112
+
113
+ # Process both
114
+ if os.path.exists(input_char_path):
115
+ process_char_dict(input_char_path, os.path.join(output_dir, "pinyin.txt"))
116
+ else:
117
+ print(f"Warning: {input_char_path} not found.")
118
+
119
+ if os.path.exists(input_phrase_path):
120
+ process_phrase_dict(input_phrase_path, os.path.join(output_dir, "pinyin_phrase.txt"))
121
+ else:
122
+ print(f"Warning: {input_phrase_path} not found.")
cpp/scripts/export_vocab.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ import os
4
+ from pathlib import Path
5
+
6
+ # Add src to sys.path to import kokoro_onnx
7
+ sys.path.append(str(Path(__file__).parent.parent / "src"))
8
+
9
+ try:
10
+ from kokoro_onnx.config import get_vocab
11
+ except ImportError:
12
+ print("Error: Could not import kokoro_onnx.config. Make sure you are in the root of the repo.")
13
+ sys.exit(1)
14
+
15
+ def export_vocab(output_path):
16
+ try:
17
+ vocab = get_vocab()
18
+ except Exception as e:
19
+ print(f"Error loading vocab: {e}")
20
+ return
21
+
22
+ print(f"Loaded {len(vocab)} tokens.")
23
+
24
+ with open(output_path, 'w', encoding='utf-8') as f:
25
+ for token, idx in vocab.items():
26
+ # Escape special characters to keep the file parseable line by line
27
+ # We use tab as separator
28
+ safe_token = token.replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
29
+ f.write(f"{safe_token}\t{idx}\n")
30
+
31
+ print(f"Exported vocab to {output_path}")
32
+
33
+ if __name__ == "__main__":
34
+ if len(sys.argv) < 2:
35
+ print("Usage: python export_vocab.py <output_vocab.txt>")
36
+ sys.exit(1)
37
+
38
+ export_vocab(sys.argv[1])
cpp/scripts/export_voices.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import struct
3
+ import sys
4
+ import os
5
+ import torch
6
+ import glob
7
+ from tqdm import tqdm
8
+
9
+ def export_voices(voices_path, output_path):
10
+ os.makedirs(output_path, exist_ok=True)
11
+ for pt_path in tqdm(glob.glob(voices_path + "/*.pt")):
12
+ voice = torch.load(pt_path, weights_only=True)
13
+ voice_name = os.path.splitext(os.path.basename(pt_path))[0]
14
+ voice_npy = voice.numpy()
15
+ voice_npy.tofile(os.path.join(output_path, voice_name + ".bin"))
16
+
17
+ if __name__ == "__main__":
18
+ export_voices("../checkpoints/voices", "voices")
cpp/src/AudioFile.h ADDED
@@ -0,0 +1,1293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //=======================================================================
2
+ /** @file AudioFile.h
3
+ * @author Adam Stark
4
+ * @copyright Copyright (C) 2017 Adam Stark
5
+ *
6
+ * This file is part of the 'AudioFile' library
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ */
21
+ //=======================================================================
22
+
23
+ #ifndef _AS_AudioFile_h
24
+ #define _AS_AudioFile_h
25
+
26
+ #include <iostream>
27
+ #include <vector>
28
+ #include <cassert>
29
+ #include <string>
30
+ #include <cstring>
31
+ #include <fstream>
32
+ #include <unordered_map>
33
+ #include <iterator>
34
+ #include <algorithm>
35
+
36
+ // disable some warnings on Windows
37
+ #if defined (_MSC_VER)
38
+ __pragma(warning (push))
39
+ __pragma(warning (disable : 4244))
40
+ __pragma(warning (disable : 4457))
41
+ __pragma(warning (disable : 4458))
42
+ __pragma(warning (disable : 4389))
43
+ __pragma(warning (disable : 4996))
44
+ #elif defined (__GNUC__)
45
+ _Pragma("GCC diagnostic push")
46
+ _Pragma("GCC diagnostic ignored \"-Wconversion\"")
47
+ _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
48
+ _Pragma("GCC diagnostic ignored \"-Wshadow\"")
49
+ #endif
50
+
51
+ //=============================================================
52
+ /** The different types of audio file, plus some other types to
53
+ * indicate a failure to load a file, or that one hasn't been
54
+ * loaded yet
55
+ */
56
+ enum class AudioFileFormat
57
+ {
58
+ Error,
59
+ NotLoaded,
60
+ Wave,
61
+ Aiff
62
+ };
63
+
64
+ //=============================================================
65
+ template <class T>
66
+ class AudioFile
67
+ {
68
+ public:
69
+
70
+ //=============================================================
71
+ typedef std::vector<std::vector<T> > AudioBuffer;
72
+
73
+ //=============================================================
74
+ /** Constructor */
75
+ AudioFile();
76
+
77
+ /** Constructor, using a given file path to load a file */
78
+ AudioFile (std::string filePath);
79
+
80
+ //=============================================================
81
+ /** Loads an audio file from a given file path.
82
+ * @Returns true if the file was successfully loaded
83
+ */
84
+ bool load (std::string filePath);
85
+
86
+ /** Saves an audio file to a given file path.
87
+ * @Returns true if the file was successfully saved
88
+ */
89
+ bool save (std::string filePath, AudioFileFormat format = AudioFileFormat::Wave);
90
+
91
+ //=============================================================
92
+ /** @Returns the sample rate */
93
+ uint32_t getSampleRate() const;
94
+
95
+ /** @Returns the number of audio channels in the buffer */
96
+ int getNumChannels() const;
97
+
98
+ /** @Returns true if the audio file is mono */
99
+ bool isMono() const;
100
+
101
+ /** @Returns true if the audio file is stereo */
102
+ bool isStereo() const;
103
+
104
+ /** @Returns the bit depth of each sample */
105
+ int getBitDepth() const;
106
+
107
+ /** @Returns the number of samples per channel */
108
+ int getNumSamplesPerChannel() const;
109
+
110
+ /** @Returns the length in seconds of the audio file based on the number of samples and sample rate */
111
+ double getLengthInSeconds() const;
112
+
113
+ /** Prints a summary of the audio file to the console */
114
+ void printSummary() const;
115
+
116
+ //=============================================================
117
+
118
+ /** Set the audio buffer for this AudioFile by copying samples from another buffer.
119
+ * @Returns true if the buffer was copied successfully.
120
+ */
121
+ bool setAudioBuffer (AudioBuffer& newBuffer);
122
+
123
+ /** Sets the audio buffer to a given number of channels and number of samples per channel. This will try to preserve
124
+ * the existing audio, adding zeros to any new channels or new samples in a given channel.
125
+ */
126
+ void setAudioBufferSize (int numChannels, int numSamples);
127
+
128
+ /** Sets the number of samples per channel in the audio buffer. This will try to preserve
129
+ * the existing audio, adding zeros to new samples in a given channel if the number of samples is increased.
130
+ */
131
+ void setNumSamplesPerChannel (int numSamples);
132
+
133
+ /** Sets the number of channels. New channels will have the correct number of samples and be initialised to zero */
134
+ void setNumChannels (int numChannels);
135
+
136
+ /** Sets the bit depth for the audio file. If you use the save() function, this bit depth rate will be used */
137
+ void setBitDepth (int numBitsPerSample);
138
+
139
+ /** Sets the sample rate for the audio file. If you use the save() function, this sample rate will be used */
140
+ void setSampleRate (uint32_t newSampleRate);
141
+
142
+ //=============================================================
143
+ /** Sets whether the library should log error messages to the console. By default this is true */
144
+ void shouldLogErrorsToConsole (bool logErrors);
145
+
146
+ //=============================================================
147
+ /** A vector of vectors holding the audio samples for the AudioFile. You can
148
+ * access the samples by channel and then by sample index, i.e:
149
+ *
150
+ * samples[channel][sampleIndex]
151
+ */
152
+ AudioBuffer samples;
153
+
154
+ //=============================================================
155
+ /** An optional iXML chunk that can be added to the AudioFile.
156
+ */
157
+ std::string iXMLChunk;
158
+
159
+ private:
160
+
161
+ //=============================================================
162
+ enum class Endianness
163
+ {
164
+ LittleEndian,
165
+ BigEndian
166
+ };
167
+
168
+ //=============================================================
169
+ AudioFileFormat determineAudioFileFormat (std::vector<uint8_t>& fileData);
170
+ bool decodeWaveFile (std::vector<uint8_t>& fileData);
171
+ bool decodeAiffFile (std::vector<uint8_t>& fileData);
172
+
173
+ //=============================================================
174
+ bool saveToWaveFile (std::string filePath);
175
+ bool saveToAiffFile (std::string filePath);
176
+
177
+ //=============================================================
178
+ void clearAudioBuffer();
179
+
180
+ //=============================================================
181
+ int32_t fourBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness = Endianness::LittleEndian);
182
+ int16_t twoBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness = Endianness::LittleEndian);
183
+ int getIndexOfString (std::vector<uint8_t>& source, std::string s);
184
+ int getIndexOfChunk (std::vector<uint8_t>& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness = Endianness::LittleEndian);
185
+
186
+ //=============================================================
187
+ T sixteenBitIntToSample (int16_t sample);
188
+ int16_t sampleToSixteenBitInt (T sample);
189
+
190
+ //=============================================================
191
+ uint8_t sampleToSingleByte (T sample);
192
+ T singleByteToSample (uint8_t sample);
193
+
194
+ uint32_t getAiffSampleRate (std::vector<uint8_t>& fileData, int sampleRateStartIndex);
195
+ bool tenByteMatch (std::vector<uint8_t>& v1, int startIndex1, std::vector<uint8_t>& v2, int startIndex2);
196
+ void addSampleRateToAiffData (std::vector<uint8_t>& fileData, uint32_t sampleRate);
197
+ T clamp (T v1, T minValue, T maxValue);
198
+
199
+ //=============================================================
200
+ void addStringToFileData (std::vector<uint8_t>& fileData, std::string s);
201
+ void addInt32ToFileData (std::vector<uint8_t>& fileData, int32_t i, Endianness endianness = Endianness::LittleEndian);
202
+ void addInt16ToFileData (std::vector<uint8_t>& fileData, int16_t i, Endianness endianness = Endianness::LittleEndian);
203
+
204
+ //=============================================================
205
+ bool writeDataToFile (std::vector<uint8_t>& fileData, std::string filePath);
206
+
207
+ //=============================================================
208
+ void reportError (std::string errorMessage);
209
+
210
+ //=============================================================
211
+ AudioFileFormat audioFileFormat;
212
+ uint32_t sampleRate;
213
+ int bitDepth;
214
+ bool logErrorsToConsole {true};
215
+ };
216
+
217
+
218
+ //=============================================================
219
+ // Pre-defined 10-byte representations of common sample rates
220
+ static std::unordered_map <uint32_t, std::vector<uint8_t>> aiffSampleRateTable = {
221
+ {8000, {64, 11, 250, 0, 0, 0, 0, 0, 0, 0}},
222
+ {11025, {64, 12, 172, 68, 0, 0, 0, 0, 0, 0}},
223
+ {16000, {64, 12, 250, 0, 0, 0, 0, 0, 0, 0}},
224
+ {22050, {64, 13, 172, 68, 0, 0, 0, 0, 0, 0}},
225
+ {32000, {64, 13, 250, 0, 0, 0, 0, 0, 0, 0}},
226
+ {37800, {64, 14, 147, 168, 0, 0, 0, 0, 0, 0}},
227
+ {44056, {64, 14, 172, 24, 0, 0, 0, 0, 0, 0}},
228
+ {44100, {64, 14, 172, 68, 0, 0, 0, 0, 0, 0}},
229
+ {47250, {64, 14, 184, 146, 0, 0, 0, 0, 0, 0}},
230
+ {48000, {64, 14, 187, 128, 0, 0, 0, 0, 0, 0}},
231
+ {50000, {64, 14, 195, 80, 0, 0, 0, 0, 0, 0}},
232
+ {50400, {64, 14, 196, 224, 0, 0, 0, 0, 0, 0}},
233
+ {88200, {64, 15, 172, 68, 0, 0, 0, 0, 0, 0}},
234
+ {96000, {64, 15, 187, 128, 0, 0, 0, 0, 0, 0}},
235
+ {176400, {64, 16, 172, 68, 0, 0, 0, 0, 0, 0}},
236
+ {192000, {64, 16, 187, 128, 0, 0, 0, 0, 0, 0}},
237
+ {352800, {64, 17, 172, 68, 0, 0, 0, 0, 0, 0}},
238
+ {2822400, {64, 20, 172, 68, 0, 0, 0, 0, 0, 0}},
239
+ {5644800, {64, 21, 172, 68, 0, 0, 0, 0, 0, 0}}
240
+ };
241
+
242
+ //=============================================================
243
+ enum WavAudioFormat
244
+ {
245
+ PCM = 0x0001,
246
+ IEEEFloat = 0x0003,
247
+ ALaw = 0x0006,
248
+ MULaw = 0x0007,
249
+ Extensible = 0xFFFE
250
+ };
251
+
252
+ //=============================================================
253
+ enum AIFFAudioFormat
254
+ {
255
+ Uncompressed,
256
+ Compressed,
257
+ Error
258
+ };
259
+
260
+ //=============================================================
261
+ /* IMPLEMENTATION */
262
+ //=============================================================
263
+
264
+ //=============================================================
265
+ template <class T>
266
+ AudioFile<T>::AudioFile()
267
+ {
268
+ static_assert(std::is_floating_point<T>::value, "ERROR: This version of AudioFile only supports floating point sample formats");
269
+
270
+ bitDepth = 16;
271
+ sampleRate = 44100;
272
+ samples.resize (1);
273
+ samples[0].resize (0);
274
+ audioFileFormat = AudioFileFormat::NotLoaded;
275
+ }
276
+
277
+ //=============================================================
278
+ template <class T>
279
+ AudioFile<T>::AudioFile (std::string filePath)
280
+ : AudioFile<T>()
281
+ {
282
+ load (filePath);
283
+ }
284
+
285
+ //=============================================================
286
+ template <class T>
287
+ uint32_t AudioFile<T>::getSampleRate() const
288
+ {
289
+ return sampleRate;
290
+ }
291
+
292
+ //=============================================================
293
+ template <class T>
294
+ int AudioFile<T>::getNumChannels() const
295
+ {
296
+ return (int)samples.size();
297
+ }
298
+
299
+ //=============================================================
300
+ template <class T>
301
+ bool AudioFile<T>::isMono() const
302
+ {
303
+ return getNumChannels() == 1;
304
+ }
305
+
306
+ //=============================================================
307
+ template <class T>
308
+ bool AudioFile<T>::isStereo() const
309
+ {
310
+ return getNumChannels() == 2;
311
+ }
312
+
313
+ //=============================================================
314
+ template <class T>
315
+ int AudioFile<T>::getBitDepth() const
316
+ {
317
+ return bitDepth;
318
+ }
319
+
320
+ //=============================================================
321
+ template <class T>
322
+ int AudioFile<T>::getNumSamplesPerChannel() const
323
+ {
324
+ if (samples.size() > 0)
325
+ return (int) samples[0].size();
326
+ else
327
+ return 0;
328
+ }
329
+
330
+ //=============================================================
331
+ template <class T>
332
+ double AudioFile<T>::getLengthInSeconds() const
333
+ {
334
+ return (double)getNumSamplesPerChannel() / (double)sampleRate;
335
+ }
336
+
337
+ //=============================================================
338
+ template <class T>
339
+ void AudioFile<T>::printSummary() const
340
+ {
341
+ std::cout << "|======================================|" << std::endl;
342
+ std::cout << "Num Channels: " << getNumChannels() << std::endl;
343
+ std::cout << "Num Samples Per Channel: " << getNumSamplesPerChannel() << std::endl;
344
+ std::cout << "Sample Rate: " << sampleRate << std::endl;
345
+ std::cout << "Bit Depth: " << bitDepth << std::endl;
346
+ std::cout << "Length in Seconds: " << getLengthInSeconds() << std::endl;
347
+ std::cout << "|======================================|" << std::endl;
348
+ }
349
+
350
+ //=============================================================
351
+ template <class T>
352
+ bool AudioFile<T>::setAudioBuffer (AudioBuffer& newBuffer)
353
+ {
354
+ int numChannels = (int)newBuffer.size();
355
+
356
+ if (numChannels <= 0)
357
+ {
358
+ assert (false && "The buffer your are trying to use has no channels");
359
+ return false;
360
+ }
361
+
362
+ size_t numSamples = newBuffer[0].size();
363
+
364
+ // set the number of channels
365
+ samples.resize (newBuffer.size());
366
+
367
+ for (int k = 0; k < getNumChannels(); k++)
368
+ {
369
+ assert (newBuffer[k].size() == numSamples);
370
+
371
+ samples[k].resize (numSamples);
372
+
373
+ for (size_t i = 0; i < numSamples; i++)
374
+ {
375
+ samples[k][i] = newBuffer[k][i];
376
+ }
377
+ }
378
+
379
+ return true;
380
+ }
381
+
382
+ //=============================================================
383
+ template <class T>
384
+ void AudioFile<T>::setAudioBufferSize (int numChannels, int numSamples)
385
+ {
386
+ samples.resize (numChannels);
387
+ setNumSamplesPerChannel (numSamples);
388
+ }
389
+
390
+ //=============================================================
391
+ template <class T>
392
+ void AudioFile<T>::setNumSamplesPerChannel (int numSamples)
393
+ {
394
+ int originalSize = getNumSamplesPerChannel();
395
+
396
+ for (int i = 0; i < getNumChannels();i++)
397
+ {
398
+ samples[i].resize (numSamples);
399
+
400
+ // set any new samples to zero
401
+ if (numSamples > originalSize)
402
+ std::fill (samples[i].begin() + originalSize, samples[i].end(), (T)0.);
403
+ }
404
+ }
405
+
406
+ //=============================================================
407
+ template <class T>
408
+ void AudioFile<T>::setNumChannels (int numChannels)
409
+ {
410
+ int originalNumChannels = getNumChannels();
411
+ int originalNumSamplesPerChannel = getNumSamplesPerChannel();
412
+
413
+ samples.resize (numChannels);
414
+
415
+ // make sure any new channels are set to the right size
416
+ // and filled with zeros
417
+ if (numChannels > originalNumChannels)
418
+ {
419
+ for (int i = originalNumChannels; i < numChannels; i++)
420
+ {
421
+ samples[i].resize (originalNumSamplesPerChannel);
422
+ std::fill (samples[i].begin(), samples[i].end(), (T)0.);
423
+ }
424
+ }
425
+ }
426
+
427
+ //=============================================================
428
+ template <class T>
429
+ void AudioFile<T>::setBitDepth (int numBitsPerSample)
430
+ {
431
+ bitDepth = numBitsPerSample;
432
+ }
433
+
434
+ //=============================================================
435
+ template <class T>
436
+ void AudioFile<T>::setSampleRate (uint32_t newSampleRate)
437
+ {
438
+ sampleRate = newSampleRate;
439
+ }
440
+
441
+ //=============================================================
442
+ template <class T>
443
+ void AudioFile<T>::shouldLogErrorsToConsole (bool logErrors)
444
+ {
445
+ logErrorsToConsole = logErrors;
446
+ }
447
+
448
+ //=============================================================
449
+ template <class T>
450
+ bool AudioFile<T>::load (std::string filePath)
451
+ {
452
+ std::ifstream file (filePath, std::ios::binary);
453
+
454
+ // check the file exists
455
+ if (! file.good())
456
+ {
457
+ reportError ("ERROR: File doesn't exist or otherwise can't load file\n" + filePath);
458
+ return false;
459
+ }
460
+
461
+ std::vector<uint8_t> fileData;
462
+
463
+ file.unsetf (std::ios::skipws);
464
+
465
+ file.seekg (0, std::ios::end);
466
+ size_t length = file.tellg();
467
+ file.seekg (0, std::ios::beg);
468
+
469
+ // allocate
470
+ fileData.resize (length);
471
+
472
+ file.read(reinterpret_cast<char*> (fileData.data()), length);
473
+ file.close();
474
+
475
+ if (file.gcount() != length)
476
+ {
477
+ reportError ("ERROR: Couldn't read entire file\n" + filePath);
478
+ return false;
479
+ }
480
+
481
+ // get audio file format
482
+ audioFileFormat = determineAudioFileFormat (fileData);
483
+
484
+ if (audioFileFormat == AudioFileFormat::Wave)
485
+ {
486
+ return decodeWaveFile (fileData);
487
+ }
488
+ else if (audioFileFormat == AudioFileFormat::Aiff)
489
+ {
490
+ return decodeAiffFile (fileData);
491
+ }
492
+ else
493
+ {
494
+ reportError ("Audio File Type: Error");
495
+ return false;
496
+ }
497
+ }
498
+
499
+ //=============================================================
500
+ template <class T>
501
+ bool AudioFile<T>::decodeWaveFile (std::vector<uint8_t>& fileData)
502
+ {
503
+ // -----------------------------------------------------------
504
+ // HEADER CHUNK
505
+ std::string headerChunkID (fileData.begin(), fileData.begin() + 4);
506
+ //int32_t fileSizeInBytes = fourBytesToInt (fileData, 4) + 8;
507
+ std::string format (fileData.begin() + 8, fileData.begin() + 12);
508
+
509
+ // -----------------------------------------------------------
510
+ // try and find the start points of key chunks
511
+ int indexOfDataChunk = getIndexOfChunk (fileData, "data", 12);
512
+ int indexOfFormatChunk = getIndexOfChunk (fileData, "fmt ", 12);
513
+ int indexOfXMLChunk = getIndexOfChunk (fileData, "iXML", 12);
514
+
515
+ // if we can't find the data or format chunks, or the IDs/formats don't seem to be as expected
516
+ // then it is unlikely we'll able to read this file, so abort
517
+ if (indexOfDataChunk == -1 || indexOfFormatChunk == -1 || headerChunkID != "RIFF" || format != "WAVE")
518
+ {
519
+ reportError ("ERROR: this doesn't seem to be a valid .WAV file");
520
+ return false;
521
+ }
522
+
523
+ // -----------------------------------------------------------
524
+ // FORMAT CHUNK
525
+ int f = indexOfFormatChunk;
526
+ std::string formatChunkID (fileData.begin() + f, fileData.begin() + f + 4);
527
+ //int32_t formatChunkSize = fourBytesToInt (fileData, f + 4);
528
+ uint16_t audioFormat = twoBytesToInt (fileData, f + 8);
529
+ uint16_t numChannels = twoBytesToInt (fileData, f + 10);
530
+ sampleRate = (uint32_t) fourBytesToInt (fileData, f + 12);
531
+ uint32_t numBytesPerSecond = fourBytesToInt (fileData, f + 16);
532
+ uint16_t numBytesPerBlock = twoBytesToInt (fileData, f + 20);
533
+ bitDepth = (int) twoBytesToInt (fileData, f + 22);
534
+
535
+ uint16_t numBytesPerSample = static_cast<uint16_t> (bitDepth) / 8;
536
+
537
+ // check that the audio format is PCM or Float or extensible
538
+ if (audioFormat != WavAudioFormat::PCM && audioFormat != WavAudioFormat::IEEEFloat && audioFormat != WavAudioFormat::Extensible)
539
+ {
540
+ reportError ("ERROR: this .WAV file is encoded in a format that this library does not support at present");
541
+ return false;
542
+ }
543
+
544
+ // check the number of channels is mono or stereo
545
+ if (numChannels < 1 || numChannels > 128)
546
+ {
547
+ reportError ("ERROR: this WAV file seems to be an invalid number of channels (or corrupted?)");
548
+ return false;
549
+ }
550
+
551
+ // check header data is consistent
552
+ if (numBytesPerSecond != static_cast<uint32_t> ((numChannels * sampleRate * bitDepth) / 8) || numBytesPerBlock != (numChannels * numBytesPerSample))
553
+ {
554
+ reportError ("ERROR: the header data in this WAV file seems to be inconsistent");
555
+ return false;
556
+ }
557
+
558
+ // check bit depth is either 8, 16, 24 or 32 bit
559
+ if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32)
560
+ {
561
+ reportError ("ERROR: this file has a bit depth that is not 8, 16, 24 or 32 bits");
562
+ return false;
563
+ }
564
+
565
+ // -----------------------------------------------------------
566
+ // DATA CHUNK
567
+ int d = indexOfDataChunk;
568
+ std::string dataChunkID (fileData.begin() + d, fileData.begin() + d + 4);
569
+ int32_t dataChunkSize = fourBytesToInt (fileData, d + 4);
570
+
571
+ int numSamples = dataChunkSize / (numChannels * bitDepth / 8);
572
+ int samplesStartIndex = indexOfDataChunk + 8;
573
+
574
+ clearAudioBuffer();
575
+ samples.resize (numChannels);
576
+
577
+ for (int i = 0; i < numSamples; i++)
578
+ {
579
+ for (int channel = 0; channel < numChannels; channel++)
580
+ {
581
+ int sampleIndex = samplesStartIndex + (numBytesPerBlock * i) + channel * numBytesPerSample;
582
+
583
+ if ((sampleIndex + (bitDepth / 8) - 1) >= fileData.size())
584
+ {
585
+ reportError ("ERROR: read file error as the metadata indicates more samples than there are in the file data");
586
+ return false;
587
+ }
588
+
589
+ if (bitDepth == 8)
590
+ {
591
+ T sample = singleByteToSample (fileData[sampleIndex]);
592
+ samples[channel].push_back (sample);
593
+ }
594
+ else if (bitDepth == 16)
595
+ {
596
+ int16_t sampleAsInt = twoBytesToInt (fileData, sampleIndex);
597
+ T sample = sixteenBitIntToSample (sampleAsInt);
598
+ samples[channel].push_back (sample);
599
+ }
600
+ else if (bitDepth == 24)
601
+ {
602
+ int32_t sampleAsInt = 0;
603
+ sampleAsInt = (fileData[sampleIndex + 2] << 16) | (fileData[sampleIndex + 1] << 8) | fileData[sampleIndex];
604
+
605
+ if (sampleAsInt & 0x800000) // if the 24th bit is set, this is a negative number in 24-bit world
606
+ sampleAsInt = sampleAsInt | ~0xFFFFFF; // so make sure sign is extended to the 32 bit float
607
+
608
+ T sample = (T)sampleAsInt / (T)8388608.;
609
+ samples[channel].push_back (sample);
610
+ }
611
+ else if (bitDepth == 32)
612
+ {
613
+ int32_t sampleAsInt = fourBytesToInt (fileData, sampleIndex);
614
+ T sample;
615
+
616
+ if (audioFormat == WavAudioFormat::IEEEFloat)
617
+ sample = (T)reinterpret_cast<float&> (sampleAsInt);
618
+ else // assume PCM
619
+ sample = (T) sampleAsInt / static_cast<float> (std::numeric_limits<std::int32_t>::max());
620
+
621
+ samples[channel].push_back (sample);
622
+ }
623
+ else
624
+ {
625
+ assert (false);
626
+ }
627
+ }
628
+ }
629
+
630
+ // -----------------------------------------------------------
631
+ // iXML CHUNK
632
+ if (indexOfXMLChunk != -1)
633
+ {
634
+ int32_t chunkSize = fourBytesToInt (fileData, indexOfXMLChunk + 4);
635
+ iXMLChunk = std::string ((const char*) &fileData[indexOfXMLChunk + 8], chunkSize);
636
+ }
637
+
638
+ return true;
639
+ }
640
+
641
+ //=============================================================
642
+ template <class T>
643
+ bool AudioFile<T>::decodeAiffFile (std::vector<uint8_t>& fileData)
644
+ {
645
+ // -----------------------------------------------------------
646
+ // HEADER CHUNK
647
+ std::string headerChunkID (fileData.begin(), fileData.begin() + 4);
648
+ //int32_t fileSizeInBytes = fourBytesToInt (fileData, 4, Endianness::BigEndian) + 8;
649
+ std::string format (fileData.begin() + 8, fileData.begin() + 12);
650
+
651
+ int audioFormat = format == "AIFF" ? AIFFAudioFormat::Uncompressed : format == "AIFC" ? AIFFAudioFormat::Compressed : AIFFAudioFormat::Error;
652
+
653
+ // -----------------------------------------------------------
654
+ // try and find the start points of key chunks
655
+ int indexOfCommChunk = getIndexOfChunk (fileData, "COMM", 12, Endianness::BigEndian);
656
+ int indexOfSoundDataChunk = getIndexOfChunk (fileData, "SSND", 12, Endianness::BigEndian);
657
+ int indexOfXMLChunk = getIndexOfChunk (fileData, "iXML", 12, Endianness::BigEndian);
658
+
659
+ // if we can't find the data or format chunks, or the IDs/formats don't seem to be as expected
660
+ // then it is unlikely we'll able to read this file, so abort
661
+ if (indexOfSoundDataChunk == -1 || indexOfCommChunk == -1 || headerChunkID != "FORM" || audioFormat == AIFFAudioFormat::Error)
662
+ {
663
+ reportError ("ERROR: this doesn't seem to be a valid AIFF file");
664
+ return false;
665
+ }
666
+
667
+ // -----------------------------------------------------------
668
+ // COMM CHUNK
669
+ int p = indexOfCommChunk;
670
+ std::string commChunkID (fileData.begin() + p, fileData.begin() + p + 4);
671
+ //int32_t commChunkSize = fourBytesToInt (fileData, p + 4, Endianness::BigEndian);
672
+ int16_t numChannels = twoBytesToInt (fileData, p + 8, Endianness::BigEndian);
673
+ int32_t numSamplesPerChannel = fourBytesToInt (fileData, p + 10, Endianness::BigEndian);
674
+ bitDepth = (int) twoBytesToInt (fileData, p + 14, Endianness::BigEndian);
675
+ sampleRate = getAiffSampleRate (fileData, p + 16);
676
+
677
+ // check the sample rate was properly decoded
678
+ if (sampleRate == 0)
679
+ {
680
+ reportError ("ERROR: this AIFF file has an unsupported sample rate");
681
+ return false;
682
+ }
683
+
684
+ // check the number of channels is mono or stereo
685
+ if (numChannels < 1 ||numChannels > 2)
686
+ {
687
+ reportError ("ERROR: this AIFF file seems to be neither mono nor stereo (perhaps multi-track, or corrupted?)");
688
+ return false;
689
+ }
690
+
691
+ // check bit depth is either 8, 16, 24 or 32-bit
692
+ if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32)
693
+ {
694
+ reportError ("ERROR: this file has a bit depth that is not 8, 16, 24 or 32 bits");
695
+ return false;
696
+ }
697
+
698
+ // -----------------------------------------------------------
699
+ // SSND CHUNK
700
+ int s = indexOfSoundDataChunk;
701
+ std::string soundDataChunkID (fileData.begin() + s, fileData.begin() + s + 4);
702
+ int32_t soundDataChunkSize = fourBytesToInt (fileData, s + 4, Endianness::BigEndian);
703
+ int32_t offset = fourBytesToInt (fileData, s + 8, Endianness::BigEndian);
704
+ //int32_t blockSize = fourBytesToInt (fileData, s + 12, Endianness::BigEndian);
705
+
706
+ int numBytesPerSample = bitDepth / 8;
707
+ int numBytesPerFrame = numBytesPerSample * numChannels;
708
+ int totalNumAudioSampleBytes = numSamplesPerChannel * numBytesPerFrame;
709
+ int samplesStartIndex = s + 16 + (int)offset;
710
+
711
+ // sanity check the data
712
+ if ((soundDataChunkSize - 8) != totalNumAudioSampleBytes || totalNumAudioSampleBytes > static_cast<long>(fileData.size() - samplesStartIndex))
713
+ {
714
+ reportError ("ERROR: the metadatafor this file doesn't seem right");
715
+ return false;
716
+ }
717
+
718
+ clearAudioBuffer();
719
+ samples.resize (numChannels);
720
+
721
+ for (int i = 0; i < numSamplesPerChannel; i++)
722
+ {
723
+ for (int channel = 0; channel < numChannels; channel++)
724
+ {
725
+ int sampleIndex = samplesStartIndex + (numBytesPerFrame * i) + channel * numBytesPerSample;
726
+
727
+ if ((sampleIndex + (bitDepth / 8) - 1) >= fileData.size())
728
+ {
729
+ reportError ("ERROR: read file error as the metadata indicates more samples than there are in the file data");
730
+ return false;
731
+ }
732
+
733
+ if (bitDepth == 8)
734
+ {
735
+ int8_t sampleAsSigned8Bit = (int8_t)fileData[sampleIndex];
736
+ T sample = (T)sampleAsSigned8Bit / (T)128.;
737
+ samples[channel].push_back (sample);
738
+ }
739
+ else if (bitDepth == 16)
740
+ {
741
+ int16_t sampleAsInt = twoBytesToInt (fileData, sampleIndex, Endianness::BigEndian);
742
+ T sample = sixteenBitIntToSample (sampleAsInt);
743
+ samples[channel].push_back (sample);
744
+ }
745
+ else if (bitDepth == 24)
746
+ {
747
+ int32_t sampleAsInt = 0;
748
+ sampleAsInt = (fileData[sampleIndex] << 16) | (fileData[sampleIndex + 1] << 8) | fileData[sampleIndex + 2];
749
+
750
+ if (sampleAsInt & 0x800000) // if the 24th bit is set, this is a negative number in 24-bit world
751
+ sampleAsInt = sampleAsInt | ~0xFFFFFF; // so make sure sign is extended to the 32 bit float
752
+
753
+ T sample = (T)sampleAsInt / (T)8388608.;
754
+ samples[channel].push_back (sample);
755
+ }
756
+ else if (bitDepth == 32)
757
+ {
758
+ int32_t sampleAsInt = fourBytesToInt (fileData, sampleIndex, Endianness::BigEndian);
759
+ T sample;
760
+
761
+ if (audioFormat == AIFFAudioFormat::Compressed)
762
+ sample = (T)reinterpret_cast<float&> (sampleAsInt);
763
+ else // assume uncompressed
764
+ sample = (T) sampleAsInt / static_cast<float> (std::numeric_limits<std::int32_t>::max());
765
+
766
+ samples[channel].push_back (sample);
767
+ }
768
+ else
769
+ {
770
+ assert (false);
771
+ }
772
+ }
773
+ }
774
+
775
+ // -----------------------------------------------------------
776
+ // iXML CHUNK
777
+ if (indexOfXMLChunk != -1)
778
+ {
779
+ int32_t chunkSize = fourBytesToInt (fileData, indexOfXMLChunk + 4);
780
+ iXMLChunk = std::string ((const char*) &fileData[indexOfXMLChunk + 8], chunkSize);
781
+ }
782
+
783
+ return true;
784
+ }
785
+
786
+ //=============================================================
787
+ template <class T>
788
+ uint32_t AudioFile<T>::getAiffSampleRate (std::vector<uint8_t>& fileData, int sampleRateStartIndex)
789
+ {
790
+ for (auto it : aiffSampleRateTable)
791
+ {
792
+ if (tenByteMatch (fileData, sampleRateStartIndex, it.second, 0))
793
+ return it.first;
794
+ }
795
+
796
+ return 0;
797
+ }
798
+
799
+ //=============================================================
800
+ template <class T>
801
+ bool AudioFile<T>::tenByteMatch (std::vector<uint8_t>& v1, int startIndex1, std::vector<uint8_t>& v2, int startIndex2)
802
+ {
803
+ for (int i = 0; i < 10; i++)
804
+ {
805
+ if (v1[startIndex1 + i] != v2[startIndex2 + i])
806
+ return false;
807
+ }
808
+
809
+ return true;
810
+ }
811
+
812
+ //=============================================================
813
+ template <class T>
814
+ void AudioFile<T>::addSampleRateToAiffData (std::vector<uint8_t>& fileData, uint32_t sampleRate)
815
+ {
816
+ if (aiffSampleRateTable.count (sampleRate) > 0)
817
+ {
818
+ for (int i = 0; i < 10; i++)
819
+ fileData.push_back (aiffSampleRateTable[sampleRate][i]);
820
+ }
821
+ }
822
+
823
+ //=============================================================
824
+ template <class T>
825
+ bool AudioFile<T>::save (std::string filePath, AudioFileFormat format)
826
+ {
827
+ if (format == AudioFileFormat::Wave)
828
+ {
829
+ return saveToWaveFile (filePath);
830
+ }
831
+ else if (format == AudioFileFormat::Aiff)
832
+ {
833
+ return saveToAiffFile (filePath);
834
+ }
835
+
836
+ return false;
837
+ }
838
+
839
+ //=============================================================
840
+ template <class T>
841
+ bool AudioFile<T>::saveToWaveFile (std::string filePath)
842
+ {
843
+ std::vector<uint8_t> fileData;
844
+
845
+ int32_t dataChunkSize = getNumSamplesPerChannel() * (getNumChannels() * bitDepth / 8);
846
+ int16_t audioFormat = bitDepth == 32 ? WavAudioFormat::IEEEFloat : WavAudioFormat::PCM;
847
+ int32_t formatChunkSize = audioFormat == WavAudioFormat::PCM ? 16 : 18;
848
+ int32_t iXMLChunkSize = static_cast<int32_t> (iXMLChunk.size());
849
+
850
+ // -----------------------------------------------------------
851
+ // HEADER CHUNK
852
+ addStringToFileData (fileData, "RIFF");
853
+
854
+ // The file size in bytes is the header chunk size (4, not counting RIFF and WAVE) + the format
855
+ // chunk size (24) + the metadata part of the data chunk plus the actual data chunk size
856
+ int32_t fileSizeInBytes = 4 + formatChunkSize + 8 + 8 + dataChunkSize;
857
+ if (iXMLChunkSize > 0)
858
+ {
859
+ fileSizeInBytes += (8 + iXMLChunkSize);
860
+ }
861
+
862
+ addInt32ToFileData (fileData, fileSizeInBytes);
863
+
864
+ addStringToFileData (fileData, "WAVE");
865
+
866
+ // -----------------------------------------------------------
867
+ // FORMAT CHUNK
868
+ addStringToFileData (fileData, "fmt ");
869
+ addInt32ToFileData (fileData, formatChunkSize); // format chunk size (16 for PCM)
870
+ addInt16ToFileData (fileData, audioFormat); // audio format
871
+ addInt16ToFileData (fileData, (int16_t)getNumChannels()); // num channels
872
+ addInt32ToFileData (fileData, (int32_t)sampleRate); // sample rate
873
+
874
+ int32_t numBytesPerSecond = (int32_t) ((getNumChannels() * sampleRate * bitDepth) / 8);
875
+ addInt32ToFileData (fileData, numBytesPerSecond);
876
+
877
+ int16_t numBytesPerBlock = getNumChannels() * (bitDepth / 8);
878
+ addInt16ToFileData (fileData, numBytesPerBlock);
879
+
880
+ addInt16ToFileData (fileData, (int16_t)bitDepth);
881
+
882
+ if (audioFormat == WavAudioFormat::IEEEFloat)
883
+ addInt16ToFileData (fileData, 0); // extension size
884
+
885
+ // -----------------------------------------------------------
886
+ // DATA CHUNK
887
+ addStringToFileData (fileData, "data");
888
+ addInt32ToFileData (fileData, dataChunkSize);
889
+
890
+ for (int i = 0; i < getNumSamplesPerChannel(); i++)
891
+ {
892
+ for (int channel = 0; channel < getNumChannels(); channel++)
893
+ {
894
+ if (bitDepth == 8)
895
+ {
896
+ uint8_t byte = sampleToSingleByte (samples[channel][i]);
897
+ fileData.push_back (byte);
898
+ }
899
+ else if (bitDepth == 16)
900
+ {
901
+ int16_t sampleAsInt = sampleToSixteenBitInt (samples[channel][i]);
902
+ addInt16ToFileData (fileData, sampleAsInt);
903
+ }
904
+ else if (bitDepth == 24)
905
+ {
906
+ int32_t sampleAsIntAgain = (int32_t) (samples[channel][i] * (T)8388608.);
907
+
908
+ uint8_t bytes[3];
909
+ bytes[2] = (uint8_t) (sampleAsIntAgain >> 16) & 0xFF;
910
+ bytes[1] = (uint8_t) (sampleAsIntAgain >> 8) & 0xFF;
911
+ bytes[0] = (uint8_t) sampleAsIntAgain & 0xFF;
912
+
913
+ fileData.push_back (bytes[0]);
914
+ fileData.push_back (bytes[1]);
915
+ fileData.push_back (bytes[2]);
916
+ }
917
+ else if (bitDepth == 32)
918
+ {
919
+ int32_t sampleAsInt;
920
+
921
+ if (audioFormat == WavAudioFormat::IEEEFloat)
922
+ sampleAsInt = (int32_t) reinterpret_cast<int32_t&> (samples[channel][i]);
923
+ else // assume PCM
924
+ sampleAsInt = (int32_t) (samples[channel][i] * std::numeric_limits<int32_t>::max());
925
+
926
+ addInt32ToFileData (fileData, sampleAsInt, Endianness::LittleEndian);
927
+ }
928
+ else
929
+ {
930
+ assert (false && "Trying to write a file with unsupported bit depth");
931
+ return false;
932
+ }
933
+ }
934
+ }
935
+
936
+ // -----------------------------------------------------------
937
+ // iXML CHUNK
938
+ if (iXMLChunkSize > 0)
939
+ {
940
+ addStringToFileData (fileData, "iXML");
941
+ addInt32ToFileData (fileData, iXMLChunkSize);
942
+ addStringToFileData (fileData, iXMLChunk);
943
+ }
944
+
945
+ // check that the various sizes we put in the metadata are correct
946
+ if (fileSizeInBytes != static_cast<int32_t> (fileData.size() - 8) || dataChunkSize != (getNumSamplesPerChannel() * getNumChannels() * (bitDepth / 8)))
947
+ {
948
+ reportError ("ERROR: couldn't save file to " + filePath);
949
+ return false;
950
+ }
951
+
952
+ // try to write the file
953
+ return writeDataToFile (fileData, filePath);
954
+ }
955
+
956
+ //=============================================================
957
+ template <class T>
958
+ bool AudioFile<T>::saveToAiffFile (std::string filePath)
959
+ {
960
+ std::vector<uint8_t> fileData;
961
+
962
+ int32_t numBytesPerSample = bitDepth / 8;
963
+ int32_t numBytesPerFrame = numBytesPerSample * getNumChannels();
964
+ int32_t totalNumAudioSampleBytes = getNumSamplesPerChannel() * numBytesPerFrame;
965
+ int32_t soundDataChunkSize = totalNumAudioSampleBytes + 8;
966
+ int32_t iXMLChunkSize = static_cast<int32_t> (iXMLChunk.size());
967
+
968
+ // -----------------------------------------------------------
969
+ // HEADER CHUNK
970
+ addStringToFileData (fileData, "FORM");
971
+
972
+ // The file size in bytes is the header chunk size (4, not counting FORM and AIFF) + the COMM
973
+ // chunk size (26) + the metadata part of the SSND chunk plus the actual data chunk size
974
+ int32_t fileSizeInBytes = 4 + 26 + 16 + totalNumAudioSampleBytes;
975
+ if (iXMLChunkSize > 0)
976
+ {
977
+ fileSizeInBytes += (8 + iXMLChunkSize);
978
+ }
979
+
980
+ addInt32ToFileData (fileData, fileSizeInBytes, Endianness::BigEndian);
981
+
982
+ addStringToFileData (fileData, "AIFF");
983
+
984
+ // -----------------------------------------------------------
985
+ // COMM CHUNK
986
+ addStringToFileData (fileData, "COMM");
987
+ addInt32ToFileData (fileData, 18, Endianness::BigEndian); // commChunkSize
988
+ addInt16ToFileData (fileData, getNumChannels(), Endianness::BigEndian); // num channels
989
+ addInt32ToFileData (fileData, getNumSamplesPerChannel(), Endianness::BigEndian); // num samples per channel
990
+ addInt16ToFileData (fileData, bitDepth, Endianness::BigEndian); // bit depth
991
+ addSampleRateToAiffData (fileData, sampleRate);
992
+
993
+ // -----------------------------------------------------------
994
+ // SSND CHUNK
995
+ addStringToFileData (fileData, "SSND");
996
+ addInt32ToFileData (fileData, soundDataChunkSize, Endianness::BigEndian);
997
+ addInt32ToFileData (fileData, 0, Endianness::BigEndian); // offset
998
+ addInt32ToFileData (fileData, 0, Endianness::BigEndian); // block size
999
+
1000
+ for (int i = 0; i < getNumSamplesPerChannel(); i++)
1001
+ {
1002
+ for (int channel = 0; channel < getNumChannels(); channel++)
1003
+ {
1004
+ if (bitDepth == 8)
1005
+ {
1006
+ uint8_t byte = sampleToSingleByte (samples[channel][i]);
1007
+ fileData.push_back (byte);
1008
+ }
1009
+ else if (bitDepth == 16)
1010
+ {
1011
+ int16_t sampleAsInt = sampleToSixteenBitInt (samples[channel][i]);
1012
+ addInt16ToFileData (fileData, sampleAsInt, Endianness::BigEndian);
1013
+ }
1014
+ else if (bitDepth == 24)
1015
+ {
1016
+ int32_t sampleAsIntAgain = (int32_t) (samples[channel][i] * (T)8388608.);
1017
+
1018
+ uint8_t bytes[3];
1019
+ bytes[0] = (uint8_t) (sampleAsIntAgain >> 16) & 0xFF;
1020
+ bytes[1] = (uint8_t) (sampleAsIntAgain >> 8) & 0xFF;
1021
+ bytes[2] = (uint8_t) sampleAsIntAgain & 0xFF;
1022
+
1023
+ fileData.push_back (bytes[0]);
1024
+ fileData.push_back (bytes[1]);
1025
+ fileData.push_back (bytes[2]);
1026
+ }
1027
+ else if (bitDepth == 32)
1028
+ {
1029
+ // write samples as signed integers (no implementation yet for floating point, but looking at WAV implementation should help)
1030
+ int32_t sampleAsInt = (int32_t) (samples[channel][i] * std::numeric_limits<int32_t>::max());
1031
+ addInt32ToFileData (fileData, sampleAsInt, Endianness::BigEndian);
1032
+ }
1033
+ else
1034
+ {
1035
+ assert (false && "Trying to write a file with unsupported bit depth");
1036
+ return false;
1037
+ }
1038
+ }
1039
+ }
1040
+
1041
+ // -----------------------------------------------------------
1042
+ // iXML CHUNK
1043
+ if (iXMLChunkSize > 0)
1044
+ {
1045
+ addStringToFileData (fileData, "iXML");
1046
+ addInt32ToFileData (fileData, iXMLChunkSize, Endianness::BigEndian);
1047
+ addStringToFileData (fileData, iXMLChunk);
1048
+ }
1049
+
1050
+ // check that the various sizes we put in the metadata are correct
1051
+ if (fileSizeInBytes != static_cast<int32_t> (fileData.size() - 8) || soundDataChunkSize != getNumSamplesPerChannel() * numBytesPerFrame + 8)
1052
+ {
1053
+ reportError ("ERROR: couldn't save file to " + filePath);
1054
+ return false;
1055
+ }
1056
+
1057
+ // try to write the file
1058
+ return writeDataToFile (fileData, filePath);
1059
+ }
1060
+
1061
+ //=============================================================
1062
+ template <class T>
1063
+ bool AudioFile<T>::writeDataToFile (std::vector<uint8_t>& fileData, std::string filePath)
1064
+ {
1065
+ std::ofstream outputFile (filePath, std::ios::binary);
1066
+
1067
+ if (outputFile.is_open())
1068
+ {
1069
+ for (size_t i = 0; i < fileData.size(); i++)
1070
+ {
1071
+ char value = (char) fileData[i];
1072
+ outputFile.write (&value, sizeof (char));
1073
+ }
1074
+
1075
+ outputFile.close();
1076
+
1077
+ return true;
1078
+ }
1079
+
1080
+ return false;
1081
+ }
1082
+
1083
+ //=============================================================
1084
+ template <class T>
1085
+ void AudioFile<T>::addStringToFileData (std::vector<uint8_t>& fileData, std::string s)
1086
+ {
1087
+ for (size_t i = 0; i < s.length();i++)
1088
+ fileData.push_back ((uint8_t) s[i]);
1089
+ }
1090
+
1091
+ //=============================================================
1092
+ template <class T>
1093
+ void AudioFile<T>::addInt32ToFileData (std::vector<uint8_t>& fileData, int32_t i, Endianness endianness)
1094
+ {
1095
+ uint8_t bytes[4];
1096
+
1097
+ if (endianness == Endianness::LittleEndian)
1098
+ {
1099
+ bytes[3] = (i >> 24) & 0xFF;
1100
+ bytes[2] = (i >> 16) & 0xFF;
1101
+ bytes[1] = (i >> 8) & 0xFF;
1102
+ bytes[0] = i & 0xFF;
1103
+ }
1104
+ else
1105
+ {
1106
+ bytes[0] = (i >> 24) & 0xFF;
1107
+ bytes[1] = (i >> 16) & 0xFF;
1108
+ bytes[2] = (i >> 8) & 0xFF;
1109
+ bytes[3] = i & 0xFF;
1110
+ }
1111
+
1112
+ for (int i = 0; i < 4; i++)
1113
+ fileData.push_back (bytes[i]);
1114
+ }
1115
+
1116
+ //=============================================================
1117
+ template <class T>
1118
+ void AudioFile<T>::addInt16ToFileData (std::vector<uint8_t>& fileData, int16_t i, Endianness endianness)
1119
+ {
1120
+ uint8_t bytes[2];
1121
+
1122
+ if (endianness == Endianness::LittleEndian)
1123
+ {
1124
+ bytes[1] = (i >> 8) & 0xFF;
1125
+ bytes[0] = i & 0xFF;
1126
+ }
1127
+ else
1128
+ {
1129
+ bytes[0] = (i >> 8) & 0xFF;
1130
+ bytes[1] = i & 0xFF;
1131
+ }
1132
+
1133
+ fileData.push_back (bytes[0]);
1134
+ fileData.push_back (bytes[1]);
1135
+ }
1136
+
1137
+ //=============================================================
1138
+ template <class T>
1139
+ void AudioFile<T>::clearAudioBuffer()
1140
+ {
1141
+ for (size_t i = 0; i < samples.size();i++)
1142
+ {
1143
+ samples[i].clear();
1144
+ }
1145
+
1146
+ samples.clear();
1147
+ }
1148
+
1149
+ //=============================================================
1150
+ template <class T>
1151
+ AudioFileFormat AudioFile<T>::determineAudioFileFormat (std::vector<uint8_t>& fileData)
1152
+ {
1153
+ std::string header (fileData.begin(), fileData.begin() + 4);
1154
+
1155
+ if (header == "RIFF")
1156
+ return AudioFileFormat::Wave;
1157
+ else if (header == "FORM")
1158
+ return AudioFileFormat::Aiff;
1159
+ else
1160
+ return AudioFileFormat::Error;
1161
+ }
1162
+
1163
+ //=============================================================
1164
+ template <class T>
1165
+ int32_t AudioFile<T>::fourBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness)
1166
+ {
1167
+ int32_t result;
1168
+
1169
+ if (endianness == Endianness::LittleEndian)
1170
+ result = (source[startIndex + 3] << 24) | (source[startIndex + 2] << 16) | (source[startIndex + 1] << 8) | source[startIndex];
1171
+ else
1172
+ result = (source[startIndex] << 24) | (source[startIndex + 1] << 16) | (source[startIndex + 2] << 8) | source[startIndex + 3];
1173
+
1174
+ return result;
1175
+ }
1176
+
1177
+ //=============================================================
1178
+ template <class T>
1179
+ int16_t AudioFile<T>::twoBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness)
1180
+ {
1181
+ int16_t result;
1182
+
1183
+ if (endianness == Endianness::LittleEndian)
1184
+ result = (source[startIndex + 1] << 8) | source[startIndex];
1185
+ else
1186
+ result = (source[startIndex] << 8) | source[startIndex + 1];
1187
+
1188
+ return result;
1189
+ }
1190
+
1191
+ //=============================================================
1192
+ template <class T>
1193
+ int AudioFile<T>::getIndexOfString (std::vector<uint8_t>& source, std::string stringToSearchFor)
1194
+ {
1195
+ int index = -1;
1196
+ int stringLength = (int)stringToSearchFor.length();
1197
+
1198
+ for (size_t i = 0; i < source.size() - stringLength;i++)
1199
+ {
1200
+ std::string section (source.begin() + i, source.begin() + i + stringLength);
1201
+
1202
+ if (section == stringToSearchFor)
1203
+ {
1204
+ index = static_cast<int> (i);
1205
+ break;
1206
+ }
1207
+ }
1208
+
1209
+ return index;
1210
+ }
1211
+
1212
+ //=============================================================
1213
+ template <class T>
1214
+ int AudioFile<T>::getIndexOfChunk (std::vector<uint8_t>& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness)
1215
+ {
1216
+ constexpr int dataLen = 4;
1217
+ if (chunkHeaderID.size() != dataLen)
1218
+ {
1219
+ assert (false && "Invalid chunk header ID string");
1220
+ return -1;
1221
+ }
1222
+
1223
+ int i = startIndex;
1224
+ while (i < source.size() - dataLen)
1225
+ {
1226
+ if (memcmp (&source[i], chunkHeaderID.data(), dataLen) == 0)
1227
+ {
1228
+ return i;
1229
+ }
1230
+
1231
+ i += dataLen;
1232
+ auto chunkSize = fourBytesToInt (source, i, endianness);
1233
+ i += (dataLen + chunkSize);
1234
+ }
1235
+
1236
+ return -1;
1237
+ }
1238
+
1239
+ //=============================================================
1240
+ template <class T>
1241
+ T AudioFile<T>::sixteenBitIntToSample (int16_t sample)
1242
+ {
1243
+ return static_cast<T> (sample) / static_cast<T> (32768.);
1244
+ }
1245
+
1246
+ //=============================================================
1247
+ template <class T>
1248
+ int16_t AudioFile<T>::sampleToSixteenBitInt (T sample)
1249
+ {
1250
+ sample = clamp (sample, -1., 1.);
1251
+ return static_cast<int16_t> (sample * 32767.);
1252
+ }
1253
+
1254
+ //=============================================================
1255
+ template <class T>
1256
+ uint8_t AudioFile<T>::sampleToSingleByte (T sample)
1257
+ {
1258
+ sample = clamp (sample, -1., 1.);
1259
+ sample = (sample + 1.) / 2.;
1260
+ return static_cast<uint8_t> (sample * 255.);
1261
+ }
1262
+
1263
+ //=============================================================
1264
+ template <class T>
1265
+ T AudioFile<T>::singleByteToSample (uint8_t sample)
1266
+ {
1267
+ return static_cast<T> (sample - 128) / static_cast<T> (128.);
1268
+ }
1269
+
1270
+ //=============================================================
1271
+ template <class T>
1272
+ T AudioFile<T>::clamp (T value, T minValue, T maxValue)
1273
+ {
1274
+ value = std::min (value, maxValue);
1275
+ value = std::max (value, minValue);
1276
+ return value;
1277
+ }
1278
+
1279
+ //=============================================================
1280
+ template <class T>
1281
+ void AudioFile<T>::reportError (std::string errorMessage)
1282
+ {
1283
+ if (logErrorsToConsole)
1284
+ std::cout << errorMessage << std::endl;
1285
+ }
1286
+
1287
+ #if defined (_MSC_VER)
1288
+ __pragma(warning (pop))
1289
+ #elif defined (__GNUC__)
1290
+ _Pragma("GCC diagnostic pop")
1291
+ #endif
1292
+
1293
+ #endif /* AudioFile_h */
cpp/src/EnG2P.h ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <unordered_map>
4
+ #include <vector>
5
+ #include <fstream>
6
+ #include <sstream>
7
+ #include <iostream>
8
+ #include <algorithm>
9
+
10
+ class EnG2P {
11
+ public:
12
+ EnG2P(const std::string& dict_path) {
13
+ load_dict(dict_path);
14
+ init_arpabet_map();
15
+ }
16
+
17
+ std::string convert(const std::string& word) {
18
+ std::string upper_word = word;
19
+ // Simple strip of punctuation if needed?
20
+ // But let's just assume the input is somewhat clean or strict match first.
21
+
22
+ // Trim common punctuation from ends just in case
23
+ size_t start = 0;
24
+ while (start < upper_word.size() && !isalnum((unsigned char)upper_word[start])) start++;
25
+ size_t end = upper_word.size();
26
+ while (end > start && !isalnum((unsigned char)upper_word[end-1])) end--;
27
+
28
+ std::string clean_word = upper_word.substr(start, end - start);
29
+ std::string prefix = upper_word.substr(0, start);
30
+ std::string suffix = upper_word.substr(end);
31
+
32
+ std::transform(clean_word.begin(), clean_word.end(), clean_word.begin(), ::toupper);
33
+
34
+ // std::cout << "Debug EnG2P: Query [" << clean_word << "]" << std::endl;
35
+
36
+ if (dict_.count(clean_word)) {
37
+ return prefix + arpabet_to_ipa(dict_.at(clean_word)) + suffix;
38
+ }
39
+
40
+ // Fallback: return original
41
+ return word;
42
+ }
43
+
44
+ private:
45
+ std::unordered_map<std::string, std::vector<std::string>> dict_;
46
+ std::unordered_map<std::string, std::string> arpabet_map_;
47
+
48
+ void load_dict(const std::string& path) {
49
+ std::ifstream file(path);
50
+ if (!file.is_open()) {
51
+ // Silent fail or log?
52
+ std::cerr << "[EnG2P] Warning: Failed to open CMU dict: " << path << std::endl;
53
+ return;
54
+ }
55
+ std::string line;
56
+ int count = 0;
57
+ while (std::getline(file, line)) {
58
+ if (line.empty()) continue;
59
+ // CMU dict lines start with word, possibly with symbols like !EXCLAMATION-POINT
60
+ // Standard format: WORD PH ON E M ES
61
+ if (!isalpha(line[0]) && line[0] != '\'') continue; // Basic filtering
62
+
63
+ std::stringstream ss(line);
64
+ std::string word, ph;
65
+ ss >> word;
66
+
67
+ // Handle variants like WORD(1)
68
+ size_t paren = word.find('(');
69
+ if (paren != std::string::npos) {
70
+ word = word.substr(0, paren);
71
+ }
72
+
73
+ // Normalize to UPPERCASE
74
+ std::transform(word.begin(), word.end(), word.begin(), ::toupper);
75
+
76
+ std::vector<std::string> phonemes;
77
+ while (ss >> ph) {
78
+ phonemes.push_back(ph);
79
+ }
80
+
81
+ // Only keep first variant if multiple exist (CMU dict is sorted, usually main first)
82
+ if (!dict_.count(word)) {
83
+ dict_[word] = phonemes;
84
+ count++;
85
+ // if (count < 5) std::cout << "Debug CMU: Loaded [" << word << "]" << std::endl;
86
+ }
87
+ }
88
+ std::cout << "[EnG2P] Loaded " << dict_.size() << " words from CMU dict." << std::endl;
89
+
90
+ }
91
+
92
+ void init_arpabet_map() {
93
+ // Mapping ARPABET to IPA
94
+ // Note: This is a simplified mapping.
95
+ // Stress: 1 (primary) -> ˈ, 2 (secondary) -> ˌ, 0 (unstressed) -> nothing/schwa
96
+ arpabet_map_ = {
97
+ {"AA0", "ɑ"}, {"AA1", "ˈɑ"}, {"AA2", "ˌɑ"},
98
+ {"AE0", "æ"}, {"AE1", "ˈæ"}, {"AE2", "ˌæ"},
99
+ {"AH0", "ə"}, {"AH1", "ˈʌ"}, {"AH2", "ˌʌ"},
100
+ {"AO0", "ɔ"}, {"AO1", "ˈɔ"}, {"AO2", "ˌɔ"},
101
+ {"AW0", "aʊ"}, {"AW1", "ˈaʊ"}, {"AW2", "ˌaʊ"},
102
+ {"AY0", "aɪ"}, {"AY1", "ˈaɪ"}, {"AY2", "ˌaɪ"},
103
+ {"B", "b"}, {"CH", "tʃ"}, {"D", "d"}, {"DH", "ð"},
104
+ {"EH0", "ɛ"}, {"EH1", "ˈɛ"}, {"EH2", "ˌɛ"},
105
+ {"ER0", "ɚ"}, {"ER1", "ˈɝ"}, {"ER2", "ˌɝ"},
106
+ {"EY0", "eɪ"}, {"EY1", "ˈeɪ"}, {"EY2", "ˌeɪ"},
107
+ {"F", "f"}, {"G", "ɡ"}, {"HH", "h"},
108
+ {"IH0", "ɪ"}, {"IH1", "ˈɪ"}, {"IH2", "ˌɪ"},
109
+ {"IY0", "i"}, {"IY1", "ˈi"}, {"IY2", "ˌi"},
110
+ {"JH", "dʒ"}, {"K", "k"}, {"L", "l"},
111
+ {"M", "m"}, {"N", "n"}, {"NG", "ŋ"},
112
+ {"OW0", "oʊ"}, {"OW1", "ˈoʊ"}, {"OW2", "ˌoʊ"},
113
+ {"OY0", "ɔɪ"}, {"OY1", "ˈɔɪ"}, {"OY2", "ˌɔɪ"},
114
+ {"P", "p"}, {"R", "r"}, {"S", "s"}, {"SH", "ʃ"},
115
+ {"T", "t"}, {"TH", "θ"},
116
+ {"UH0", "ʊ"}, {"UH1", "ˈʊ"}, {"UH2", "ˌʊ"},
117
+ {"UW0", "u"}, {"UW1", "ˈu"}, {"UW2", "ˌu"},
118
+ {"V", "v"}, {"W", "w"}, {"Y", "j"}, {"Z", "z"}, {"ZH", "ʒ"}
119
+ };
120
+ }
121
+
122
+ std::string arpabet_to_ipa(const std::vector<std::string>& phonemes) {
123
+ std::string res;
124
+ for (const auto& p : phonemes) {
125
+ if (arpabet_map_.count(p)) {
126
+ res += arpabet_map_.at(p);
127
+ } else {
128
+ // Fallback: try removing digit
129
+ std::string base = p;
130
+ if (!base.empty() && isdigit(base.back())) base.pop_back();
131
+ if (arpabet_map_.count(base)) {
132
+ res += arpabet_map_.at(base);
133
+ }
134
+ }
135
+ }
136
+ return res;
137
+ }
138
+ };
cpp/src/JiebaProcessor.h ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "ZHG2P.h"
4
+ #include "PinyinFinder.h"
5
+ #include "cppjieba/Jieba.hpp"
6
+ #include "Utils.h"
7
+ #include <regex>
8
+
9
+ class JiebaProcessor : public TextProcessor {
10
+ public:
11
+ JiebaProcessor(const std::string& dict_path,
12
+ const std::string& hmm_path,
13
+ const std::string& user_dict_path,
14
+ const std::string& idf_path,
15
+ const std::string& stop_word_path,
16
+ const std::string& pinyin_char_path,
17
+ const std::string& pinyin_word_path)
18
+ : jieba(dict_path, hmm_path, user_dict_path, idf_path, stop_word_path)
19
+ {
20
+ finder = std::make_shared<PinyinFinder>();
21
+ if (!finder->init(pinyin_char_path, pinyin_word_path)) {
22
+ std::cerr << "Failed to init PinyinFinder" << std::endl;
23
+ }
24
+ }
25
+
26
+ std::vector<std::pair<std::string, std::string>> cut(const std::string& text) override {
27
+ std::vector<std::pair<std::string, std::string>> result;
28
+ std::vector<std::pair<std::string, std::string>> tag_words;
29
+
30
+ // Use cppjieba Tagging
31
+ jieba.Tag(text, tag_words);
32
+
33
+ for (const auto& w : tag_words) {
34
+ std::string word = w.first;
35
+ std::string tag = w.second;
36
+
37
+ // Ensure punctuation is 'x' (jieba might return 'w' for punct)
38
+ if (tag == "w") tag = "x";
39
+
40
+ // FIX: If tag is 'x' but contains Chinese characters, force it to a valid tag (e.g. 'n')
41
+ // This prevents words like "我要" being tagged as 'x' and skipped by G2P.
42
+ if (tag == "x") {
43
+ bool has_cn = false;
44
+ for (unsigned char c : word) {
45
+ if (c >= 0xE4 && c <= 0xE9) {
46
+ has_cn = true;
47
+ break;
48
+ }
49
+ }
50
+ if (has_cn) tag = "n";
51
+ }
52
+
53
+ result.push_back({word, tag});
54
+ }
55
+ return result;
56
+ }
57
+
58
+ std::vector<std::string> word_to_pinyin(const std::string& word) override {
59
+ std::vector<std::string> pinyins;
60
+ if (finder) {
61
+ finder->find_best_pinyin(word, pinyins);
62
+ }
63
+ return pinyins;
64
+ }
65
+
66
+ std::string convert_numbers(const std::string& text) override {
67
+ // Regex to find numbers: integers, floats, and IP-like strings
68
+ // Examples: 123, -123, 3.14, 192.168.0.1
69
+ // Pattern: [-+]?\d+(?:\.\d+)*
70
+
71
+ std::regex num_regex("[-+]?\\d+(?:\\.\\d+)*");
72
+ std::string result;
73
+
74
+ auto words_begin = std::sregex_iterator(text.begin(), text.end(), num_regex);
75
+ auto words_end = std::sregex_iterator();
76
+
77
+ size_t last_pos = 0;
78
+
79
+ for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
80
+ std::smatch match = *i;
81
+ std::string match_str = match.str();
82
+
83
+ // Append text before the number
84
+ result += text.substr(last_pos, match.position() - last_pos);
85
+
86
+ // Convert number to Chinese
87
+ result += BasicStringUtil::NumberToChinese(match_str);
88
+
89
+ last_pos = match.position() + match.length();
90
+ }
91
+
92
+ // Append remaining text
93
+ if (last_pos < text.length()) {
94
+ result += text.substr(last_pos);
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ private:
101
+ cppjieba::Jieba jieba;
102
+ std::shared_ptr<PinyinFinder> finder;
103
+ };
cpp/src/Kokoro.cpp ADDED
@@ -0,0 +1,1216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "Kokoro.h"
2
+ #include "Tokenizer.h"
3
+ #include <fstream>
4
+ #include <sstream>
5
+ #include <regex>
6
+ #include <numeric>
7
+ #include <cstring>
8
+ #include <algorithm>
9
+ #include <math.h>
10
+ #include <locale>
11
+ #include <codecvt>
12
+ #include "utils/logger.hpp"
13
+ #include "librosa/eigen3/Eigen/Dense"
14
+ #include "librosa/librosa.h"
15
+ #include "split_utils.hpp"
16
+
17
+ using namespace std;
18
+
19
+ typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> DynMat;
20
+
21
+ typedef std::vector<std::vector<std::complex<float>>> FFT_RESULT;
22
+
23
+ // DEBUG
24
+ template <typename T>
25
+ static void save_file(const std::vector<T>& data, const char* filename) {
26
+ FILE* fp = fopen(filename, "wb");
27
+ fwrite(data.data(), sizeof(T), data.size(), fp);
28
+ fclose(fp);
29
+ }
30
+
31
+ void save_fft_result(const FFT_RESULT& f, const char* filename) {
32
+ FILE* fp = fopen(filename, "wb");
33
+ for (size_t i = 0; i < f.size(); i++) {
34
+ for (size_t j = 0; j < f[0].size(); j++) {
35
+ float real = f[i][j].real();
36
+ float imag = f[i][j].imag();
37
+ fwrite(&real, sizeof(float), 1, fp);
38
+ fwrite(&imag, sizeof(float), 1, fp);
39
+ }
40
+ }
41
+ fclose(fp);
42
+ }
43
+
44
+ // Helper functions
45
+ static std::vector<float> sigmoid(const std::vector<float>& x) {
46
+ std::vector<float> result(x.size());
47
+ for (int i = 0; i < x.size(); i++) {
48
+ result[i] = 1.0f / (1.0f + expf(-x[i]));
49
+ }
50
+ return result;
51
+ }
52
+
53
+ template <typename T>
54
+ std::vector<T> load_file(const char* filename) {
55
+ // 打开文件(二进制模式)
56
+ std::ifstream file(filename, std::ios::binary);
57
+
58
+ // 获取文件大小
59
+ file.seekg(0, std::ios::end);
60
+ size_t file_size = file.tellg();
61
+ file.seekg(0, std::ios::beg);
62
+
63
+ // 计算float数量
64
+ size_t num_floats = file_size / sizeof(T);
65
+
66
+ // 创建向量并读取数据
67
+ std::vector<T> result;
68
+ result.resize(num_floats);
69
+ file.read(reinterpret_cast<char*>(result.data()), file_size);
70
+
71
+ file.close();
72
+ return result;
73
+ }
74
+
75
+ template <typename T>
76
+ vector<size_t> argsort(const vector<T> &v, int len, bool reverse) {
77
+ // initialize original index locations
78
+ vector<size_t> idx(len);
79
+ iota(idx.begin(), idx.end(), 0);
80
+
81
+ // sort indexes based on comparing values in v
82
+ // using std::stable_sort instead of std::sort
83
+ // to avoid unnecessary index re-orderings
84
+ // when v contains elements of equal values
85
+ if (!reverse)
86
+ stable_sort(idx.begin(), idx.end(),
87
+ [&v](size_t i1, size_t i2) {return v[i1] < v[i2];});
88
+ else
89
+ stable_sort(idx.begin(), idx.end(),
90
+ [&v](size_t i1, size_t i2) {return v[i1] > v[i2];});
91
+
92
+ return idx;
93
+ }
94
+
95
+ template <typename T>
96
+ vector<T> np_repeat(const vector<T> &v, const vector<int>& times) {
97
+ vector<T> result;
98
+ for (size_t i = 0; i < times.size(); i++) {
99
+ for (int n = 0; n < times[i]; n++)
100
+ result.push_back(v[i]);
101
+ }
102
+ return result;
103
+ }
104
+
105
+ template <typename T>
106
+ std::vector<T> linspace(T a, T b, size_t N) {
107
+ T h = (b - a) / static_cast<T>(N-1);
108
+ std::vector<T> xs(N);
109
+ typename std::vector<T>::iterator x;
110
+ T val;
111
+ for (x = xs.begin(), val = a; x != xs.end(); ++x, val += h)
112
+ *x = val;
113
+ return xs;
114
+ }
115
+
116
+ /**
117
+ * 清理文本:移除多余空格、控制字符等
118
+ * @param text 输入文本
119
+ * @return 清理后的文本
120
+ */
121
+ string clean_text(const string& text) {
122
+ if (text.empty()) return "";
123
+
124
+ string result;
125
+ result.reserve(text.length());
126
+
127
+ // 第一步:处理空格和替换控制字符
128
+ bool last_was_space = false;
129
+ for (char c : text) {
130
+ // 保留换行符、回车符、制表符
131
+ if (c == '\n' || c == '\r' || c == '\t') {
132
+ result.push_back(c);
133
+ last_was_space = false;
134
+ }
135
+ // 处理普通空格
136
+ else if (isspace(static_cast<unsigned char>(c))) {
137
+ if (!last_was_space) {
138
+ result.push_back(' ');
139
+ last_was_space = true;
140
+ }
141
+ }
142
+ // 保留可打印字符(ASCII >= 32)
143
+ else if (static_cast<unsigned char>(c) >= 32) {
144
+ result.push_back(c);
145
+ last_was_space = false;
146
+ }
147
+ // 其他控制字符被忽略
148
+ }
149
+
150
+ // 第二步:去除首尾空格
151
+ // 去除开头的空格
152
+ size_t start = 0;
153
+ while (start < result.length() && result[start] == ' ') {
154
+ start++;
155
+ }
156
+
157
+ // 去除结尾的空格
158
+ size_t end = result.length();
159
+ while (end > start && result[end - 1] == ' ') {
160
+ end--;
161
+ }
162
+
163
+ return result.substr(start, end - start);
164
+ }
165
+
166
+ /**
167
+ * 拼接音频片段
168
+ * @param segment_data_list 音频片段列表(每个片段是float向量)
169
+ * @param sample_rate 采样率,默认24000
170
+ * @param speed 语速,默认1.0
171
+ * @param pause_duration 停顿时长(秒),默认0.5
172
+ * @return 拼接后的音频数据(std::vector<float>)
173
+ */
174
+ std::vector<float> audio_numpy_concat(
175
+ const std::vector<std::vector<float>>& segment_data_list,
176
+ int sample_rate = 24000,
177
+ float speed = 1.0f,
178
+ float pause_duration = 0.5f
179
+ ) {
180
+ // 如果输入为空,返回空向量
181
+ if (segment_data_list.empty()) {
182
+ return std::vector<float>();
183
+ }
184
+
185
+ // 计算停顿的样本数
186
+ int pause_samples = 0;
187
+ if (pause_duration > 0.0f && speed > 0.0f) {
188
+ pause_samples = static_cast<int>((sample_rate * pause_duration) / speed);
189
+ if (pause_samples < 0) {
190
+ pause_samples = 0;
191
+ }
192
+ }
193
+
194
+ // 首先计算总长度,预分配内存(提高性能)
195
+ size_t total_length = 0;
196
+ for (const auto& segment : segment_data_list) {
197
+ total_length += segment.size();
198
+ }
199
+
200
+ // 添加停顿的长度(在片段之间)
201
+ size_t num_pauses = 0;
202
+ if (segment_data_list.size() > 1 && pause_samples > 0) {
203
+ num_pauses = segment_data_list.size() - 1;
204
+ total_length += pause_samples * num_pauses;
205
+ }
206
+
207
+ // 创建结果向量并预分配内存
208
+ std::vector<float> result;
209
+ result.reserve(total_length);
210
+
211
+ // 拼接所有片段
212
+ for (size_t i = 0; i < segment_data_list.size(); ++i) {
213
+ // 添加当前音频片段
214
+ const auto& current_segment = segment_data_list[i];
215
+ result.insert(result.end(),
216
+ current_segment.begin(),
217
+ current_segment.end());
218
+
219
+ // 如果不是最后一个片段,添加停顿
220
+ if (i < segment_data_list.size() - 1 && pause_samples > 0) {
221
+ result.insert(result.end(), pause_samples, 0.0f);
222
+ }
223
+ }
224
+
225
+ return result;
226
+ }
227
+
228
+
229
+ Kokoro::Kokoro()
230
+ {
231
+
232
+ }
233
+
234
+ Kokoro::~Kokoro() {
235
+ // Resources cleaned up by wrappers
236
+ }
237
+
238
+ bool Kokoro::init(const std::string& model_path, int max_seq_len, const std::string& voices_path, const std::string& voice_name, const std::string& vocab_path) {
239
+ max_seq_len_ = max_seq_len;
240
+ voices_path_ = voices_path;
241
+ voice_name_ = voice_name;
242
+
243
+ env_ = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "Kokoro");
244
+ // Initialize session options
245
+ Ort::SessionOptions session_options;
246
+ session_options.SetIntraOpNumThreads(1);
247
+ // session_options.SetLogSeverityLevel(ORT_LOGGING_LEVEL_VERBOSE);
248
+ session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
249
+
250
+ // Load models
251
+ std::string model1_path = model_path + "/kokoro_part1_96.axmodel";
252
+ std::string model2_path = model_path + "/kokoro_part2_96.axmodel";
253
+ std::string model3_path = model_path + "/kokoro_part3_96.axmodel";
254
+ std::string model4_path = model_path + "/model4_har_sim.onnx";
255
+
256
+ // Load voice
257
+ if (!get_voice_style(voices_path, voice_name)) {
258
+ ALOGE("Load voice failed!");
259
+ return false;
260
+ }
261
+
262
+ int ret = model1_.load_model(model1_path.c_str());
263
+ if (0 != ret) {
264
+ ALOGE("load model1 %s failed! ret=0x%x", model1_path.c_str(), ret);
265
+ return false;
266
+ }
267
+
268
+ ret = model2_.load_model(model2_path.c_str());
269
+ if (0 != ret) {
270
+ ALOGE("load model2 %s failed! ret=0x%x", model2_path.c_str(), ret);
271
+ return false;
272
+ }
273
+
274
+ ret = model3_.load_model(model3_path.c_str());
275
+ if (0 != ret) {
276
+ ALOGE("load model3 %s failed! ret=0x%x", model3_path.c_str(), ret);
277
+ return false;
278
+ }
279
+
280
+ model4_ = Ort::Session(env_, model4_path.c_str(), session_options);
281
+
282
+ // Load vocab
283
+ std::map<std::string, int> vocab;
284
+ std::ifstream in(vocab_path);
285
+ if (in.is_open()) {
286
+ std::string line;
287
+ while (std::getline(in, line)) {
288
+ // Expected format: token<TAB>id
289
+ size_t tab = line.find('\t');
290
+ if (tab != std::string::npos) {
291
+ std::string token = line.substr(0, tab);
292
+ std::string id_str = line.substr(tab + 1);
293
+ // Unescape token if needed (\n, \r, \t)
294
+ size_t pos = 0;
295
+ while((pos = token.find("\\n", pos)) != std::string::npos) { token.replace(pos, 2, "\n"); pos += 1; }
296
+ pos = 0;
297
+ while((pos = token.find("\\r", pos)) != std::string::npos) { token.replace(pos, 2, "\r"); pos += 1; }
298
+ pos = 0;
299
+ while((pos = token.find("\\t", pos)) != std::string::npos) { token.replace(pos, 2, "\t"); pos += 1; }
300
+
301
+ try {
302
+ vocab[token] = std::stoi(id_str);
303
+ } catch (...) {}
304
+ }
305
+ }
306
+ ALOGI("Loaded %d tokens from %s", vocab.size(), vocab_path.c_str());
307
+ } else {
308
+ ALOGE("Failed to open vocab file %s", vocab_path.c_str());
309
+ return false;
310
+ }
311
+
312
+ // Initialize Tokenizer
313
+ tokenizer_ = std::make_unique<Tokenizer>(TokenizerConfig{}, vocab);
314
+
315
+ // Prepare model outputs
316
+ duration_.resize(model1_.get_output_size(0) / sizeof(float));
317
+ d_.resize(model1_.get_output_size(1) / sizeof(float));
318
+
319
+ // F0_pred, N_pred, asr = outputs2
320
+ F0_pred_.resize(model2_.get_output_size(0) / sizeof(float));
321
+ N_pred_.resize(model2_.get_output_size(1) / sizeof(float));
322
+ asr_.resize(model2_.get_output_size(2) / sizeof(float));
323
+
324
+ x_.resize(model3_.get_output_size(0) / sizeof(float));
325
+
326
+ duration_shape_ = model1_.get_output_shape(0);
327
+ d_shape_ = model1_.get_output_shape(1);
328
+
329
+ F0_pred_shape_ = model2_.get_output_shape(0);
330
+
331
+ x_shape_ = model3_.get_output_shape(0);
332
+
333
+ return true;
334
+ }
335
+
336
+ bool Kokoro::get_voice_style(const std::string& voices_path, const std::string& voice_name) {
337
+ // 打开文件(二进制模式)
338
+ std::string voice_bin_path = voices_path + "/" + voice_name + ".bin";
339
+ std::ifstream file(voice_bin_path, std::ios::binary);
340
+ if (!file.is_open()) {
341
+ ALOGE("Open file %s failed!", voice_bin_path.c_str());
342
+ return false;
343
+ }
344
+
345
+ // 获取文件大小
346
+ file.seekg(0, std::ios::end);
347
+ size_t file_size = file.tellg();
348
+ file.seekg(0, std::ios::beg);
349
+
350
+ // 计算float数量
351
+ size_t num_floats = file_size / sizeof(float);
352
+
353
+ voice_pack_size_ = num_floats / STYLE_DIM;
354
+
355
+ // 创建向量并读取数据
356
+ voice_tensor_.resize(num_floats);
357
+ file.read(reinterpret_cast<char*>(voice_tensor_.data()), file_size);
358
+
359
+ file.close();
360
+ return true;
361
+ }
362
+
363
+ std::vector<std::string> Kokoro::_split_phonemes(const std::string& phonemes) {
364
+ std::vector<std::string> batches;
365
+ std::regex re("([.,!?;])");
366
+ std::sregex_token_iterator it(phonemes.begin(), phonemes.end(), re, {-1, 0}); // -1 for non-match, 0 for match
367
+ std::sregex_token_iterator end;
368
+
369
+ std::string current_batch;
370
+
371
+ for (; it != end; ++it) {
372
+ std::string part = *it;
373
+ // Removing leading/trailing whitespace
374
+ part = std::regex_replace(part, std::regex("^\\s+|\\s+$"), "");
375
+
376
+ if (part.empty()) continue;
377
+
378
+ if (current_batch.length() + part.length() + 1 >= MAX_PHONEME_LENGTH) {
379
+ batches.push_back(current_batch);
380
+ current_batch = part;
381
+ } else {
382
+ if (std::string(".,!?;").find(part) != std::string::npos) {
383
+ current_batch += part;
384
+ } else {
385
+ if (!current_batch.empty()) current_batch += " ";
386
+ current_batch += part;
387
+ }
388
+ }
389
+ }
390
+ if (!current_batch.empty()) {
391
+ batches.push_back(current_batch);
392
+ }
393
+ return batches;
394
+ }
395
+
396
+ void Kokoro::_prepare_input_ids(std::vector<int>& input_ids, int& actual_len, bool& is_doubled) {
397
+ // 准备输入ID,对短输入进行复制处理
398
+ is_doubled = false;
399
+ int original_actual_len = actual_len;
400
+
401
+ // printf("actual_len 3: %d\n", actual_len);
402
+ if (actual_len <= DOUBLE_INPUT_THRESHOLD) {
403
+ // printf("doubled!\n");
404
+ is_doubled = true;
405
+ // valid_content = input_ids[:, :actual_len]
406
+ std::vector<int> valid_content(input_ids.begin(), input_ids.begin() + actual_len);
407
+ // input_ids_doubled = np.concatenate([valid_content, valid_content], axis=1)
408
+ std::vector<int> input_ids_doubled;
409
+ // input_ids_doubled.reserve(2 * actual_len);
410
+ input_ids_doubled.insert(input_ids_doubled.end(), valid_content.begin(), valid_content.end());
411
+ input_ids_doubled.insert(input_ids_doubled.end(), valid_content.begin(), valid_content.end());
412
+
413
+ // padding_len = self.max_seq_len_ - input_ids_doubled.shape[1]
414
+ int padding_len = max_seq_len_ - 2 * actual_len;
415
+ // printf("padding_len: %d\n", padding_len);
416
+ if (padding_len > 0) {
417
+ // input_ids = np.concatenate([input_ids_doubled, np.zeros((1, padding_len), dtype=input_ids.dtype)], axis=1)
418
+ std::vector<int> padding(padding_len, 0);
419
+ input_ids_doubled.insert(input_ids_doubled.end(), padding.begin(), padding.end());
420
+ }
421
+ else {
422
+ // input_ids = input_ids_doubled[:, :self.max_seq_len_]
423
+ input_ids_doubled.resize(max_seq_len_);
424
+ }
425
+
426
+ // save_file(input_ids_doubled, "input_ids2_1.bin");
427
+
428
+ input_ids = input_ids_doubled;
429
+ actual_len = std::min(original_actual_len * 2, max_seq_len_);
430
+ }
431
+ }
432
+
433
+ void Kokoro::_compute_external_preprocessing(const std::vector<int>& input_ids, int actual_len, std::vector<int>& input_lengths, std::vector<uint8_t>& text_mask) {
434
+ // 计算输入预处理:长度和mask
435
+ // input_lengths = np.full((input_ids.shape[0],), actual_len, dtype=np.int64)
436
+ input_lengths = std::vector<int>{actual_len};
437
+ // text_mask = np.arange(self.max_seq_len_)[np.newaxis, :] >= input_lengths[:, np.newaxis]
438
+ text_mask.resize(max_seq_len_);
439
+ for (int i = 0; i < max_seq_len_; i++) {
440
+ text_mask[i] = (i >= actual_len) ? 1 : 0;
441
+ }
442
+ }
443
+
444
+ void Kokoro::_process_duration(const std::vector<float>& duration, int actual_len, float speed, std::vector<int>& pred_dur, int& total_frames) {
445
+ // """处理duration预测,调整到固定帧数"""
446
+ // duration_processed = 1.0 / (1.0 + np.exp(-duration))
447
+ // duration_processed = duration_processed.sum(axis=-1) / speed
448
+ // pred_dur_original = np.round(duration_processed).clip(min=1).astype(np.int64).squeeze()
449
+ std::vector<int> pred_dur_original(actual_len, 0);
450
+ std::vector<float> duration_processed = sigmoid(duration);
451
+ for (int i = 0; i < actual_len; i++) {
452
+ float sum = 0;
453
+
454
+ // duration shape: [1, 96, 50]
455
+ for (int n = 0; n < duration_shape_[2]; n++) {
456
+ sum += duration_processed[i * duration_shape_[2] + n];
457
+ }
458
+ sum /= speed;
459
+
460
+ pred_dur_original[i] = int(std::max(1.f, roundf(sum)));
461
+ }
462
+
463
+ // # 分离实际内容和padding
464
+ // pred_dur_actual = pred_dur_original[:actual_len]
465
+ // pred_dur_padding = np.zeros(self.max_seq_len_ - actual_len, dtype=np.int64)
466
+ // pred_dur = np.concatenate([pred_dur_actual, pred_dur_padding])
467
+ std::vector<int> pred_dur_padding(max_seq_len_ - actual_len, 0);
468
+ pred_dur = pred_dur_original;
469
+ pred_dur.insert(pred_dur.end(), pred_dur_padding.begin(), pred_dur_padding.end());
470
+
471
+
472
+ // # 调整实际内容部分,只处理长度超出情况
473
+ // fixed_total_frames = self.max_seq_len_ * 2
474
+ // diff = fixed_total_frames - pred_dur[:actual_len].sum()
475
+
476
+ // if diff < 0:
477
+ // # 减少帧数
478
+ // indices = np.argsort(pred_dur[:actual_len])[::-1]
479
+ // decreased = 0
480
+ // for idx in indices:
481
+ // if pred_dur[idx] > 1 and decreased < abs(diff):
482
+ // pred_dur[idx] -= 1
483
+ // decreased += 1
484
+ // if decreased >= abs(diff):
485
+ // break
486
+
487
+ // 调整实际内容部分,只处理长度超出情况
488
+ int fixed_total_frames = max_seq_len_ * 2;
489
+ int actual_frames = std::accumulate(pred_dur.begin(), pred_dur.begin() + actual_len, 0);
490
+ int diff = fixed_total_frames - actual_frames;
491
+
492
+ if (diff < 0) {
493
+ // 减少帧数
494
+ auto indices = argsort(pred_dur, actual_len, true);
495
+ int decreased = 0;
496
+ for (auto idx : indices) {
497
+ if (pred_dur[idx] > 1 && decreased < std::abs(diff)) {
498
+ pred_dur[idx]--;
499
+ decreased++;
500
+ }
501
+ if (decreased >= std::abs(diff))
502
+ break;
503
+ }
504
+ }
505
+
506
+ // # 将剩余帧数分配到padding部分
507
+ // remaining_frames = fixed_total_frames - pred_dur[:actual_len].sum()
508
+ // padding_len = self.max_seq_len_ - actual_len
509
+ // if remaining_frames > 0 and padding_len > 0:
510
+ // frames_per_padding = remaining_frames // padding_len
511
+ // remainder = remaining_frames % padding_len
512
+ // pred_dur[actual_len:] = frames_per_padding
513
+ // if remainder > 0:
514
+ // pred_dur[actual_len:actual_len+remainder] += 1
515
+
516
+ actual_frames = std::accumulate(pred_dur.begin(), pred_dur.begin() + actual_len, 0);
517
+ int remaining_frames = fixed_total_frames - actual_frames;
518
+ int padding_len = max_seq_len_ - actual_len;
519
+ // printf("actual_len 4: %d\n ", actual_len);
520
+ // printf("remaining_frames 4: %d\n", remaining_frames);
521
+ // printf("padding_len 4: %d\n", padding_len);
522
+
523
+ if (remaining_frames > 0 && padding_len > 0) {
524
+ int frames_per_padding = remaining_frames / padding_len;
525
+ int remainder = remaining_frames % padding_len;
526
+
527
+ for (int i = actual_len; i < pred_dur.size(); i++)
528
+ pred_dur[i] = frames_per_padding;
529
+
530
+ if (remainder > 0) {
531
+ for (int i = actual_len; i < actual_len + remainder; i++)
532
+ pred_dur[i] += 1;
533
+ }
534
+ }
535
+
536
+ // total_frames = pred_dur.sum()
537
+ total_frames = std::accumulate(pred_dur.begin(), pred_dur.end(), 0);
538
+ // printf("total_frames: %d\n", total_frames);
539
+ }
540
+
541
+ std::vector<float> Kokoro::_create_alignment_matrix(const std::vector<int>& pred_dur, int total_frames) {
542
+ // """创建对齐矩阵"""
543
+ // indices = np.repeat(np.arange(self.max_seq_len_), pred_dur)
544
+ // pred_aln_trg = np.zeros((self.max_seq_len_, total_frames), dtype=np.float32)
545
+ // if len(indices) > 0:
546
+ // pred_aln_trg[indices, np.arange(total_frames)] = 1.0
547
+ // return pred_aln_trg[np.newaxis, ...]
548
+
549
+ std::vector<int> seq_range(max_seq_len_);
550
+ std::iota(seq_range.begin(), seq_range.end(), 0);
551
+ auto indices = np_repeat(seq_range, pred_dur);
552
+
553
+ std::vector<float> pred_aln_trg(max_seq_len_ * total_frames);
554
+ if (!indices.empty()) {
555
+ int col = 0;
556
+ for (auto i : indices) {
557
+ pred_aln_trg[i * total_frames + col] = 1.0f;
558
+ col++;
559
+ }
560
+ }
561
+
562
+ return pred_aln_trg;
563
+ }
564
+
565
+ void Kokoro::_compute_har_onnx(std::vector<float>& F0_pred, std::vector<float>& har) {
566
+ // Querying model inputs is possible but let's just assume one set for this translation or use a check.
567
+ // For brevity, I'll use the older "tokens" set as default or try to match python logic if I can access names.
568
+ int64_t input_shape[] = {F0_pred_shape_[0], F0_pred_shape_[1]};
569
+ std::vector<const char*> input_names = {"F0_pred"};
570
+
571
+ std::vector<Ort::Value> input_tensors;
572
+
573
+ // Create tensors
574
+ auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
575
+
576
+ input_tensors.push_back(Ort::Value::CreateTensor<float>(
577
+ memory_info, F0_pred.data(), F0_pred.size(), input_shape, F0_pred_shape_.size()));
578
+
579
+ // Check model output name usually
580
+ // Or get it from session
581
+ std::vector<const char*> output_names = {"har"};
582
+
583
+ auto output_tensors = model4_.Run(
584
+ Ort::RunOptions{nullptr},
585
+ input_names.data(),
586
+ input_tensors.data(),
587
+ input_tensors.size(),
588
+ output_names.data(),
589
+ output_names.size()
590
+ );
591
+
592
+ auto& output_tensor = output_tensors.front();
593
+
594
+ // 获取输出信息
595
+ auto tensor_info = output_tensor.GetTensorTypeAndShapeInfo();
596
+ size_t element_count = tensor_info.GetElementCount();
597
+ auto output_shape = tensor_info.GetShape();
598
+
599
+ float* output_data = output_tensor.GetTensorMutableData<float>();
600
+
601
+ har.resize(element_count);
602
+ std::memcpy(har.data(), output_data, element_count * sizeof(float));
603
+ }
604
+
605
+ void Kokoro::_postprocess_x_to_audio(std::vector<float>& x, std::vector<float>& audio) {
606
+ // 将频谱转换为音频波形
607
+ // spec_part = x[:, :self.N_FFT//2+1, :]
608
+ // phase_part = x[:, self.N_FFT//2+1:, :]
609
+ int half_n_fft = N_FFT / 2 + 1;
610
+ int num_frames = x_shape_[2];
611
+ std::vector<float> spec_part(half_n_fft * num_frames);
612
+ std::vector<float> phase_part(half_n_fft * num_frames);
613
+ std::vector<float> cos_part(half_n_fft * num_frames);
614
+ spec_part.assign(x.begin(), x.begin() + half_n_fft * num_frames);
615
+ phase_part.assign(x.begin() + half_n_fft * num_frames, x.end());
616
+
617
+ // spec = np.exp(spec_part)
618
+ // phase = np.sin(phase_part)
619
+
620
+ // spec_torch = torch.from_numpy(spec).float()
621
+ // phase_torch = torch.from_numpy(phase).float()
622
+ // cos_part = torch.sqrt(1.0 - phase_torch.pow(2).clamp(0, 1))
623
+
624
+ // real = spec_torch * cos_part
625
+ // imag = spec_torch * phase_torch
626
+ // complex_spec = torch.complex(real, imag)
627
+
628
+ for (int i = 0; i < half_n_fft * num_frames; i++) {
629
+ spec_part[i] = expf(spec_part[i]);
630
+ phase_part[i] = sinf(phase_part[i]);
631
+ cos_part[i] = sqrtf(1.f - std::max(0.f, std::min(powf(phase_part[i], 2), 1.0f)));
632
+ }
633
+
634
+ // save_file(spec_part, "spec.bin");
635
+ // save_file(phase_part, "phase.bin");
636
+ // save_file(cos_part, "cos_part.bin");
637
+
638
+ FFT_RESULT complex_spec(half_n_fft, vector<complex<float>>(num_frames));
639
+ for (int i = 0; i < half_n_fft; i++) {
640
+ for (int n = 0; n < num_frames; n++) {
641
+ float spec = spec_part[i * num_frames + n];
642
+
643
+ float real_part = spec * cos_part[i * num_frames + n];
644
+ float imag_part = spec * phase_part[i * num_frames + n];
645
+
646
+ complex_spec[i][n] = std::complex<float>(real_part, imag_part);
647
+ }
648
+ }
649
+
650
+ // save_fft_result(complex_spec, "complex_spec.bin");
651
+
652
+ // audio = torch.istft(
653
+ // complex_spec, n_fft=self.N_FFT, hop_length=self.HOP_LENGTH,
654
+ // win_length=self.N_FFT, window=torch.hann_window(self.N_FFT),
655
+ // center=True, return_complex=False
656
+ // )
657
+
658
+ audio = librosa::Feature::istft(complex_spec, N_FFT, HOP_LENGTH, "hann", true, "reflect", false);
659
+ }
660
+
661
+ bool Kokoro::inference_single_chunk(
662
+ std::vector<int>& input_ids,
663
+ const std::vector<float>& ref_s,
664
+ int actual_len,
665
+ float speed,
666
+ std::vector<float>& audio,
667
+ int& actual_content_frames,
668
+ int& total_frames
669
+ ) {
670
+ int ret = 0;
671
+ // Prepare inputs
672
+ bool is_doubled = false;
673
+ // printf("actual_len 2: %d\n", actual_len);
674
+
675
+ // save_file(input_ids, "input_ids.bin");
676
+
677
+ _prepare_input_ids(input_ids, actual_len, is_doubled);
678
+
679
+ // save_file(input_ids, "input_ids2.bin");
680
+
681
+ std::vector<int> input_lengths;
682
+ std::vector<uint8_t> text_mask;
683
+ _compute_external_preprocessing(input_ids, actual_len, input_lengths, text_mask);
684
+
685
+ // save_file(input_ids, "input_ids3.bin");
686
+
687
+ // outputs1 = self.session1.run(None, {'input_ids': input_ids.astype(np.int32), 'ref_s': ref_s, 'text_mask': text_mask.astype(np.uint8)})
688
+ std::vector<void*> model1_inputs{(void*)input_ids.data(), (void*)ref_s.data(), (void*)text_mask.data()};
689
+ std::vector<void*> model1_outputs{(void*)duration_.data(), (void*)d_.data()};
690
+
691
+ // printf("run model 1\n");
692
+ model1_.set_inputs(model1_inputs);
693
+ ret = model1_.run();
694
+ if (0 != ret) {
695
+ ALOGE("Run model1 failed! ret=0x%x", ret);
696
+ return false;
697
+ }
698
+ model1_.get_outputs(model1_outputs);
699
+
700
+ // save_file(input_ids, "input_ids.bin");
701
+ // save_file(ref_s, "ref_s.bin");
702
+ // save_file(duration_, "duration.bin");
703
+
704
+ // 处理duration并对齐
705
+ std::vector<int> pred_dur;
706
+ _process_duration(duration_, actual_len, speed, pred_dur, total_frames);
707
+ auto pred_aln_trg = _create_alignment_matrix(pred_dur, total_frames);
708
+
709
+ // save_file(pred_dur, "pred_dur.bin");
710
+ // save_file(pred_aln_trg, "pred_aln_trg.bin");
711
+ // save_file(d_, "d.bin");
712
+
713
+ // Model2: 预测F0和ASR特征
714
+ // d_transposed = np.transpose(d, (0, 2, 1))
715
+ // en = d_transposed @ pred_aln_trg
716
+ DynMat M_d = Eigen::Map<DynMat>(
717
+ d_.data(),
718
+ d_shape_[1], // 96
719
+ d_shape_[2] // 640
720
+ );
721
+
722
+ DynMat M_pred_aln_trg = Eigen::Map<DynMat>(
723
+ pred_aln_trg.data(),
724
+ max_seq_len_, // 96
725
+ total_frames // 192
726
+ );
727
+
728
+ DynMat M_en = M_d.transpose() * M_pred_aln_trg;
729
+ std::vector<float> en(M_en.size());
730
+ std::memcpy(en.data(), M_en.data(), M_en.size() * sizeof(float));
731
+
732
+ // save_file(en, "en.bin");
733
+
734
+ std::vector<float> text_mask_float;
735
+ std::transform(text_mask.begin(), text_mask.end(),
736
+ std::back_inserter(text_mask_float),
737
+ [](uint8_t i) { return static_cast<float>(i); });
738
+
739
+ // outputs2 = self.session2.run(None, {
740
+ // 'en': en.astype(np.float32),
741
+ // 'ref_s': ref_s,
742
+ // 'input_ids': input_ids.astype(np.int32),
743
+ // 'text_mask': text_mask.astype(np.float32),
744
+ // 'pred_aln_trg': pred_aln_trg.astype(np.float32)
745
+ // })
746
+ // F0_pred, N_pred, asr = outputs2
747
+ std::vector<void*> model2_inputs{
748
+ (void*)en.data(),
749
+ (void*)ref_s.data(),
750
+ (void*)input_ids.data(),
751
+ (void*)text_mask_float.data(),
752
+ (void*)pred_aln_trg.data()
753
+ };
754
+ std::vector<void*> model2_outputs{
755
+ (void*)F0_pred_.data(),
756
+ (void*)N_pred_.data(),
757
+ (void*)asr_.data()
758
+ };
759
+
760
+ // printf("run model 2\n");
761
+ // printf("M_en.size(): %d\n", M_en.rows() * M_en.cols());
762
+ // printf("ref_s.size(): %d\n", ref_s.size());
763
+ // printf("input_ids.size(): %d\n", input_ids.size());
764
+ // printf("text_mask_float.size(): %d\n", text_mask_float.size());
765
+ // printf("pred_aln_trg.size(): %d\n", pred_aln_trg.size());
766
+ model2_.set_inputs(model2_inputs);
767
+ ret = model2_.run();
768
+ if (0 != ret) {
769
+ ALOGE("Run model2 failed! ret=0x%x", ret);
770
+ return false;
771
+ }
772
+ model2_.get_outputs(model2_outputs);
773
+
774
+ // save_file(F0_pred_, "F0_pred.bin");
775
+ // save_file(N_pred_, "N_pred.bin");
776
+ // save_file(asr_, "asr.bin");
777
+
778
+ std::vector<float> har;
779
+ _compute_har_onnx(F0_pred_, har);
780
+
781
+ // har = load_file<float>("../har.bin");
782
+ // save_file(har, "har.bin");
783
+
784
+ // outputs3 = self.session3.run(None, {
785
+ // 'asr': asr, 'F0_pred': F0_pred, 'N_pred': N_pred, 'ref_s': ref_s, 'har': har
786
+ // })
787
+ // x = outputs3[0]
788
+ std::vector<void*> model3_inputs{
789
+ (void*)asr_.data(),
790
+ (void*)F0_pred_.data(),
791
+ (void*)N_pred_.data(),
792
+ (void*)ref_s.data(),
793
+ (void*)har.data()
794
+ };
795
+
796
+ // printf("run model 3\n");
797
+ model3_.set_inputs(model3_inputs);
798
+ ret = model3_.run();
799
+ if (0 != ret) {
800
+ ALOGE("Run model3 failed! ret=0x%x", ret);
801
+ return false;
802
+ }
803
+ model3_.get_output(0, x_.data());
804
+
805
+ // save_file(x_, "x.bin");
806
+
807
+ // 转换为音频
808
+ _postprocess_x_to_audio(x_, audio);
809
+ actual_content_frames = std::accumulate(pred_dur.begin(), pred_dur.begin() + actual_len, 0);
810
+
811
+ // 如果输入被复制了,截取前一半音频
812
+ // if is_doubled:
813
+ // audio = audio[:len(audio) // 2]
814
+ // actual_content_frames = actual_content_frames // 2
815
+ // total_frames = total_frames // 2
816
+ if (is_doubled) {
817
+ int audio_len = audio.size();
818
+ audio.erase(audio.begin() + audio_len / 2, audio.end());
819
+ actual_content_frames = actual_content_frames / 2;
820
+ total_frames = total_frames / 2;
821
+ }
822
+
823
+ // save_file(audio, "audio.bin");
824
+
825
+ return true;
826
+ }
827
+
828
+ bool Kokoro::inference(
829
+ std::vector<int>& input_ids,
830
+ const std::vector<float>& ref_s,
831
+ float speed,
832
+ float fade_out_duration,
833
+ std::vector<float>& audio
834
+ ) {
835
+ int actual_len = input_ids.size();
836
+ // 填充到固定长度
837
+ int padding_len = max_seq_len_ - actual_len;
838
+ if (padding_len > 0) {
839
+ std::vector<int> padding(padding_len, 0);
840
+ input_ids.insert(input_ids.end(), padding.begin(), padding.end());
841
+ }
842
+
843
+ int fade_samples = 0;
844
+ if (fade_out_duration > 0) {
845
+ fade_samples = int(SAMPLE_RATE * fade_out_duration);
846
+ }
847
+
848
+ // printf("actual_len 1: %d\n", actual_len);
849
+
850
+ int actual_content_frames;
851
+ int total_frames;
852
+ if (!inference_single_chunk(input_ids, ref_s, actual_len, speed, audio, actual_content_frames, total_frames)) {
853
+ return false;
854
+ }
855
+
856
+ _trim_audio_by_content(
857
+ audio, actual_content_frames, total_frames, actual_len
858
+ );
859
+
860
+ if (fade_samples > 0)
861
+ apply_fade_out(audio, fade_samples);
862
+
863
+ return true;
864
+ }
865
+
866
+ bool Kokoro::run_batch_inference(std::vector<MergedGroup>& merged_group, const std::string& voice_name,
867
+ float speed, float fade_out_duration, int sr,
868
+ std::vector<std::vector<float>>& audio_list
869
+ ) {
870
+ // 重新加载音色
871
+ if (voice_name_ != voice_name) {
872
+ if (!get_voice_style(voices_path_, voice_name)) {
873
+ ALOGW("Load voice %s failed, fallback to original voice %s", voice_name.c_str(), voice_name_.c_str());
874
+ }
875
+ }
876
+
877
+ // 批量推理
878
+ for (auto& group : merged_group) {
879
+ if (group.is_long_split) {
880
+ // 长句分割:对每个子片段推理后拼接
881
+ std::vector<float> combined_audio;
882
+ for (auto& sub : group.sub_results) {
883
+ int phoneme_len = sub.input_ids.size() - 2;
884
+ auto ref_s = load_voice_embedding(phoneme_len);
885
+
886
+ std::vector<float> audio;
887
+ if (!inference(sub.input_ids, ref_s, speed, 0, audio)) {
888
+ return false;
889
+ }
890
+ combined_audio.insert(combined_audio.end(), audio.begin(), audio.end());
891
+ }
892
+ audio_list.push_back(combined_audio);
893
+ } else {
894
+ // 短句或合并句:直接推理
895
+ // DEBUG
896
+ // group.input_ids = load_file<int>("../input_ids.bin");
897
+ // printf("group.input_ids.size() = %d\n", group.input_ids.size());
898
+ int phoneme_len = group.input_ids.size() - 2;
899
+
900
+ auto ref_s = load_voice_embedding(phoneme_len);
901
+ std::vector<float> audio;
902
+ if (!inference(group.input_ids, ref_s, speed, fade_out_duration, audio)) {
903
+ return false;
904
+ }
905
+ audio_list.push_back(audio);
906
+ }
907
+ }
908
+
909
+ return true;
910
+ }
911
+
912
+ void Kokoro::split_input_ids_semantic(std::vector<int>& input_ids, int max_seq_len_, int& actual_len) {
913
+ // input_ids分割
914
+ // content = input_ids[0, 1:-1]
915
+ // chunk_with_special = np.concatenate([[0], content, [0]])
916
+ actual_len = input_ids.size();
917
+
918
+ // 填充到固定长度
919
+ int padding_len = max_seq_len_ - actual_len;
920
+ if (padding_len > 0) {
921
+ std::vector<int> padding(padding_len, 0);
922
+ input_ids.insert(input_ids.end(), padding.begin(), padding.end());
923
+ }
924
+ }
925
+
926
+ void Kokoro::_trim_audio_by_content(std::vector<float>& audio, int actual_content_frames, int total_frames, int actual_len) {
927
+ // 根据实际内容比例裁剪音频
928
+ int padding_len = max_seq_len_ - actual_len;
929
+ if (padding_len > 0) {
930
+ float content_ratio = actual_content_frames * 1.0f / total_frames;
931
+ int audio_len_to_keep = int(audio.size() * content_ratio);
932
+ audio.resize(audio_len_to_keep);
933
+ }
934
+ }
935
+
936
+ void Kokoro::apply_fade_out(std::vector<float>& audio, int fade_samples) {
937
+ // 末尾淡出音频
938
+ if (audio.size() <= fade_samples || fade_samples <= 0)
939
+ return;
940
+
941
+ std::vector<float> fade_out = linspace(1.0f, 0.0f, fade_samples);
942
+ // audio_faded = audio.copy()
943
+ // audio_faded[-fade_samples:] *= fade_out
944
+ // return audio_faded
945
+ for (int i = 0; i < fade_samples; i++) {
946
+ audio[i - fade_samples + audio.size()] *= fade_out[i];
947
+ }
948
+ }
949
+
950
+ bool Kokoro::tts(
951
+ const std::string& text,
952
+ const std::string& lang_code,
953
+ const std::string& voice_name,
954
+ float speed,
955
+ int sample_rate,
956
+ float fade_out,
957
+ float pause_duration,
958
+ std::vector<float>& generated_audio
959
+ ) {
960
+ // merged_groups = process_and_merge_sentences(
961
+ // args.text, args.lang, g2p, g2p_type, vocab, max_merge_len=args.max_len
962
+ // )
963
+ auto merged_groups = process_and_merge_sentences(text, lang_code, max_seq_len_);
964
+
965
+ // save_file(merged_groups[0].input_ids, "input_ids.bin");
966
+
967
+ // audio_list = run_batch_inference(
968
+ // engine, merged_groups, args.voice, vocab,
969
+ // speed=SPEED, fade_out_duration=args.fade_out
970
+ // )
971
+ std::vector<std::vector<float>> audio_list;
972
+ if (!run_batch_inference(merged_groups, voice_name, speed, fade_out, sample_rate, audio_list)) {
973
+ ALOGE("run_batch_inference failed!");
974
+ return false;
975
+ }
976
+
977
+ generated_audio = audio_numpy_concat(audio_list, sample_rate, speed, pause_duration);
978
+
979
+ return true;
980
+ }
981
+
982
+ void Kokoro::generate_input_ids_from_text(const std::string& text, std::vector<int>& input_ids, std::string& phonemes) {
983
+ phonemes = tokenizer_->phonemize(text);
984
+ input_ids = tokenizer_->tokenize(phonemes);
985
+ input_ids.insert(input_ids.begin(), 0);
986
+ input_ids.push_back(0);
987
+ }
988
+
989
+ // 迭代版本(避免递归栈溢出)
990
+ std::vector<SentenceInfo> Kokoro::split_long_sentence(
991
+ const std::string& sentence,
992
+ const std::string& lang_code,
993
+ int max_merge_len
994
+ ) {
995
+ std::vector<SentenceInfo> result;
996
+
997
+ // 使用栈来模拟递归
998
+ struct Task {
999
+ std::string sentence;
1000
+ int depth;
1001
+
1002
+ Task(const std::string& s, int d) : sentence(s), depth(d) {}
1003
+ };
1004
+
1005
+ std::vector<Task> stack;
1006
+ stack.emplace_back(sentence, 0);
1007
+
1008
+ const int MAX_DEPTH = 20;
1009
+
1010
+ while (!stack.empty()) {
1011
+ Task current = std::move(stack.back());
1012
+ stack.pop_back();
1013
+
1014
+ if (current.depth > MAX_DEPTH) {
1015
+ continue; // 跳过超过最大深度的任务
1016
+ }
1017
+
1018
+ try {
1019
+ std::vector<int> input_ids;
1020
+ std::string phonemes;
1021
+
1022
+ generate_input_ids_from_text(current.sentence, input_ids, phonemes);
1023
+
1024
+ // 计算内容长度
1025
+ int content_len = input_ids.size();
1026
+
1027
+ if (content_len <= max_merge_len) {
1028
+ result.emplace_back(current.sentence, input_ids, phonemes, content_len);
1029
+ continue;
1030
+ }
1031
+
1032
+ // 需要分割
1033
+ std::string first_half, second_half;
1034
+
1035
+ if (lang_code == std::string("z") || lang_code == std::string("j")) {
1036
+ // 中文/日文分割
1037
+ size_t mid = current.sentence.length() / 2;
1038
+ first_half = current.sentence.substr(0, mid);
1039
+ second_half = current.sentence.substr(mid);
1040
+ } else {
1041
+ // 英文分割
1042
+ std::istringstream iss(current.sentence);
1043
+ std::vector<std::string> words;
1044
+ std::string word;
1045
+
1046
+ while (iss >> word) {
1047
+ words.push_back(word);
1048
+ }
1049
+
1050
+ if (words.size() > 1) {
1051
+ size_t mid_word = words.size() / 2;
1052
+
1053
+ // 构建前半部分
1054
+ std::ostringstream oss1;
1055
+ for (size_t i = 0; i < mid_word; ++i) {
1056
+ if (i > 0) oss1 << " ";
1057
+ oss1 << words[i];
1058
+ }
1059
+ first_half = oss1.str();
1060
+
1061
+ // 构建后半部分
1062
+ std::ostringstream oss2;
1063
+ for (size_t i = mid_word; i < words.size(); ++i) {
1064
+ if (i > mid_word) oss2 << " ";
1065
+ oss2 << words[i];
1066
+ }
1067
+ second_half = oss2.str();
1068
+ } else {
1069
+ // 只有一个单词,按字符分割
1070
+ size_t mid = current.sentence.length() / 2;
1071
+ first_half = current.sentence.substr(0, mid);
1072
+ second_half = current.sentence.substr(mid);
1073
+ }
1074
+ }
1075
+
1076
+ // 将分割后的任务推入栈中(先处理后半部分,再处理前半部分)
1077
+ stack.emplace_back(second_half, current.depth + 1);
1078
+ stack.emplace_back(first_half, current.depth + 1);
1079
+
1080
+ } catch (...) {
1081
+ // 异常处理
1082
+ continue;
1083
+ }
1084
+ }
1085
+
1086
+ return result;
1087
+ }
1088
+
1089
+ std::vector<MergedGroup> Kokoro::process_and_merge_sentences(
1090
+ const std::string& text,
1091
+ const std::string& lang_code,
1092
+ int max_merge_len
1093
+ ) {
1094
+ // 1. 清理文本
1095
+ std::string cleaned_text = clean_text(text);
1096
+
1097
+ // 2. 分割句子
1098
+ std::vector<std::string> sentences = split_sentence(cleaned_text, lang_code);
1099
+ // printf("sentence num: %d\n", sentences.size());
1100
+
1101
+ // 3. 为每个句子生成 input_ids
1102
+ std::vector<SentenceInfo> sentence_data;
1103
+ sentence_data.reserve(sentences.size());
1104
+
1105
+ for (const auto& sentence : sentences) {
1106
+ try {
1107
+ std::vector<int> input_ids;
1108
+ std::string phonemes;
1109
+
1110
+ // printf("sentence: %s\n", sentence.c_str());
1111
+
1112
+ generate_input_ids_from_text(sentence, input_ids, phonemes);
1113
+
1114
+ int content_len = static_cast<int>(input_ids.size());
1115
+
1116
+ if (content_len <= max_merge_len) {
1117
+ // 短句
1118
+ sentence_data.emplace_back(sentence, input_ids, phonemes, content_len);
1119
+ } else {
1120
+ // 长句,需要分割
1121
+ std::vector<SentenceInfo> sub_results = split_long_sentence(
1122
+ sentence, lang_code, max_merge_len);
1123
+
1124
+ sentence_data.emplace_back(sentence, sub_results);
1125
+ }
1126
+
1127
+ } catch (const std::exception& e) {
1128
+ std::cerr << "错误处理句子 '" << sentence << "': " << e.what() << std::endl;
1129
+ } catch (...) {
1130
+ std::cerr << "未知错误处理句子 '" << sentence << "'" << std::endl;
1131
+ }
1132
+ }
1133
+
1134
+ // 4. 检查是否生成了任何数据
1135
+ if (sentence_data.empty()) {
1136
+ throw std::runtime_error("没有生成任何 input_ids");
1137
+ }
1138
+
1139
+ // 5. 长句保持分割,短句合并
1140
+ std::vector<MergedGroup> merged_groups;
1141
+ merged_groups.reserve(sentence_data.size()); // 预分配
1142
+
1143
+ size_t i = 0;
1144
+ while (i < sentence_data.size()) {
1145
+ const SentenceInfo& current = sentence_data[i];
1146
+
1147
+ if (current.is_long) {
1148
+ // 长句:直接添加分割结果
1149
+ merged_groups.emplace_back(current.sub_results);
1150
+ ++i;
1151
+ } else {
1152
+ // 短句:尝试合并
1153
+ std::vector<std::string> merged_sentences;
1154
+ int total_len = 0;
1155
+ size_t j = i;
1156
+
1157
+ // 尝试合并尽可能多的短句
1158
+ while (j < sentence_data.size() && !sentence_data[j].is_long) {
1159
+ int next_len = sentence_data[j].content_len;
1160
+
1161
+ // 检查是否超过最大长度
1162
+ if (total_len + next_len <= max_merge_len) {
1163
+ merged_sentences.push_back(sentence_data[j].sentence);
1164
+ total_len += next_len;
1165
+ ++j;
1166
+ } else {
1167
+ break;
1168
+ }
1169
+ }
1170
+
1171
+ // 如果第一个句子就超过长度,至少包含它
1172
+ if (j == i) {
1173
+ merged_sentences.push_back(sentence_data[i].sentence);
1174
+ ++j;
1175
+ }
1176
+
1177
+ // 生成合并后的文本
1178
+ std::ostringstream oss;
1179
+ for (size_t k = 0; k < merged_sentences.size(); ++k) {
1180
+ if (k > 0) {
1181
+ oss << " "; // 用空格连接句子
1182
+ }
1183
+ oss << merged_sentences[k];
1184
+ }
1185
+ std::string merged_text = oss.str();
1186
+
1187
+ // 重新生成合并后的 input_ids
1188
+ std::vector<int> merged_input_ids;
1189
+ std::string merged_phonemes;
1190
+
1191
+ generate_input_ids_from_text(merged_text, merged_input_ids, merged_phonemes);
1192
+
1193
+ merged_groups.emplace_back(merged_input_ids, merged_phonemes);
1194
+
1195
+ i = j; // 移动到下一组
1196
+ }
1197
+ }
1198
+
1199
+ return merged_groups;
1200
+ }
1201
+
1202
+ std::vector<float> Kokoro::load_voice_embedding(int phoneme_len) {
1203
+ // if phoneme_len is not None and phoneme_len < pack.shape[0]:
1204
+ // ref_s = pack[phoneme_len:phoneme_len+1]
1205
+ // else:
1206
+ // idx = pack.shape[0] // 2
1207
+ // ref_s = pack[idx:idx+1]
1208
+ std::vector<float> ref_s(STYLE_DIM);
1209
+ if (phoneme_len < voice_pack_size_) {
1210
+ ref_s.assign(voice_tensor_.begin() + phoneme_len * STYLE_DIM, voice_tensor_.begin() + (phoneme_len + 1) * STYLE_DIM);
1211
+ } else {
1212
+ int idx = voice_pack_size_ / 2;
1213
+ ref_s.assign(voice_tensor_.begin() + idx * STYLE_DIM, voice_tensor_.begin() + (idx + 1) * STYLE_DIM);
1214
+ }
1215
+ return ref_s;
1216
+ }
cpp/src/Kokoro.h ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <vector>
5
+ #include <memory>
6
+ #include <map>
7
+ #include <optional>
8
+ #include <iostream>
9
+ #include <algorithm>
10
+ #include <cmath>
11
+ #include "ax_model_runner/ax_model_runner.hpp"
12
+ #include "onnxruntime_cxx_api.h"
13
+
14
+ // Forward declarations or placeholder for dependencies
15
+ class Tokenizer;
16
+ struct KoKoroConfig;
17
+
18
+ // Constants from config
19
+ const int MAX_PHONEME_LENGTH = 510; // max position embedding - 2
20
+ const int SAMPLE_RATE = 24000; // Example value
21
+
22
+ // Preprocess parameters
23
+ const int FIXED_SEQ_LEN = 96;
24
+ const int N_FFT = 20;
25
+ const int HOP_LENGTH = 5;
26
+ const int DOUBLE_INPUT_THRESHOLD = 32; // 输入长度小于此值时复制一倍,适配短文本
27
+ const int STYLE_DIM = 256;
28
+
29
+ const float DEFAULT_SPEED = 1.0;
30
+ const float DEFAULT_FADE_OUT = 0.05;
31
+ const float DEFAULT_PAUSE = 0.05;
32
+
33
+
34
+ // 定义结构体来存储句子信息
35
+ struct SentenceInfo {
36
+ std::string sentence;
37
+ std::vector<int> input_ids;
38
+ std::string phonemes;
39
+ int content_len;
40
+ bool is_long;
41
+ std::vector<SentenceInfo> sub_results; // 长句的分割结果
42
+
43
+ // 构造函数:短句
44
+ SentenceInfo(const std::string& s,
45
+ const std::vector<int>& ids,
46
+ const std::string& ph,
47
+ int len)
48
+ : sentence(s), input_ids(ids), phonemes(ph),
49
+ content_len(len), is_long(false) {}
50
+
51
+ // 构造函数:长句
52
+ SentenceInfo(const std::string& s,
53
+ const std::vector<SentenceInfo>& sub)
54
+ : sentence(s), content_len(0), is_long(true), sub_results(sub) {}
55
+ };
56
+
57
+ // 合并组的结果
58
+ struct MergedGroup {
59
+ bool is_long_split = false;
60
+ std::vector<int> input_ids;
61
+ std::string phonemes;
62
+ std::vector<SentenceInfo> sub_results;
63
+
64
+ // 构造函数:短句合并组
65
+ MergedGroup(const std::vector<int>& ids, const std::string& ph)
66
+ : is_long_split(false), input_ids(ids), phonemes(ph) {}
67
+
68
+ // 构造函数:长句分割组
69
+ MergedGroup(const std::vector<SentenceInfo>& sub)
70
+ : is_long_split(true), sub_results(sub) {}
71
+ };
72
+
73
+
74
+ class Kokoro {
75
+ public:
76
+ Kokoro();
77
+ ~Kokoro();
78
+
79
+ bool init(const std::string& model_path,
80
+ int max_seq_len = FIXED_SEQ_LEN,
81
+ const std::string& voices_path = "./voices",
82
+ const std::string& voice_name = "af_heart",
83
+ const std::string& vocab_path = "dict/vocab.txt");
84
+
85
+ bool get_voice_style(const std::string& voices_path, const std::string& voice_name);
86
+
87
+ bool tts(
88
+ const std::string& text,
89
+ const std::string& lang_code,
90
+ const std::string& voice_name,
91
+ float speed,
92
+ int sample_rate,
93
+ float fade_out,
94
+ float pause_duration,
95
+ std::vector<float>& generated_audio
96
+ );
97
+
98
+ bool inference(
99
+ std::vector<int>& input_ids,
100
+ const std::vector<float>& ref_s,
101
+ float speed,
102
+ float fade_out_duration,
103
+ std::vector<float>& audio
104
+ );
105
+
106
+ bool run_batch_inference(std::vector<MergedGroup>& merged_group, const std::string& voice_name,
107
+ float speed, float fade_out_duration, int sr,
108
+ std::vector<std::vector<float>>& audio_list
109
+ );
110
+
111
+ private:
112
+ // Internal methods
113
+ bool inference_single_chunk(
114
+ std::vector<int>& input_ids,
115
+ const std::vector<float>& ref_s,
116
+ int actual_len,
117
+ float speed,
118
+ std::vector<float>& audio,
119
+ int& actual_content_frames,
120
+ int& total_frames
121
+ );
122
+
123
+ void split_input_ids_semantic(std::vector<int>& input_ids, int fixed_seq_len, int& actual_len);
124
+
125
+ void _trim_audio_by_content(std::vector<float>& audio, int actual_content_frames, int total_frames, int actual_len);
126
+
127
+ void apply_fade_out(std::vector<float>& audio, int fade_samples);
128
+
129
+ std::vector<std::string> _split_phonemes(const std::string& phonemes);
130
+
131
+ void _prepare_input_ids(std::vector<int>& input_ids, int& actual_len, bool& is_doubled);
132
+
133
+ void _compute_external_preprocessing(const std::vector<int>& input_ids, int actual_len, std::vector<int>& input_lengths, std::vector<uint8_t>& text_mask);
134
+
135
+ // 处理duration并对齐
136
+ void _process_duration(const std::vector<float>& duration, int actual_len, float speed, std::vector<int>& pred_dur, int& total_frames);
137
+ std::vector<float> _create_alignment_matrix(const std::vector<int>& pred_dur, int total_frames);
138
+
139
+ void _compute_har_onnx(std::vector<float>& F0_pred, std::vector<float>& har);
140
+
141
+ void _postprocess_x_to_audio(std::vector<float>& x, std::vector<float>& audio);
142
+
143
+ void generate_input_ids_from_text(const std::string& text, std::vector<int>& input_ids, std::string& phonemes);
144
+
145
+ std::vector<SentenceInfo> split_long_sentence(
146
+ const std::string& sentence,
147
+ const std::string& lang_code,
148
+ int max_merge_len = 78
149
+ );
150
+
151
+ std::vector<MergedGroup> process_and_merge_sentences(
152
+ const std::string& text,
153
+ const std::string& lang_code,
154
+ int max_merge_len = 96
155
+ );
156
+
157
+ std::vector<float> load_voice_embedding(int phoneme_len);
158
+
159
+ private:
160
+ int max_seq_len_;
161
+ std::string voices_path_;
162
+ std::string voice_name_;
163
+ int voice_pack_size_;
164
+ std::vector<float> voice_tensor_;
165
+
166
+ AxModelRunner model1_, model2_, model3_;
167
+ Ort::Env env_;
168
+ Ort::Session model4_{nullptr};
169
+ Ort::AllocatorWithDefaultOptions allocator_;
170
+
171
+ // Placeholder for voices data: map from name to vector
172
+ std::map<std::string, std::vector<float>> voices_;
173
+
174
+ std::unique_ptr<Tokenizer> tokenizer_;
175
+
176
+ // model outputs data
177
+ std::vector<float> duration_, d_;
178
+ std::vector<float> F0_pred_, N_pred_, asr_;
179
+ std::vector<float> x_;
180
+
181
+ // model outputs shape
182
+ std::vector<int> duration_shape_, d_shape_;
183
+ std::vector<int> F0_pred_shape_;
184
+ std::vector<int> x_shape_;
185
+ };
cpp/src/PinyinFinder.cpp ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "PinyinFinder.h"
2
+
3
+ #include <regex>
4
+ #include <vector>
5
+ #include <locale>
6
+ #include <limits>
7
+ #include <cstring>
8
+ #include <cstdio>
9
+
10
+ PinyinFinder::PinyinFinder() {}
11
+
12
+ PinyinFinder::~PinyinFinder() {}
13
+
14
+ bool PinyinFinder::init(const std::string& singleCharacterDictPath,
15
+ const std::string& wordsDictPath) {
16
+
17
+ FILE* fp = fopen(singleCharacterDictPath.c_str(), "r");
18
+ if (fp == NULL) {
19
+ LOG_WARNING << "Failed to open file: " << singleCharacterDictPath << std::endl;
20
+ return false;
21
+ }
22
+ char line[4096] = {0};
23
+ int tn = 0;
24
+ while (fgets(line, sizeof(line) - 1, fp)) {
25
+ if (line[0] == '#') continue;
26
+ size_t len = strlen(line);
27
+ while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')) {
28
+ line[--len] = '\0';
29
+ }
30
+ if (len == 0) continue;
31
+
32
+ // Format: U+XXXX: pinyin # comment
33
+ if (strncmp(line, "U+", 2) != 0) continue;
34
+
35
+ char* colon = strchr(line, ':');
36
+ if (!colon) continue;
37
+
38
+ // Parse Hex
39
+ // U+ is at line[0], hex starts at line[2], length is colon - line - 2
40
+ std::string hexStr(line + 2, colon - (line + 2));
41
+ uint32_t unicode = 0;
42
+ try {
43
+ unicode = std::stoul(hexStr, nullptr, 16);
44
+ } catch(...) { continue; }
45
+
46
+ // Parse Pinyin
47
+ char* pinyinStart = colon + 1;
48
+ while (*pinyinStart == ' ' || *pinyinStart == '\t') pinyinStart++;
49
+
50
+ char* hash = strchr(pinyinStart, '#');
51
+ char* pinyinEnd = hash ? hash : (line + len);
52
+ while (pinyinEnd > pinyinStart && (*(pinyinEnd-1) == ' ' || *(pinyinEnd-1) == '\t')) {
53
+ pinyinEnd--;
54
+ }
55
+
56
+ if (pinyinEnd <= pinyinStart) continue;
57
+
58
+ std::string pinyin(pinyinStart, pinyinEnd - pinyinStart);
59
+
60
+ UnicodeStr ustr;
61
+ ustr.append(1, static_cast<UnicodeCharT>(unicode));
62
+
63
+ std::vector<std::string> ss;
64
+ BasicStringUtil::SplitString(pinyin.c_str(), pinyin.size(), ',', &ss);
65
+ if (!ss.empty()) {
66
+ word_pinyin_dict_[ustr] = ss[0];
67
+ }
68
+ tn += 1;
69
+ }
70
+ LOG_INFO << "total pinyin character count: " << tn << std::endl;
71
+
72
+ fclose(fp);
73
+ fp = fopen(wordsDictPath.c_str(), "r");
74
+ if (fp == NULL) {
75
+ LOG_WARNING << "Failed to open file: " << wordsDictPath << std::endl;
76
+ return false;
77
+ }
78
+
79
+ // Format: word: pinyin1 pinyin2
80
+ int pc = 0;
81
+ while (fgets(line, sizeof(line) - 1, fp)) {
82
+ if (line[0] == '#') continue;
83
+
84
+ // Strip comments
85
+ char* comment = strchr(line, '#');
86
+ if (comment) *comment = '\0';
87
+
88
+ size_t len = strlen(line);
89
+ while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')) {
90
+ line[--len] = '\0';
91
+ }
92
+ if (len == 0) continue;
93
+
94
+ char* colon = strchr(line, ':');
95
+ if (!colon) continue;
96
+
97
+ // Parse Word
98
+ char* wordEnd = colon;
99
+ while (wordEnd > line && (*(wordEnd-1) == ' ' || *(wordEnd-1) == '\t')) {
100
+ wordEnd--;
101
+ }
102
+ std::string word(line, wordEnd - line);
103
+ if (word.empty()) continue;
104
+
105
+ // Parse Pinyin
106
+ char* pinyinStart = colon + 1;
107
+ while (*pinyinStart == ' ' || *pinyinStart == '\t') pinyinStart++;
108
+
109
+ char* pinyinEnd = line + len;
110
+ while (pinyinEnd > pinyinStart && (*(pinyinEnd-1) == ' ' || *(pinyinEnd-1) == '\t')) {
111
+ pinyinEnd--;
112
+ }
113
+
114
+ std::string pinyin(pinyinStart, pinyinEnd - pinyinStart);
115
+
116
+ UnicodeStr ustr;
117
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), ustr);
118
+ word_pinyin_dict_[ustr] = pinyin;
119
+
120
+ pc += 1;
121
+ }
122
+ LOG_INFO << "total pinyin phrase count: " << pc << std::endl;
123
+ return true;
124
+ }
125
+
126
+ void PinyinFinder::find_best_pinyin(const std::string& phrasestr, std::vector<std::string>& pinyins) {
127
+ UnicodeStr phrase;
128
+ BasicStringUtil::u8tou16(phrasestr.c_str(), phrasestr.size(), phrase);
129
+ int n = phrase.size();
130
+ if (n == 0) return;
131
+
132
+ std::vector<std::vector<int>> dp(n, std::vector<int>(n, std::numeric_limits<int>::max()));
133
+ std::vector<std::vector<int>> opts(n, std::vector<int>(n, -1));
134
+
135
+ for (int length = 1; length <= n; ++length) {
136
+ for (int i = 0; i <= n - length; ++i) {
137
+ int j = i + length - 1;
138
+ if (length == 1) {
139
+ dp[i][j] = 1;
140
+ opts[i][j] = j;
141
+ } else {
142
+ int maxtry = length;
143
+ if (length > kMaxChars) {
144
+ maxtry = kMaxChars;
145
+ }
146
+ for (int k = maxtry; k >= 1; k--) {
147
+ int to = i + k - 1;
148
+ UnicodeStr sub = phrase.substr(i, k);
149
+
150
+ if (word_pinyin_dict_.find(sub) != word_pinyin_dict_.end()) {
151
+ if (to == j) {
152
+ dp[i][j] = k == 1 ? 1 : 0; // Preference for longer matches
153
+ opts[i][j] = j;
154
+ } else {
155
+ // Cost calculation logic from original code
156
+ // k == 1 means we took a single char
157
+ int cost = (k == 1) ? (dp[to + 1][j] + 1) : dp[to + 1][j];
158
+
159
+ if (dp[i][j] > cost) {
160
+ dp[i][j] = cost;
161
+ opts[i][j] = to;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ // Construct the best pinyin string using dp and opts
171
+ int i = 0;
172
+ int j = n - 1;
173
+ while (i <= j) {
174
+ int opt = opts[i][j];
175
+ if(opt == -1){
176
+ opt = i;
177
+ }
178
+ UnicodeStr sub = phrase.substr(i, opt - i + 1);
179
+ auto it = word_pinyin_dict_.find(sub);
180
+
181
+ if(it == word_pinyin_dict_.end()){
182
+ std::string tstr;
183
+ BasicStringUtil::u16tou8(sub.data(), sub.size(), tstr);
184
+ pinyins.emplace_back(tstr);
185
+ } else {
186
+ std::vector<std::string> tmps;
187
+ BasicStringUtil::SplitString(it->second.c_str(), it->second.size(), ' ', &tmps);
188
+ for(auto& str: tmps){
189
+ pinyins.emplace_back(str);
190
+ }
191
+ }
192
+ i = opt + 1;
193
+ }
194
+ }
cpp/src/PinyinFinder.h ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <vector>
5
+ #include <unordered_map>
6
+ #include "Utils.h"
7
+
8
+ class PinyinFinder {
9
+ public:
10
+ using UnicodeCharT = char16_t;
11
+ using UnicodeStr = std::u16string;
12
+
13
+ PinyinFinder();
14
+ ~PinyinFinder();
15
+
16
+ bool init(const std::string& singleCharacterDictPath, const std::string& wordsDictPath);
17
+
18
+ void find_best_pinyin(const std::string& phrasestr, std::vector<std::string>& pinyins);
19
+
20
+ private:
21
+ std::unordered_map<UnicodeStr, std::string> word_pinyin_dict_;
22
+ static const int kMaxChars = 8; // 最大匹配长度,通常不需要太大
23
+ };
cpp/src/Tokenizer.cpp ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "Tokenizer.h"
2
+ #include "JiebaProcessor.h"
3
+ #include "ZHG2P.h"
4
+ #include <iostream>
5
+ #include <vector>
6
+
7
+ Tokenizer::Tokenizer(const TokenizerConfig& config, const std::map<std::string, int>& vocab)
8
+ : vocab_(vocab) {
9
+
10
+ std::string d = config.dict_dir;
11
+ if (!d.empty() && d.back() != '/') d += "/";
12
+
13
+ std::string jieba_dict = d + config.jieba_dict;
14
+ std::string hmm_model = d + config.hmm_model;
15
+ std::string user_dict = d + config.user_dict;
16
+ std::string idf_path = d + config.idf_path;
17
+ std::string stop_word_path = d + config.stop_word_path;
18
+ std::string pinyin_char = d + config.pinyin_char;
19
+ std::string pinyin_phrase = d + config.pinyin_phrase;
20
+ std::string cmu_dict = d + config.cmu_dict;
21
+
22
+ try {
23
+ processor_ = std::make_shared<JiebaProcessor>(
24
+ jieba_dict, hmm_model, user_dict, idf_path, stop_word_path, pinyin_char, pinyin_phrase
25
+ );
26
+ g2p_ = std::make_unique<ZHG2P>(processor_, "1.1", "<unk>", cmu_dict);
27
+ } catch (const std::exception& e) {
28
+ std::cerr << "Failed to initialize Tokenizer dependencies: " << e.what() << std::endl;
29
+ }
30
+ }
31
+
32
+ Tokenizer::~Tokenizer() = default;
33
+
34
+ static std::vector<std::string> split_utf8(const std::string& str) {
35
+ std::vector<std::string> chars;
36
+ for (size_t i = 0; i < str.length();) {
37
+ unsigned char c = static_cast<unsigned char>(str[i]);
38
+ size_t char_len = 0;
39
+ if (c < 0x80) char_len = 1;
40
+ else if ((c & 0xE0) == 0xC0) char_len = 2;
41
+ else if ((c & 0xF0) == 0xE0) char_len = 3;
42
+ else if ((c & 0xF8) == 0xF0) char_len = 4;
43
+ else char_len = 1;
44
+
45
+ if (i + char_len > str.length()) char_len = str.length() - i;
46
+
47
+ chars.push_back(str.substr(i, char_len));
48
+ i += char_len;
49
+ }
50
+ return chars;
51
+ }
52
+
53
+ std::vector<int> Tokenizer::tokenize(const std::string& phonemes) {
54
+ std::vector<int> tokens;
55
+ std::vector<std::string> chars = split_utf8(phonemes);
56
+
57
+ for (const auto& c : chars) {
58
+ if (vocab_.count(c)) {
59
+ tokens.push_back(vocab_.at(c));
60
+ }
61
+ }
62
+ return tokens;
63
+ }
64
+
65
+ std::string Tokenizer::phonemize(const std::string& text, bool norm) {
66
+ if (!g2p_) return text;
67
+ auto result = (*g2p_)(text);
68
+ return result.first;
69
+ }
cpp/src/Tokenizer.h ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <vector>
4
+ #include <map>
5
+ #include <memory>
6
+
7
+ class ZHG2P;
8
+ class JiebaProcessor;
9
+
10
+ struct TokenizerConfig {
11
+ std::string dict_dir = "dict/";
12
+ std::string jieba_dict = "jieba.dict.utf8";
13
+ std::string hmm_model = "hmm_model.utf8";
14
+ std::string user_dict = "user.dict.utf8";
15
+ std::string idf_path = "idf.utf8";
16
+ std::string stop_word_path = "stop_words.utf8";
17
+ std::string pinyin_char = "pinyin.txt";
18
+ std::string pinyin_phrase = "pinyin_phrase.txt";
19
+ std::string cmu_dict = "cmudict-0.7b/cmudict.dict";
20
+ };
21
+
22
+ class Tokenizer {
23
+ public:
24
+ Tokenizer(const TokenizerConfig& config = {}, const std::map<std::string, int>& vocab = {});
25
+ ~Tokenizer();
26
+
27
+ std::vector<int> tokenize(const std::string& phonemes);
28
+ std::string phonemize(const std::string& text, bool norm = true);
29
+
30
+ private:
31
+ std::map<std::string, int> vocab_;
32
+ std::shared_ptr<JiebaProcessor> processor_;
33
+ std::unique_ptr<ZHG2P> g2p_;
34
+ };
cpp/src/ToneSandhi.cpp ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "ToneSandhi.h"
2
+ #include "Utils.h"
3
+ #include <iostream>
4
+ #include <algorithm>
5
+
6
+ // Helper to check if string ends with tone 4 '4'
7
+ static bool is_tone4(const std::string& final) {
8
+ return !final.empty() && final.back() == '4';
9
+ }
10
+
11
+ // Helper to check if string ends with tone 3 '3'
12
+ static bool is_tone3(const std::string& final) {
13
+ return !final.empty() && final.back() == '3';
14
+ }
15
+
16
+ // Change tone to '2'
17
+ static void to_tone2(std::string& final) {
18
+ if (!final.empty() && isdigit(final.back())) {
19
+ final.back() = '2';
20
+ } else {
21
+ final += '2';
22
+ }
23
+ }
24
+
25
+ // Change tone to '5' (neutral)
26
+ static void to_tone5(std::string& final) {
27
+ if (!final.empty() && isdigit(final.back())) {
28
+ final.back() = '5';
29
+ } else {
30
+ final += '5';
31
+ }
32
+ }
33
+
34
+ ToneSandhi::ToneSandhi() {
35
+ punc = "、:,;。?!“”‘’':,;.?!" ;
36
+
37
+ must_neural_tone_words = {
38
+ "麻烦", "麻利", "鸳鸯", "高粱", "骨头", "骆驼", "马虎", "首饰", "馒头", "馄饨", "风筝",
39
+ "难为", "队伍", "阔气", "闺女", "门道", "锄头", "铺盖", "铃铛", "铁匠", "钥匙", "里脊",
40
+ "里头", "部分", "那么", "道士", "造化", "迷糊", "连累", "这么", "这个", "运气", "过去",
41
+ "软和", "转悠", "踏实", "跳蚤", "跟头", "趔趄", "财主", "豆腐", "讲究", "记性", "记号",
42
+ "认识", "规矩", "见识", "裁缝", "补丁", "衣裳", "衣服", "衙门", "街坊", "行李", "行当",
43
+ "蛤蟆", "蘑菇", "薄荷", "葫芦", "葡萄", "萝卜", "荸荠", "苗条", "苗头", "苍蝇", "芝麻",
44
+ "舒服", "舒坦", "舌头", "自在", "膏药", "脾气", "脑袋", "脊梁", "能耐", "胳膊", "胭脂",
45
+ "胡萝", "胡琴", "胡同", "聪明", "耽误", "耽搁", "耷拉", "耳朵", "老爷", "老实", "老婆",
46
+ "戏弄", "将军", "翻腾", "罗嗦", "罐头", "编辑", "结实", "红火", "累赘", "糨糊", "糊涂",
47
+ "精神", "粮食", "簸箕", "篱笆", "算计", "算盘", "答应", "笤帚", "笑语", "笑话", "窟窿",
48
+ "窝囊", "窗户", "稳当", "稀罕", "称呼", "秧歌", "秀气", "秀才", "福气", "祖宗", "砚台",
49
+ "码头", "石榴", "石头", "石匠", "知识", "眼睛", "眯缝", "眨巴", "眉毛", "相声", "盘算",
50
+ "白净", "痢疾", "痛快", "疟疾", "疙瘩", "疏忽", "畜生", "生意", "甘蔗", "琵琶", "琢磨",
51
+ "琉璃", "玻璃", "玫瑰", "玄乎", "狐狸", "状元", "特务", "牲口", "牙碜", "牌楼", "爽快",
52
+ "爱人", "热闹", "烧饼", "烟筒", "烂糊", "点心", "炊帚", "灯笼", "火候", "漂亮", "滑溜",
53
+ "溜达", "温和", "清楚", "消息", "浪头", "活泼", "比方", "正经", "欺负", "模糊", "槟榔",
54
+ "棺材", "棒槌", "棉花", "核桃", "栅栏", "柴火", "架势", "枕头", "枇杷", "机灵", "本事",
55
+ "木头", "木匠", "朋友", "月饼", "月亮", "暖和", "明白", "时候", "新鲜", "故事", "收拾",
56
+ "收成", "提防", "挖苦", "挑剔", "指甲", "指头", "拾掇", "拳头", "拨弄", "招牌", "招呼",
57
+ "抬举", "护士", "折腾", "扫帚", "打量", "打算", "打扮", "打听", "打发", "扎实", "扁担",
58
+ "戒指", "懒得", "意识", "意思", "悟性", "怪物", "思量", "怎么", "念头", "念叨", "别人",
59
+ "快活", "忙活", "志气", "心思", "得罪", "张罗", "弟兄", "开通", "应酬", "庄稼", "干事",
60
+ "帮手", "帐篷", "希罕", "师父", "师傅", "巴结", "巴掌", "差事", "工夫", "岁数", "屁股",
61
+ "尾巴", "少爷", "小气", "小伙", "将就", "对头", "对付", "寡妇", "家伙", "客气", "实在",
62
+ "官司", "学问", "字号", "嫁妆", "媳妇", "媒人", "婆家", "娘家", "委屈", "姑娘", "姐夫",
63
+ "妯娌", "妥当", "妖精", "奴才", "女婿", "头发", "太阳", "大爷", "大方", "大意", "大夫",
64
+ "多少", "多么", "外甥", "壮实", "地道", "地方", "在乎", "困难", "嘴巴", "嘱咐", "嘟囔",
65
+ "嘀咕", "喜欢", "喇嘛", "喇叭", "商量", "唾沫", "哑巴", "哈欠", "哆嗦", "咳嗽", "和尚",
66
+ "告诉", "告示", "含糊", "吓唬", "后头", "名字", "名堂", "合同", "吆喝", "叫唤", "口袋",
67
+ "厚道", "厉害", "千斤", "包袱", "包涵", "匀称", "勤快", "动静", "动弹", "功夫", "力气",
68
+ "前头", "刺猬", "刺激", "别扭", "利落", "利索", "利害", "分析", "出息", "凑合", "凉快",
69
+ "冷战", "冤枉", "冒失", "养活", "关系", "先生", "兄弟", "便宜", "使唤", "佩服", "作坊",
70
+ "体面", "位置", "似的", "伙计", "休息", "什么", "人家", "亲戚", "亲家", "交情", "云彩",
71
+ "事情", "买卖", "主意", "丫头", "丧气", "两口", "东西", "东家", "世故", "不由", "下水",
72
+ "下巴", "上头", "上司", "丈夫", "丈���", "一辈", "那个", "菩萨", "父亲", "母亲", "咕噜",
73
+ "邋遢", "费用", "冤家", "甜头", "介绍", "荒唐", "大人", "泥鳅", "幸福", "熟悉", "计划",
74
+ "扑腾", "蜡烛", "姥爷", "照顾", "喉咙", "吉他", "弄堂", "蚂蚱", "凤凰", "拖沓", "寒碜",
75
+ "糟蹋", "倒腾", "报复", "逻辑", "盘缠", "喽啰", "牢骚", "咖喱", "扫把", "惦记"
76
+ };
77
+
78
+ must_not_neural_tone_words = {
79
+ "男子", "女子", "分子", "原子", "量子", "莲子", "石子", "瓜子", "电子", "人人", "虎虎",
80
+ "幺幺", "干嘛", "学子", "哈哈", "数数", "袅袅", "局地", "以下", "娃哈哈", "花花草草", "留得",
81
+ "耕地", "想想", "熙熙", "攘攘", "卵子", "死死", "冉冉", "恳恳", "佼佼", "吵吵", "打打",
82
+ "考考", "整整", "莘莘", "落地", "算子", "家家户户", "青青"
83
+ };
84
+ }
85
+
86
+ void ToneSandhi::setPinyinProvider(PinyinProvider provider) {
87
+ pinyin_provider = provider;
88
+ }
89
+
90
+ bool ToneSandhi::_all_tone_three(const std::vector<std::string>& finals) {
91
+ for (const auto& f : finals) {
92
+ if (!is_tone3(f)) return false;
93
+ }
94
+ return true;
95
+ }
96
+
97
+ std::vector<std::string> ToneSandhi::_split_word(const std::string& word) {
98
+ std::u16string u16;
99
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
100
+
101
+ std::vector<std::string> res;
102
+ if (u16.length() <= 1) {
103
+ res.push_back(word);
104
+ return res;
105
+ }
106
+
107
+ size_t split_idx = 1;
108
+ if (u16.length() >= 3) split_idx = 2;
109
+ if (u16.length() >= 4) split_idx = 2;
110
+
111
+ std::u16string s1 = u16.substr(0, split_idx);
112
+ std::u16string s2 = u16.substr(split_idx);
113
+
114
+ std::string u8_1, u8_2;
115
+ BasicStringUtil::u16tou8(s1.data(), s1.size(), u8_1);
116
+ BasicStringUtil::u16tou8(s2.data(), s2.size(), u8_2);
117
+
118
+ res.push_back(u8_1);
119
+ res.push_back(u8_2);
120
+ return res;
121
+ }
122
+
123
+ std::vector<std::string> ToneSandhi::_bu_sandhi(const std::string& word, std::vector<std::string> finals) {
124
+ std::string BU = "不";
125
+ if (word.length() > BU.length() * 2) {
126
+ std::u16string u16;
127
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
128
+ if (u16.length() == 3 && u16[1] == 0x4E0D) { // 不
129
+ if (finals.size() > 1) to_tone5(finals[1]);
130
+ return finals;
131
+ }
132
+ }
133
+
134
+ std::u16string u16;
135
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
136
+ for (size_t i = 0; i < u16.length(); ++i) {
137
+ if (u16[i] == 0x4E0D && i + 1 < u16.length()) {
138
+ if (finals.size() > i + 1 && is_tone4(finals[i+1])) {
139
+ if (finals.size() > i) to_tone2(finals[i]);
140
+ }
141
+ }
142
+ }
143
+ return finals;
144
+ }
145
+
146
+ std::vector<std::string> ToneSandhi::_yi_sandhi(const std::string& word, std::vector<std::string> finals) {
147
+ std::string YI = "一";
148
+ if (word.find(YI) == std::string::npos) return finals;
149
+
150
+ std::u16string u16;
151
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
152
+
153
+ bool all_numeric = true;
154
+ for (auto c : u16) {
155
+ if (c != 0x4E00 && !(c >= '0' && c <= '9') &&
156
+ c != 0x96F6 && c != 0x4E8C && c != 0x4E09 && c != 0x56DB &&
157
+ c != 0x4E94 && c != 0x516D && c != 0x4E03 && c != 0x516B && c != 0x4E5D && c != 0x5341) {
158
+ all_numeric = false;
159
+ break;
160
+ }
161
+ }
162
+
163
+ if (all_numeric) return finals;
164
+
165
+ if (u16.length() == 3 && u16[1] == 0x4E00 && u16[0] == u16[2]) {
166
+ if (finals.size() > 1) to_tone5(finals[1]);
167
+ } else if (word.find("第一") == 0) {
168
+ } else {
169
+ for (size_t i = 0; i < u16.length(); ++i) {
170
+ if (u16[i] == 0x4E00 && i + 1 < u16.length()) {
171
+ if (finals.size() > i + 1) {
172
+ if (is_tone4(finals[i+1]) || finals[i+1].back() == '5') {
173
+ if (finals.size() > i) to_tone2(finals[i]);
174
+ } else {
175
+ std::string& f = finals[i];
176
+ if (isdigit(f.back())) f.back() = '4';
177
+ else f += '4';
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ return finals;
184
+ }
185
+
186
+ std::vector<std::string> ToneSandhi::_neural_sandhi(const std::string& word, const std::string& pos, std::vector<std::string> finals) {
187
+ if (must_not_neural_tone_words.count(word)) return finals;
188
+
189
+ std::u16string u16;
190
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
191
+
192
+ if (u16.length() > 1) {
193
+ for (size_t j = 1; j < u16.length(); ++j) {
194
+ if (u16[j] == u16[j-1]) {
195
+ if (pos.find('n') == 0 || pos.find('v') == 0 || pos.find('a') == 0) {
196
+ if (finals.size() > j) to_tone5(finals[j]);
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ if (u16.length() >= 1) {
203
+ char16_t last = u16.back();
204
+ std::u16string particles = u"吧呢啊呐噻嘛吖嗨呐哦哒滴哩哟喽啰耶喔诶";
205
+ if (particles.find(last) != std::string::npos) {
206
+ if (!finals.empty()) to_tone5(finals.back());
207
+ }
208
+ else if (last == 0x7684 || last == 0x5730 || last == 0x5F97) {
209
+ if (!finals.empty()) to_tone5(finals.back());
210
+ }
211
+ else if (u16.length() == 1 && (last == 0x4E86 || last == 0x7740 || last == 0x8FC7)) {
212
+ if (pos == "ul" || pos == "uz" || pos == "ug")
213
+ if (!finals.empty()) to_tone5(finals.back());
214
+ }
215
+ else if (u16.length() > 1 && (last == 0x4EEC || last == 0x5B50) && (pos == "r" || pos == "n")) {
216
+ if (!finals.empty()) to_tone5(finals.back());
217
+ }
218
+ else if (u16.length() > 1 && (last == 0x4E0A || last == 0x4E0B) && (pos == "s" || pos == "l" || pos == "f")) {
219
+ if (!finals.empty()) to_tone5(finals.back());
220
+ }
221
+ else if (u16.length() > 1 && (last == 0x6765 || last == 0x53BB)) {
222
+ char16_t prev = u16[u16.length()-2];
223
+ std::u16string dirs = u"上下进出回过起开";
224
+ if (dirs.find(prev) != std::string::npos) {
225
+ if (!finals.empty()) to_tone5(finals.back());
226
+ }
227
+ }
228
+ }
229
+
230
+ if (must_neural_tone_words.count(word)) {
231
+ if (!finals.empty()) to_tone5(finals.back());
232
+ }
233
+
234
+ return finals;
235
+ }
236
+
237
+ std::vector<std::string> ToneSandhi::_three_sandhi(const std::string& word, std::vector<std::string> finals) {
238
+ std::u16string u16;
239
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16);
240
+
241
+ if (u16.length() == 2 && _all_tone_three(finals)) {
242
+ if (finals.size() > 0) to_tone2(finals[0]);
243
+ } else if (u16.length() == 3) {
244
+ auto word_list = _split_word(word);
245
+ if (_all_tone_three(finals)) {
246
+ std::u16string p1;
247
+ BasicStringUtil::u8tou16(word_list[0].c_str(), word_list[0].size(), p1);
248
+ if (p1.length() == 2) {
249
+ if (finals.size() > 1) {
250
+ to_tone2(finals[0]);
251
+ to_tone2(finals[1]);
252
+ }
253
+ } else if (p1.length() == 1) {
254
+ if (finals.size() > 1) {
255
+ to_tone2(finals[1]);
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ return finals;
262
+ }
263
+
264
+ std::vector<std::string> ToneSandhi::modified_tone(const std::string& word, const std::string& pos,
265
+ std::vector<std::string> finals) {
266
+ finals = _bu_sandhi(word, finals);
267
+ finals = _yi_sandhi(word, finals);
268
+ finals = _neural_sandhi(word, pos, finals);
269
+ finals = _three_sandhi(word, finals);
270
+ return finals;
271
+ }
272
+
273
+ std::vector<std::pair<std::string, std::string>> ToneSandhi::_merge_bu(const std::vector<std::pair<std::string, std::string>>& seg) {
274
+ std::vector<std::pair<std::string, std::string>> new_seg;
275
+ std::string BU = "不";
276
+
277
+ for (size_t i = 0; i < seg.size(); ++i) {
278
+ std::string word = seg[i].first;
279
+ std::string pos = seg[i].second;
280
+
281
+ if (pos != "x" && pos != "eng") {
282
+ std::string last_word = "";
283
+ if (!new_seg.empty()) last_word = new_seg.back().first;
284
+
285
+ if (last_word == BU) {
286
+ new_seg.back().first += word;
287
+ continue;
288
+ }
289
+ }
290
+ new_seg.push_back({word, pos});
291
+ }
292
+ return new_seg;
293
+ }
294
+
295
+ std::vector<std::pair<std::string, std::string>> ToneSandhi::_merge_yi(const std::vector<std::pair<std::string, std::string>>& seg) {
296
+ return seg;
297
+ }
298
+
299
+ std::vector<std::pair<std::string, std::string>> ToneSandhi::_merge_reduplication(const std::vector<std::pair<std::string, std::string>>& seg) {
300
+ return seg;
301
+ }
302
+
303
+ std::vector<std::pair<std::string, std::string>> ToneSandhi::_merge_er(const std::vector<std::pair<std::string, std::string>>& seg) {
304
+ std::vector<std::pair<std::string, std::string>> new_seg;
305
+ for (size_t i = 0; i < seg.size(); ++i) {
306
+ if (i > 0 && seg[i].first == "儿" && !new_seg.empty() && new_seg.back().second != "x" && new_seg.back().second != "eng") {
307
+ new_seg.back().first += seg[i].first;
308
+ } else {
309
+ new_seg.push_back(seg[i]);
310
+ }
311
+ }
312
+ return new_seg;
313
+ }
314
+
315
+ std::vector<std::pair<std::string, std::string>> ToneSandhi::pre_merge_for_modify(
316
+ const std::vector<std::pair<std::string, std::string>>& seg) {
317
+ auto res = _merge_bu(seg);
318
+ res = _merge_yi(res);
319
+ res = _merge_reduplication(res);
320
+ res = _merge_er(res);
321
+ return res;
322
+ }
cpp/src/ToneSandhi.h ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <vector>
4
+ #include <unordered_set>
5
+ #include <utility>
6
+ #include <memory>
7
+ #include <functional>
8
+
9
+ class ToneSandhi {
10
+ public:
11
+ using PinyinProvider = std::function<std::vector<std::string>(const std::string&)>;
12
+
13
+ ToneSandhi();
14
+
15
+ void setPinyinProvider(PinyinProvider provider);
16
+
17
+ // Core logic: pre-merge
18
+ std::vector<std::pair<std::string, std::string>> pre_merge_for_modify(
19
+ const std::vector<std::pair<std::string, std::string>>& seg);
20
+
21
+ // Core logic: modify tone
22
+ std::vector<std::string> modified_tone(const std::string& word, const std::string& pos,
23
+ std::vector<std::string> finals);
24
+
25
+ private:
26
+ // Internal sandhi rules
27
+ std::vector<std::string> _bu_sandhi(const std::string& word, std::vector<std::string> finals);
28
+ std::vector<std::string> _yi_sandhi(const std::string& word, std::vector<std::string> finals);
29
+ std::vector<std::string> _neural_sandhi(const std::string& word, const std::string& pos, std::vector<std::string> finals);
30
+ std::vector<std::string> _three_sandhi(const std::string& word, std::vector<std::string> finals);
31
+
32
+ // Merge rules
33
+ std::vector<std::pair<std::string, std::string>> _merge_bu(const std::vector<std::pair<std::string, std::string>>& seg);
34
+ std::vector<std::pair<std::string, std::string>> _merge_yi(const std::vector<std::pair<std::string, std::string>>& seg);
35
+ std::vector<std::pair<std::string, std::string>> _merge_reduplication(const std::vector<std::pair<std::string, std::string>>& seg);
36
+ std::vector<std::pair<std::string, std::string>> _merge_er(const std::vector<std::pair<std::string, std::string>>& seg);
37
+
38
+ // Helpers
39
+ bool _all_tone_three(const std::vector<std::string>& finals);
40
+ std::vector<std::string> _split_word(const std::string& word);
41
+
42
+ std::unordered_set<std::string> must_neural_tone_words;
43
+ std::unordered_set<std::string> must_not_neural_tone_words;
44
+ std::string punc;
45
+ PinyinProvider pinyin_provider;
46
+ };
cpp/src/Utils.h ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <vector>
4
+ #include <sstream>
5
+ #include <iostream>
6
+ #include <codecvt>
7
+ #include <locale>
8
+ #include <cassert>
9
+
10
+ // 简单的日志宏替代 absl/log
11
+ #define LOG_INFO std::cout << "[INFO] "
12
+ #define LOG_WARNING std::cerr << "[WARN] "
13
+ #define CHECK(condition) \
14
+ if (!(condition)) { \
15
+ std::cerr << "[FATAL] Check failed: " << #condition << " at " << __FILE__ << ":" << __LINE__ << std::endl; \
16
+ std::terminate(); \
17
+ }
18
+
19
+ namespace BasicStringUtil {
20
+
21
+ // 简单的 UTF-8 <-> UTF-16 转换
22
+ // 注意: std::codecvt_utf8_utf16 在 C++17 被标记为 deprecated,但它是目前最便携的标准方案。
23
+
24
+ inline void u8tou16(const char* src, size_t len, std::u16string& dst) {
25
+ if (len == 0) { dst = u""; return; }
26
+ try {
27
+ std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
28
+ dst = converter.from_bytes(src, src + len);
29
+ } catch (...) {
30
+ dst = u"";
31
+ }
32
+ }
33
+
34
+ inline void u16tou8(const char16_t* src, size_t len, std::string& dst) {
35
+ if (len == 0) { dst = ""; return; }
36
+ try {
37
+ std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
38
+ dst = converter.to_bytes(src, src + len);
39
+ } catch (...) {
40
+ dst = "";
41
+ }
42
+ }
43
+
44
+ inline void SplitString(const std::string& str, char delimiter, std::vector<std::string>* result) {
45
+ std::stringstream ss(str);
46
+ std::string item;
47
+ while (std::getline(ss, item, delimiter)) {
48
+ if (!item.empty()) {
49
+ result->push_back(item);
50
+ }
51
+ }
52
+ }
53
+
54
+ inline void SplitString(const char* str, size_t len, char delimiter, std::vector<std::string>* result) {
55
+ std::string s(str, len);
56
+ SplitString(s, delimiter, result);
57
+ }
58
+
59
+ inline std::string DigitToChinese(char c) {
60
+ switch(c) {
61
+ case '0': return "零";
62
+ case '1': return "一";
63
+ case '2': return "二";
64
+ case '3': return "三";
65
+ case '4': return "四";
66
+ case '5': return "五";
67
+ case '6': return "六";
68
+ case '7': return "七";
69
+ case '8': return "八";
70
+ case '9': return "九";
71
+ default: return "";
72
+ }
73
+ }
74
+
75
+ // 简单的数字转中文逻辑
76
+ // 支持整数和小数
77
+ // 例如: 123 -> 一百二十三, 3.14 -> 三点一四
78
+ // 增强: 支持 IP 地址或版本号 (1.2.3.4) -> 一点二点三点四
79
+ inline std::string NumberToChinese(const std::string& num_str) {
80
+ if (num_str.empty()) return "";
81
+
82
+ // Check for multiple dots -> IP/Version -> Read digits one by one
83
+ int dot_count = 0;
84
+ for (char c : num_str) {
85
+ if (c == '.') dot_count++;
86
+ }
87
+
88
+ if (dot_count > 1) {
89
+ std::string res;
90
+ for (char c : num_str) {
91
+ if (c == '.') {
92
+ res += "点";
93
+ } else if (isdigit(c)) {
94
+ res += DigitToChinese(c);
95
+ } else {
96
+ // Should not happen if regex is correct, but safe fallback
97
+ res += c;
98
+ }
99
+ }
100
+ return res;
101
+ }
102
+
103
+ std::string res;
104
+ size_t start = 0;
105
+ if (num_str[0] == '-') {
106
+ res += "负";
107
+ start = 1;
108
+ } else if (num_str[0] == '+') {
109
+ start = 1;
110
+ }
111
+
112
+ size_t dot_pos = num_str.find('.');
113
+ std::string integer_part = num_str.substr(start, dot_pos - start);
114
+ std::string decimal_part;
115
+ if (dot_pos != std::string::npos) {
116
+ decimal_part = num_str.substr(dot_pos + 1);
117
+ }
118
+
119
+ // 处理整数部分
120
+ if (integer_part.empty()) {
121
+ res += "零";
122
+ } else {
123
+ // 简单的中文数字读法实现
124
+ // 分组:每4位一组 (个, 万, 亿)
125
+ // 由于 C++ 处理 UTF-8 字符串比较麻烦,这里尽量简化逻辑
126
+ // 也可以选择直接按位读(如果是编号),但通常 TTS 需要数值读法。
127
+ // 简单实现:如果太长(>12位),按位读;否则按数值读。
128
+
129
+ if (integer_part.length() > 12) {
130
+ for (char c : integer_part) {
131
+ res += DigitToChinese(c);
132
+ }
133
+ } else {
134
+ // 数值读法
135
+ const char* units[] = {"", "十", "百", "千"};
136
+ const char* big_units[] = {"", "万", "亿", "兆"};
137
+
138
+ // 去除前导零
139
+ size_t first_nonzero = integer_part.find_first_not_of('0');
140
+ if (first_nonzero == std::string::npos) {
141
+ res += "零";
142
+ } else {
143
+ std::string s = integer_part.substr(first_nonzero);
144
+ int len = s.length();
145
+
146
+ // 倒序处理,每4位一组
147
+ int group_count = (len + 3) / 4;
148
+
149
+ bool zero_flag = false; // 前面是否有零需要补
150
+
151
+ for (int i = 0; i < group_count; ++i) {
152
+ int group_idx = group_count - 1 - i; // 当前处理的是第几组(从高位到低位)
153
+ int start_idx = std::max(0, len - (i + 1) * 4);
154
+ int end_idx = len - i * 4;
155
+ std::string group_str = s.substr(start_idx, end_idx - start_idx);
156
+
157
+ std::string group_res;
158
+ bool group_has_value = false;
159
+ bool last_is_zero = false;
160
+
161
+ int g_len = group_str.length();
162
+ for (int j = 0; j < g_len; ++j) {
163
+ char digit = group_str[j];
164
+ int unit_idx = g_len - 1 - j;
165
+
166
+ if (digit == '0') {
167
+ last_is_zero = true;
168
+ } else {
169
+ if (last_is_zero) {
170
+ group_res += "零";
171
+ last_is_zero = false;
172
+ }
173
+ // 处理 "一十" -> "十" 的情况 (仅在首位且值为1且单位为十)
174
+ // 但如果是 "一百一十",中间的 "一" 不能省。
175
+ // 这里的逻辑简化:如果是整个数字的开头,且是十位,且是1,则省去一
176
+ // e.g. 12 -> 十二, 112 -> 一百一十二
177
+ if (digit == '1' && unit_idx == 1 && group_res.empty() && zero_flag == false && i == 0 && g_len == 2) {
178
+ // Don't output "一", just unit
179
+ } else {
180
+ group_res += DigitToChinese(digit);
181
+ }
182
+ group_res += units[unit_idx];
183
+ group_has_value = true;
184
+ }
185
+ }
186
+
187
+ if (group_has_value) {
188
+ if (zero_flag && group_res.find("零") != 0) {
189
+ // 如果前面组有遗留零,或者本组开头不是零(但实际上前面可能有空档),通常由 last_is_zero 控制组内零
190
+ // 跨组零比较复杂。简单策略:如果上一组有值,本组不是从千位开始(即不满4位),补零
191
+ // 这里简化:如果之前有非零组,且当前组不满4位,或者高位是0,需要补零。
192
+ // 为了简化代码,暂不处理复杂的跨组补零,除了最简单的。
193
+ }
194
+ // 实际上跨组零通常在 "1001" -> "一千零一"
195
+ // 如果 s = 10001 (len=5), group 1 = '1', group 0 = '0001'
196
+ // group 1 res = "一", big_unit = "万"
197
+ // group 0: '0'->zero, '0'->zero, '0'->zero, '1'->'一'
198
+ // 应该输出 "一万零一"
199
+ // 我们可以在每组输出前,如果组内开头是0,且前面已经有输出了,加个零
200
+ if (!res.empty() && group_str[0] == '0') {
201
+ // check if we already ended with zero
202
+ // UTF-8 check is hard, just append and fix later or assume
203
+ // simple: append "零"
204
+ if (res.substr(res.length() - 3) != "零")
205
+ res += "零";
206
+ }
207
+
208
+ res += group_res;
209
+ res += big_units[i]; // big_units index is reversed? No, big_units index should be `i` from the end
210
+ // Wait, my `i` loop is from 0 (lowest group) to group_count-1 (highest).
211
+ // Actually I want to process from Highest to Lowest.
212
+ // Let's restart the loop logic above to be clearer.
213
+ }
214
+ }
215
+
216
+ // Re-implementation with correct order: High to Low
217
+ res = ""; // clear and restart
218
+ if (num_str[0] == '-') res += "负";
219
+
220
+ int remaining = len;
221
+ bool need_zero = false;
222
+
223
+ for (int i = 0; i < len; ++i) {
224
+ int digit = s[i] - '0';
225
+ int pos = len - 1 - i; // 10^pos
226
+ int unit_idx = pos % 4;
227
+ int big_unit_idx = pos / 4;
228
+
229
+ if (digit == 0) {
230
+ if (unit_idx == 0 && big_unit_idx > 0 && (need_zero || (i > 0 && (s[i-1]-'0')!=0) )) {
231
+ // End of a big unit group (万, 亿)
232
+ // If the whole group was 0, we don't add unit.
233
+ // But we need to check if this group had any value.
234
+ // Complex. Let's fallback to a simpler recursive or chunk-based approach.
235
+ }
236
+ need_zero = true;
237
+ } else {
238
+ if (need_zero) {
239
+ res += "零";
240
+ need_zero = false;
241
+ }
242
+ // 处理 "一十" -> "十" (仅在值在10-19之间)
243
+ // 10-19: s.length() == 2, i=0, digit=1
244
+ if (digit == 1 && unit_idx == 1 && len == 2 && i == 0) {
245
+ // skip "一"
246
+ } else {
247
+ res += DigitToChinese(s[i]);
248
+ }
249
+ res += units[unit_idx];
250
+ }
251
+
252
+ if (unit_idx == 0 && big_unit_idx > 0) {
253
+ // Check if this 4-digit group had any non-zero
254
+ // Look back up to 4 chars
255
+ bool group_has_value = false;
256
+ int start_chk = std::max(0, i - 3);
257
+ for(int k=start_chk; k<=i; ++k) if(s[k] != '0') group_has_value = true;
258
+
259
+ if (group_has_value) {
260
+ res += big_units[big_unit_idx];
261
+ need_zero = false; // Unit added, reset zero pending?
262
+ // No, "10001" -> One Wan Zero One.
263
+ // If we finish "Wan", next is "0001". next digit is 0, so need_zero becomes true.
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ // 处理小数部分
272
+ if (!decimal_part.empty()) {
273
+ res += "点";
274
+ for (char c : decimal_part) {
275
+ res += DigitToChinese(c);
276
+ }
277
+ }
278
+
279
+ return res;
280
+ }
281
+ }
282
+
283
+ class TextProcessor {
284
+ public:
285
+ virtual ~TextProcessor() = default;
286
+ // Return pairs of (word, pos_tag)
287
+ virtual std::vector<std::pair<std::string, std::string>> cut(const std::string& text) = 0;
288
+ virtual std::vector<std::string> word_to_pinyin(const std::string& word) = 0;
289
+ virtual std::string convert_numbers(const std::string& text) = 0;
290
+ };
cpp/src/ZHFrontend.cpp ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "ZHFrontend.h"
2
+ #include "ZHG2P.h"
3
+ #include <algorithm>
4
+ #include <iostream>
5
+
6
+ ZHFrontend::ZHFrontend(std::shared_ptr<TextProcessor> processor, const std::string& unk)
7
+ : processor(processor), unk(unk) {
8
+
9
+ punc = {";", ":", ",", ".", "!", "?", "—", "…", "\"", "(", ")", "“", "”"};
10
+ must_erhua = {"小院儿", "胡同儿", "范儿", "老汉儿", "撒欢儿", "寻老礼儿", "妥妥儿", "媳妇儿"};
11
+ not_erhua = {
12
+ "虐儿", "为儿", "护儿", "瞒儿", "救儿", "替儿", "有儿", "一儿", "我儿", "俺儿", "妻儿",
13
+ "拐儿", "聋儿", "乞儿", "患儿", "幼儿", "孤儿", "婴儿", "婴幼儿", "连体儿", "脑瘫儿",
14
+ "流浪儿", "体弱儿", "混血儿", "蜜雪儿", "舫儿", "祖儿", "美儿", "应采儿", "可儿", "侄儿",
15
+ "孙儿", "侄孙儿", "女儿", "男儿", "红孩儿", "花儿", "虫儿", "马儿", "鸟儿", "猪儿", "猫儿",
16
+ "狗儿", "少儿"
17
+ };
18
+
19
+ tone_modifier.setPinyinProvider([this](const std::string& word) {
20
+ return this->processor->word_to_pinyin(word);
21
+ });
22
+ }
23
+
24
+ ZHFrontend::InitFinal ZHFrontend::_get_initials_finals(const std::string& word) {
25
+ InitFinal res;
26
+ auto pinyins = processor->word_to_pinyin(word);
27
+
28
+ // Handle '嗯' special case check
29
+ // Check if word contains 嗯 (UTF-8: E5 97 AF)
30
+ // For simplicity, we iterate pinyins. If pinyin is "n" or "ng", we might need check.
31
+ // Pypinyin logic: if word has '嗯', set final to 'n2'.
32
+ // We skip this detailed logic for now.
33
+
34
+ for (const auto& py : pinyins) {
35
+ auto parts = ZHG2P::parse_pinyin(py);
36
+
37
+ std::string final_with_tone = parts.final + std::to_string(parts.tone);
38
+
39
+ // Special handling for zi, ci, si, zhi, chi, shi, ri
40
+ if (parts.final == "i") {
41
+ if (parts.initial == "z" || parts.initial == "c" || parts.initial == "s") {
42
+ final_with_tone = "ii" + std::to_string(parts.tone);
43
+ } else if (parts.initial == "zh" || parts.initial == "ch" || parts.initial == "sh" || parts.initial == "r") {
44
+ final_with_tone = "iii" + std::to_string(parts.tone);
45
+ }
46
+ }
47
+
48
+ res.initials.push_back(parts.initial);
49
+ res.finals.push_back(final_with_tone);
50
+ }
51
+ return res;
52
+ }
53
+
54
+ ZHFrontend::InitFinal ZHFrontend::_merge_erhua(const std::vector<std::string>& initials,
55
+ const std::vector<std::string>& finals,
56
+ const std::string& word, const std::string& pos) {
57
+
58
+ std::vector<std::string> new_initials;
59
+ std::vector<std::string> new_finals = finals;
60
+
61
+ // fix er1 -> er2
62
+ std::string er = "儿";
63
+ // Need to match word char index with final index. Assuming 1-to-1.
64
+ // Since word is UTF-8 string, we need u16 conversion to index it.
65
+ std::u16string u16word;
66
+ BasicStringUtil::u8tou16(word.c_str(), word.size(), u16word);
67
+
68
+ if (u16word.size() != finals.size()) {
69
+ // Mismatch, return as is
70
+ InitFinal res;
71
+ res.initials = initials;
72
+ res.finals = finals;
73
+ return res;
74
+ }
75
+
76
+ for (size_t i = 0; i < new_finals.size(); ++i) {
77
+ if (i == new_finals.size() - 1 && u16word[i] == 0x513F && new_finals[i] == "er1") {
78
+ new_finals[i] = "er2";
79
+ }
80
+ }
81
+
82
+ if (!must_erhua.count(word) && (not_erhua.count(word) || pos == "a" || pos == "j" || pos == "nr")) {
83
+ InitFinal res;
84
+ res.initials = initials;
85
+ res.finals = new_finals;
86
+ return res;
87
+ }
88
+
89
+ std::vector<std::string> merged_initials;
90
+ std::vector<std::string> merged_finals;
91
+
92
+ for (size_t i = 0; i < new_finals.size(); ++i) {
93
+ // er2 or er5
94
+ if (i == new_finals.size() - 1 && u16word[i] == 0x513F && (new_finals[i] == "er2" || new_finals[i] == "er5")
95
+ && !merged_finals.empty()) {
96
+ // Check word[-2:] not in not_erhua. Skipping for simplicity.
97
+
98
+ // Merge: remove last digit of prev final, add 'R', add last digit
99
+ std::string& prev = merged_finals.back();
100
+ if (!prev.empty() && isdigit(prev.back())) {
101
+ char tone = prev.back();
102
+ prev.pop_back();
103
+ prev += "R";
104
+ prev += tone;
105
+ } else {
106
+ prev += "R5"; // Fallback
107
+ }
108
+ } else {
109
+ merged_initials.push_back(initials[i]);
110
+ merged_finals.push_back(new_finals[i]);
111
+ }
112
+ }
113
+
114
+ InitFinal res;
115
+ res.initials = merged_initials;
116
+ res.finals = merged_finals;
117
+ return res;
118
+ }
119
+
120
+ std::vector<MToken> ZHFrontend::operator()(const std::string& text, bool with_erhua) {
121
+ std::vector<MToken> tokens;
122
+
123
+ auto seg_cut = processor->cut(text);
124
+ seg_cut = tone_modifier.pre_merge_for_modify(seg_cut);
125
+
126
+ for (const auto& pair : seg_cut) {
127
+ std::string word = pair.first;
128
+ std::string pos = pair.second;
129
+
130
+ MToken tk;
131
+ tk.text = word;
132
+ tk.tag = pos;
133
+
134
+ if (pos == "x" || pos == "eng") {
135
+ if (pos == "x" && punc.count(word)) {
136
+ tk.phonemes.push_back(word);
137
+ }
138
+ // If eng, we might want to keep it or transcribe it?
139
+ // Python code: if pos in ('x', 'eng'): ...
140
+ // Here we just keep it as text in phonemes if it's punct, else empty?
141
+ // The prompt example showed: [['w', 'o3', ...]]
142
+ // For English, maybe we should keep it as is.
143
+ if (pos == "eng") tk.phonemes.push_back(word);
144
+
145
+ tokens.push_back(tk);
146
+ continue;
147
+ }
148
+
149
+ // g2p
150
+ auto init_finals = _get_initials_finals(word);
151
+
152
+ // tone sandhi
153
+ auto modified_finals = tone_modifier.modified_tone(word, pos, init_finals.finals);
154
+
155
+ // er hua
156
+ if (with_erhua) {
157
+ init_finals = _merge_erhua(init_finals.initials, modified_finals, word, pos);
158
+ } else {
159
+ init_finals.finals = modified_finals;
160
+ }
161
+
162
+ // Zip initials and finals into phonemes
163
+ for (size_t i = 0; i < init_finals.initials.size(); ++i) {
164
+ if (i < init_finals.finals.size()) {
165
+ // We need to handle the mapping back to IPA?
166
+ // The ZHFrontend returns py-like tokens (initial, final_with_tone).
167
+ // ZHG2P logic usually takes pinyin string.
168
+ // Here we return raw components: "z", "hong1".
169
+
170
+ // The prompt says: [['w', 'o3', 'm', 'en2', ' '], ...]
171
+ // So it splits initial and final.
172
+
173
+ if (!init_finals.initials[i].empty()) {
174
+ tk.phonemes.push_back(init_finals.initials[i]);
175
+ }
176
+ tk.phonemes.push_back(init_finals.finals[i]);
177
+ }
178
+ }
179
+ tokens.push_back(tk);
180
+ }
181
+ return tokens;
182
+ }
cpp/src/ZHFrontend.h ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <vector>
4
+ #include <memory>
5
+ #include <unordered_set>
6
+ #include "ToneSandhi.h"
7
+ #include "Utils.h"
8
+
9
+ struct MToken {
10
+ std::string text;
11
+ std::string tag;
12
+ std::string whitespace;
13
+ std::vector<std::string> phonemes;
14
+ };
15
+
16
+ class ZHFrontend {
17
+ public:
18
+ ZHFrontend(std::shared_ptr<TextProcessor> processor, const std::string& unk = "?");
19
+
20
+ std::vector<MToken> operator()(const std::string& text, bool with_erhua = true);
21
+
22
+ private:
23
+ std::string unk;
24
+ std::shared_ptr<TextProcessor> processor;
25
+ ToneSandhi tone_modifier;
26
+
27
+ std::unordered_set<std::string> must_erhua;
28
+ std::unordered_set<std::string> not_erhua;
29
+ std::unordered_set<std::string> punc;
30
+
31
+ struct InitFinal {
32
+ std::vector<std::string> initials;
33
+ std::vector<std::string> finals;
34
+ };
35
+
36
+ InitFinal _get_initials_finals(const std::string& word);
37
+ InitFinal _merge_erhua(const std::vector<std::string>& initials,
38
+ const std::vector<std::string>& finals,
39
+ const std::string& word, const std::string& pos);
40
+ };
cpp/src/ZHG2P.cpp ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "ZHG2P.h"
2
+ #include <iostream>
3
+ #include <regex>
4
+ #include <sstream>
5
+ #include <algorithm>
6
+ #include <unordered_set>
7
+
8
+ // ==========================================
9
+ // Data Tables from transcription.py
10
+ // ==========================================
11
+
12
+ static const std::unordered_map<std::string, std::vector<std::string>> INITIAL_MAPPING = {
13
+ {"b", {"p"}}, {"c", {"ʦʰ"}}, {"ch", {"ʈʂʰ"}}, {"d", {"t"}},
14
+ {"f", {"f"}}, {"g", {"k"}}, {"h", {"x"}}, {"j", {"ʨ"}},
15
+ {"k", {"kʰ"}}, {"l", {"l"}}, {"m", {"m"}}, {"n", {"n"}},
16
+ {"p", {"pʰ"}}, {"q", {"ʨʰ"}}, {"r", {"ɻ"}}, {"s", {"s"}},
17
+ {"sh", {"ʂ"}}, {"t", {"tʰ"}}, {"x", {"ɕ"}}, {"z", {"ʦ"}},
18
+ {"zh", {"ʈʂ"}}
19
+ };
20
+
21
+ static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING = {
22
+ {"a", {"a0"}}, {"ai", {"ai̯0"}}, {"an", {"a0", "n"}}, {"ang", {"a0", "ŋ"}},
23
+ {"ao", {"au̯0"}}, {"e", {"ɤ0"}}, {"ei", {"ei̯0"}}, {"en", {"ə0", "n"}},
24
+ {"eng", {"ə0", "ŋ"}}, {"er", {"ɚ0"}}, {"i", {"i0"}}, {"ia", {"j", "a0"}},
25
+ {"ian", {"j", "ɛ0", "n"}}, {"iang", {"j", "a0", "ŋ"}}, {"iao", {"j", "au̯0"}},
26
+ {"ie", {"j", "e0"}}, {"in", {"i0", "n"}}, {"ing", {"i0", "ŋ"}},
27
+ {"iong", {"j", "ʊ0", "ŋ"}}, {"iou", {"j", "ou̯0"}}, {"ong", {"ʊ0", "ŋ"}},
28
+ {"ou", {"ou̯0"}}, {"o", {"o0"}}, {"u", {"u0"}}, {"ua", {"w", "a0"}},
29
+ {"uai", {"w", "ai̯0"}}, {"uan", {"w", "a0", "n"}}, {"uang", {"w", "a0", "ŋ"}},
30
+ {"ui", {"w", "ei̯0"}}, {"un", {"w", "ə0", "n"}}, {"ueng", {"w", "ə0", "ŋ"}},
31
+ {"uo", {"w", "o0"}}, {"ue", {"ɥ", "e0"}}, {"uen", {"w", "ə0", "n"}}, {"uei", {"w", "ei̯0"}},
32
+ {"ü", {"y0"}}, {"üe", {"ɥ", "e0"}}, {"üan", {"ɥ", "ɛ0", "n"}}, {"ün", {"y0", "n"}},
33
+ {"van", {"ɥ", "ɛ0", "n"}}, {"vn", {"y0", "n"}}, {"ve", {"ɥ", "e0"}}, {"v", {"y0"}},
34
+ // ZHFrontend special finals for apical vowels
35
+ {"ii", {"ɹ̩0"}}, // for z, c, s
36
+ {"iii", {"ɻ̩0"}} // for zh, ch, sh, r
37
+ };
38
+
39
+ static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING_ZH_CH_SH_R = {
40
+ {"i", {"ɻ̩0"}}
41
+ };
42
+
43
+ static const std::unordered_map<std::string, std::vector<std::string>> FINAL_MAPPING_Z_C_S = {
44
+ {"i", {"ɹ̩0"}}
45
+ };
46
+
47
+ static const std::unordered_map<int, std::string> TONE_MAPPING = {
48
+ {1, "˥"}, {2, "˧˥"}, {3, "˧˩˧"}, {4, "˥˩"}, {5, ""}
49
+ };
50
+
51
+ static const std::unordered_map<std::string, std::pair<std::string, int>> TONE_VOWELS = {
52
+ {u8"ā", {u8"a", 1}}, {u8"á", {u8"a", 2}}, {u8"ǎ", {u8"a", 3}}, {u8"à", {u8"a", 4}},
53
+ {u8"ē", {u8"e", 1}}, {u8"é", {u8"e", 2}}, {u8"ě", {u8"e", 3}}, {u8"è", {u8"e", 4}},
54
+ {u8"ī", {u8"i", 1}}, {u8"í", {u8"i", 2}}, {u8"ǐ", {u8"i", 3}}, {u8"ì", {u8"i", 4}},
55
+ {u8"ō", {u8"o", 1}}, {u8"ó", {u8"o", 2}}, {u8"ǒ", {u8"o", 3}}, {u8"ò", {u8"o", 4}},
56
+ {u8"ū", {u8"u", 1}}, {u8"ú", {u8"u", 2}}, {u8"ǔ", {u8"u", 3}}, {u8"ù", {u8"u", 4}},
57
+ {u8"ǖ", {u8"v", 1}}, {u8"ǘ", {u8"v", 2}}, {u8"ǚ", {u8"v", 3}}, {u8"ǜ", {u8"v", 4}},
58
+ {u8"ń", {u8"n", 2}}, {u8"ň", {u8"n", 3}}, {u8"ǹ", {u8"n", 4}},
59
+ {u8"ḿ", {u8"m", 2}}, {u8"m̀", {u8"m", 4}}
60
+ };
61
+
62
+ // ==========================================
63
+ // Utility Functions
64
+ // ==========================================
65
+
66
+ static const std::unordered_map<char, std::string> LETTER_TO_IPA = {
67
+ {'A', "ei̯"}, {'B', "pi"}, {'C', "si"}, {'D', "ti"}, {'E', "i"},
68
+ {'F', "ef"}, {'G', "tʂi"}, {'H', "ei̯tʂ"}, {'I', "ai̯"}, {'J', "tʂei̯"},
69
+ {'K', "kʰei̯"}, {'L', "el"}, {'M', "em"}, {'N', "en"}, {'O', "ou̯"},
70
+ {'P', "pʰi"}, {'Q', "kʰju"}, {'R', "aɻ"}, {'S', "es"}, {'T', "tʰi"},
71
+ {'U', "ju"}, {'V', "vi"}, {'W', "tʌplju"}, {'X', "eks"}, {'Y', "wai̯"},
72
+ {'Z', "zi"},
73
+ {'a', "ei̯"}, {'b', "pi"}, {'c', "si"}, {'d', "ti"}, {'e', "i"},
74
+ {'f', "ef"}, {'g', "tʂi"}, {'h', "ei̯tʂ"}, {'i', "ai̯"}, {'j', "tʂei̯"},
75
+ {'k', "kʰei̯"}, {'l', "el"}, {'m', "em"}, {'n', "en"}, {'o', "ou̯"},
76
+ {'p', "pʰi"}, {'q', "kʰju"}, {'r', "aɻ"}, {'s', "es"}, {'t', "tʰi"},
77
+ {'u', "ju"}, {'v', "vi"}, {'w', "tʌplju"}, {'x', "eks"}, {'y', "wai̯"},
78
+ {'z', "zi"}
79
+ };
80
+
81
+ static std::string replace_all(std::string str, const std::string& from, const std::string& to) {
82
+ if (from.empty()) return str;
83
+ size_t start_pos = 0;
84
+ while((start_pos = str.find(from, start_pos)) != std::string::npos) {
85
+ str.replace(start_pos, from.length(), to);
86
+ start_pos += to.length();
87
+ }
88
+ return str;
89
+ }
90
+
91
+ static std::string join(const std::vector<std::string>& vec, const std::string& delim) {
92
+ std::string res;
93
+ for (size_t i = 0; i < vec.size(); ++i) {
94
+ if (i > 0) res += delim;
95
+ res += vec[i];
96
+ }
97
+ return res;
98
+ }
99
+
100
+ static std::string trim(const std::string& str) {
101
+ size_t first = str.find_first_not_of(" \t\n\r");
102
+ if (std::string::npos == first) return "";
103
+ size_t last = str.find_last_not_of(" \t\n\r");
104
+ return str.substr(first, (last - first + 1));
105
+ }
106
+
107
+ // ==========================================
108
+ // ZHG2P Implementation
109
+ // ==========================================
110
+
111
+ ZHG2P::ZHG2P(std::shared_ptr<TextProcessor> proc, const std::string& ver, const std::string& u, const std::string& eng_dict_path)
112
+ : processor(std::move(proc)), version(ver), unk(u) {
113
+ if (version == "1.1") {
114
+ frontend = std::make_unique<ZHFrontend>(processor, unk);
115
+ }
116
+ if (!eng_dict_path.empty()) {
117
+ std::cout << "Loading English G2P dict from " << eng_dict_path << "..." << std::endl;
118
+ eng_g2p = std::make_unique<EnG2P>(eng_dict_path);
119
+ }
120
+ }
121
+
122
+ std::string ZHG2P::retone(std::string p) {
123
+ p = replace_all(p, "˧˩˧", "↓");
124
+ p = replace_all(p, "˧˥", "↗");
125
+ p = replace_all(p, "˥˩", "↘");
126
+ p = replace_all(p, "˥", "→");
127
+
128
+ // ɨ handling
129
+ p = replace_all(p, "\u027B\u0329", "ɨ");
130
+ p = replace_all(p, "\u0279\u0329", "ɨ");
131
+ p = replace_all(p, "ɻ̩", "ɨ");
132
+ p = replace_all(p, "ɹ̩", "ɨ");
133
+
134
+ return p;
135
+ }
136
+
137
+ ZHG2P::PinyinParts ZHG2P::parse_pinyin(const std::string& raw_pinyin) {
138
+ PinyinParts parts;
139
+ parts.tone = 5;
140
+ std::string pinyin = trim(raw_pinyin);
141
+
142
+ if (pinyin.empty()) return parts;
143
+
144
+ // Normalize pinyin (handle tone marks like ā -> a, tone=1)
145
+ std::string base = "";
146
+ int detected_tone = 5;
147
+
148
+ for (size_t i = 0; i < pinyin.length(); ) {
149
+ bool matched = false;
150
+ // Check against TONE_VOWELS keys
151
+ // Iterate map - not efficient but works given small map and short pinyin string
152
+ for (const auto& kv : TONE_VOWELS) {
153
+ if (pinyin.compare(i, kv.first.length(), kv.first) == 0) {
154
+ if (detected_tone == 5) detected_tone = kv.second.second;
155
+ base += kv.second.first;
156
+ i += kv.first.length();
157
+ matched = true;
158
+ break;
159
+ }
160
+ }
161
+ if (!matched) {
162
+ base += pinyin[i];
163
+ i++;
164
+ }
165
+ }
166
+
167
+ // Check explicitly written number tone (e.g. zhong1) - overrides mark if present (unlikely mixed)
168
+ if (!base.empty()) {
169
+ char last = base.back();
170
+ if (isdigit(static_cast<unsigned char>(last))) {
171
+ parts.tone = last - '0';
172
+ base.pop_back();
173
+ } else {
174
+ parts.tone = detected_tone;
175
+ }
176
+ }
177
+
178
+ base = replace_all(base, "v", "ü");
179
+
180
+ // Handle y and w (standard pinyin normalization)
181
+ if (base.rfind("yi", 0) == 0) {
182
+ base = base.substr(1); // yi -> i
183
+ } else if (base.rfind("y", 0) == 0) {
184
+ if (base.length() > 1 && base[1] == 'u') {
185
+ base = "ü" + base.substr(2); // yu -> ü
186
+ } else {
187
+ base = "i" + base.substr(1); // ya -> ia, you -> iou
188
+ }
189
+ } else if (base.rfind("wu", 0) == 0) {
190
+ base = base.substr(1); // wu -> u
191
+ } else if (base.rfind("w", 0) == 0) {
192
+ base = "u" + base.substr(1); // wa -> ua, wo -> uo, wei -> uei
193
+ }
194
+
195
+ std::string p_initial = "";
196
+
197
+ static const std::vector<std::string> multi_initials = {"zh", "ch", "sh"};
198
+ for (const auto& ini : multi_initials) {
199
+ if (base.rfind(ini, 0) == 0) {
200
+ p_initial = ini;
201
+ break;
202
+ }
203
+ }
204
+ if (p_initial.empty()) {
205
+ std::string first_char = base.substr(0, 1);
206
+ if (INITIAL_MAPPING.count(first_char)) {
207
+ p_initial = first_char;
208
+ }
209
+ }
210
+
211
+ parts.initial = p_initial;
212
+ parts.final = base.substr(p_initial.length());
213
+
214
+ return parts;
215
+ }
216
+
217
+ std::string ZHG2P::pinyin_to_ipa_convert(const std::string& pinyin) {
218
+ auto parts = parse_pinyin(pinyin);
219
+ std::vector<std::string> ipa_segments;
220
+
221
+ // 1. Initial
222
+ if (!parts.initial.empty() && INITIAL_MAPPING.count(parts.initial)) {
223
+ ipa_segments.push_back(INITIAL_MAPPING.at(parts.initial)[0]);
224
+ }
225
+
226
+ // 2. Final
227
+ std::vector<std::string> final_phonemes;
228
+ bool handled = false;
229
+ bool is_erhua = false;
230
+
231
+ if (!parts.final.empty() && parts.final.back() == 'R') {
232
+ is_erhua = true;
233
+ parts.final.pop_back();
234
+ }
235
+
236
+ if ((parts.initial == "zh" || parts.initial == "ch" || parts.initial == "sh" || parts.initial == "r")
237
+ && FINAL_MAPPING_ZH_CH_SH_R.count(parts.final)) {
238
+ final_phonemes = FINAL_MAPPING_ZH_CH_SH_R.at(parts.final);
239
+ handled = true;
240
+ } else if ((parts.initial == "z" || parts.initial == "c" || parts.initial == "s")
241
+ && FINAL_MAPPING_Z_C_S.count(parts.final)) {
242
+ final_phonemes = FINAL_MAPPING_Z_C_S.at(parts.final);
243
+ handled = true;
244
+ }
245
+
246
+ if (!handled && FINAL_MAPPING.count(parts.final)) {
247
+ final_phonemes = FINAL_MAPPING.at(parts.final);
248
+ }
249
+
250
+ if (final_phonemes.empty() && !parts.final.empty()) {
251
+ final_phonemes.push_back(parts.final);
252
+ }
253
+
254
+ // 3. Apply Tone
255
+ std::string tone_mark = (TONE_MAPPING.count(parts.tone)) ? TONE_MAPPING.at(parts.tone) : "";
256
+
257
+ for (const auto& ph : final_phonemes) {
258
+ std::string processed = ph;
259
+ processed = replace_all(processed, "0", tone_mark);
260
+ ipa_segments.push_back(processed);
261
+ }
262
+
263
+ if (is_erhua) {
264
+ ipa_segments.push_back("ɚ"); // or proper IPA for rhoticity
265
+ }
266
+
267
+ return join(ipa_segments, "");
268
+ }
269
+
270
+ std::string ZHG2P::py2ipa(const std::string& py) {
271
+ std::string ipa = pinyin_to_ipa_convert(py);
272
+ return retone(ipa);
273
+ }
274
+
275
+ std::string ZHG2P::map_punctuation(std::string text) {
276
+ // Note: using u8 string literals
277
+ text = replace_all(text, u8"、", ", ");
278
+ text = replace_all(text, u8",", ", ");
279
+ text = replace_all(text, u8"。", ". ");
280
+ text = replace_all(text, u8".", ". ");
281
+ text = replace_all(text, u8"!", "! ");
282
+ text = replace_all(text, u8":", ": ");
283
+ text = replace_all(text, u8";", "; ");
284
+ text = replace_all(text, u8"?", "? ");
285
+ text = replace_all(text, u8"«", u8" “");
286
+ text = replace_all(text, u8"»", u8"” ");
287
+ text = replace_all(text, u8"《", u8" “");
288
+ text = replace_all(text, u8"》", u8"” ");
289
+ text = replace_all(text, u8"「", u8" “");
290
+ text = replace_all(text, u8"」", u8"” ");
291
+ text = replace_all(text, u8"【", u8" “");
292
+ text = replace_all(text, u8"】", u8"” ");
293
+ text = replace_all(text, u8"(", " (");
294
+ text = replace_all(text, u8")", ") ");
295
+
296
+ size_t first = text.find_first_not_of(" \t\n\r");
297
+ if (std::string::npos == first) return text;
298
+ size_t last = text.find_last_not_of(" \t\n\r");
299
+ return text.substr(first, (last - first + 1));
300
+ }
301
+
302
+ bool ZHG2P::is_chinese(const std::string& str) {
303
+ // Simple heuristic check for CJK range in UTF-8
304
+ for (unsigned char c : str) {
305
+ if (c >= 0xE4 && c <= 0xE9) return true;
306
+ }
307
+ return false;
308
+ }
309
+
310
+ std::string ZHG2P::legacy_call(const std::string& text) {
311
+ std::string result = "";
312
+
313
+ auto words = processor->cut(text);
314
+ for (const auto& pair : words) {
315
+ std::string w = pair.first;
316
+ if (is_chinese(w)) {
317
+ auto pinyins = processor->word_to_pinyin(w);
318
+ for (const auto& py : pinyins) {
319
+ result += py2ipa(py);
320
+ }
321
+ // segment = ' '.join(word2ipa(w) for w in words)
322
+ // So YES, space between words.
323
+ result += " ";
324
+ } else {
325
+ result += w;
326
+ }
327
+ }
328
+
329
+ // Trim trailing space if needed
330
+ if (!result.empty() && result.back() == ' ') result.pop_back();
331
+
332
+ // Remove \u032F (815)
333
+ result = replace_all(result, "\u032F", "");
334
+ return result;
335
+ }
336
+
337
+ std::pair<std::string, std::string> ZHG2P::operator()(const std::string& text) {
338
+ if (text.empty()) return {"", ""};
339
+
340
+ std::string processed_text = text;
341
+ if (processor) {
342
+ processed_text = processor->convert_numbers(processed_text);
343
+ }
344
+ processed_text = map_punctuation(processed_text);
345
+
346
+ // Default to legacy_call for now as we don't have the 1.1 frontend ported
347
+ if (frontend) {
348
+ auto tokens = (*frontend)(processed_text);
349
+ std::string result;
350
+ bool last_was_eng = false;
351
+
352
+ for (const auto& tk : tokens) {
353
+ bool is_eng = (tk.tag == "eng");
354
+
355
+ // Logic to add spaces around English words
356
+ // 1. Before English word (if not at start)
357
+ if (is_eng && !result.empty() && result.back() != ' ') {
358
+ result += " ";
359
+ }
360
+ // 2. After English word (if next is not punctuation)
361
+ if (last_was_eng && !is_eng && tk.tag != "x" && !result.empty() && result.back() != ' ') {
362
+ result += " ";
363
+ }
364
+
365
+ if (tk.tag == "x" || tk.tag == "eng") {
366
+ for (const auto& p : tk.phonemes) {
367
+ std::string converted_part;
368
+ if (eng_g2p && tk.tag == "eng") {
369
+ converted_part = eng_g2p->convert(p);
370
+ }
371
+
372
+ // Fallback for English words: letter by letter
373
+ if ((converted_part.empty() || converted_part == p) && tk.tag == "eng") {
374
+ // If conversion failed or returned same (meaning no dict entry found typically),
375
+ // try letter mapping for pure alpha strings
376
+ bool all_alpha = true;
377
+ for(char c : p) {
378
+ if(!isalpha(c)) { all_alpha = false; break; }
379
+ }
380
+
381
+ if (all_alpha) {
382
+ converted_part = "";
383
+ for(char c : p) {
384
+ if(LETTER_TO_IPA.count(c)) {
385
+ converted_part += LETTER_TO_IPA.at(c);
386
+ } else {
387
+ converted_part += c;
388
+ }
389
+ }
390
+ } else {
391
+ converted_part = p; // keep as is if not all alpha
392
+ }
393
+ } else if (converted_part.empty()) {
394
+ converted_part = p;
395
+ }
396
+
397
+ result += converted_part;
398
+ }
399
+ } else {
400
+ // Split phonemes into pinyin syllables based on tone digits
401
+ std::string pinyin_acc;
402
+ for (const auto& p : tk.phonemes) {
403
+ pinyin_acc += p;
404
+ // Check if p contains a digit
405
+ bool has_digit = false;
406
+ for (char c : p) {
407
+ if (isdigit(static_cast<unsigned char>(c))) {
408
+ has_digit = true;
409
+ break;
410
+ }
411
+ }
412
+
413
+ if (has_digit) {
414
+ result += py2ipa(pinyin_acc);
415
+ pinyin_acc.clear();
416
+ }
417
+ }
418
+ // Handle any trailing part
419
+ if (!pinyin_acc.empty()) {
420
+ result += py2ipa(pinyin_acc);
421
+ }
422
+ }
423
+ last_was_eng = is_eng;
424
+ }
425
+ return {result, ""};
426
+ }
427
+ return {legacy_call(processed_text), ""};
428
+ }
cpp/src/ZHG2P.h ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+ #include <string>
3
+ #include <vector>
4
+ #include <unordered_map>
5
+ #include <memory>
6
+ #include <utility>
7
+ #include "Utils.h"
8
+ #include "ZHFrontend.h"
9
+ #include "EnG2P.h"
10
+
11
+ // ZHG2P class definition...
12
+
13
+ class ZHG2P {
14
+ public:
15
+ // 构造函数注入文本处理器依赖
16
+ ZHG2P(std::shared_ptr<TextProcessor> processor, const std::string& version = "1.1", const std::string& unk = "<unk>", const std::string& eng_dict_path = "");
17
+
18
+ // 主调用接口: 返回 (IPA字符串, 附加信息/None)
19
+ std::pair<std::string, std::string> operator()(const std::string& text);
20
+
21
+ // 静态工具方法
22
+ static std::string retone(std::string p);
23
+ static std::string py2ipa(const std::string& py);
24
+ static std::string map_punctuation(std::string text);
25
+
26
+ // 核心逻辑
27
+ std::string legacy_call(const std::string& text);
28
+
29
+ struct PinyinParts {
30
+ std::string initial;
31
+ std::string final;
32
+ int tone;
33
+ };
34
+ static PinyinParts parse_pinyin(const std::string& pinyin);
35
+
36
+ bool is_chinese(const std::string& str);
37
+
38
+ private:
39
+ std::string version;
40
+ std::string unk;
41
+ std::shared_ptr<TextProcessor> processor;
42
+ std::unique_ptr<ZHFrontend> frontend;
43
+ std::unique_ptr<EnG2P> eng_g2p;
44
+
45
+ // IPA 转换相关的内部结构
46
+ static std::string pinyin_to_ipa_convert(const std::string& pinyin);
47
+
48
+ };
cpp/src/ax_model_runner/ax_model_runner.cpp ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**************************************************************************************************
2
+ *
3
+ * Copyright (c) 2019-2025 Axera Semiconductor (Ningbo) Co., Ltd. All Rights Reserved.
4
+ *
5
+ * This source file is the property of Axera Semiconductor (Ningbo) Co., Ltd. and
6
+ * may not be copied or distributed in any isomorphic form without the prior
7
+ * written consent of Axera Semiconductor (Ningbo) Co., Ltd.
8
+ *
9
+ **************************************************************************************************/
10
+
11
+ #include "ax_model_runner/ax_model_runner.hpp"
12
+ #include "utils/logger.hpp"
13
+
14
+ #include <ax_sys_api.h>
15
+
16
+ #include <stdio.h>
17
+ #include <string.h>
18
+ #include <stdlib.h>
19
+
20
+ #include <vector>
21
+ #include <cstdint>
22
+
23
+ #define IO_CMM_ALIGN_SIZE 128
24
+
25
+ AxModelRunner::AxModelRunner():
26
+ m_handle(nullptr),
27
+ m_pIOinfo(nullptr),
28
+ m_input_num(0),
29
+ m_output_num(0),
30
+ m_loaded(false) {
31
+
32
+ memset(&m_io, 0, sizeof(AX_ENGINE_IO_T));
33
+ }
34
+
35
+ AxModelRunner::~AxModelRunner() {
36
+ unload_model();
37
+ }
38
+
39
+ int AxModelRunner::load_model(const char* model_path, bool use_cmm, IO_BUFFER_STRATEGY_T strategy) {
40
+ FILE* fp = fopen(model_path, "rb");
41
+ if (NULL == fp) {
42
+ ALOGE("open model failed! model_path: %s", model_path);
43
+ return -1;
44
+ }
45
+
46
+ fseek(fp, 0, SEEK_END);
47
+ int model_size = (int)ftell(fp);
48
+
49
+ fseek(fp, 0, SEEK_SET);
50
+
51
+ void *model_buffer = nullptr;
52
+ AX_U64 u64ModelBufferPhyAddr = 0;
53
+ AX_VOID* pModelBufferVirAddr = NULL;
54
+ std::vector<char> model_data;
55
+
56
+ if (use_cmm) {
57
+ AX_SYS_MemAlloc(&u64ModelBufferPhyAddr, &pModelBufferVirAddr, model_size, IO_CMM_ALIGN_SIZE, (AX_S8 *)model_path);
58
+ model_buffer = pModelBufferVirAddr;
59
+ } else {
60
+ model_data.resize(model_size);
61
+ model_buffer = model_data.data();
62
+ }
63
+
64
+ fread(model_buffer, sizeof(char), model_size, fp);
65
+ fclose(fp);
66
+
67
+ auto free_model_buffer = [&]() {
68
+ if (use_cmm) {
69
+ if (u64ModelBufferPhyAddr != 0) {
70
+ AX_SYS_MemFree(u64ModelBufferPhyAddr, &pModelBufferVirAddr);
71
+ }
72
+ }
73
+ else {
74
+ std::vector<char>().swap(model_data);
75
+ }
76
+ return;
77
+ };
78
+
79
+ int ret = load_model_from_mem(model_buffer, model_size);
80
+ if (0 != ret) {
81
+ ALOGE("load_model_from_mem failed! ret=0x%x", ret);
82
+ free_model_buffer();
83
+ return ret;
84
+ }
85
+
86
+ m_strategy = strategy;
87
+ ret = _prepare_io();
88
+ if (0 != ret) {
89
+ ALOGE("_prepare_io failed! ret=0x%x", ret);
90
+ free_model_buffer();
91
+ _free_io();
92
+ return ret;
93
+ }
94
+
95
+ m_loaded = (ret == 0);
96
+
97
+ return ret;
98
+ }
99
+
100
+ int AxModelRunner::load_model_from_mem(void* data, int size) {
101
+ int ret = AX_ENGINE_CreateHandle(&m_handle, data, size);
102
+ if (0 != ret) {
103
+ ALOGE("AX_ENGINE_CreateHandle failed! ret=0x%x", ret);
104
+ return ret;
105
+ }
106
+
107
+ ret = AX_ENGINE_CreateContext(m_handle);
108
+ if (0 != ret) {
109
+ ALOGE("AX_ENGINE_CreateContext failed! ret=0x%x", ret);
110
+ return ret;
111
+ }
112
+
113
+ return ret;
114
+ }
115
+
116
+ int AxModelRunner::unload_model(void) {
117
+ int ret = 0;
118
+ if (m_handle != 0) {
119
+ ret = AX_ENGINE_DestroyHandle(m_handle);
120
+ if (0 == ret)
121
+ m_handle = 0;
122
+ }
123
+
124
+ _free_io();
125
+
126
+ return ret;
127
+ }
128
+
129
+ int AxModelRunner::run(void) {
130
+ int ret = AX_ENGINE_RunSync(m_handle, &m_io);
131
+ if (0 != ret) {
132
+ ALOGE("AX_ENGINE_RunSync failed! ret=0x%x", ret);
133
+ return ret;
134
+ }
135
+ return ret;
136
+ }
137
+
138
+ int AxModelRunner::set_input(int index, void* data) {
139
+ if (!m_loaded) {
140
+ ALOGE("model is not loaded!");
141
+ return -1;
142
+ }
143
+
144
+ if (index < 0) index += m_input_num;
145
+ if (index > m_input_num - 1) {
146
+ ALOGE("index(%d) exceed input_num(%d)", index, m_input_num);
147
+ return -1;
148
+ }
149
+
150
+ if (!data) {
151
+ ALOGE("data is null");
152
+ return -1;
153
+ }
154
+
155
+ memcpy(m_io.pInputs[index].pVirAddr, data, m_io.pInputs[index].nSize);
156
+ if (m_strategy == IO_BUFFER_STRATEGY_CACHED)
157
+ _cache_io_flush(m_io.pInputs[index]);
158
+
159
+ return 0;
160
+ }
161
+
162
+ int AxModelRunner::set_inputs(const std::vector<void*>& datas) {
163
+ if (!m_loaded) {
164
+ ALOGE("model is not loaded!");
165
+ return -1;
166
+ }
167
+
168
+ for (int index = 0; index < m_input_num; index++) {
169
+ void* data = datas[index];
170
+ if (!data) {
171
+ ALOGE("index %d data is null", index);
172
+ return -1;
173
+ }
174
+
175
+ memcpy(m_io.pInputs[index].pVirAddr, data, m_io.pInputs[index].nSize);
176
+ if (m_strategy == IO_BUFFER_STRATEGY_CACHED)
177
+ _cache_io_flush(m_io.pInputs[index]);
178
+ }
179
+
180
+ return 0;
181
+ }
182
+
183
+ int AxModelRunner::get_output(int index, void* data) {
184
+ if (!m_loaded) {
185
+ ALOGE("model is not loaded!");
186
+ return -1;
187
+ }
188
+
189
+ if (index < 0) index += m_output_num;
190
+ if (index > m_output_num - 1) {
191
+ ALOGE("index(%d) exceed output_num(%d)", index, m_output_num);
192
+ return -1;
193
+ }
194
+
195
+ if (!data) {
196
+ ALOGE("data is null");
197
+ return -1;
198
+ }
199
+
200
+ if (m_strategy == IO_BUFFER_STRATEGY_CACHED)
201
+ _cache_io_flush(m_io.pOutputs[index]);
202
+
203
+ memcpy(data, m_io.pOutputs[index].pVirAddr, m_io.pOutputs[index].nSize);
204
+
205
+ return 0;
206
+ }
207
+
208
+ int AxModelRunner::get_outputs(const std::vector<void*>& datas) {
209
+ if (!m_loaded) {
210
+ ALOGE("model is not loaded!");
211
+ return -1;
212
+ }
213
+
214
+ for (int index = 0; index < m_output_num; index++) {
215
+ void* data = datas[index];
216
+ if (!data) {
217
+ ALOGE("index %d data is null", index);
218
+ return -1;
219
+ }
220
+
221
+ if (m_strategy == IO_BUFFER_STRATEGY_CACHED)
222
+ _cache_io_flush(m_io.pOutputs[index]);
223
+
224
+ memcpy(data, m_io.pOutputs[index].pVirAddr, m_io.pOutputs[index].nSize);
225
+ }
226
+
227
+ return 0;
228
+ }
229
+
230
+ void* AxModelRunner::get_input_ptr(int index) {
231
+ if (!m_loaded) {
232
+ ALOGE("model is not loaded!");
233
+ return nullptr;
234
+ }
235
+
236
+ if (index < 0) index += m_input_num;
237
+ if (index > m_input_num - 1) {
238
+ ALOGE("index(%d) exceed input_num(%d)", index, m_input_num);
239
+ return nullptr;
240
+ }
241
+
242
+ return m_io.pInputs[index].pVirAddr;
243
+ }
244
+
245
+ void* AxModelRunner::get_output_ptr(int index) {
246
+ if (!m_loaded) {
247
+ ALOGE("model is not loaded!");
248
+ return nullptr;
249
+ }
250
+
251
+ if (index < 0) index += m_output_num;
252
+ if (index > m_output_num - 1) {
253
+ ALOGE("index(%d) exceed output_num(%d)", index, m_output_num);
254
+ return nullptr;
255
+ }
256
+
257
+ return m_io.pOutputs[index].pVirAddr;
258
+ }
259
+
260
+ const char* AxModelRunner::get_input_name(int index) {
261
+ if (!m_loaded) {
262
+ ALOGE("model is not loaded!");
263
+ return nullptr;
264
+ }
265
+
266
+ if (index < 0) index += m_input_num;
267
+ if (index > m_input_num - 1) {
268
+ ALOGE("index(%d) exceed input_num(%d)", index, m_input_num);
269
+ return nullptr;
270
+ }
271
+
272
+ return m_input_names[index].c_str();
273
+ }
274
+
275
+ const char* AxModelRunner::get_output_name(int index) {
276
+ if (!m_loaded) {
277
+ ALOGE("model is not loaded!");
278
+ return nullptr;
279
+ }
280
+
281
+ if (index < 0) index += m_output_num;
282
+ if (index > m_output_num - 1) {
283
+ ALOGE("index(%d) exceed output_num(%d)", index, m_output_num);
284
+ return nullptr;
285
+ }
286
+
287
+ return m_output_names[index].c_str();
288
+ }
289
+
290
+ int AxModelRunner::get_input_size(int index) {
291
+ if (!m_loaded) {
292
+ ALOGE("model is not loaded!");
293
+ return 0;
294
+ }
295
+
296
+ if (index < 0) index += m_input_num;
297
+ if (index > m_input_num - 1) {
298
+ ALOGE("index(%d) exceed input_num(%d)", index, m_input_num);
299
+ return 0;
300
+ }
301
+
302
+ return m_pIOinfo->pInputs[index].nSize;
303
+ }
304
+
305
+ int AxModelRunner::get_output_size(int index) {
306
+ if (!m_loaded) {
307
+ ALOGE("model is not loaded!");
308
+ return 0;
309
+ }
310
+
311
+ if (index < 0) index += m_output_num;
312
+ if (index > m_output_num - 1) {
313
+ ALOGE("index(%d) exceed output_num(%d)", index, m_output_num);
314
+ return 0;
315
+ }
316
+
317
+ return m_pIOinfo->pOutputs[index].nSize;
318
+ }
319
+
320
+ std::vector<int> AxModelRunner::get_input_shape(int index) {
321
+ std::vector<int> shape;
322
+ if (!m_loaded) {
323
+ ALOGE("model is not loaded!");
324
+ return shape;
325
+ }
326
+
327
+ if (index < 0) index += m_input_num;
328
+ if (index > m_input_num - 1) {
329
+ ALOGE("index(%d) exceed input_num(%d)", index, m_input_num);
330
+ return shape;
331
+ }
332
+
333
+ shape.resize(m_pIOinfo->pInputs[index].nShapeSize);
334
+ for (int i = 0; i < shape.size(); i++) {
335
+ shape[i] = m_pIOinfo->pInputs[index].pShape[i];
336
+ }
337
+ return shape;
338
+ }
339
+
340
+ std::vector<int> AxModelRunner::get_output_shape(int index) {
341
+ std::vector<int> shape;
342
+ if (!m_loaded) {
343
+ ALOGE("model is not loaded!");
344
+ return shape;
345
+ }
346
+
347
+ if (index < 0) index += m_output_num;
348
+ if (index > m_output_num - 1) {
349
+ ALOGE("index(%d) exceed output_num(%d)", index, m_output_num);
350
+ return shape;
351
+ }
352
+
353
+ shape.resize(m_pIOinfo->pOutputs[index].nShapeSize);
354
+ for (int i = 0; i < shape.size(); i++) {
355
+ shape[i] = m_pIOinfo->pOutputs[index].pShape[i];
356
+ }
357
+ return shape;
358
+ }
359
+
360
+ // ================ PRIVATE ================
361
+ int AxModelRunner::_prepare_io() {
362
+ int ret = AX_ENGINE_GetIOInfo(m_handle, &m_pIOinfo);
363
+ if (0 != ret) {
364
+ ALOGE("AX_ENGINE_GetIOInfo failed! ret=0x%x", ret);
365
+ return ret;
366
+ }
367
+
368
+ m_input_num = m_pIOinfo->nInputSize;
369
+ m_output_num = m_pIOinfo->nOutputSize;
370
+
371
+ m_io.nInputSize = m_pIOinfo->nInputSize;
372
+ m_io.nOutputSize = m_pIOinfo->nOutputSize;
373
+
374
+ m_io.pInputs = new AX_ENGINE_IO_BUFFER_T[m_pIOinfo->nInputSize];
375
+ m_io.pOutputs = new AX_ENGINE_IO_BUFFER_T[m_pIOinfo->nOutputSize];
376
+
377
+ for (int i = 0; i < m_pIOinfo->nInputSize; i++) {
378
+ const char* layer_name = m_pIOinfo->pInputs[i].pName;
379
+ m_input_names.push_back(std::string(layer_name));
380
+
381
+ ret = _alloc_io_buffer(m_io.pInputs[i], m_pIOinfo->pInputs[i], m_strategy);
382
+ if (0 != ret) {
383
+ ALOGE("_alloc_io_buffer for input[%d] failed! ret=0x%x", i, ret);
384
+ return ret;
385
+ }
386
+ }
387
+
388
+ for (int i = 0; i < m_pIOinfo->nOutputSize; i++) {
389
+ const char* layer_name = m_pIOinfo->pOutputs[i].pName;
390
+ m_output_names.push_back(std::string(layer_name));
391
+
392
+ ret = _alloc_io_buffer(m_io.pOutputs[i], m_pIOinfo->pOutputs[i], m_strategy);
393
+ if (0 != ret) {
394
+ ALOGE("_alloc_io_buffer for output[%d] failed! ret=0x%x", i, ret);
395
+ return ret;
396
+ }
397
+ }
398
+
399
+ return ret;
400
+ }
401
+
402
+ void AxModelRunner::_free_io() {
403
+ for (size_t i = 0; i < m_io.nInputSize; i++) {
404
+ if (0 != m_io.pInputs[i].phyAddr)
405
+ AX_SYS_MemFree(m_io.pInputs[i].phyAddr, m_io.pInputs[i].pVirAddr);
406
+ }
407
+
408
+ for (size_t i = 0; i < m_io.nOutputSize; i++) {
409
+ if (0 != m_io.pOutputs[i].phyAddr)
410
+ AX_SYS_MemFree(m_io.pOutputs[i].phyAddr, m_io.pOutputs[i].pVirAddr);
411
+ }
412
+
413
+ delete[] m_io.pInputs;
414
+ delete[] m_io.pOutputs;
415
+ memset(&m_io, 0, sizeof(AX_ENGINE_IO_T));
416
+ }
417
+
418
+ int AxModelRunner::_alloc_io_buffer(AX_ENGINE_IO_BUFFER_T& buffer,
419
+ const AX_ENGINE_IOMETA_T &meta, IO_BUFFER_STRATEGY_T strategy) {
420
+ int ret = 0;
421
+
422
+ memset(&buffer, 0, sizeof(AX_ENGINE_IO_BUFFER_T));
423
+ buffer.nSize = meta.nSize;
424
+
425
+ if (IO_BUFFER_STRATEGY_DEFAULT == strategy) {
426
+ AX_SYS_MemAlloc((AX_U64*)&buffer.phyAddr,
427
+ (AX_VOID**)&buffer.pVirAddr,
428
+ meta.nSize, IO_CMM_ALIGN_SIZE, (const AX_S8*)meta.pName);
429
+ } else {
430
+ AX_SYS_MemAllocCached((AX_U64*)&buffer.phyAddr,
431
+ (AX_VOID**)&buffer.pVirAddr,
432
+ meta.nSize, IO_CMM_ALIGN_SIZE, (const AX_S8*)meta.pName);
433
+ }
434
+
435
+ return ret;
436
+ }
437
+
438
+ void AxModelRunner::_cache_io_flush(AX_ENGINE_IO_BUFFER_T &buffer) {
439
+ if (buffer.phyAddr != 0) {
440
+ AX_SYS_MflushCache(buffer.phyAddr, buffer.pVirAddr, buffer.nSize);
441
+ }
442
+ }
cpp/src/ax_model_runner/ax_model_runner.hpp ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**************************************************************************************************
2
+ *
3
+ * Copyright (c) 2019-2025 Axera Semiconductor (Ningbo) Co., Ltd. All Rights Reserved.
4
+ *
5
+ * This source file is the property of Axera Semiconductor (Ningbo) Co., Ltd. and
6
+ * may not be copied or distributed in any isomorphic form without the prior
7
+ * written consent of Axera Semiconductor (Ningbo) Co., Ltd.
8
+ *
9
+ **************************************************************************************************/
10
+
11
+ #pragma once
12
+
13
+ #include "ax_engine_api.h"
14
+
15
+ #include <vector>
16
+ #include <string>
17
+
18
+ typedef enum _IO_BUFFER_STRATEGY_T {
19
+ IO_BUFFER_STRATEGY_DEFAULT = 0,
20
+ IO_BUFFER_STRATEGY_CACHED
21
+ } IO_BUFFER_STRATEGY_T;
22
+
23
+ class AxModelRunner {
24
+ public:
25
+ AxModelRunner();
26
+
27
+ ~AxModelRunner();
28
+
29
+ int load_model(const char* model_path, bool use_cmm = true, IO_BUFFER_STRATEGY_T strategy = IO_BUFFER_STRATEGY_CACHED);
30
+
31
+ int load_model_from_mem(void* data, int size);
32
+
33
+ int unload_model(void);
34
+
35
+ int run(void);
36
+
37
+ int set_input(int index, void* data);
38
+ int set_inputs(const std::vector<void*>& datas);
39
+
40
+ int get_output(int index, void* data);
41
+ int get_outputs(const std::vector<void*>& datas);
42
+
43
+ inline int get_input_num(void) {
44
+ return m_input_num;
45
+ }
46
+
47
+ inline int get_output_num(void) {
48
+ return m_output_num;
49
+ }
50
+
51
+ void* get_input_ptr(int index);
52
+
53
+ void* get_output_ptr(int index);
54
+
55
+ const char* get_input_name(int index);
56
+
57
+ const char* get_output_name(int index);
58
+
59
+ int get_input_size(int index);
60
+
61
+ int get_output_size(int index);
62
+
63
+ std::vector<int> get_input_shape(int index);
64
+
65
+ std::vector<int> get_output_shape(int index);
66
+
67
+ private:
68
+ int _prepare_io();
69
+ void _free_io();
70
+ int _alloc_io_buffer(AX_ENGINE_IO_BUFFER_T &buffer,
71
+ const AX_ENGINE_IOMETA_T &meta, IO_BUFFER_STRATEGY_T strategy);
72
+ void _cache_io_flush(AX_ENGINE_IO_BUFFER_T &buffer);
73
+
74
+ private:
75
+ AX_ENGINE_HANDLE m_handle;
76
+ AX_ENGINE_IO_T m_io;
77
+ AX_ENGINE_IO_INFO_T* m_pIOinfo;
78
+ int m_input_num;
79
+ int m_output_num;
80
+ IO_BUFFER_STRATEGY_T m_strategy;
81
+ std::vector<std::string> m_input_names;
82
+ std::vector<std::string> m_output_names;
83
+ bool m_loaded;
84
+ };
cpp/src/cppjieba/DictTrie.hpp ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef CPPJIEBA_DICT_TRIE_HPP
2
+ #define CPPJIEBA_DICT_TRIE_HPP
3
+
4
+ #include <algorithm>
5
+ #include <fstream>
6
+ #include <cstring>
7
+ #include <cstdlib>
8
+ #include <cmath>
9
+ #include <deque>
10
+ #include <set>
11
+ #include <string>
12
+ #include <unordered_set>
13
+ #include "limonp/StringUtil.hpp"
14
+ #include "limonp/Logging.hpp"
15
+ #include "Unicode.hpp"
16
+ #include "Trie.hpp"
17
+
18
+ namespace cppjieba {
19
+
20
+ const double MIN_DOUBLE = -3.14e+100;
21
+ const double MAX_DOUBLE = 3.14e+100;
22
+ const size_t DICT_COLUMN_NUM = 3;
23
+ const char* const UNKNOWN_TAG = "";
24
+
25
+ class DictTrie {
26
+ public:
27
+ enum UserWordWeightOption {
28
+ WordWeightMin,
29
+ WordWeightMedian,
30
+ WordWeightMax,
31
+ }; // enum UserWordWeightOption
32
+
33
+ DictTrie(const std::string& dict_path, const std::string& user_dict_paths = "", UserWordWeightOption user_word_weight_opt = WordWeightMedian) {
34
+ Init(dict_path, user_dict_paths, user_word_weight_opt);
35
+ }
36
+
37
+ ~DictTrie() {
38
+ delete trie_;
39
+ }
40
+
41
+ bool InsertUserWord(const std::string& word, const std::string& tag = UNKNOWN_TAG) {
42
+ DictUnit node_info;
43
+ if (!MakeNodeInfo(node_info, word, user_word_default_weight_, tag)) {
44
+ return false;
45
+ }
46
+ active_node_infos_.push_back(node_info);
47
+ trie_->InsertNode(node_info.word, &active_node_infos_.back());
48
+ return true;
49
+ }
50
+
51
+ bool InsertUserWord(const std::string& word,int freq, const std::string& tag = UNKNOWN_TAG) {
52
+ DictUnit node_info;
53
+ double weight = freq ? log(1.0 * freq / freq_sum_) : user_word_default_weight_ ;
54
+ if (!MakeNodeInfo(node_info, word, weight , tag)) {
55
+ return false;
56
+ }
57
+ active_node_infos_.push_back(node_info);
58
+ trie_->InsertNode(node_info.word, &active_node_infos_.back());
59
+ return true;
60
+ }
61
+
62
+ bool DeleteUserWord(const std::string& word, const std::string& tag = UNKNOWN_TAG) {
63
+ DictUnit node_info;
64
+ if (!MakeNodeInfo(node_info, word, user_word_default_weight_, tag)) {
65
+ return false;
66
+ }
67
+ trie_->DeleteNode(node_info.word, &node_info);
68
+ return true;
69
+ }
70
+
71
+ const DictUnit* Find(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end) const {
72
+ return trie_->Find(begin, end);
73
+ }
74
+
75
+ void Find(RuneStrArray::const_iterator begin,
76
+ RuneStrArray::const_iterator end,
77
+ std::vector<struct Dag>&res,
78
+ size_t max_word_len = MAX_WORD_LENGTH) const {
79
+ trie_->Find(begin, end, res, max_word_len);
80
+ }
81
+
82
+ bool Find(const std::string& word)
83
+ {
84
+ const DictUnit *tmp = NULL;
85
+ RuneStrArray runes;
86
+ if (!DecodeUTF8RunesInString(word, runes))
87
+ {
88
+ XLOG(ERROR) << "Decode failed.";
89
+ }
90
+ tmp = Find(runes.begin(), runes.end());
91
+ if (tmp == NULL)
92
+ {
93
+ return false;
94
+ }
95
+ else
96
+ {
97
+ return true;
98
+ }
99
+ }
100
+
101
+ bool IsUserDictSingleChineseWord(const Rune& word) const {
102
+ return IsIn(user_dict_single_chinese_word_, word);
103
+ }
104
+
105
+ double GetMinWeight() const {
106
+ return min_weight_;
107
+ }
108
+
109
+ void InserUserDictNode(const std::string& line) {
110
+ std::vector<std::string> buf;
111
+ DictUnit node_info;
112
+ limonp::Split(line, buf, " ");
113
+ if(buf.size() == 1){
114
+ MakeNodeInfo(node_info,
115
+ buf[0],
116
+ user_word_default_weight_,
117
+ UNKNOWN_TAG);
118
+ } else if (buf.size() == 2) {
119
+ MakeNodeInfo(node_info,
120
+ buf[0],
121
+ user_word_default_weight_,
122
+ buf[1]);
123
+ } else if (buf.size() == 3) {
124
+ int freq = atoi(buf[1].c_str());
125
+ assert(freq_sum_ > 0.0);
126
+ double weight = log(1.0 * freq / freq_sum_);
127
+ MakeNodeInfo(node_info, buf[0], weight, buf[2]);
128
+ }
129
+ static_node_infos_.push_back(node_info);
130
+ if (node_info.word.size() == 1) {
131
+ user_dict_single_chinese_word_.insert(node_info.word[0]);
132
+ }
133
+ }
134
+
135
+ void LoadUserDict(const std::vector<std::string>& buf) {
136
+ for (size_t i = 0; i < buf.size(); i++) {
137
+ InserUserDictNode(buf[i]);
138
+ }
139
+ }
140
+
141
+ void LoadUserDict(const std::set<std::string>& buf) {
142
+ std::set<std::string>::const_iterator iter;
143
+ for (iter = buf.begin(); iter != buf.end(); iter++){
144
+ InserUserDictNode(*iter);
145
+ }
146
+ }
147
+
148
+ void LoadUserDict(const std::string& filePaths) {
149
+ std::vector<std::string> files = limonp::Split(filePaths, "|;");
150
+ for (size_t i = 0; i < files.size(); i++) {
151
+ std::ifstream ifs(files[i].c_str());
152
+ XCHECK(ifs.is_open()) << "open " << files[i] << " failed";
153
+ std::string line;
154
+
155
+ while(getline(ifs, line)) {
156
+ if (line.size() == 0) {
157
+ continue;
158
+ }
159
+ InserUserDictNode(line);
160
+ }
161
+ }
162
+ }
163
+
164
+
165
+ private:
166
+ void Init(const std::string& dict_path, const std::string& user_dict_paths, UserWordWeightOption user_word_weight_opt) {
167
+ LoadDict(dict_path);
168
+ freq_sum_ = CalcFreqSum(static_node_infos_);
169
+ CalculateWeight(static_node_infos_, freq_sum_);
170
+ SetStaticWordWeights(user_word_weight_opt);
171
+
172
+ if (user_dict_paths.size()) {
173
+ LoadUserDict(user_dict_paths);
174
+ }
175
+ Shrink(static_node_infos_);
176
+ CreateTrie(static_node_infos_);
177
+ }
178
+
179
+ void CreateTrie(const std::vector<DictUnit>& dictUnits) {
180
+ assert(dictUnits.size());
181
+ std::vector<Unicode> words;
182
+ std::vector<const DictUnit*> valuePointers;
183
+ for (size_t i = 0 ; i < dictUnits.size(); i ++) {
184
+ words.push_back(dictUnits[i].word);
185
+ valuePointers.push_back(&dictUnits[i]);
186
+ }
187
+
188
+ trie_ = new Trie(words, valuePointers);
189
+ }
190
+
191
+ bool MakeNodeInfo(DictUnit& node_info,
192
+ const std::string& word,
193
+ double weight,
194
+ const std::string& tag) {
195
+ if (!DecodeUTF8RunesInString(word, node_info.word)) {
196
+ XLOG(ERROR) << "UTF-8 decode failed for dict word: " << word;
197
+ return false;
198
+ }
199
+ node_info.weight = weight;
200
+ node_info.tag = tag;
201
+ return true;
202
+ }
203
+
204
+ void LoadDict(const std::string& filePath) {
205
+ std::ifstream ifs(filePath.c_str());
206
+ XCHECK(ifs.is_open()) << "open " << filePath << " failed.";
207
+ std::string line;
208
+ std::vector<std::string> buf;
209
+
210
+ DictUnit node_info;
211
+ while (getline(ifs, line)) {
212
+ limonp::Split(line, buf, " ");
213
+ XCHECK(buf.size() == DICT_COLUMN_NUM) << "split result illegal, line:" << line;
214
+ MakeNodeInfo(node_info,
215
+ buf[0],
216
+ atof(buf[1].c_str()),
217
+ buf[2]);
218
+ static_node_infos_.push_back(node_info);
219
+ }
220
+ }
221
+
222
+ static bool WeightCompare(const DictUnit& lhs, const DictUnit& rhs) {
223
+ return lhs.weight < rhs.weight;
224
+ }
225
+
226
+ void SetStaticWordWeights(UserWordWeightOption option) {
227
+ XCHECK(!static_node_infos_.empty());
228
+ std::vector<DictUnit> x = static_node_infos_;
229
+ std::sort(x.begin(), x.end(), WeightCompare);
230
+ min_weight_ = x[0].weight;
231
+ max_weight_ = x[x.size() - 1].weight;
232
+ median_weight_ = x[x.size() / 2].weight;
233
+ switch (option) {
234
+ case WordWeightMin:
235
+ user_word_default_weight_ = min_weight_;
236
+ break;
237
+ case WordWeightMedian:
238
+ user_word_default_weight_ = median_weight_;
239
+ break;
240
+ default:
241
+ user_word_default_weight_ = max_weight_;
242
+ break;
243
+ }
244
+ }
245
+
246
+ double CalcFreqSum(const std::vector<DictUnit>& node_infos) const {
247
+ double sum = 0.0;
248
+ for (size_t i = 0; i < node_infos.size(); i++) {
249
+ sum += node_infos[i].weight;
250
+ }
251
+ return sum;
252
+ }
253
+
254
+ void CalculateWeight(std::vector<DictUnit>& node_infos, double sum) const {
255
+ assert(sum > 0.0);
256
+ for (size_t i = 0; i < node_infos.size(); i++) {
257
+ DictUnit& node_info = node_infos[i];
258
+ assert(node_info.weight > 0.0);
259
+ node_info.weight = log(double(node_info.weight)/sum);
260
+ }
261
+ }
262
+
263
+ void Shrink(std::vector<DictUnit>& units) const {
264
+ std::vector<DictUnit>(units.begin(), units.end()).swap(units);
265
+ }
266
+
267
+ std::vector<DictUnit> static_node_infos_;
268
+ std::deque<DictUnit> active_node_infos_; // must not be std::vector
269
+ Trie * trie_;
270
+
271
+ double freq_sum_;
272
+ double min_weight_;
273
+ double max_weight_;
274
+ double median_weight_;
275
+ double user_word_default_weight_;
276
+ std::unordered_set<Rune> user_dict_single_chinese_word_;
277
+ };
278
+ }
279
+
280
+ #endif