yangrongzhao commited on
Commit ·
fa7a22e
1
Parent(s): 7606a76
Update cpp, jieba still buggy
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +6 -0
- cpp/.gitattributes +3 -0
- cpp/CMakeLists.txt +65 -0
- cpp/build.sh +8 -0
- cpp/cmake/msp_dependencies.cmake +24 -0
- cpp/dict/cmudict-0.7b/LICENSE +33 -0
- cpp/dict/cmudict-0.7b/README +32 -0
- cpp/dict/cmudict-0.7b/README.developer +66 -0
- cpp/dict/cmudict-0.7b/cmudict.dict +0 -0
- cpp/dict/cmudict-0.7b/cmudict.phones +39 -0
- cpp/dict/cmudict-0.7b/cmudict.symbols +84 -0
- cpp/dict/cmudict-0.7b/cmudict.vp +54 -0
- cpp/dict/cmudict-0.7b/make_ps_dict.py +188 -0
- cpp/dict/hmm_model.utf8 +0 -0
- cpp/dict/idf.utf8 +0 -0
- cpp/dict/jieba.dict.utf8 +0 -0
- cpp/dict/pinyin.txt +0 -0
- cpp/dict/pinyin_phrase.txt +0 -0
- cpp/dict/pos_dict/char_state_tab.utf8 +0 -0
- cpp/dict/pos_dict/prob_emit.utf8 +0 -0
- cpp/dict/pos_dict/prob_start.utf8 +259 -0
- cpp/dict/pos_dict/prob_trans.utf8 +0 -0
- cpp/dict/stop_words.utf8 +1534 -0
- cpp/dict/user.dict.utf8 +4 -0
- cpp/dict/vocab.txt +114 -0
- cpp/download_bsp.sh +5 -0
- cpp/main.cpp +115 -0
- cpp/scripts/compare.py +14 -0
- cpp/scripts/convert_dict.py +122 -0
- cpp/scripts/export_vocab.py +38 -0
- cpp/scripts/export_voices.py +18 -0
- cpp/src/AudioFile.h +1293 -0
- cpp/src/EnG2P.h +138 -0
- cpp/src/JiebaProcessor.h +103 -0
- cpp/src/Kokoro.cpp +1216 -0
- cpp/src/Kokoro.h +185 -0
- cpp/src/PinyinFinder.cpp +194 -0
- cpp/src/PinyinFinder.h +23 -0
- cpp/src/Tokenizer.cpp +69 -0
- cpp/src/Tokenizer.h +34 -0
- cpp/src/ToneSandhi.cpp +322 -0
- cpp/src/ToneSandhi.h +46 -0
- cpp/src/Utils.h +290 -0
- cpp/src/ZHFrontend.cpp +182 -0
- cpp/src/ZHFrontend.h +40 -0
- cpp/src/ZHG2P.cpp +428 -0
- cpp/src/ZHG2P.h +48 -0
- cpp/src/ax_model_runner/ax_model_runner.cpp +442 -0
- cpp/src/ax_model_runner/ax_model_runner.hpp +84 -0
- 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 |
+
&ersand 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 |
+
"
|
| 1202 |
+
“
|
| 1203 |
+
”
|
| 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
|