Upload the cpp version
Browse files- .gitattributes +6 -12
- README.md +13 -0
- cpp/.gitattributes +2 -0
- cpp/CMakeLists.txt +49 -0
- cpp/README.md +286 -0
- cpp/build_ax650.sh +29 -0
- cpp/cmake/msp_dependencies.cmake +26 -0
- cpp/download_bsp.sh +4 -0
- cpp/install/ax650/zipvoice_axera +3 -0
- cpp/scripts/compare_vocoder.py +277 -0
- cpp/scripts/export_vocos_onnx.py +125 -0
- cpp/scripts/gen_cat_tokens.py +44 -0
- cpp/scripts/gen_pinyin_table.py +95 -0
- cpp/scripts/generate_vocoder_calib.py +141 -0
- cpp/scripts/py_daemon.py +57 -0
- cpp/scripts/quantize_vocoder.sh +123 -0
- cpp/src/EngineWrapper.cpp +282 -0
- cpp/src/EngineWrapper.hpp +62 -0
- cpp/src/cmdline.hpp +732 -0
- cpp/src/fbank.cpp +219 -0
- cpp/src/fbank.hpp +65 -0
- cpp/src/pinyin_table.hpp +0 -0
- cpp/src/tokenizer.cpp +306 -0
- cpp/src/tokenizer.hpp +79 -0
- cpp/src/vocoder.cpp +153 -0
- cpp/src/vocoder.hpp +66 -0
- cpp/src/wav_writer.hpp +84 -0
- cpp/src/zipvoice_engine.cpp +614 -0
- cpp/src/zipvoice_engine.hpp +144 -0
- cpp/third_party/kissfft/_kiss_fft_guts.h +182 -0
- cpp/third_party/kissfft/arch.h +261 -0
- cpp/third_party/kissfft/common.h +56 -0
- cpp/third_party/kissfft/kiss_fft.c +601 -0
- cpp/third_party/kissfft/kiss_fft.h +203 -0
- cpp/third_party/kissfft/opus_types.h +159 -0
- cpp/toolchains/aarch64-none-linux-gnu.toolchain.cmake +22 -0
- cpp/utils/checker.h +27 -0
- cpp/utils/io.hpp +212 -0
- cpp/utils/logger.h +64 -0
- cpp/vocoder/vocos_full.axmodel +3 -0
- cpp/zipvoice.cpp +1082 -0
.gitattributes
CHANGED
|
@@ -35,6 +35,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
assets/moss_prompts/en_4_4p5s.wav filter=lfs diff=lfs merge=lfs -text
|
| 37 |
assets/moss_prompts/zh_1_4p5s.wav filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 38 |
models/zipvoice_ax650/decoder_part0.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 39 |
models/zipvoice_ax650/decoder_part1.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 40 |
models/zipvoice_ax650/decoder_part2.axmodel filter=lfs diff=lfs merge=lfs -text
|
|
@@ -50,19 +52,11 @@ models/zipvoice_distill_ax650/decoder_part1.axmodel filter=lfs diff=lfs merge=lf
|
|
| 50 |
models/zipvoice_distill_ax650/decoder_part2.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 51 |
models/zipvoice_distill_ax650/decoder_part3.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 52 |
models/zipvoice_distill_ax650/encoder.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 53 |
-
outputs/
|
| 54 |
outputs/en_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 55 |
-
outputs/
|
| 56 |
outputs/en_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 57 |
-
outputs/
|
| 58 |
outputs/zh_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 59 |
-
outputs/
|
| 60 |
outputs/zh_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 61 |
-
en_long_paragraph_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 62 |
-
en_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 63 |
-
en_sentence_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 64 |
-
en_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 65 |
-
zh_long_paragraph_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 66 |
-
zh_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 67 |
-
zh_sentence_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 68 |
-
zh_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
assets/moss_prompts/en_4_4p5s.wav filter=lfs diff=lfs merge=lfs -text
|
| 37 |
assets/moss_prompts/zh_1_4p5s.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
cpp/install/ax650/zipvoice_axera filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
cpp/vocoder/vocos_full.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 40 |
models/zipvoice_ax650/decoder_part0.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 41 |
models/zipvoice_ax650/decoder_part1.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 42 |
models/zipvoice_ax650/decoder_part2.axmodel filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 52 |
models/zipvoice_distill_ax650/decoder_part2.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 53 |
models/zipvoice_distill_ax650/decoder_part3.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 54 |
models/zipvoice_distill_ax650/encoder.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
outputs/en_long_paragraph_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 56 |
outputs/en_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
outputs/en_sentence_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 58 |
outputs/en_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
outputs/zh_long_paragraph_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 60 |
outputs/zh_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
outputs/zh_sentence_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
| 62 |
outputs/zh_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -265,3 +265,16 @@ RTF: 0.3045
|
|
| 265 |
## 参考
|
| 266 |
|
| 267 |
- [ZipVoice](https://github.com/k2-fsa/ZipVoice)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
## 参考
|
| 266 |
|
| 267 |
- [ZipVoice](https://github.com/k2-fsa/ZipVoice)
|
| 268 |
+
|
| 269 |
+
## C++ 版本
|
| 270 |
+
|
| 271 |
+
纯 C++ 推理实现,全链路 axmodel,性能更高。详见 [/data/shared/huyuan/TTS/ZipVoice.AXERA/cpp/README.md](cpp/README.md)。
|
| 272 |
+
|
| 273 |
+
### 性能对比 (AX650 Distill, 中文段落, 44.97s 音频)
|
| 274 |
+
|
| 275 |
+
| 版本 | 推理耗时 | RTF |
|
| 276 |
+
|------|---------|-----|
|
| 277 |
+
| Python | 13.457s | 0.30 |
|
| 278 |
+
| C++ | 7.357s | 0.16 |
|
| 279 |
+
|
| 280 |
+
C++ 版本比 Python 快约 **1.8x**,vocoder IRFFT 使用 kissfft 比 Python torch.istft 快约 30x。
|
cpp/.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
cpp/CMakeLists.txt
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
| 2 |
+
project(zipvoice_axera)
|
| 3 |
+
|
| 4 |
+
set(CMAKE_CXX_STANDARD 14)
|
| 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 -fdata-sections -ffunction-sections")
|
| 10 |
+
endif()
|
| 11 |
+
|
| 12 |
+
# AXERA MSP (Media Software Platform) dependencies
|
| 13 |
+
# Adjust these paths to match your SDK installation
|
| 14 |
+
include(cmake/msp_dependencies.cmake)
|
| 15 |
+
|
| 16 |
+
include_directories(${MSP_INC_DIR})
|
| 17 |
+
link_directories(${MSP_LIB_DIR})
|
| 18 |
+
|
| 19 |
+
# Project includes (project root so "utils/io.hpp" and "src/EngineWrapper.hpp" resolve)
|
| 20 |
+
include_directories(${CMAKE_SOURCE_DIR})
|
| 21 |
+
|
| 22 |
+
# Source files
|
| 23 |
+
set(SRC
|
| 24 |
+
src/EngineWrapper.cpp
|
| 25 |
+
src/tokenizer.cpp
|
| 26 |
+
src/fbank.cpp
|
| 27 |
+
src/zipvoice_engine.cpp
|
| 28 |
+
src/vocoder.cpp
|
| 29 |
+
third_party/kissfft/kiss_fft.c
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
add_executable(${PROJECT_NAME} zipvoice.cpp ${SRC})
|
| 33 |
+
target_include_directories(${PROJECT_NAME} PRIVATE third_party/kissfft)
|
| 34 |
+
target_link_libraries(${PROJECT_NAME} ${MSP_LIBS})
|
| 35 |
+
|
| 36 |
+
install(TARGETS ${PROJECT_NAME}
|
| 37 |
+
RUNTIME DESTINATION ./)
|
| 38 |
+
set_target_properties(${PROJECT_NAME}
|
| 39 |
+
PROPERTIES
|
| 40 |
+
INSTALL_RPATH "$ORIGIN/")
|
| 41 |
+
|
| 42 |
+
# Print configuration summary
|
| 43 |
+
message(STATUS "========================================")
|
| 44 |
+
message(STATUS "ZipVoice AXERA C++ Build Configuration")
|
| 45 |
+
message(STATUS "========================================")
|
| 46 |
+
message(STATUS "MSP_INC_DIR: ${MSP_INC_DIR}")
|
| 47 |
+
message(STATUS "MSP_LIB_DIR: ${MSP_LIB_DIR}")
|
| 48 |
+
message(STATUS "MSP_LIBS: ${MSP_LIBS}")
|
| 49 |
+
message(STATUS "========================================")
|
cpp/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ZipVoice AXERA C++
|
| 2 |
+
|
| 3 |
+
ZipVoice TTS 在 AXERA NPU 板卡上的 C++ 推理实现。全链路 axmodel,无 Python 依赖(英文分词除外)。
|
| 4 |
+
|
| 5 |
+
## 性能 (AX650)
|
| 6 |
+
|
| 7 |
+
### Distill 模型
|
| 8 |
+
|
| 9 |
+
| 场景 | 音频 | 耗时 | RTF |
|
| 10 |
+
|------|------|------|-----|
|
| 11 |
+
| 中文句子 | 6.41s | 1.057s | 0.165 |
|
| 12 |
+
| 中文段落 | 44.97s | 7.357s | 0.164 |
|
| 13 |
+
| 英文句子 | 6.41s | 1.067s | 0.166 |
|
| 14 |
+
| 英文段落 | 59.31s | 10.529s | 0.178 |
|
| 15 |
+
|
| 16 |
+
### 普通模型
|
| 17 |
+
|
| 18 |
+
| 场景 | 音频 | 耗时 | RTF |
|
| 19 |
+
|------|------|------|-----|
|
| 20 |
+
| 中文句子 | 6.41s | 5.709s | 0.891 |
|
| 21 |
+
| 中文段落 | 44.97s | 39.714s | 0.883 |
|
| 22 |
+
| 英文句子 | 6.41s | 5.693s | 0.888 |
|
| 23 |
+
| 英文段落 | 59.31s | 56.759s | 0.957 |
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
## 目录结构
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
├── CMakeLists.txt # 构建配置
|
| 31 |
+
├── zipvoice.cpp # 主程序
|
| 32 |
+
├── build_ax650.sh # 交叉编译脚本
|
| 33 |
+
├── download_bsp.sh # 下载 BSP SDK
|
| 34 |
+
├── cmake/
|
| 35 |
+
│ └── msp_dependencies.cmake # MSP SDK 配置
|
| 36 |
+
├── toolchains/
|
| 37 |
+
│ └── aarch64-none-linux-gnu.toolchain.cmake # 工具链
|
| 38 |
+
├── third_party/kissfft/ # kissfft (vocoder IRFFT)
|
| 39 |
+
├── utils/ # 日志/IO/检查
|
| 40 |
+
├── src/ # 核心源码
|
| 41 |
+
│ ├── EngineWrapper.* # AX引擎封装
|
| 42 |
+
│ ├── tokenizer.* # 中文分词 (41K汉字pinyin映射)
|
| 43 |
+
│ ├── pinyin_table.hpp # 汉字→拼音表 (自动生成)
|
| 44 |
+
│ ├── fbank.* # Mel滤波器 (FFT)
|
| 45 |
+
│ ├── zipvoice_engine.* # 推理引擎 (encoder+decoder4)
|
| 46 |
+
│ ├── vocoder.* # 声码器 (axmodel+kissfft IRFFT)
|
| 47 |
+
│ └── wav_writer.hpp # WAV写入
|
| 48 |
+
├── vocoder/
|
| 49 |
+
│ └── vocos_full.axmodel # 量化声码器 (15MB)
|
| 50 |
+
├── scripts/ # Python辅助脚本
|
| 51 |
+
│ ├── gen_cat_tokens.py # 预计算token
|
| 52 |
+
│ ├── gen_pinyin_table.py # 生成汉字映射表
|
| 53 |
+
│ ├── export_vocos_onnx.py # 导出ONNX
|
| 54 |
+
│ ├── generate_vocoder_calib.py # 校准数据
|
| 55 |
+
│ └── quantize_vocoder.sh # pulsar2量化
|
| 56 |
+
└── README.md
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## 环境准备
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
# 下载 BSP SDK
|
| 63 |
+
bash download_bsp.sh
|
| 64 |
+
|
| 65 |
+
# 安装交叉编译器
|
| 66 |
+
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
|
| 67 |
+
tar -xf gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
|
| 68 |
+
# 编译时传 -DTOOLCHAIN_DIR=/path/to/gcc-arm-... 或在 toolchains/ 中修改
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
## 编译
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
mkdir build_ax650 && cd build_ax650
|
| 75 |
+
cmake .. \
|
| 76 |
+
-DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-none-linux-gnu.toolchain.cmake \
|
| 77 |
+
-DTOOLCHAIN_DIR=/path/to/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu \
|
| 78 |
+
-DCMAKE_INSTALL_PREFIX=../install/ax650 \
|
| 79 |
+
-DCMAKE_BUILD_TYPE=Release
|
| 80 |
+
make -j$(nproc) && make install
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## 板端推理
|
| 84 |
+
|
| 85 |
+
所有命令在 repo 根目录(`ZipVoice.AXERA/`)下执行。
|
| 86 |
+
|
| 87 |
+
### 中文
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
# 普通模型 — 句子
|
| 91 |
+
cpp/install/ax650/zipvoice_axera \
|
| 92 |
+
--model-dir ./models/zipvoice_ax650 \
|
| 93 |
+
--token-file ./resources/zipvoice_hf/zipvoice/tokens.txt \
|
| 94 |
+
--prompt-wav ./assets/moss_prompts/zh_1_4p5s.wav \
|
| 95 |
+
--prompt-text "不管怎么样我和汤姆还是要感谢贝尔卡金的援手" \
|
| 96 |
+
--text "今天午后天气很好,我打开窗户,听见远处有人聊天,水杯也轻轻晃了一下。" \
|
| 97 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 98 |
+
--output-wav output.wav --seed 42
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
```text
|
| 102 |
+
推理结果:
|
| 103 |
+
Segments: 1
|
| 104 |
+
Audio duration: 6.41 s
|
| 105 |
+
Total time: 5.71 s
|
| 106 |
+
RTF (端到端): 0.891
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
# 蒸馏模型 — 句子 (约3倍速)
|
| 111 |
+
cpp/install/ax650/zipvoice_axera \
|
| 112 |
+
--model-dir ./models/zipvoice_distill_ax650 \
|
| 113 |
+
--token-file ./resources/zipvoice_hf/zipvoice/tokens.txt \
|
| 114 |
+
--prompt-wav ./assets/moss_prompts/zh_1_4p5s.wav \
|
| 115 |
+
--prompt-text "不管怎么样我和汤姆还是要感谢贝尔卡金的援手" \
|
| 116 |
+
--text "今天午后天气很好,我打开窗户,听见远处有人聊天,水杯也轻轻晃了一下。" \
|
| 117 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 118 |
+
--output-wav output.wav --seed 42
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
```text
|
| 122 |
+
推理结果:
|
| 123 |
+
Segments: 1
|
| 124 |
+
Audio duration: 6.41 s
|
| 125 |
+
Total time: 1.06 s
|
| 126 |
+
RTF (端到端): 0.165
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
# 普通模型 — 段落
|
| 131 |
+
cpp/install/ax650/zipvoice_axera \
|
| 132 |
+
--model-dir ./models/zipvoice_ax650 \
|
| 133 |
+
--token-file ./resources/zipvoice_hf/zipvoice/tokens.txt \
|
| 134 |
+
--prompt-wav ./assets/moss_prompts/zh_1_4p5s.wav \
|
| 135 |
+
--prompt-text "不管怎么样我和汤姆还是要感谢贝尔卡金的援手" \
|
| 136 |
+
--text-file ./assets/paragraphs/zh_ginkgo.txt \
|
| 137 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 138 |
+
--output-wav output.wav --seed 42
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
```text
|
| 142 |
+
推理结果:
|
| 143 |
+
Segments: 7
|
| 144 |
+
Audio duration: 44.97 s
|
| 145 |
+
Total time: 39.71 s
|
| 146 |
+
RTF (端到端): 0.883
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
```bash
|
| 150 |
+
# 蒸馏��型 — 段落
|
| 151 |
+
cpp/install/ax650/zipvoice_axera \
|
| 152 |
+
--model-dir ./models/zipvoice_distill_ax650 \
|
| 153 |
+
--token-file ./resources/zipvoice_hf/zipvoice/tokens.txt \
|
| 154 |
+
--prompt-wav ./assets/moss_prompts/zh_1_4p5s.wav \
|
| 155 |
+
--prompt-text "不管怎么样我和汤姆还是要感谢贝尔卡金的援手" \
|
| 156 |
+
--text-file ./assets/paragraphs/zh_ginkgo.txt \
|
| 157 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 158 |
+
--output-wav output.wav --seed 42
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
```text
|
| 162 |
+
推理结果:
|
| 163 |
+
Segments: 7
|
| 164 |
+
Audio duration: 44.97 s
|
| 165 |
+
Total time: 7.38 s
|
| 166 |
+
RTF (端到端): 0.164
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
### 英文 (加 `--repo-dir .` 自动调 Python 分词器)
|
| 170 |
+
|
| 171 |
+
```bash
|
| 172 |
+
# 普通模型 — 句子
|
| 173 |
+
cpp/install/ax650/zipvoice_axera \
|
| 174 |
+
--model-dir ./models/zipvoice_ax650 \
|
| 175 |
+
--prompt-wav ./assets/moss_prompts/en_4_4p5s.wav \
|
| 176 |
+
--prompt-text "This is almost twice the current industry production level per train." \
|
| 177 |
+
--text "This morning, a small train left the station, carrying sleepy passengers toward a bright coastal town." \
|
| 178 |
+
--repo-dir . \
|
| 179 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 180 |
+
--output-wav output_en.wav --seed 42
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
```text
|
| 184 |
+
推理结果:
|
| 185 |
+
Segments: 1
|
| 186 |
+
Audio duration: 6.41 s
|
| 187 |
+
Total time: 5.69 s
|
| 188 |
+
RTF (端到端): 0.888
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
# 蒸馏模型 — 句子
|
| 193 |
+
cpp/install/ax650/zipvoice_axera \
|
| 194 |
+
--model-dir ./models/zipvoice_distill_ax650 \
|
| 195 |
+
--prompt-wav ./assets/moss_prompts/en_4_4p5s.wav \
|
| 196 |
+
--prompt-text "This is almost twice the current industry production level per train." \
|
| 197 |
+
--text "This morning, a small train left the station, carrying sleepy passengers toward a bright coastal town." \
|
| 198 |
+
--repo-dir . \
|
| 199 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 200 |
+
--output-wav output_en.wav --seed 42
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
```text
|
| 204 |
+
推理结果:
|
| 205 |
+
Segments: 1
|
| 206 |
+
Audio duration: 6.41 s
|
| 207 |
+
Total time: 1.06 s
|
| 208 |
+
RTF (端到端): 0.166
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
# 普通模型 — 段落
|
| 213 |
+
cpp/install/ax650/zipvoice_axera \
|
| 214 |
+
--model-dir ./models/zipvoice_ax650 \
|
| 215 |
+
--prompt-wav ./assets/moss_prompts/en_4_4p5s.wav \
|
| 216 |
+
--prompt-text "This is almost twice the current industry production level per train." \
|
| 217 |
+
--text-file ./assets/paragraphs/en_scavenger.txt \
|
| 218 |
+
--repo-dir . \
|
| 219 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 220 |
+
--output-wav output_en.wav --seed 42
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
```text
|
| 224 |
+
推理结果:
|
| 225 |
+
Segments: 10
|
| 226 |
+
Audio duration: 59.31 s
|
| 227 |
+
Total time: 56.76 s
|
| 228 |
+
RTF (端到端): 0.957
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
```bash
|
| 232 |
+
# 蒸馏模型 — 段落
|
| 233 |
+
cpp/install/ax650/zipvoice_axera \
|
| 234 |
+
--model-dir ./models/zipvoice_distill_ax650 \
|
| 235 |
+
--prompt-wav ./assets/moss_prompts/en_4_4p5s.wav \
|
| 236 |
+
--prompt-text "This is almost twice the current industry production level per train." \
|
| 237 |
+
--text-file ./assets/paragraphs/en_scavenger.txt \
|
| 238 |
+
--repo-dir . \
|
| 239 |
+
--vocoder-model ./cpp/vocoder/vocos_full.axmodel \
|
| 240 |
+
--output-wav output_en.wav --seed 42
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
```text
|
| 244 |
+
推理结果:
|
| 245 |
+
Segments: 10
|
| 246 |
+
Audio duration: 59.31 s
|
| 247 |
+
Total time: 10.56 s
|
| 248 |
+
RTF (端到端): 0.178
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
## 命令行参数
|
| 252 |
+
|
| 253 |
+
| 参数 | 说明 | 默认值 |
|
| 254 |
+
|------|------|--------|
|
| 255 |
+
| `--model-dir` | 模型目录 | 必填 |
|
| 256 |
+
| `--token-file` | tokens.txt (中文) | 可选 |
|
| 257 |
+
| `--prompt-wav` | 提示音频 | 必填 |
|
| 258 |
+
| `--prompt-text` | 提示文本 | 必填 |
|
| 259 |
+
| `--text` / `--text-file` | 合成文本 | 二选一 |
|
| 260 |
+
| `--repo-dir` | repo根目录 (英文分词) | 可选 |
|
| 261 |
+
| `--vocoder-model` | vocos axmodel 路径 | 必填 |
|
| 262 |
+
| `--output-wav` | 输出WAV | output.wav |
|
| 263 |
+
| `--num-step` | 采样步数 | 10 (distill: 4) |
|
| 264 |
+
| `--speed` | 语速 | 1.0 |
|
| 265 |
+
| `--seed` | 随机种子 | 42 |
|
| 266 |
+
|
| 267 |
+
## 重新量化 vocoder
|
| 268 |
+
|
| 269 |
+
```bash
|
| 270 |
+
cd cpp/scripts
|
| 271 |
+
python3 export_vocos_onnx.py # 导出ONNX
|
| 272 |
+
python3 generate_vocoder_calib.py # 生成校准数据
|
| 273 |
+
source /path/to/npu_dev && bash quantize_vocoder.sh # 量化
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
## 实现要点
|
| 277 |
+
|
| 278 |
+
- **中文分词**: C++ 内置, 基于 pypinyin 的 41K 汉字→拼音映射表, 0ms 开销
|
| 279 |
+
- **英文分词**: Python daemon 持久进程, 首次 import ~8s, 后续每次 ~10ms
|
| 280 |
+
- **长文本分段**: 对齐 Python `text_processing.build_segments`, 自动按 token/帧数约束切段
|
| 281 |
+
- **声码器**: vocos axmodel (NPU) + kissfft IRFFT (CPU), 比手写逐点版快 ~30x
|
| 282 |
+
- **RTF 口径**: 仅计入增量推理 (tokenizer + encoder + decoder + vocoder), 不含一次性初始化
|
| 283 |
+
|
| 284 |
+
## 许可
|
| 285 |
+
|
| 286 |
+
MIT License
|
cpp/build_ax650.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Cross-compile ZipVoice AXERA for AX650 / AX630C
|
| 3 |
+
#
|
| 4 |
+
# Prerequisite: download_bsp.sh (run once to clone ax650n_bsp_sdk)
|
| 5 |
+
# cross compiler at /data/shared/huyuan/toolchains/
|
| 6 |
+
|
| 7 |
+
set -e
|
| 8 |
+
|
| 9 |
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
| 10 |
+
cd "$SCRIPT_DIR"
|
| 11 |
+
|
| 12 |
+
rm -rf build_ax650
|
| 13 |
+
mkdir -p build_ax650 && cd build_ax650
|
| 14 |
+
|
| 15 |
+
cmake .. \
|
| 16 |
+
-DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-none-linux-gnu.toolchain.cmake \
|
| 17 |
+
-DTOOLCHAIN_DIR=/data/shared/huyuan/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu \
|
| 18 |
+
-DCMAKE_INSTALL_PREFIX=../install/ax650 \
|
| 19 |
+
-DCMAKE_BUILD_TYPE=Release \
|
| 20 |
+
$@
|
| 21 |
+
|
| 22 |
+
make -j$(nproc)
|
| 23 |
+
make install
|
| 24 |
+
|
| 25 |
+
echo ""
|
| 26 |
+
echo "========================================"
|
| 27 |
+
echo "Build complete!"
|
| 28 |
+
echo "Executable: $(pwd)/../install/ax650/zipvoice_axera"
|
| 29 |
+
echo "========================================"
|
cpp/cmake/msp_dependencies.cmake
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# BSP MSP (Media Software Platform) Dependencies Configuration
|
| 2 |
+
# Uses ax650n_bsp_sdk cloned from https://github.com/AXERA-TECH/ax650n_bsp_sdk.git
|
| 3 |
+
#
|
| 4 |
+
# Reference: melotts.axera-main/cpp/cmake/msp_dependencies.cmake
|
| 5 |
+
|
| 6 |
+
# bsp
|
| 7 |
+
if(NOT BSP_MSP_DIR)
|
| 8 |
+
set(BSP_MSP_DIR ${CMAKE_SOURCE_DIR}/ax650n_bsp_sdk/msp/out)
|
| 9 |
+
endif()
|
| 10 |
+
message(STATUS "BSP_MSP_DIR = ${BSP_MSP_DIR}")
|
| 11 |
+
|
| 12 |
+
# check bsp exist
|
| 13 |
+
if(NOT EXISTS ${BSP_MSP_DIR})
|
| 14 |
+
message(FATAL_ERROR "FATAL: BSP_MSP_DIR ${BSP_MSP_DIR} not exist. Run download_bsp.sh first.")
|
| 15 |
+
endif()
|
| 16 |
+
|
| 17 |
+
set(MSP_INC_DIR ${BSP_MSP_DIR}/include)
|
| 18 |
+
set(MSP_LIB_DIR ${BSP_MSP_DIR}/lib)
|
| 19 |
+
|
| 20 |
+
list(APPEND MSP_LIBS
|
| 21 |
+
ax_sys
|
| 22 |
+
ax_engine
|
| 23 |
+
ax_interpreter)
|
| 24 |
+
|
| 25 |
+
# Allow the linker to find transitive dependencies in the BSP lib dir
|
| 26 |
+
link_directories(${MSP_LIB_DIR})
|
cpp/download_bsp.sh
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
if [ ! -d ax650n_bsp_sdk ]; then
|
| 3 |
+
git clone https://github.com/AXERA-TECH/ax650n_bsp_sdk.git
|
| 4 |
+
fi
|
cpp/install/ax650/zipvoice_axera
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0d15168590f21a4255f223055008fb6832babf8a1f096c39f93afe0d78fb4c98
|
| 3 |
+
size 582624
|
cpp/scripts/compare_vocoder.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Compare PyTorch vocoder vs quantized axmodel output.
|
| 4 |
+
Saves intermediate tensors for board-side comparison.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
# Dev machine: generate test data
|
| 8 |
+
python3 compare_vocoder.py --save-test-data
|
| 9 |
+
|
| 10 |
+
# Board: run axmodel on same input, save output
|
| 11 |
+
python3 compare_vocoder.py --run-axmodel
|
| 12 |
+
|
| 13 |
+
# Dev machine: compare results
|
| 14 |
+
python3 compare_vocoder.py --compare
|
| 15 |
+
"""
|
| 16 |
+
import sys, os, math, argparse
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
REPO_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 20 |
+
ONNX_DIR = os.path.join(REPO_DIR, 'cpp', 'vocoder_onnx')
|
| 21 |
+
TEST_DIR = os.path.join(ONNX_DIR, 'test_data')
|
| 22 |
+
os.makedirs(TEST_DIR, exist_ok=True)
|
| 23 |
+
|
| 24 |
+
FEAT_SCALE = 0.1
|
| 25 |
+
N_FFT = 1024
|
| 26 |
+
HOP = 256
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def irfft_overlap_add(real_spec, imag_spec):
|
| 30 |
+
"""C++ IRFFT + overlap-add, verified to match PyTorch istft (cos_sim=0.999999)."""
|
| 31 |
+
n_freqs = N_FFT // 2 + 1
|
| 32 |
+
T = real_spec.shape[1]
|
| 33 |
+
window = 0.5 * (1.0 - np.cos(2.0 * math.pi * np.arange(N_FFT) / (N_FFT - 1)))
|
| 34 |
+
window_sq = window ** 2
|
| 35 |
+
|
| 36 |
+
# Build IRFFT basis
|
| 37 |
+
irfft_cos = np.zeros((N_FFT, n_freqs), dtype=np.float32)
|
| 38 |
+
irfft_sin = np.zeros((N_FFT, n_freqs), dtype=np.float32)
|
| 39 |
+
for n in range(N_FFT):
|
| 40 |
+
for k in range(n_freqs):
|
| 41 |
+
ang = 2.0 * math.pi * k * n / N_FFT
|
| 42 |
+
if k == 0:
|
| 43 |
+
irfft_cos[n, k] = 1.0 / N_FFT
|
| 44 |
+
irfft_sin[n, k] = 0.0
|
| 45 |
+
elif k == n_freqs - 1:
|
| 46 |
+
irfft_cos[n, k] = math.cos(ang) / N_FFT
|
| 47 |
+
irfft_sin[n, k] = 0.0
|
| 48 |
+
else:
|
| 49 |
+
irfft_cos[n, k] = math.cos(ang) * (2.0 / N_FFT)
|
| 50 |
+
irfft_sin[n, k] = math.sin(ang) * (2.0 / N_FFT)
|
| 51 |
+
|
| 52 |
+
out_len = (T - 1) * HOP + N_FFT
|
| 53 |
+
audio = np.zeros(out_len, dtype=np.float32)
|
| 54 |
+
envelope = np.zeros(out_len, dtype=np.float32)
|
| 55 |
+
|
| 56 |
+
for t in range(T):
|
| 57 |
+
r = real_spec[0, t, :]
|
| 58 |
+
im = imag_spec[0, t, :]
|
| 59 |
+
frame = irfft_cos @ r - irfft_sin @ im
|
| 60 |
+
pos = t * HOP
|
| 61 |
+
for n in range(N_FFT):
|
| 62 |
+
p = pos + n
|
| 63 |
+
if p < out_len:
|
| 64 |
+
audio[p] += frame[n] * window[n]
|
| 65 |
+
envelope[p] += window_sq[n]
|
| 66 |
+
|
| 67 |
+
audio /= np.maximum(envelope, 1e-10)
|
| 68 |
+
pad = N_FFT // 2
|
| 69 |
+
return audio[pad:pad + (T - 1) * HOP]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def compare(name, ref, test):
|
| 73 |
+
ref = np.asarray(ref, dtype=np.float32).flatten()
|
| 74 |
+
test = np.asarray(test, dtype=np.float32).flatten()
|
| 75 |
+
if len(ref) != len(test):
|
| 76 |
+
print(f" [{name}] SIZE MISMATCH: ref={ref.shape} test={test.shape}")
|
| 77 |
+
return
|
| 78 |
+
diff = np.abs(ref - test)
|
| 79 |
+
sig = np.mean(np.abs(ref)) + 1e-10
|
| 80 |
+
cos = np.dot(ref, test) / (np.linalg.norm(ref) * np.linalg.norm(test) + 1e-10)
|
| 81 |
+
print(f" [{name}] max_err={diff.max():.2e} rel_err={diff.mean()/sig:.2e} cos_sim={cos:.6f}")
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def cmd_save_test_data():
|
| 85 |
+
"""Generate test mel and save PT/ONNX reference outputs."""
|
| 86 |
+
import torch
|
| 87 |
+
sys.path.insert(0, REPO_DIR)
|
| 88 |
+
from scripts.local_vocos import LocalVocos
|
| 89 |
+
import onnxruntime as ort
|
| 90 |
+
|
| 91 |
+
# Load models
|
| 92 |
+
vocoder = LocalVocos()
|
| 93 |
+
sd = torch.load(f'{REPO_DIR}/resources/vocos-mel-24khz/pytorch_model.bin',
|
| 94 |
+
weights_only=True, map_location='cpu')
|
| 95 |
+
sd = {k: v for k, v in sd.items() if k.startswith(('backbone.', 'head.'))}
|
| 96 |
+
vocoder.load_state_dict(sd)
|
| 97 |
+
vocoder.eval()
|
| 98 |
+
|
| 99 |
+
sess_f = ort.InferenceSession(f'{ONNX_DIR}/vocos_full_B1_T620.onnx')
|
| 100 |
+
|
| 101 |
+
# Load real mel or generate random
|
| 102 |
+
mel_bin = os.path.join(REPO_DIR, 'cpp', 'output_mel.bin')
|
| 103 |
+
if os.path.exists(mel_bin):
|
| 104 |
+
real_mel = np.fromfile(mel_bin, dtype=np.float32).reshape(-1, 100) # [T, 100]
|
| 105 |
+
T = real_mel.shape[0]
|
| 106 |
+
# Undo feat_scale, transpose to [1, 100, T]
|
| 107 |
+
mel_input = (real_mel / FEAT_SCALE).T[np.newaxis, :, :].astype(np.float32) # [1, 100, T]
|
| 108 |
+
print(f"Using real mel: shape={mel_input.shape}, range=[{mel_input.min():.3f}, {mel_input.max():.3f}]")
|
| 109 |
+
else:
|
| 110 |
+
T = 200
|
| 111 |
+
mel_input = np.random.RandomState(42).randn(1, 100, T).astype(np.float32) * 3.0
|
| 112 |
+
print(f"Using random mel: shape={mel_input.shape}")
|
| 113 |
+
|
| 114 |
+
# Pad to 620 for ONNX
|
| 115 |
+
T_pad = 620
|
| 116 |
+
mel_onnx = np.zeros((1, 100, T_pad), dtype=np.float32)
|
| 117 |
+
mel_onnx[:, :, :T] = mel_input[:, :, :T]
|
| 118 |
+
|
| 119 |
+
# PT inference
|
| 120 |
+
mel_pt = torch.from_numpy(mel_input)
|
| 121 |
+
with torch.no_grad():
|
| 122 |
+
features_pt = vocoder.backbone(mel_pt)
|
| 123 |
+
audio_pt = vocoder.head(features_pt).squeeze().numpy()
|
| 124 |
+
h = vocoder.head.out(features_pt)
|
| 125 |
+
mag, phase = h.chunk(2, dim=-1)
|
| 126 |
+
mag = torch.exp(mag).clamp(max=1e2)
|
| 127 |
+
real_pt = (mag * torch.cos(phase)).numpy()
|
| 128 |
+
imag_pt = (mag * torch.sin(phase)).numpy()
|
| 129 |
+
|
| 130 |
+
# ONNX inference
|
| 131 |
+
onnx_out = sess_f.run(None, {'mel': mel_onnx})
|
| 132 |
+
real_onnx = onnx_out[0][:, :T, :]
|
| 133 |
+
imag_onnx = onnx_out[1][:, :T, :]
|
| 134 |
+
audio_onnx = irfft_overlap_add(real_onnx, imag_onnx)
|
| 135 |
+
|
| 136 |
+
# Save everything
|
| 137 |
+
np.save(f'{TEST_DIR}/mel_input.npy', mel_input)
|
| 138 |
+
np.save(f'{TEST_DIR}/mel_onnx_padded.npy', mel_onnx)
|
| 139 |
+
np.save(f'{TEST_DIR}/pt_real.npy', real_pt)
|
| 140 |
+
np.save(f'{TEST_DIR}/pt_imag.npy', imag_pt)
|
| 141 |
+
np.save(f'{TEST_DIR}/pt_audio.npy', audio_pt)
|
| 142 |
+
np.save(f'{TEST_DIR}/onnx_real.npy', real_onnx)
|
| 143 |
+
np.save(f'{TEST_DIR}/onnx_imag.npy', imag_onnx)
|
| 144 |
+
np.save(f'{TEST_DIR}/onnx_audio.npy', audio_onnx)
|
| 145 |
+
np.save(f'{TEST_DIR}/T_frames.npy', np.array([T], dtype=np.int32))
|
| 146 |
+
|
| 147 |
+
# Meta
|
| 148 |
+
with open(f'{TEST_DIR}/info.txt', 'w') as f:
|
| 149 |
+
f.write(f"T={T}\n")
|
| 150 |
+
f.write(f"feat_scale={FEAT_SCALE}\n")
|
| 151 |
+
f.write(f"mel_range=[{mel_input.min():.4f}, {mel_input.max():.4f}]\n")
|
| 152 |
+
f.write(f"pt_real_range=[{real_pt.min():.4f}, {real_pt.max():.4f}]\n")
|
| 153 |
+
f.write(f"pt_audio_len={len(audio_pt)}\n")
|
| 154 |
+
|
| 155 |
+
# Verify PT vs ONNX
|
| 156 |
+
print("\n=== PT vs ONNX (dev machine) ===")
|
| 157 |
+
compare('real_spectrum', real_pt, real_onnx)
|
| 158 |
+
compare('imag_spectrum', imag_pt, imag_onnx)
|
| 159 |
+
compare('audio', audio_pt, audio_onnx)
|
| 160 |
+
|
| 161 |
+
# Also write audio files for listening
|
| 162 |
+
import soundfile as sf
|
| 163 |
+
sf.write(f'{TEST_DIR}/pt_audio.wav', audio_pt, 24000)
|
| 164 |
+
sf.write(f'{TEST_DIR}/onnx_audio.wav', audio_onnx, 24000)
|
| 165 |
+
|
| 166 |
+
print(f"\nTest data saved to {TEST_DIR}/")
|
| 167 |
+
print("Copy to board and run: python3 compare_vocoder.py --run-axmodel")
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def cmd_run_axmodel():
|
| 171 |
+
"""Run axmodel on board with the same test mel, save output."""
|
| 172 |
+
import onnxruntime as ort
|
| 173 |
+
from axengine import InferenceSession
|
| 174 |
+
|
| 175 |
+
T = int(np.load(f'{TEST_DIR}/T_frames.npy')[0])
|
| 176 |
+
mel_onnx = np.load(f'{TEST_DIR}/mel_onnx_padded.npy')
|
| 177 |
+
|
| 178 |
+
# Run axmodel
|
| 179 |
+
model_path = f'{ONNX_DIR}/axmodel/vocos_full.axmodel'
|
| 180 |
+
if not os.path.exists(model_path):
|
| 181 |
+
print(f"ERROR: {model_path} not found")
|
| 182 |
+
return
|
| 183 |
+
|
| 184 |
+
print(f"Loading axmodel: {model_path}")
|
| 185 |
+
sess = InferenceSession(model_path)
|
| 186 |
+
|
| 187 |
+
print(f"Running inference (mel shape={mel_onnx.shape})...")
|
| 188 |
+
outputs = sess.run(None, {'mel': mel_onnx})
|
| 189 |
+
print(f"Output keys: {list(outputs.keys()) if isinstance(outputs, dict) else type(outputs)}")
|
| 190 |
+
|
| 191 |
+
# Extract real/imag
|
| 192 |
+
if isinstance(outputs, dict):
|
| 193 |
+
real_ax = outputs['real'][:, :T, :]
|
| 194 |
+
imag_ax = outputs['imag'][:, :T, :]
|
| 195 |
+
elif isinstance(outputs, (list, tuple)):
|
| 196 |
+
real_ax = outputs[0][:, :T, :]
|
| 197 |
+
imag_ax = outputs[1][:, :T, :]
|
| 198 |
+
else:
|
| 199 |
+
real_ax = outputs[:, :T, :] # guess
|
| 200 |
+
imag_ax = None
|
| 201 |
+
|
| 202 |
+
audio_ax = irfft_overlap_add(real_ax, imag_ax)
|
| 203 |
+
|
| 204 |
+
# Save
|
| 205 |
+
np.save(f'{TEST_DIR}/ax_real.npy', real_ax)
|
| 206 |
+
np.save(f'{TEST_DIR}/ax_imag.npy', imag_ax)
|
| 207 |
+
np.save(f'{TEST_DIR}/ax_audio.npy', audio_ax)
|
| 208 |
+
import soundfile as sf
|
| 209 |
+
sf.write(f'{TEST_DIR}/ax_audio.wav', audio_ax, 24000)
|
| 210 |
+
|
| 211 |
+
# Compare with ONNX reference
|
| 212 |
+
onnx_real = np.load(f'{TEST_DIR}/onnx_real.npy')
|
| 213 |
+
onnx_imag = np.load(f'{TEST_DIR}/onnx_imag.npy')
|
| 214 |
+
onnx_audio = np.load(f'{TEST_DIR}/onnx_audio.npy')
|
| 215 |
+
|
| 216 |
+
print("\n=== axmodel vs ONNX (board) ===")
|
| 217 |
+
compare('real_spectrum', onnx_real, real_ax)
|
| 218 |
+
compare('imag_spectrum', onnx_imag, imag_ax)
|
| 219 |
+
compare('audio', onnx_audio, audio_ax)
|
| 220 |
+
|
| 221 |
+
print(f"\nResults saved to {TEST_DIR}/")
|
| 222 |
+
print("Copy back to dev machine and run: python3 compare_vocoder.py --compare")
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def cmd_compare():
|
| 226 |
+
"""Compare all outputs (dev machine, after copying ax_*.npy from board)."""
|
| 227 |
+
pt_audio = np.load(f'{TEST_DIR}/pt_audio.npy')
|
| 228 |
+
onnx_audio = np.load(f'{TEST_DIR}/onnx_audio.npy')
|
| 229 |
+
ax_audio = np.load(f'{TEST_DIR}/ax_audio.npy')
|
| 230 |
+
|
| 231 |
+
pt_real = np.load(f'{TEST_DIR}/pt_real.npy')
|
| 232 |
+
onnx_real = np.load(f'{TEST_DIR}/onnx_real.npy')
|
| 233 |
+
ax_real = np.load(f'{TEST_DIR}/ax_real.npy')
|
| 234 |
+
|
| 235 |
+
pt_imag = np.load(f'{TEST_DIR}/pt_imag.npy')
|
| 236 |
+
onnx_imag = np.load(f'{TEST_DIR}/onnx_imag.npy')
|
| 237 |
+
ax_imag = np.load(f'{TEST_DIR}/ax_imag.npy')
|
| 238 |
+
|
| 239 |
+
print("=== Full Comparison ===")
|
| 240 |
+
print("\n--- Spectrum ---")
|
| 241 |
+
compare('real: PT vs ONNX', pt_real, onnx_real)
|
| 242 |
+
compare('real: PT vs axmodel', pt_real, ax_real)
|
| 243 |
+
compare('real: ONNX vs axmodel', onnx_real, ax_real)
|
| 244 |
+
print()
|
| 245 |
+
compare('imag: PT vs ONNX', pt_imag, onnx_imag)
|
| 246 |
+
compare('imag: PT vs axmodel', pt_imag, ax_imag)
|
| 247 |
+
compare('imag: ONNX vs axmodel', onnx_imag, ax_imag)
|
| 248 |
+
|
| 249 |
+
print("\n--- Audio ---")
|
| 250 |
+
compare('audio: PT vs ONNX', pt_audio, onnx_audio)
|
| 251 |
+
compare('audio: PT vs axmodel', pt_audio, ax_audio)
|
| 252 |
+
compare('audio: ONNX vs axmodel', onnx_audio, ax_audio)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def main():
|
| 256 |
+
parser = argparse.ArgumentParser()
|
| 257 |
+
parser.add_argument('--save-test-data', action='store_true')
|
| 258 |
+
parser.add_argument('--run-axmodel', action='store_true')
|
| 259 |
+
parser.add_argument('--compare', action='store_true')
|
| 260 |
+
args = parser.parse_args()
|
| 261 |
+
|
| 262 |
+
if args.save_test_data:
|
| 263 |
+
cmd_save_test_data()
|
| 264 |
+
elif args.run_axmodel:
|
| 265 |
+
cmd_run_axmodel()
|
| 266 |
+
elif args.compare:
|
| 267 |
+
cmd_compare()
|
| 268 |
+
else:
|
| 269 |
+
print("Usage: --save-test-data | --run-axmodel | --compare")
|
| 270 |
+
print("\nWorkflow:")
|
| 271 |
+
print(" 1. Dev machine: python3 compare_vocoder.py --save-test-data")
|
| 272 |
+
print(" 2. Copy TEST_DIR to board, run: python3 compare_vocoder.py --run-axmodel")
|
| 273 |
+
print(" 3. Copy ax_*.npy back to dev, run: python3 compare_vocoder.py --compare")
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
if __name__ == '__main__':
|
| 277 |
+
main()
|
cpp/scripts/export_vocos_onnx.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Export Vocos ONNX models with onnxsim optimization.
|
| 4 |
+
|
| 5 |
+
Static shapes (B=1, T=620):
|
| 6 |
+
vocos_backbone_B1_T620.onnx mel [1,100,620] → features [1,620,512]
|
| 7 |
+
vocos_head_linear_B1_T620.onnx features [1,620,512] → (real, imag) [1,620,513]
|
| 8 |
+
vocos_full_B1_T620.onnx mel [1,100,620] → (real, imag) [1,620,513]
|
| 9 |
+
|
| 10 |
+
IRFFT + window + overlap-add is handled in C++ (simple, ~50 lines with existing FFT).
|
| 11 |
+
"""
|
| 12 |
+
import sys, os, math
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
import onnx
|
| 17 |
+
from onnxsim import simplify
|
| 18 |
+
|
| 19 |
+
REPO_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 20 |
+
sys.path.insert(0, REPO_DIR)
|
| 21 |
+
from scripts.local_vocos import LocalVocos, ISTFTHead
|
| 22 |
+
|
| 23 |
+
OUT_DIR = os.path.join(REPO_DIR, 'cpp', 'vocoder_onnx')
|
| 24 |
+
os.makedirs(OUT_DIR, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class HeadLinearOnly(nn.Module):
|
| 28 |
+
"""Linear(512→1026) + mag/phase → real/imag spectrum. No ISTFT."""
|
| 29 |
+
def __init__(self, head: ISTFTHead):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.out = head.out # Linear(512, 1026)
|
| 32 |
+
def forward(self, x): # x: [B, T, 512]
|
| 33 |
+
h = self.out(x); mag, phase = h.chunk(2, dim=-1)
|
| 34 |
+
mag = torch.exp(mag).clamp(max=1e2)
|
| 35 |
+
return mag * torch.cos(phase), mag * torch.sin(phase)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class FullModel(nn.Module):
|
| 39 |
+
def __init__(self, backbone, head_linear):
|
| 40 |
+
super().__init__()
|
| 41 |
+
self.backbone = backbone
|
| 42 |
+
self.head_linear = head_linear
|
| 43 |
+
def forward(self, mel):
|
| 44 |
+
return self.head_linear(self.backbone(mel))
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def export_onnx(model, dummy, path, in_names, out_names, dyn=None):
|
| 48 |
+
tmp = path + '.tmp'
|
| 49 |
+
torch.onnx.export(model, dummy, tmp,
|
| 50 |
+
input_names=in_names, output_names=out_names,
|
| 51 |
+
dynamic_axes=dyn, opset_version=17, do_constant_folding=True)
|
| 52 |
+
model_simp, check = simplify(tmp)
|
| 53 |
+
assert check, f"onnxsim verification failed for {path}"
|
| 54 |
+
onnx.save(model_simp, path)
|
| 55 |
+
os.remove(tmp)
|
| 56 |
+
size_mb = os.path.getsize(path) / 1024 / 1024
|
| 57 |
+
print(f" {os.path.basename(path):50s} {size_mb:6.1f} MB")
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def verify(name, pt_list, onnx_list):
|
| 61 |
+
for i, (pt, onnx_val) in enumerate(zip(pt_list, onnx_list)):
|
| 62 |
+
diff = np.abs(pt - onnx_val)
|
| 63 |
+
sig = max(np.mean(np.abs(pt)), 1e-10)
|
| 64 |
+
print(f" [{name}/{i}] max_err={diff.max():.2e} rel_err={diff.mean()/sig:.2e} "
|
| 65 |
+
f"shape={pt.shape}")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def main():
|
| 69 |
+
print("Loading Vocos...")
|
| 70 |
+
vocoder = LocalVocos()
|
| 71 |
+
sd = torch.load(f'{REPO_DIR}/resources/vocos-mel-24khz/pytorch_model.bin',
|
| 72 |
+
weights_only=True, map_location='cpu')
|
| 73 |
+
sd = {k: v for k, v in sd.items() if k.startswith(('backbone.', 'head.'))}
|
| 74 |
+
vocoder.load_state_dict(sd)
|
| 75 |
+
vocoder.eval()
|
| 76 |
+
|
| 77 |
+
backbone = vocoder.backbone
|
| 78 |
+
head_linear = HeadLinearOnly(vocoder.head).eval()
|
| 79 |
+
|
| 80 |
+
B, T = 1, 620
|
| 81 |
+
dummy_mel = torch.randn(B, 100, T)
|
| 82 |
+
|
| 83 |
+
with torch.no_grad():
|
| 84 |
+
bb_out = backbone(dummy_mel)
|
| 85 |
+
pt_real, pt_imag = head_linear(bb_out)
|
| 86 |
+
|
| 87 |
+
print(f"PT output: real={pt_real.shape}, imag={pt_imag.shape}")
|
| 88 |
+
|
| 89 |
+
# --- Export static (B=1, T=620) ---
|
| 90 |
+
print("\n=== Static models (B=1, T=620) ===")
|
| 91 |
+
export_onnx(backbone, dummy_mel,
|
| 92 |
+
f'{OUT_DIR}/vocos_backbone_B1_T620.onnx',
|
| 93 |
+
['mel'], ['features'])
|
| 94 |
+
export_onnx(head_linear, bb_out,
|
| 95 |
+
f'{OUT_DIR}/vocos_head_linear_B1_T620.onnx',
|
| 96 |
+
['features'], ['real', 'imag'])
|
| 97 |
+
export_onnx(FullModel(backbone, head_linear).eval(), dummy_mel,
|
| 98 |
+
f'{OUT_DIR}/vocos_full_B1_T620.onnx',
|
| 99 |
+
['mel'], ['real', 'imag'])
|
| 100 |
+
|
| 101 |
+
# --- Verify with onnxruntime ---
|
| 102 |
+
print("\n=== Verification ===")
|
| 103 |
+
import onnxruntime as ort
|
| 104 |
+
|
| 105 |
+
for tag, model_file, input_dict, pt_expected in [
|
| 106 |
+
('backbone', 'vocos_backbone_B1_T620.onnx',
|
| 107 |
+
{'mel': dummy_mel.numpy()}, [bb_out.numpy()]),
|
| 108 |
+
('head_linear', 'vocos_head_linear_B1_T620.onnx',
|
| 109 |
+
{'features': bb_out.numpy()}, [pt_real.numpy(), pt_imag.numpy()]),
|
| 110 |
+
('full', 'vocos_full_B1_T620.onnx',
|
| 111 |
+
{'mel': dummy_mel.numpy()}, [pt_real.numpy(), pt_imag.numpy()]),
|
| 112 |
+
]:
|
| 113 |
+
sess = ort.InferenceSession(f'{OUT_DIR}/{model_file}')
|
| 114 |
+
onnx_out = sess.run(None, input_dict)
|
| 115 |
+
verify(tag, pt_expected, onnx_out)
|
| 116 |
+
|
| 117 |
+
print("\nDone! All exported and verified.")
|
| 118 |
+
for f in sorted(os.listdir(OUT_DIR)):
|
| 119 |
+
if 'B1_T620' in f:
|
| 120 |
+
size = os.path.getsize(f'{OUT_DIR}/{f}') / 1024 / 1024
|
| 121 |
+
print(f" {f} ({size:.1f} MB)")
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == '__main__':
|
| 125 |
+
main()
|
cpp/scripts/gen_cat_tokens.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate cat_tokens binary for C++ ZipVoice inference. Works for any language.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python3 gen_cat_tokens.py --prompt "prompt text" --text "target text"
|
| 6 |
+
python3 gen_cat_tokens.py --prompt-file prompt.txt --text-file target.txt
|
| 7 |
+
"""
|
| 8 |
+
import sys, os, argparse
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
REPO_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # cpp/scripts → repo root
|
| 12 |
+
sys.path.insert(0, REPO_DIR)
|
| 13 |
+
|
| 14 |
+
from scripts.local_tokenizer import LocalEmiliaTokenizer
|
| 15 |
+
from scripts.text_processing import normalize_punctuation
|
| 16 |
+
|
| 17 |
+
parser = argparse.ArgumentParser()
|
| 18 |
+
parser.add_argument('--prompt', default='')
|
| 19 |
+
parser.add_argument('--text', default='')
|
| 20 |
+
parser.add_argument('--prompt-file', default='')
|
| 21 |
+
parser.add_argument('--text-file', default='')
|
| 22 |
+
parser.add_argument('--output', default='cat_tokens.bin')
|
| 23 |
+
parser.add_argument('--max-tokens', type=int, default=384)
|
| 24 |
+
args = parser.parse_args()
|
| 25 |
+
|
| 26 |
+
prompt = args.prompt or (open(args.prompt_file).read().strip() if args.prompt_file else '')
|
| 27 |
+
text = args.text or (open(args.text_file).read().strip() if args.text_file else '')
|
| 28 |
+
|
| 29 |
+
if not prompt or not text:
|
| 30 |
+
print("ERROR: provide --prompt/--prompt-file and --text/--text-file")
|
| 31 |
+
sys.exit(1)
|
| 32 |
+
|
| 33 |
+
token_file = os.path.join(REPO_DIR, 'resources', 'zipvoice_hf', 'zipvoice', 'tokens.txt')
|
| 34 |
+
tokenizer = LocalEmiliaTokenizer(token_file=token_file)
|
| 35 |
+
|
| 36 |
+
pids = tokenizer.texts_to_token_ids([normalize_punctuation(prompt)])[0]
|
| 37 |
+
tids = tokenizer.texts_to_token_ids([normalize_punctuation(text)])[0]
|
| 38 |
+
|
| 39 |
+
cat = pids + tids + [tokenizer.pad_id]
|
| 40 |
+
ct = np.full((args.max_tokens,), tokenizer.pad_id, dtype=np.int32)
|
| 41 |
+
ct[:len(cat)] = np.array(cat, dtype=np.int32)
|
| 42 |
+
ct.tofile(args.output)
|
| 43 |
+
|
| 44 |
+
print(f'prompt_tokens_len={len(pids)} text_tokens_len={len(tids)} saved: {args.output}')
|
cpp/scripts/gen_pinyin_table.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate C++ header with Chinese character → pinyin token mapping."""
|
| 3 |
+
import sys, os
|
| 4 |
+
REPO_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 5 |
+
sys.path.insert(0, REPO_DIR)
|
| 6 |
+
|
| 7 |
+
from pypinyin.pinyin_dict import pinyin_dict
|
| 8 |
+
from pypinyin.contrib.tone_convert import to_initials, to_finals_tone3
|
| 9 |
+
|
| 10 |
+
TOKEN_FILE = os.path.join(REPO_DIR, 'resources', 'zipvoice_hf', 'zipvoice', 'tokens.txt')
|
| 11 |
+
OUTPUT = os.path.join(REPO_DIR, 'cpp', 'src', 'pinyin_table.hpp')
|
| 12 |
+
|
| 13 |
+
# Load token2id
|
| 14 |
+
token2id = {}
|
| 15 |
+
with open(TOKEN_FILE) as f:
|
| 16 |
+
for line in f:
|
| 17 |
+
if '\t' in line:
|
| 18 |
+
t, tid = line.rstrip().split('\t')
|
| 19 |
+
token2id[t] = int(tid)
|
| 20 |
+
|
| 21 |
+
# Build char → token IDs
|
| 22 |
+
char_entries = []
|
| 23 |
+
for codepoint, pinyins in pinyin_dict.items():
|
| 24 |
+
ch = chr(codepoint)
|
| 25 |
+
py = pinyins.split(',')[0]
|
| 26 |
+
initial = to_initials(py, strict=False)
|
| 27 |
+
final = to_finals_tone3(py, strict=False, neutral_tone_with_five=True)
|
| 28 |
+
if not final: continue
|
| 29 |
+
|
| 30 |
+
ini_token = initial + '0' if initial else ''
|
| 31 |
+
tokens = []
|
| 32 |
+
if ini_token and ini_token in token2id:
|
| 33 |
+
tokens.append(token2id[ini_token])
|
| 34 |
+
if final in token2id:
|
| 35 |
+
tokens.append(token2id[final])
|
| 36 |
+
|
| 37 |
+
if tokens:
|
| 38 |
+
char_entries.append((codepoint, tokens))
|
| 39 |
+
|
| 40 |
+
# Sort by codepoint for binary search
|
| 41 |
+
char_entries.sort(key=lambda x: x[0])
|
| 42 |
+
|
| 43 |
+
# Generate C++ header
|
| 44 |
+
with open(OUTPUT, 'w') as f:
|
| 45 |
+
f.write('''// Auto-generated Chinese character → token ID mapping
|
| 46 |
+
// Source: pypinyin.pinyin_dict, tokens.txt
|
| 47 |
+
// Generated by gen_pinyin_table.py
|
| 48 |
+
|
| 49 |
+
#pragma once
|
| 50 |
+
#include <cstdint>
|
| 51 |
+
#include <string>
|
| 52 |
+
#include <vector>
|
| 53 |
+
|
| 54 |
+
struct PinyinEntry {
|
| 55 |
+
uint32_t codepoint;
|
| 56 |
+
int16_t token1;
|
| 57 |
+
int16_t token2; // -1 if only one token
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
// Sorted by codepoint, use binary search
|
| 61 |
+
static const PinyinEntry PINYIN_TABLE[] = {
|
| 62 |
+
''')
|
| 63 |
+
|
| 64 |
+
for codepoint, tokens in char_entries:
|
| 65 |
+
t1 = tokens[0]
|
| 66 |
+
t2 = tokens[1] if len(tokens) > 1 else -1
|
| 67 |
+
f.write(f' {{0x{codepoint:04X}, {t1}, {t2}}}, // {chr(codepoint)}\n')
|
| 68 |
+
|
| 69 |
+
f.write(f'''}};
|
| 70 |
+
|
| 71 |
+
static const int PINYIN_TABLE_SIZE = {len(char_entries)};
|
| 72 |
+
|
| 73 |
+
// Lookup tokens for a Chinese character. Returns empty vector if not found.
|
| 74 |
+
inline std::vector<int> pinyin_lookup(uint32_t codepoint) {{
|
| 75 |
+
// Binary search
|
| 76 |
+
int lo = 0, hi = PINYIN_TABLE_SIZE - 1;
|
| 77 |
+
while (lo <= hi) {{
|
| 78 |
+
int mid = (lo + hi) / 2;
|
| 79 |
+
if (PINYIN_TABLE[mid].codepoint == codepoint) {{
|
| 80 |
+
std::vector<int> result;
|
| 81 |
+
result.push_back(PINYIN_TABLE[mid].token1);
|
| 82 |
+
if (PINYIN_TABLE[mid].token2 >= 0)
|
| 83 |
+
result.push_back(PINYIN_TABLE[mid].token2);
|
| 84 |
+
return result;
|
| 85 |
+
}}
|
| 86 |
+
if (PINYIN_TABLE[mid].codepoint < codepoint)
|
| 87 |
+
lo = mid + 1;
|
| 88 |
+
else
|
| 89 |
+
hi = mid - 1;
|
| 90 |
+
}}
|
| 91 |
+
return {{}};
|
| 92 |
+
}}
|
| 93 |
+
''')
|
| 94 |
+
|
| 95 |
+
print(f"Generated {OUTPUT}: {len(char_entries)} entries")
|
cpp/scripts/generate_vocoder_calib.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Generate vocoder calibration data on dev machine (no axengine needed).
|
| 4 |
+
|
| 5 |
+
Collects mel features from:
|
| 6 |
+
1. Real mel output files (output_mel.bin from previous C++ runs)
|
| 7 |
+
2. Random mel features in the expected value range
|
| 8 |
+
3. Zero/silence mel features
|
| 9 |
+
Then runs them through ONNX to get intermediate features for head_linear calibration.
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
python3 generate_vocoder_calib.py --output-dir ./calib_data_vocoder
|
| 13 |
+
"""
|
| 14 |
+
import sys, os, argparse, json, glob
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
import numpy as np
|
| 17 |
+
import onnxruntime as ort
|
| 18 |
+
|
| 19 |
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
| 20 |
+
REPO_DIR = SCRIPT_DIR.parent.parent # scripts/ → cpp/ → repo root
|
| 21 |
+
ONNX_DIR = SCRIPT_DIR.parent / "vocoder_onnx" # cpp/vocoder_onnx
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
parser = argparse.ArgumentParser()
|
| 26 |
+
parser.add_argument("--output-dir", default=str(ONNX_DIR / "calib_data"))
|
| 27 |
+
parser.add_argument("--num-samples", type=int, default=16)
|
| 28 |
+
parser.add_argument("--seed", type=int, default=42)
|
| 29 |
+
args = parser.parse_args()
|
| 30 |
+
|
| 31 |
+
out_dir = Path(args.output_dir)
|
| 32 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 33 |
+
rng = np.random.RandomState(args.seed)
|
| 34 |
+
|
| 35 |
+
T = 620
|
| 36 |
+
mel_samples = []
|
| 37 |
+
|
| 38 |
+
# --- Source 1: Real mel features if available (undo feat_scale to match vocoder input) ---
|
| 39 |
+
feat_scale = 0.1
|
| 40 |
+
for pattern in ["output_mel.bin", "output_mel_debug.bin"]:
|
| 41 |
+
for p in [REPO_DIR / pattern, SCRIPT_DIR / pattern]:
|
| 42 |
+
if p.exists():
|
| 43 |
+
mel = np.fromfile(str(p), dtype=np.float32).reshape(-1, 100).T[np.newaxis, :, :] / feat_scale # [1, 100, frames]
|
| 44 |
+
gen_frames = min(mel.shape[2], T)
|
| 45 |
+
padded = np.zeros((1, 100, T), dtype=np.float32)
|
| 46 |
+
padded[0, :, :gen_frames] = mel[0, :, :gen_frames]
|
| 47 |
+
mel_samples.append(padded)
|
| 48 |
+
print(f"Real mel from {p}: shape={mel.shape}")
|
| 49 |
+
|
| 50 |
+
# --- Source 2: Random mel in vocoder input range (after /feat_scale) ---
|
| 51 |
+
# Python: features / 0.1 before vocoder → range ~[-8.6, 4.0]
|
| 52 |
+
# So calibration mel should be in [feat_scaled] range, divided by feat_scale
|
| 53 |
+
# to match the actual vocoder input distribution
|
| 54 |
+
feat_scale = 0.1
|
| 55 |
+
for i in range(max(0, args.num_samples - len(mel_samples))):
|
| 56 |
+
length = rng.randint(100, T + 1)
|
| 57 |
+
# Generate in feat_scaled range, then undo feat_scale
|
| 58 |
+
mel = (rng.randn(1, 100, T).astype(np.float32) * 0.3 - 0.04) / feat_scale
|
| 59 |
+
mel[0, :, length:] = 0.0
|
| 60 |
+
mel_samples.append(mel)
|
| 61 |
+
|
| 62 |
+
# --- Source 3: Edge cases (with feat_scale undo) ---
|
| 63 |
+
mel_samples.append(np.zeros((1, 100, T), dtype=np.float32))
|
| 64 |
+
mel_samples.append(np.ones((1, 100, T), dtype=np.float32) * 0.5 / feat_scale)
|
| 65 |
+
mel_samples.append(rng.randn(1, 100, T).astype(np.float32) * 0.1 / feat_scale)
|
| 66 |
+
mel_samples.append(rng.randn(1, 100, T).astype(np.float32) / feat_scale)
|
| 67 |
+
|
| 68 |
+
print(f"\nTotal mel samples: {len(mel_samples)}")
|
| 69 |
+
|
| 70 |
+
# --- Run ONNX to get backbone outputs (for head_linear calibration) ---
|
| 71 |
+
bb_path = ONNX_DIR / "vocos_backbone_B1_T620.onnx"
|
| 72 |
+
hl_path = ONNX_DIR / "vocos_head_linear_B1_T620.onnx"
|
| 73 |
+
|
| 74 |
+
if not bb_path.exists():
|
| 75 |
+
print(f"ERROR: {bb_path} not found. Run export_vocos_onnx.py first.")
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
sess_bb = ort.InferenceSession(str(bb_path))
|
| 79 |
+
print(f"Loaded backbone: {bb_path}")
|
| 80 |
+
|
| 81 |
+
# --- Save backbone calibration data ---
|
| 82 |
+
bb_dir = out_dir / "vocos_backbone" / "mel"
|
| 83 |
+
bb_dir.mkdir(parents=True, exist_ok=True)
|
| 84 |
+
bb_entries = []
|
| 85 |
+
backbone_outputs = []
|
| 86 |
+
|
| 87 |
+
for i, mel in enumerate(mel_samples):
|
| 88 |
+
np.save(bb_dir / f"{i:04d}.npy", mel)
|
| 89 |
+
bb_entries.append({"file": f"mel/{i:04d}.npy", "shape": list(mel.shape)})
|
| 90 |
+
|
| 91 |
+
# Run ONNX to get head_linear input
|
| 92 |
+
feat = sess_bb.run(None, {'mel': mel})[0]
|
| 93 |
+
backbone_outputs.append(feat)
|
| 94 |
+
|
| 95 |
+
print(f"Backbone calibration: {len(bb_entries)} samples")
|
| 96 |
+
|
| 97 |
+
# --- Save head_linear calibration data ---
|
| 98 |
+
hl_dir = out_dir / "vocos_head_linear" / "features"
|
| 99 |
+
hl_dir.mkdir(parents=True, exist_ok=True)
|
| 100 |
+
hl_entries = []
|
| 101 |
+
|
| 102 |
+
for i, feat in enumerate(backbone_outputs):
|
| 103 |
+
np.save(hl_dir / f"{i:04d}.npy", feat)
|
| 104 |
+
hl_entries.append({"file": f"features/{i:04d}.npy", "shape": list(feat.shape)})
|
| 105 |
+
|
| 106 |
+
print(f"Head_linear calibration: {len(hl_entries)} samples")
|
| 107 |
+
|
| 108 |
+
# --- Verify head_linear ONNX with calibration data ---
|
| 109 |
+
if hl_path.exists():
|
| 110 |
+
sess_hl = ort.InferenceSession(str(hl_path))
|
| 111 |
+
for i in range(min(3, len(backbone_outputs))):
|
| 112 |
+
r, im = sess_hl.run(None, {'features': backbone_outputs[i]})
|
| 113 |
+
print(f" Verify head[{i}]: real range=[{r.min():.3f},{r.max():.3f}], "
|
| 114 |
+
f"imag range=[{im.min():.3f},{im.max():.3f}]")
|
| 115 |
+
|
| 116 |
+
# --- Manifest ---
|
| 117 |
+
manifest = {
|
| 118 |
+
"description": "Vocoder ONNX calibration data",
|
| 119 |
+
"backbone": {
|
| 120 |
+
"model": "vocos_backbone_B1_T620.onnx",
|
| 121 |
+
"input": "mel", "shape": [1, 100, 620], "dtype": "float32",
|
| 122 |
+
"num_samples": len(bb_entries),
|
| 123 |
+
"files": bb_entries,
|
| 124 |
+
},
|
| 125 |
+
"head_linear": {
|
| 126 |
+
"model": "vocos_head_linear_B1_T620.onnx",
|
| 127 |
+
"input": "features", "shape": [1, 620, 512], "dtype": "float32",
|
| 128 |
+
"num_samples": len(hl_entries),
|
| 129 |
+
"files": hl_entries,
|
| 130 |
+
},
|
| 131 |
+
}
|
| 132 |
+
with open(out_dir / "calib_manifest.json", "w") as f:
|
| 133 |
+
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
| 134 |
+
|
| 135 |
+
print(f"\nDone! Output: {out_dir}")
|
| 136 |
+
print(f" vocos_backbone/mel/ : {len(bb_entries)} .npy files")
|
| 137 |
+
print(f" vocos_head_linear/features/: {len(hl_entries)} .npy files")
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
if __name__ == "__main__":
|
| 141 |
+
main()
|
cpp/scripts/py_daemon.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
ZipVoice Python daemon: loads tokenizer once, handles C++ requests via stdin/stdout.
|
| 4 |
+
Protocol (tab-separated lines to preserve spaces in text paths):
|
| 5 |
+
count\t<prompt_file>\t<text_file>
|
| 6 |
+
-> prints "COUNT <prompt_len> <text_len>"
|
| 7 |
+
tokenize\t<prompt_file>\t<text_file>\t<max_tokens>\t<output_bin>
|
| 8 |
+
-> prints "TOKENS <prompt_len> <text_len>"
|
| 9 |
+
quit
|
| 10 |
+
-> exits
|
| 11 |
+
"""
|
| 12 |
+
import sys, os
|
| 13 |
+
import numpy as np
|
| 14 |
+
|
| 15 |
+
REPO_DIR = sys.argv[1]
|
| 16 |
+
sys.path.insert(0, REPO_DIR)
|
| 17 |
+
|
| 18 |
+
from scripts.local_tokenizer import LocalEmiliaTokenizer
|
| 19 |
+
from scripts.text_processing import normalize_punctuation
|
| 20 |
+
|
| 21 |
+
TOKEN_FILE = os.path.join(REPO_DIR, "resources", "zipvoice_hf", "zipvoice", "tokens.txt")
|
| 22 |
+
tokenizer = LocalEmiliaTokenizer(token_file=TOKEN_FILE)
|
| 23 |
+
|
| 24 |
+
print("READY", flush=True)
|
| 25 |
+
|
| 26 |
+
for line in sys.stdin:
|
| 27 |
+
line = line.rstrip("\n")
|
| 28 |
+
if not line:
|
| 29 |
+
continue
|
| 30 |
+
parts = line.split("\t")
|
| 31 |
+
cmd = parts[0]
|
| 32 |
+
|
| 33 |
+
if cmd == "count":
|
| 34 |
+
prompt_file, text_file = parts[1], parts[2]
|
| 35 |
+
pt = normalize_punctuation(open(prompt_file).read().strip())
|
| 36 |
+
tt = normalize_punctuation(open(text_file).read().strip())
|
| 37 |
+
pids = tokenizer.texts_to_token_ids([pt])[0]
|
| 38 |
+
tids = tokenizer.texts_to_token_ids([tt])[0]
|
| 39 |
+
print(f"COUNT {len(pids)} {len(tids)}", flush=True)
|
| 40 |
+
|
| 41 |
+
elif cmd == "tokenize":
|
| 42 |
+
prompt_file, text_file, max_tokens, output_bin = parts[1], parts[2], int(parts[3]), parts[4]
|
| 43 |
+
pt = normalize_punctuation(open(prompt_file).read().strip())
|
| 44 |
+
tt = normalize_punctuation(open(text_file).read().strip())
|
| 45 |
+
pids = tokenizer.texts_to_token_ids([pt])[0]
|
| 46 |
+
tids = tokenizer.texts_to_token_ids([tt])[0]
|
| 47 |
+
cat = pids + tids + [tokenizer.pad_id]
|
| 48 |
+
if len(cat) > max_tokens:
|
| 49 |
+
print(f"ERROR too_many_tokens {len(cat)}>{max_tokens}", flush=True)
|
| 50 |
+
continue
|
| 51 |
+
ct = np.full((max_tokens,), tokenizer.pad_id, dtype=np.int32)
|
| 52 |
+
ct[:len(cat)] = np.array(cat, dtype=np.int32)
|
| 53 |
+
ct.tofile(output_bin)
|
| 54 |
+
print(f"TOKENS {len(pids)} {len(tids)}", flush=True)
|
| 55 |
+
|
| 56 |
+
elif cmd == "quit":
|
| 57 |
+
break
|
cpp/scripts/quantize_vocoder.sh
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Quantize vocoder ONNX models to axmodel using pulsar2.
|
| 3 |
+
#
|
| 4 |
+
# Prerequisites:
|
| 5 |
+
# 1. export_vocos_onnx.py (generates vocoder_onnx/*.onnx)
|
| 6 |
+
# 2. generate_vocoder_calib.py (generates vocoder_onnx/calib_data/)
|
| 7 |
+
# 3. source <npu_dev_env> (activates pulsar2, e.g. $HOME/npu-codebase/script/npu_dev)
|
| 8 |
+
#
|
| 9 |
+
# Usage:
|
| 10 |
+
# bash quantize_vocoder.sh
|
| 11 |
+
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 15 |
+
PARENT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
| 16 |
+
cd "${PARENT_DIR}"
|
| 17 |
+
|
| 18 |
+
ONNX_DIR="${PARENT_DIR}/vocoder_onnx"
|
| 19 |
+
CALIB_DIR="${ONNX_DIR}/calib_data"
|
| 20 |
+
BUILD_DIR="${ONNX_DIR}/pulsar2_build"
|
| 21 |
+
AXMODEL_DIR="${ONNX_DIR}/axmodel"
|
| 22 |
+
|
| 23 |
+
# --- Config ---
|
| 24 |
+
TARGET_HARDWARE="${TARGET_HARDWARE:-AX650}"
|
| 25 |
+
NPU_MODE="${NPU_MODE:-NPU3}"
|
| 26 |
+
CHECK_LEVEL="${CHECK_LEVEL:-0}"
|
| 27 |
+
DEFAULT_DATA_TYPE="${DEFAULT_DATA_TYPE:-U16}"
|
| 28 |
+
CALIBRATION_SIZE="${CALIBRATION_SIZE:--1}"
|
| 29 |
+
|
| 30 |
+
FULL_MODEL="${ONNX_DIR}/vocos_full_B1_T620.onnx"
|
| 31 |
+
CALIB_DIR_BB="${CALIB_DIR}/vocos_backbone"
|
| 32 |
+
|
| 33 |
+
if [ ! -f "${FULL_MODEL}" ]; then
|
| 34 |
+
echo "ERROR: ${FULL_MODEL} not found. Run export_vocos_onnx.py first."
|
| 35 |
+
exit 1
|
| 36 |
+
fi
|
| 37 |
+
|
| 38 |
+
if [ ! -d "${CALIB_DIR_BB}" ]; then
|
| 39 |
+
echo "ERROR: ${CALIB_DIR_BB} not found. Run generate_vocoder_calib.py first."
|
| 40 |
+
exit 1
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
# Activate pulsar2 if not already available
|
| 44 |
+
if ! command -v pulsar2 >/dev/null 2>&1; then
|
| 45 |
+
echo "Activating pulsar2 environment..."
|
| 46 |
+
set +u
|
| 47 |
+
source "${NPU_DEV_ENV:-$HOME/npu-codebase/script/npu_dev}"
|
| 48 |
+
set -u
|
| 49 |
+
fi
|
| 50 |
+
|
| 51 |
+
mkdir -p "${BUILD_DIR}" "${AXMODEL_DIR}"
|
| 52 |
+
|
| 53 |
+
# --- Step 1: Package calibration data as tar.gz ---
|
| 54 |
+
echo "=== Packaging calibration data ==="
|
| 55 |
+
|
| 56 |
+
# Calibration for full model: mel -> (real, imag)
|
| 57 |
+
# Input tensor is "mel", calibration data in vocos_backbone/mel/
|
| 58 |
+
FULL_CALIB_TAR="${CALIB_DIR}/vocos_full_mel.tar.gz"
|
| 59 |
+
INPUT_DIR="${CALIB_DIR_BB}/mel"
|
| 60 |
+
|
| 61 |
+
if [ ! -f "${FULL_CALIB_TAR}" ]; then
|
| 62 |
+
echo "Creating ${FULL_CALIB_TAR}..."
|
| 63 |
+
cd "${INPUT_DIR}"
|
| 64 |
+
tar czf "${FULL_CALIB_TAR}" --transform 's|.*/||' *.npy
|
| 65 |
+
cd "${SCRIPT_DIR}"
|
| 66 |
+
fi
|
| 67 |
+
echo " ${FULL_CALIB_TAR} ($(du -h "${FULL_CALIB_TAR}" | cut -f1))"
|
| 68 |
+
|
| 69 |
+
# --- Step 2: Build pulsar2 config ---
|
| 70 |
+
echo ""
|
| 71 |
+
echo "=== Quantizing vocos_full ==="
|
| 72 |
+
|
| 73 |
+
CONFIG="${BUILD_DIR}/config_vocos_full.json"
|
| 74 |
+
|
| 75 |
+
cat > "${CONFIG}" << EOF
|
| 76 |
+
{
|
| 77 |
+
"model_type": "ONNX",
|
| 78 |
+
"npu_mode": "${NPU_MODE}",
|
| 79 |
+
"input": "${FULL_MODEL}",
|
| 80 |
+
"output_name": "vocos_full.axmodel",
|
| 81 |
+
"output_dir": "${BUILD_DIR}",
|
| 82 |
+
"target_hardware": "${TARGET_HARDWARE}",
|
| 83 |
+
"onnx_opt": {"disable_onnx_optimization": false, "enable_onnxsim": true},
|
| 84 |
+
"quant": {
|
| 85 |
+
"input_configs": [
|
| 86 |
+
{"tensor_name": "mel", "calibration_dataset": "${FULL_CALIB_TAR}", "calibration_format": "Numpy", "calibration_size": ${CALIBRATION_SIZE}}
|
| 87 |
+
],
|
| 88 |
+
"layer_configs": [
|
| 89 |
+
{"op_type": "Pow", "data_type": "U8"},
|
| 90 |
+
{"start_tensor_names": ["DEFAULT"], "end_tensor_names": ["DEFAULT"], "data_type": "${DEFAULT_DATA_TYPE}"}
|
| 91 |
+
],
|
| 92 |
+
"calibration_method": "MinMax",
|
| 93 |
+
"enable_smooth_quant": true,
|
| 94 |
+
"conv_bias_data_type": "FP32",
|
| 95 |
+
"precision_analysis": true,
|
| 96 |
+
"precision_analysis_method": "EndToEnd",
|
| 97 |
+
"disable_auto_refine_scale": true,
|
| 98 |
+
"transformer_opt_level": 0
|
| 99 |
+
},
|
| 100 |
+
"input_processors": [{"tensor_name": "DEFAULT"}],
|
| 101 |
+
"compiler": {"check": ${CHECK_LEVEL}, "enable_slice_mode": false}
|
| 102 |
+
}
|
| 103 |
+
EOF
|
| 104 |
+
|
| 105 |
+
echo "Config: ${CONFIG}"
|
| 106 |
+
echo "Input shapes: mel:1x100x620"
|
| 107 |
+
|
| 108 |
+
# --- Step 3: Run pulsar2 ---
|
| 109 |
+
echo ""
|
| 110 |
+
echo "=== Running pulsar2 build ==="
|
| 111 |
+
pulsar2 build --config "${CONFIG}" --input_shapes "mel:1x100x620" 2>&1 | tee "${BUILD_DIR}/build_vocos_full.log"
|
| 112 |
+
|
| 113 |
+
# --- Step 4: Copy output ---
|
| 114 |
+
BUILD_OUTPUT="${BUILD_DIR}/vocos_full.axmodel"
|
| 115 |
+
if [ -f "${BUILD_OUTPUT}" ]; then
|
| 116 |
+
cp "${BUILD_OUTPUT}" "${AXMODEL_DIR}/"
|
| 117 |
+
echo ""
|
| 118 |
+
echo "=== Done! ==="
|
| 119 |
+
ls -lh "${AXMODEL_DIR}/vocos_full.axmodel"
|
| 120 |
+
else
|
| 121 |
+
echo "ERROR: Build failed, ${BUILD_OUTPUT} not found"
|
| 122 |
+
exit 1
|
| 123 |
+
fi
|
cpp/src/EngineWrapper.cpp
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* EngineWrapper implementation
|
| 5 |
+
**************************************************************************************************/
|
| 6 |
+
|
| 7 |
+
#include "EngineWrapper.hpp"
|
| 8 |
+
#include "utils/io.hpp"
|
| 9 |
+
|
| 10 |
+
#include <cstdlib>
|
| 11 |
+
|
| 12 |
+
static const char *strAlgoModelType[AX_ENGINE_MODEL_TYPE_BUTT] = {"3.6T", "7.2T", "10.8T"};
|
| 13 |
+
|
| 14 |
+
// NPU type enum
|
| 15 |
+
typedef enum axNPU_TYPE_E {
|
| 16 |
+
AX_NPU_DEFAULT = 0,
|
| 17 |
+
AX_STD_VNPU_1 = (1 << 0),
|
| 18 |
+
AX_STD_VNPU_2 = (1 << 1),
|
| 19 |
+
AX_STD_VNPU_3 = (1 << 2),
|
| 20 |
+
AX_BL_VNPU_1 = (1 << 3),
|
| 21 |
+
AX_BL_VNPU_2 = (1 << 4)
|
| 22 |
+
} AX_NPU_TYPE_E;
|
| 23 |
+
|
| 24 |
+
static AX_S32 CheckModelVNpu(const std::string &strModel,
|
| 25 |
+
const AX_ENGINE_MODEL_TYPE_T &eModelType,
|
| 26 |
+
const AX_S32 &nNpuType, AX_U32 &nNpuSet) {
|
| 27 |
+
AX_ENGINE_NPU_ATTR_T stNpuAttr;
|
| 28 |
+
memset(&stNpuAttr, 0x00, sizeof(stNpuAttr));
|
| 29 |
+
|
| 30 |
+
auto ret = AX_ENGINE_GetVNPUAttr(&stNpuAttr);
|
| 31 |
+
if (ret == 0) {
|
| 32 |
+
if (stNpuAttr.eHardMode == AX_ENGINE_VIRTUAL_NPU_DISABLE) {
|
| 33 |
+
nNpuSet = 0x01;
|
| 34 |
+
} else if (stNpuAttr.eHardMode == AX_ENGINE_VIRTUAL_NPU_STD) {
|
| 35 |
+
if (eModelType == AX_ENGINE_MODEL_TYPE1 || eModelType == AX_ENGINE_MODEL_TYPE2)
|
| 36 |
+
return -1;
|
| 37 |
+
if (nNpuType == 0) nNpuSet = 0x02;
|
| 38 |
+
else {
|
| 39 |
+
if (nNpuType & AX_STD_VNPU_1) nNpuSet |= 0x01;
|
| 40 |
+
if (nNpuType & AX_STD_VNPU_2) nNpuSet |= 0x02;
|
| 41 |
+
if (nNpuType & AX_STD_VNPU_3) nNpuSet |= 0x04;
|
| 42 |
+
}
|
| 43 |
+
} else if (stNpuAttr.eHardMode == AX_ENGINE_VIRTUAL_NPU_BIG_LITTLE) {
|
| 44 |
+
if (eModelType == AX_ENGINE_MODEL_TYPE2) return -1;
|
| 45 |
+
if (nNpuType == 0) {
|
| 46 |
+
nNpuSet = (eModelType == AX_ENGINE_MODEL_TYPE1) ? 0x01 : 0x02;
|
| 47 |
+
} else {
|
| 48 |
+
if (eModelType == AX_ENGINE_MODEL_TYPE1) {
|
| 49 |
+
if (nNpuType & AX_BL_VNPU_2) return -1;
|
| 50 |
+
if (nNpuType & AX_BL_VNPU_1) nNpuSet |= 0x01;
|
| 51 |
+
} else {
|
| 52 |
+
if (nNpuType & AX_BL_VNPU_1) nNpuSet |= 0x01;
|
| 53 |
+
if (nNpuType & AX_BL_VNPU_2) nNpuSet |= 0x02;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
return ret;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
EngineWrapper::EngineWrapper()
|
| 62 |
+
: m_hasInit(false), m_handle(nullptr), m_io_info(nullptr),
|
| 63 |
+
m_input_num(0), m_output_num(0) {
|
| 64 |
+
memset(&m_io, 0, sizeof(m_io));
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
EngineWrapper::~EngineWrapper() {
|
| 68 |
+
Release();
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
int EngineWrapper::Init(const char* strModelPath, uint32_t nNpuType) {
|
| 72 |
+
// 1. Load model
|
| 73 |
+
AX_BOOL bLoadModelUseCmm = AX_TRUE;
|
| 74 |
+
AX_CHAR *pModelBufferVirAddr = nullptr;
|
| 75 |
+
AX_U64 u64ModelBufferPhyAddr = 0;
|
| 76 |
+
AX_U32 nModelBufferSize = 0;
|
| 77 |
+
|
| 78 |
+
if (bLoadModelUseCmm) {
|
| 79 |
+
if (!utils::read_file(strModelPath, (AX_VOID **)&pModelBufferVirAddr,
|
| 80 |
+
u64ModelBufferPhyAddr, nModelBufferSize)) {
|
| 81 |
+
printf("Read model(%s) fail\n", strModelPath);
|
| 82 |
+
return -1;
|
| 83 |
+
}
|
| 84 |
+
} else {
|
| 85 |
+
std::vector<char> model_buffer;
|
| 86 |
+
if (!utils::read_file(strModelPath, model_buffer)) {
|
| 87 |
+
printf("Read model(%s) fail\n", strModelPath);
|
| 88 |
+
return -1;
|
| 89 |
+
}
|
| 90 |
+
pModelBufferVirAddr = model_buffer.data();
|
| 91 |
+
nModelBufferSize = model_buffer.size();
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
auto freeModelBuffer = [&]() {
|
| 95 |
+
if (bLoadModelUseCmm) {
|
| 96 |
+
if (u64ModelBufferPhyAddr != 0)
|
| 97 |
+
AX_SYS_MemFree(u64ModelBufferPhyAddr, &pModelBufferVirAddr);
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
// 1.1 Get Model Type
|
| 102 |
+
AX_ENGINE_MODEL_TYPE_T eModelType = AX_ENGINE_MODEL_TYPE0;
|
| 103 |
+
AX_S32 ret = AX_ENGINE_GetModelType(pModelBufferVirAddr, nModelBufferSize, &eModelType);
|
| 104 |
+
if (0 != ret || eModelType >= AX_ENGINE_MODEL_TYPE_BUTT) {
|
| 105 |
+
printf("%s AX_ENGINE_GetModelType fail ret=%x\n", strModelPath, ret);
|
| 106 |
+
freeModelBuffer();
|
| 107 |
+
return -1;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// 1.2 Check VNPU
|
| 111 |
+
AX_U32 nNpuSet = 0;
|
| 112 |
+
ret = CheckModelVNpu(strModelPath, eModelType, nNpuType, nNpuSet);
|
| 113 |
+
if (0 != ret) {
|
| 114 |
+
printf("CheckModelVNpu fail\n");
|
| 115 |
+
freeModelBuffer();
|
| 116 |
+
return -1;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// 2. Create handle
|
| 120 |
+
AX_ENGINE_HANDLE handle = nullptr;
|
| 121 |
+
ret = AX_ENGINE_CreateHandle(&handle, pModelBufferVirAddr, nModelBufferSize);
|
| 122 |
+
freeModelBuffer();
|
| 123 |
+
|
| 124 |
+
auto deinit_handle = [&handle]() {
|
| 125 |
+
if (handle) { AX_ENGINE_DestroyHandle(handle); }
|
| 126 |
+
return -1;
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
+
if (0 != ret || !handle) {
|
| 130 |
+
printf("Create model(%s) handle fail\n", strModelPath);
|
| 131 |
+
return deinit_handle();
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// 3. Create context
|
| 135 |
+
ret = AX_ENGINE_CreateContext(handle);
|
| 136 |
+
if (0 != ret) return deinit_handle();
|
| 137 |
+
|
| 138 |
+
// 4. Get IO info
|
| 139 |
+
m_io_info = nullptr;
|
| 140 |
+
ret = AX_ENGINE_GetIOInfo(handle, &m_io_info);
|
| 141 |
+
if (0 != ret) return deinit_handle();
|
| 142 |
+
|
| 143 |
+
m_input_num = m_io_info->nInputSize;
|
| 144 |
+
m_output_num = m_io_info->nOutputSize;
|
| 145 |
+
|
| 146 |
+
// Build name-to-index maps
|
| 147 |
+
m_input_name_to_idx.clear();
|
| 148 |
+
for (int i = 0; i < m_input_num; ++i) {
|
| 149 |
+
m_input_name_to_idx[std::string(m_io_info->pInputs[i].pName)] = i;
|
| 150 |
+
}
|
| 151 |
+
m_output_name_to_idx.clear();
|
| 152 |
+
for (int i = 0; i < m_output_num; ++i) {
|
| 153 |
+
m_output_name_to_idx[std::string(m_io_info->pOutputs[i].pName)] = i;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
// 5. Prepare IO buffers
|
| 157 |
+
ret = utils::prepare_io("enc", m_io_info, m_io, utils::IO_BUFFER_STRATEGY_DEFAULT);
|
| 158 |
+
if (0 != ret) {
|
| 159 |
+
printf("prepare io failed!\n");
|
| 160 |
+
utils::free_io(m_io);
|
| 161 |
+
return deinit_handle();
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
m_handle = handle;
|
| 165 |
+
m_hasInit = true;
|
| 166 |
+
return 0;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
int EngineWrapper::SetInput(void* pInput, int index) {
|
| 170 |
+
if (!m_hasInit || index < 0 || index >= m_input_num) return -1;
|
| 171 |
+
return utils::push_io_input(pInput, index, m_io);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
int EngineWrapper::RunSync() {
|
| 175 |
+
if (!m_hasInit) return -1;
|
| 176 |
+
auto ret = AX_ENGINE_RunSync(m_handle, &m_io);
|
| 177 |
+
if (0 != ret) {
|
| 178 |
+
printf("AX_ENGINE_RunSync failed. ret=0x%x\n", ret);
|
| 179 |
+
}
|
| 180 |
+
return ret;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
int EngineWrapper::GetOutput(void* pOutput, int index) {
|
| 184 |
+
if (!m_hasInit || index < 0 || index >= m_output_num) return -1;
|
| 185 |
+
return utils::push_io_output(pOutput, index, m_io);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
int EngineWrapper::SetInputByName(const char* name, void* pInput) {
|
| 189 |
+
auto it = m_input_name_to_idx.find(std::string(name));
|
| 190 |
+
if (it == m_input_name_to_idx.end()) {
|
| 191 |
+
printf("Input '%s' not found in model\n", name);
|
| 192 |
+
return -1;
|
| 193 |
+
}
|
| 194 |
+
return SetInput(pInput, it->second);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
int EngineWrapper::GetOutputByName(const char* name, void* pOutput) {
|
| 198 |
+
auto it = m_output_name_to_idx.find(std::string(name));
|
| 199 |
+
if (it == m_output_name_to_idx.end()) {
|
| 200 |
+
printf("Output '%s' not found in model\n", name);
|
| 201 |
+
return -1;
|
| 202 |
+
}
|
| 203 |
+
return GetOutput(pOutput, it->second);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
int EngineWrapper::GetInputSize(int index) {
|
| 207 |
+
if (index < 0 || index >= m_input_num) return -1;
|
| 208 |
+
return m_io.pInputs[index].nSize;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
int EngineWrapper::GetOutputSize(int index) {
|
| 212 |
+
if (index < 0 || index >= m_output_num) return -1;
|
| 213 |
+
return m_io.pOutputs[index].nSize;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
int EngineWrapper::GetInputSizeByName(const char* name) {
|
| 217 |
+
int idx = GetInputIndex(name);
|
| 218 |
+
if (idx < 0) return -1;
|
| 219 |
+
return GetInputSize(idx);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
int EngineWrapper::GetOutputSizeByName(const char* name) {
|
| 223 |
+
int idx = GetOutputIndex(name);
|
| 224 |
+
if (idx < 0) return -1;
|
| 225 |
+
return GetOutputSize(idx);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
int EngineWrapper::GetInputIndex(const char* name) {
|
| 229 |
+
auto it = m_input_name_to_idx.find(std::string(name));
|
| 230 |
+
return (it != m_input_name_to_idx.end()) ? it->second : -1;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
int EngineWrapper::GetOutputIndex(const char* name) {
|
| 234 |
+
auto it = m_output_name_to_idx.find(std::string(name));
|
| 235 |
+
return (it != m_output_name_to_idx.end()) ? it->second : -1;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
const char* EngineWrapper::GetInputName(int index) const {
|
| 239 |
+
if (index < 0 || index >= m_input_num) return nullptr;
|
| 240 |
+
return m_io_info->pInputs[index].pName;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
const char* EngineWrapper::GetOutputName(int index) const {
|
| 244 |
+
if (index < 0 || index >= m_output_num) return nullptr;
|
| 245 |
+
return m_io_info->pOutputs[index].pName;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
static const char* dtype_str(AX_ENGINE_DATA_TYPE_T t) {
|
| 249 |
+
switch (t) {
|
| 250 |
+
case AX_ENGINE_DT_FLOAT32: return "float32";
|
| 251 |
+
case AX_ENGINE_DT_FLOAT64: return "float64";
|
| 252 |
+
case AX_ENGINE_DT_SINT8: return "sint8";
|
| 253 |
+
case AX_ENGINE_DT_UINT8: return "uint8";
|
| 254 |
+
case AX_ENGINE_DT_SINT16: return "sint16";
|
| 255 |
+
case AX_ENGINE_DT_UINT16: return "uint16";
|
| 256 |
+
case AX_ENGINE_DT_SINT32: return "sint32";
|
| 257 |
+
case AX_ENGINE_DT_UINT32: return "uint32";
|
| 258 |
+
default: return "unknown";
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
const char* EngineWrapper::GetInputDtypeStr(int index) const {
|
| 263 |
+
if (index < 0 || index >= m_input_num) return "?";
|
| 264 |
+
return dtype_str(m_io_info->pInputs[index].eDataType);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
const char* EngineWrapper::GetOutputDtypeStr(int index) const {
|
| 268 |
+
if (index < 0 || index >= m_output_num) return "?";
|
| 269 |
+
return dtype_str(m_io_info->pOutputs[index].eDataType);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
int EngineWrapper::Release() {
|
| 273 |
+
if (m_handle) {
|
| 274 |
+
utils::free_io(m_io);
|
| 275 |
+
AX_ENGINE_DestroyHandle(m_handle);
|
| 276 |
+
m_handle = nullptr;
|
| 277 |
+
}
|
| 278 |
+
m_hasInit = false;
|
| 279 |
+
m_input_name_to_idx.clear();
|
| 280 |
+
m_output_name_to_idx.clear();
|
| 281 |
+
return 0;
|
| 282 |
+
}
|
cpp/src/EngineWrapper.hpp
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* EngineWrapper: wraps AX_ENGINE API for loading and running .axmodel files.
|
| 5 |
+
* Supports both index-based and name-based I/O access.
|
| 6 |
+
*
|
| 7 |
+
* Adapted from melotts.axera-main
|
| 8 |
+
**************************************************************************************************/
|
| 9 |
+
|
| 10 |
+
#pragma once
|
| 11 |
+
|
| 12 |
+
#include <string>
|
| 13 |
+
#include <vector>
|
| 14 |
+
#include <unordered_map>
|
| 15 |
+
#include <cstring>
|
| 16 |
+
#include <cstdint>
|
| 17 |
+
|
| 18 |
+
#include "ax_engine_api.h"
|
| 19 |
+
|
| 20 |
+
class EngineWrapper {
|
| 21 |
+
public:
|
| 22 |
+
EngineWrapper();
|
| 23 |
+
~EngineWrapper();
|
| 24 |
+
|
| 25 |
+
int Init(const char* strModelPath, uint32_t nNpuType = 0);
|
| 26 |
+
|
| 27 |
+
// Index-based I/O (simple interface)
|
| 28 |
+
int SetInput(void* pInput, int index);
|
| 29 |
+
int RunSync();
|
| 30 |
+
int GetOutput(void* pOutput, int index);
|
| 31 |
+
|
| 32 |
+
// Name-based I/O (for multi-input/output models)
|
| 33 |
+
int SetInputByName(const char* name, void* pInput);
|
| 34 |
+
int GetOutputByName(const char* name, void* pOutput);
|
| 35 |
+
|
| 36 |
+
int GetInputSize(int index);
|
| 37 |
+
int GetOutputSize(int index);
|
| 38 |
+
int GetInputSizeByName(const char* name);
|
| 39 |
+
int GetOutputSizeByName(const char* name);
|
| 40 |
+
|
| 41 |
+
int GetInputIndex(const char* name);
|
| 42 |
+
int GetOutputIndex(const char* name);
|
| 43 |
+
int GetInputCount() const { return m_input_num; }
|
| 44 |
+
int GetOutputCount() const { return m_output_num; }
|
| 45 |
+
const char* GetInputName(int index) const;
|
| 46 |
+
const char* GetOutputName(int index) const;
|
| 47 |
+
const char* GetInputDtypeStr(int index) const;
|
| 48 |
+
const char* GetOutputDtypeStr(int index) const;
|
| 49 |
+
|
| 50 |
+
bool HasInit() const { return m_hasInit; }
|
| 51 |
+
int Release();
|
| 52 |
+
|
| 53 |
+
private:
|
| 54 |
+
bool m_hasInit;
|
| 55 |
+
AX_ENGINE_HANDLE m_handle;
|
| 56 |
+
AX_ENGINE_IO_INFO_T *m_io_info;
|
| 57 |
+
AX_ENGINE_IO_T m_io;
|
| 58 |
+
int m_input_num, m_output_num;
|
| 59 |
+
|
| 60 |
+
std::unordered_map<std::string, int> m_input_name_to_idx;
|
| 61 |
+
std::unordered_map<std::string, int> m_output_name_to_idx;
|
| 62 |
+
};
|
cpp/src/cmdline.hpp
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
Copyright (c) 2009, Hideyuki Tanaka
|
| 3 |
+
All rights reserved.
|
| 4 |
+
|
| 5 |
+
Redistribution and use in source and binary forms, with or without
|
| 6 |
+
modification, are permitted provided that the following conditions are met:
|
| 7 |
+
* Redistributions of source code must retain the above copyright
|
| 8 |
+
notice, this list of conditions and the following disclaimer.
|
| 9 |
+
* Redistributions in binary form must reproduce the above copyright
|
| 10 |
+
notice, this list of conditions and the following disclaimer in the
|
| 11 |
+
documentation and/or other materials provided with the distribution.
|
| 12 |
+
* Neither the name of the <organization> nor the
|
| 13 |
+
names of its contributors may be used to endorse or promote products
|
| 14 |
+
derived from this software without specific prior written permission.
|
| 15 |
+
|
| 16 |
+
THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
|
| 17 |
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 18 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 19 |
+
DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
|
| 20 |
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 21 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 22 |
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
| 23 |
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 24 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 25 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 26 |
+
*/
|
| 27 |
+
|
| 28 |
+
#pragma once
|
| 29 |
+
|
| 30 |
+
#include <cxxabi.h>
|
| 31 |
+
|
| 32 |
+
#include <algorithm>
|
| 33 |
+
#include <cstdlib>
|
| 34 |
+
#include <cstring>
|
| 35 |
+
#include <iostream>
|
| 36 |
+
#include <map>
|
| 37 |
+
#include <sstream>
|
| 38 |
+
#include <stdexcept>
|
| 39 |
+
#include <string>
|
| 40 |
+
#include <typeinfo>
|
| 41 |
+
#include <vector>
|
| 42 |
+
|
| 43 |
+
namespace cmdline {
|
| 44 |
+
|
| 45 |
+
namespace detail {
|
| 46 |
+
|
| 47 |
+
template <typename Target, typename Source, bool Same>
|
| 48 |
+
class lexical_cast_t {
|
| 49 |
+
public:
|
| 50 |
+
static Target cast(const Source &arg) {
|
| 51 |
+
Target ret;
|
| 52 |
+
std::stringstream ss;
|
| 53 |
+
if (!(ss << arg && ss >> ret && ss.eof())) throw std::bad_cast();
|
| 54 |
+
|
| 55 |
+
return ret;
|
| 56 |
+
}
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
template <typename Target, typename Source>
|
| 60 |
+
class lexical_cast_t<Target, Source, true> {
|
| 61 |
+
public:
|
| 62 |
+
static Target cast(const Source &arg) { return arg; }
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
template <typename Source>
|
| 66 |
+
class lexical_cast_t<std::string, Source, false> {
|
| 67 |
+
public:
|
| 68 |
+
static std::string cast(const Source &arg) {
|
| 69 |
+
std::ostringstream ss;
|
| 70 |
+
ss << arg;
|
| 71 |
+
return ss.str();
|
| 72 |
+
}
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
template <typename Target>
|
| 76 |
+
class lexical_cast_t<Target, std::string, false> {
|
| 77 |
+
public:
|
| 78 |
+
static Target cast(const std::string &arg) {
|
| 79 |
+
Target ret;
|
| 80 |
+
std::istringstream ss(arg);
|
| 81 |
+
if (!(ss >> ret && ss.eof())) throw std::bad_cast();
|
| 82 |
+
return ret;
|
| 83 |
+
}
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
template <typename T1, typename T2>
|
| 87 |
+
struct is_same {
|
| 88 |
+
static const bool value = false;
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
template <typename T>
|
| 92 |
+
struct is_same<T, T> {
|
| 93 |
+
static const bool value = true;
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
template <typename Target, typename Source>
|
| 97 |
+
Target lexical_cast(const Source &arg) {
|
| 98 |
+
return lexical_cast_t<Target, Source,
|
| 99 |
+
detail::is_same<Target, Source>::value>::cast(arg);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
static inline std::string demangle(const std::string &name) {
|
| 103 |
+
int status = 0;
|
| 104 |
+
char *p = abi::__cxa_demangle(name.c_str(), 0, 0, &status);
|
| 105 |
+
std::string ret(p);
|
| 106 |
+
free(p);
|
| 107 |
+
return ret;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
template <class T>
|
| 111 |
+
std::string readable_typename() {
|
| 112 |
+
return demangle(typeid(T).name());
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
template <class T>
|
| 116 |
+
std::string default_value(T def) {
|
| 117 |
+
return detail::lexical_cast<std::string>(def);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
template <>
|
| 121 |
+
inline std::string readable_typename<std::string>() {
|
| 122 |
+
return "string";
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
} // namespace detail
|
| 126 |
+
|
| 127 |
+
//-----
|
| 128 |
+
|
| 129 |
+
class cmdline_error : public std::exception {
|
| 130 |
+
public:
|
| 131 |
+
cmdline_error(const std::string &msg) : msg(msg) {}
|
| 132 |
+
~cmdline_error() throw() {}
|
| 133 |
+
const char *what() const throw() { return msg.c_str(); }
|
| 134 |
+
|
| 135 |
+
private:
|
| 136 |
+
std::string msg;
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
template <class T>
|
| 140 |
+
struct default_reader {
|
| 141 |
+
T operator()(const std::string &str) { return detail::lexical_cast<T>(str); }
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
template <class T>
|
| 145 |
+
struct range_reader {
|
| 146 |
+
range_reader(const T &low, const T &high) : low(low), high(high) {}
|
| 147 |
+
T operator()(const std::string &s) const {
|
| 148 |
+
T ret = default_reader<T>()(s);
|
| 149 |
+
if (!(ret >= low && ret <= high))
|
| 150 |
+
throw cmdline::cmdline_error("range_error");
|
| 151 |
+
return ret;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
private:
|
| 155 |
+
T low, high;
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
+
template <class T>
|
| 159 |
+
range_reader<T> range(const T &low, const T &high) {
|
| 160 |
+
return range_reader<T>(low, high);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
template <class T>
|
| 164 |
+
struct oneof_reader {
|
| 165 |
+
T operator()(const std::string &s) {
|
| 166 |
+
T ret = default_reader<T>()(s);
|
| 167 |
+
if (std::find(alt.begin(), alt.end(), ret) == alt.end())
|
| 168 |
+
throw cmdline_error("");
|
| 169 |
+
return ret;
|
| 170 |
+
}
|
| 171 |
+
void add(const T &v) { alt.push_back(v); }
|
| 172 |
+
|
| 173 |
+
private:
|
| 174 |
+
std::vector<T> alt;
|
| 175 |
+
};
|
| 176 |
+
|
| 177 |
+
template <class T>
|
| 178 |
+
oneof_reader<T> oneof(T a1) {
|
| 179 |
+
oneof_reader<T> ret;
|
| 180 |
+
ret.add(a1);
|
| 181 |
+
return ret;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
template <class T>
|
| 185 |
+
oneof_reader<T> oneof(T a1, T a2) {
|
| 186 |
+
oneof_reader<T> ret;
|
| 187 |
+
ret.add(a1);
|
| 188 |
+
ret.add(a2);
|
| 189 |
+
return ret;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
template <class T>
|
| 193 |
+
oneof_reader<T> oneof(T a1, T a2, T a3) {
|
| 194 |
+
oneof_reader<T> ret;
|
| 195 |
+
ret.add(a1);
|
| 196 |
+
ret.add(a2);
|
| 197 |
+
ret.add(a3);
|
| 198 |
+
return ret;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
template <class T>
|
| 202 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4) {
|
| 203 |
+
oneof_reader<T> ret;
|
| 204 |
+
ret.add(a1);
|
| 205 |
+
ret.add(a2);
|
| 206 |
+
ret.add(a3);
|
| 207 |
+
ret.add(a4);
|
| 208 |
+
return ret;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
template <class T>
|
| 212 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5) {
|
| 213 |
+
oneof_reader<T> ret;
|
| 214 |
+
ret.add(a1);
|
| 215 |
+
ret.add(a2);
|
| 216 |
+
ret.add(a3);
|
| 217 |
+
ret.add(a4);
|
| 218 |
+
ret.add(a5);
|
| 219 |
+
return ret;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
template <class T>
|
| 223 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6) {
|
| 224 |
+
oneof_reader<T> ret;
|
| 225 |
+
ret.add(a1);
|
| 226 |
+
ret.add(a2);
|
| 227 |
+
ret.add(a3);
|
| 228 |
+
ret.add(a4);
|
| 229 |
+
ret.add(a5);
|
| 230 |
+
ret.add(a6);
|
| 231 |
+
return ret;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
template <class T>
|
| 235 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7) {
|
| 236 |
+
oneof_reader<T> ret;
|
| 237 |
+
ret.add(a1);
|
| 238 |
+
ret.add(a2);
|
| 239 |
+
ret.add(a3);
|
| 240 |
+
ret.add(a4);
|
| 241 |
+
ret.add(a5);
|
| 242 |
+
ret.add(a6);
|
| 243 |
+
ret.add(a7);
|
| 244 |
+
return ret;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
template <class T>
|
| 248 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8) {
|
| 249 |
+
oneof_reader<T> ret;
|
| 250 |
+
ret.add(a1);
|
| 251 |
+
ret.add(a2);
|
| 252 |
+
ret.add(a3);
|
| 253 |
+
ret.add(a4);
|
| 254 |
+
ret.add(a5);
|
| 255 |
+
ret.add(a6);
|
| 256 |
+
ret.add(a7);
|
| 257 |
+
ret.add(a8);
|
| 258 |
+
return ret;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
template <class T>
|
| 262 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9) {
|
| 263 |
+
oneof_reader<T> ret;
|
| 264 |
+
ret.add(a1);
|
| 265 |
+
ret.add(a2);
|
| 266 |
+
ret.add(a3);
|
| 267 |
+
ret.add(a4);
|
| 268 |
+
ret.add(a5);
|
| 269 |
+
ret.add(a6);
|
| 270 |
+
ret.add(a7);
|
| 271 |
+
ret.add(a8);
|
| 272 |
+
ret.add(a9);
|
| 273 |
+
return ret;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
template <class T>
|
| 277 |
+
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9,
|
| 278 |
+
T a10) {
|
| 279 |
+
oneof_reader<T> ret;
|
| 280 |
+
ret.add(a1);
|
| 281 |
+
ret.add(a2);
|
| 282 |
+
ret.add(a3);
|
| 283 |
+
ret.add(a4);
|
| 284 |
+
ret.add(a5);
|
| 285 |
+
ret.add(a6);
|
| 286 |
+
ret.add(a7);
|
| 287 |
+
ret.add(a8);
|
| 288 |
+
ret.add(a9);
|
| 289 |
+
ret.add(a10);
|
| 290 |
+
return ret;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
//-----
|
| 294 |
+
|
| 295 |
+
class parser {
|
| 296 |
+
public:
|
| 297 |
+
parser() {}
|
| 298 |
+
~parser() {
|
| 299 |
+
for (std::map<std::string, option_base *>::iterator p = options.begin();
|
| 300 |
+
p != options.end(); p++)
|
| 301 |
+
delete p->second;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
void add(const std::string &name, char short_name = 0,
|
| 305 |
+
const std::string &desc = "") {
|
| 306 |
+
if (options.count(name))
|
| 307 |
+
throw cmdline_error("multiple definition: " + name);
|
| 308 |
+
options[name] = new option_without_value(name, short_name, desc);
|
| 309 |
+
ordered.push_back(options[name]);
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
template <class T>
|
| 313 |
+
void add(const std::string &name, char short_name = 0,
|
| 314 |
+
const std::string &desc = "", bool need = true, const T def = T()) {
|
| 315 |
+
add(name, short_name, desc, need, def, default_reader<T>());
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
template <class T, class F>
|
| 319 |
+
void add(const std::string &name, char short_name = 0,
|
| 320 |
+
const std::string &desc = "", bool need = true, const T def = T(),
|
| 321 |
+
F reader = F()) {
|
| 322 |
+
if (options.count(name))
|
| 323 |
+
throw cmdline_error("multiple definition: " + name);
|
| 324 |
+
options[name] = new option_with_value_with_reader<T, F>(
|
| 325 |
+
name, short_name, need, def, desc, reader);
|
| 326 |
+
ordered.push_back(options[name]);
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
void footer(const std::string &f) { ftr = f; }
|
| 330 |
+
|
| 331 |
+
void set_program_name(const std::string &name) { prog_name = name; }
|
| 332 |
+
|
| 333 |
+
bool exist(const std::string &name) const {
|
| 334 |
+
if (options.count(name) == 0)
|
| 335 |
+
throw cmdline_error("there is no flag: --" + name);
|
| 336 |
+
return options.find(name)->second->has_set();
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
template <class T>
|
| 340 |
+
const T &get(const std::string &name) const {
|
| 341 |
+
if (options.count(name) == 0)
|
| 342 |
+
throw cmdline_error("there is no flag: --" + name);
|
| 343 |
+
const option_with_value<T> *p =
|
| 344 |
+
dynamic_cast<const option_with_value<T> *>(options.find(name)->second);
|
| 345 |
+
if (p == NULL) throw cmdline_error("type mismatch flag '" + name + "'");
|
| 346 |
+
return p->get();
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
const std::vector<std::string> &rest() const { return others; }
|
| 350 |
+
|
| 351 |
+
bool parse(const std::string &arg) {
|
| 352 |
+
std::vector<std::string> args;
|
| 353 |
+
|
| 354 |
+
std::string buf;
|
| 355 |
+
bool in_quote = false;
|
| 356 |
+
for (std::string::size_type i = 0; i < arg.length(); i++) {
|
| 357 |
+
if (arg[i] == '\"') {
|
| 358 |
+
in_quote = !in_quote;
|
| 359 |
+
continue;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
if (arg[i] == ' ' && !in_quote) {
|
| 363 |
+
args.push_back(buf);
|
| 364 |
+
buf = "";
|
| 365 |
+
continue;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
if (arg[i] == '\\') {
|
| 369 |
+
i++;
|
| 370 |
+
if (i >= arg.length()) {
|
| 371 |
+
errors.push_back("unexpected occurrence of '\\' at end of string");
|
| 372 |
+
return false;
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
buf += arg[i];
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
if (in_quote) {
|
| 380 |
+
errors.push_back("quote is not closed");
|
| 381 |
+
return false;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
if (buf.length() > 0) args.push_back(buf);
|
| 385 |
+
|
| 386 |
+
for (size_t i = 0; i < args.size(); i++)
|
| 387 |
+
std::cout << "\"" << args[i] << "\"" << std::endl;
|
| 388 |
+
|
| 389 |
+
return parse(args);
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
bool parse(const std::vector<std::string> &args) {
|
| 393 |
+
int argc = static_cast<int>(args.size());
|
| 394 |
+
std::vector<const char *> argv(argc);
|
| 395 |
+
|
| 396 |
+
for (int i = 0; i < argc; i++) argv[i] = args[i].c_str();
|
| 397 |
+
|
| 398 |
+
return parse(argc, &argv[0]);
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
bool parse(int argc, const char *const argv[]) {
|
| 402 |
+
errors.clear();
|
| 403 |
+
others.clear();
|
| 404 |
+
|
| 405 |
+
if (argc < 1) {
|
| 406 |
+
errors.push_back("argument number must be longer than 0");
|
| 407 |
+
return false;
|
| 408 |
+
}
|
| 409 |
+
if (prog_name == "") prog_name = argv[0];
|
| 410 |
+
|
| 411 |
+
std::map<char, std::string> lookup;
|
| 412 |
+
for (std::map<std::string, option_base *>::iterator p = options.begin();
|
| 413 |
+
p != options.end(); p++) {
|
| 414 |
+
if (p->first.length() == 0) continue;
|
| 415 |
+
char initial = p->second->short_name();
|
| 416 |
+
if (initial) {
|
| 417 |
+
if (lookup.count(initial) > 0) {
|
| 418 |
+
lookup[initial] = "";
|
| 419 |
+
errors.push_back(std::string("short option '") + initial +
|
| 420 |
+
"' is ambiguous");
|
| 421 |
+
return false;
|
| 422 |
+
} else
|
| 423 |
+
lookup[initial] = p->first;
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
for (int i = 1; i < argc; i++) {
|
| 428 |
+
if (strncmp(argv[i], "--", 2) == 0) {
|
| 429 |
+
const char *p = strchr(argv[i] + 2, '=');
|
| 430 |
+
if (p) {
|
| 431 |
+
std::string name(argv[i] + 2, p);
|
| 432 |
+
std::string val(p + 1);
|
| 433 |
+
set_option(name, val);
|
| 434 |
+
} else {
|
| 435 |
+
std::string name(argv[i] + 2);
|
| 436 |
+
if (options.count(name) == 0) {
|
| 437 |
+
errors.push_back("undefined option: --" + name);
|
| 438 |
+
continue;
|
| 439 |
+
}
|
| 440 |
+
if (options[name]->has_value()) {
|
| 441 |
+
if (i + 1 >= argc) {
|
| 442 |
+
errors.push_back("option needs value: --" + name);
|
| 443 |
+
continue;
|
| 444 |
+
} else {
|
| 445 |
+
i++;
|
| 446 |
+
set_option(name, argv[i]);
|
| 447 |
+
}
|
| 448 |
+
} else {
|
| 449 |
+
set_option(name);
|
| 450 |
+
}
|
| 451 |
+
}
|
| 452 |
+
} else if (strncmp(argv[i], "-", 1) == 0) {
|
| 453 |
+
if (!argv[i][1]) continue;
|
| 454 |
+
char last = argv[i][1];
|
| 455 |
+
for (int j = 2; argv[i][j]; j++) {
|
| 456 |
+
last = argv[i][j];
|
| 457 |
+
if (lookup.count(argv[i][j - 1]) == 0) {
|
| 458 |
+
errors.push_back(std::string("undefined short option: -") +
|
| 459 |
+
argv[i][j - 1]);
|
| 460 |
+
continue;
|
| 461 |
+
}
|
| 462 |
+
if (lookup[argv[i][j - 1]] == "") {
|
| 463 |
+
errors.push_back(std::string("ambiguous short option: -") +
|
| 464 |
+
argv[i][j - 1]);
|
| 465 |
+
continue;
|
| 466 |
+
}
|
| 467 |
+
set_option(lookup[argv[i][j - 1]]);
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
if (lookup.count(last) == 0) {
|
| 471 |
+
errors.push_back(std::string("undefined short option: -") + last);
|
| 472 |
+
continue;
|
| 473 |
+
}
|
| 474 |
+
if (lookup[last] == "") {
|
| 475 |
+
errors.push_back(std::string("ambiguous short option: -") + last);
|
| 476 |
+
continue;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
if (i + 1 < argc && options[lookup[last]]->has_value()) {
|
| 480 |
+
set_option(lookup[last], argv[i + 1]);
|
| 481 |
+
i++;
|
| 482 |
+
} else {
|
| 483 |
+
set_option(lookup[last]);
|
| 484 |
+
}
|
| 485 |
+
} else {
|
| 486 |
+
others.push_back(argv[i]);
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
for (std::map<std::string, option_base *>::iterator p = options.begin();
|
| 491 |
+
p != options.end(); p++)
|
| 492 |
+
if (!p->second->valid())
|
| 493 |
+
errors.push_back("need option: --" + std::string(p->first));
|
| 494 |
+
|
| 495 |
+
return errors.size() == 0;
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
void parse_check(const std::string &arg) {
|
| 499 |
+
if (!options.count("help")) add("help", '?', "print this message");
|
| 500 |
+
check(0, parse(arg));
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
void parse_check(const std::vector<std::string> &args) {
|
| 504 |
+
if (!options.count("help")) add("help", '?', "print this message");
|
| 505 |
+
check(args.size(), parse(args));
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
void parse_check(int argc, char *argv[]) {
|
| 509 |
+
if (!options.count("help")) add("help", '?', "print this message");
|
| 510 |
+
check(argc, parse(argc, argv));
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
std::string error() const { return errors.size() > 0 ? errors[0] : ""; }
|
| 514 |
+
|
| 515 |
+
std::string error_full() const {
|
| 516 |
+
std::ostringstream oss;
|
| 517 |
+
for (size_t i = 0; i < errors.size(); i++) oss << errors[i] << std::endl;
|
| 518 |
+
return oss.str();
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
std::string usage() const {
|
| 522 |
+
std::ostringstream oss;
|
| 523 |
+
oss << "usage: " << prog_name << " ";
|
| 524 |
+
for (size_t i = 0; i < ordered.size(); i++) {
|
| 525 |
+
if (ordered[i]->must()) oss << ordered[i]->short_description() << " ";
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
oss << "[options] ... " << ftr << std::endl;
|
| 529 |
+
oss << "options:" << std::endl;
|
| 530 |
+
|
| 531 |
+
size_t max_width = 0;
|
| 532 |
+
for (size_t i = 0; i < ordered.size(); i++) {
|
| 533 |
+
max_width = std::max(max_width, ordered[i]->name().length());
|
| 534 |
+
}
|
| 535 |
+
for (size_t i = 0; i < ordered.size(); i++) {
|
| 536 |
+
if (ordered[i]->short_name()) {
|
| 537 |
+
oss << " -" << ordered[i]->short_name() << ", ";
|
| 538 |
+
} else {
|
| 539 |
+
oss << " ";
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
oss << "--" << ordered[i]->name();
|
| 543 |
+
for (size_t j = ordered[i]->name().length(); j < max_width + 4; j++)
|
| 544 |
+
oss << ' ';
|
| 545 |
+
oss << ordered[i]->description() << std::endl;
|
| 546 |
+
}
|
| 547 |
+
return oss.str();
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
private:
|
| 551 |
+
void check(int argc, bool ok) {
|
| 552 |
+
if ((argc == 1 && !ok) || exist("help")) {
|
| 553 |
+
std::cerr << usage();
|
| 554 |
+
exit(0);
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
if (!ok) {
|
| 558 |
+
std::cerr << error() << std::endl << usage();
|
| 559 |
+
exit(1);
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
void set_option(const std::string &name) {
|
| 564 |
+
if (options.count(name) == 0) {
|
| 565 |
+
errors.push_back("undefined option: --" + name);
|
| 566 |
+
return;
|
| 567 |
+
}
|
| 568 |
+
if (!options[name]->set()) {
|
| 569 |
+
errors.push_back("option needs value: --" + name);
|
| 570 |
+
return;
|
| 571 |
+
}
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
void set_option(const std::string &name, const std::string &value) {
|
| 575 |
+
if (options.count(name) == 0) {
|
| 576 |
+
errors.push_back("undefined option: --" + name);
|
| 577 |
+
return;
|
| 578 |
+
}
|
| 579 |
+
if (!options[name]->set(value)) {
|
| 580 |
+
errors.push_back("option value is invalid: --" + name + "=" + value);
|
| 581 |
+
return;
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
class option_base {
|
| 586 |
+
public:
|
| 587 |
+
virtual ~option_base() {}
|
| 588 |
+
|
| 589 |
+
virtual bool has_value() const = 0;
|
| 590 |
+
virtual bool set() = 0;
|
| 591 |
+
virtual bool set(const std::string &value) = 0;
|
| 592 |
+
virtual bool has_set() const = 0;
|
| 593 |
+
virtual bool valid() const = 0;
|
| 594 |
+
virtual bool must() const = 0;
|
| 595 |
+
|
| 596 |
+
virtual const std::string &name() const = 0;
|
| 597 |
+
virtual char short_name() const = 0;
|
| 598 |
+
virtual const std::string &description() const = 0;
|
| 599 |
+
virtual std::string short_description() const = 0;
|
| 600 |
+
};
|
| 601 |
+
|
| 602 |
+
class option_without_value : public option_base {
|
| 603 |
+
public:
|
| 604 |
+
option_without_value(const std::string &name, char short_name,
|
| 605 |
+
const std::string &desc)
|
| 606 |
+
: nam(name), snam(short_name), desc(desc), has(false) {}
|
| 607 |
+
~option_without_value() {}
|
| 608 |
+
|
| 609 |
+
bool has_value() const { return false; }
|
| 610 |
+
|
| 611 |
+
bool set() {
|
| 612 |
+
has = true;
|
| 613 |
+
return true;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
bool set(const std::string &) { return false; }
|
| 617 |
+
|
| 618 |
+
bool has_set() const { return has; }
|
| 619 |
+
|
| 620 |
+
bool valid() const { return true; }
|
| 621 |
+
|
| 622 |
+
bool must() const { return false; }
|
| 623 |
+
|
| 624 |
+
const std::string &name() const { return nam; }
|
| 625 |
+
|
| 626 |
+
char short_name() const { return snam; }
|
| 627 |
+
|
| 628 |
+
const std::string &description() const { return desc; }
|
| 629 |
+
|
| 630 |
+
std::string short_description() const { return "--" + nam; }
|
| 631 |
+
|
| 632 |
+
private:
|
| 633 |
+
std::string nam;
|
| 634 |
+
char snam;
|
| 635 |
+
std::string desc;
|
| 636 |
+
bool has;
|
| 637 |
+
};
|
| 638 |
+
|
| 639 |
+
template <class T>
|
| 640 |
+
class option_with_value : public option_base {
|
| 641 |
+
public:
|
| 642 |
+
option_with_value(const std::string &name, char short_name, bool need,
|
| 643 |
+
const T &def, const std::string &desc)
|
| 644 |
+
: nam(name),
|
| 645 |
+
snam(short_name),
|
| 646 |
+
need(need),
|
| 647 |
+
has(false),
|
| 648 |
+
def(def),
|
| 649 |
+
actual(def) {
|
| 650 |
+
this->desc = full_description(desc);
|
| 651 |
+
}
|
| 652 |
+
~option_with_value() {}
|
| 653 |
+
|
| 654 |
+
const T &get() const { return actual; }
|
| 655 |
+
|
| 656 |
+
bool has_value() const { return true; }
|
| 657 |
+
|
| 658 |
+
bool set() { return false; }
|
| 659 |
+
|
| 660 |
+
bool set(const std::string &value) {
|
| 661 |
+
try {
|
| 662 |
+
actual = read(value);
|
| 663 |
+
has = true;
|
| 664 |
+
} catch (const std::exception &e) {
|
| 665 |
+
return false;
|
| 666 |
+
}
|
| 667 |
+
return true;
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
bool has_set() const { return has; }
|
| 671 |
+
|
| 672 |
+
bool valid() const {
|
| 673 |
+
if (need && !has) return false;
|
| 674 |
+
return true;
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
bool must() const { return need; }
|
| 678 |
+
|
| 679 |
+
const std::string &name() const { return nam; }
|
| 680 |
+
|
| 681 |
+
char short_name() const { return snam; }
|
| 682 |
+
|
| 683 |
+
const std::string &description() const { return desc; }
|
| 684 |
+
|
| 685 |
+
std::string short_description() const {
|
| 686 |
+
return "--" + nam + "=" + detail::readable_typename<T>();
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
protected:
|
| 690 |
+
std::string full_description(const std::string &desc) {
|
| 691 |
+
return desc + " (" + detail::readable_typename<T>() +
|
| 692 |
+
(need ? "" : " [=" + detail::default_value<T>(def) + "]") + ")";
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
virtual T read(const std::string &s) = 0;
|
| 696 |
+
|
| 697 |
+
std::string nam;
|
| 698 |
+
char snam;
|
| 699 |
+
bool need;
|
| 700 |
+
std::string desc;
|
| 701 |
+
|
| 702 |
+
bool has;
|
| 703 |
+
T def;
|
| 704 |
+
T actual;
|
| 705 |
+
};
|
| 706 |
+
|
| 707 |
+
template <class T, class F>
|
| 708 |
+
class option_with_value_with_reader : public option_with_value<T> {
|
| 709 |
+
public:
|
| 710 |
+
option_with_value_with_reader(const std::string &name, char short_name,
|
| 711 |
+
bool need, const T def,
|
| 712 |
+
const std::string &desc, F reader)
|
| 713 |
+
: option_with_value<T>(name, short_name, need, def, desc),
|
| 714 |
+
reader(reader) {}
|
| 715 |
+
|
| 716 |
+
private:
|
| 717 |
+
T read(const std::string &s) { return reader(s); }
|
| 718 |
+
|
| 719 |
+
F reader;
|
| 720 |
+
};
|
| 721 |
+
|
| 722 |
+
std::map<std::string, option_base *> options;
|
| 723 |
+
std::vector<option_base *> ordered;
|
| 724 |
+
std::string ftr;
|
| 725 |
+
|
| 726 |
+
std::string prog_name;
|
| 727 |
+
std::vector<std::string> others;
|
| 728 |
+
|
| 729 |
+
std::vector<std::string> errors;
|
| 730 |
+
};
|
| 731 |
+
|
| 732 |
+
} // namespace cmdline
|
cpp/src/fbank.cpp
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* FBANK implementation with simple DFT-based STFT.
|
| 5 |
+
* For production, use FFTW or an NPU-accelerated implementation.
|
| 6 |
+
**************************************************************************************************/
|
| 7 |
+
|
| 8 |
+
#include "fbank.hpp"
|
| 9 |
+
|
| 10 |
+
#include <algorithm>
|
| 11 |
+
#include <cstring>
|
| 12 |
+
#include <cstdio>
|
| 13 |
+
|
| 14 |
+
#ifndef M_PI
|
| 15 |
+
#define M_PI 3.14159265358979323846
|
| 16 |
+
#endif
|
| 17 |
+
|
| 18 |
+
static float hz_to_mel(float freq) {
|
| 19 |
+
return 2595.0f * std::log10(1.0f + freq / 700.0f);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
static float mel_to_hz(float mel) {
|
| 23 |
+
return 700.0f * (std::pow(10.0f, mel / 2595.0f) - 1.0f);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
MelFilterBank::MelFilterBank() : m_n_freqs(0) {}
|
| 27 |
+
|
| 28 |
+
int MelFilterBank::Init(const Config& config) {
|
| 29 |
+
m_config = config;
|
| 30 |
+
m_n_freqs = config.n_fft / 2 + 1;
|
| 31 |
+
|
| 32 |
+
CreateWindow();
|
| 33 |
+
CreateMelFilterbank();
|
| 34 |
+
|
| 35 |
+
printf("FBANK init: n_mels=%d, n_fft=%d, hop=%d, sr=%d\n",
|
| 36 |
+
config.n_mels, config.n_fft, config.hop_length, config.sampling_rate);
|
| 37 |
+
return 0;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
void MelFilterBank::CreateWindow() {
|
| 41 |
+
m_window.resize(m_config.n_fft);
|
| 42 |
+
for (int i = 0; i < m_config.n_fft; ++i) {
|
| 43 |
+
m_window[i] = 0.5f * (1.0f - std::cos(2.0f * M_PI * i / (m_config.n_fft - 1)));
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
void MelFilterBank::CreateMelFilterbank() {
|
| 48 |
+
int n_freqs = m_n_freqs;
|
| 49 |
+
int n_mels = m_config.n_mels;
|
| 50 |
+
|
| 51 |
+
// Frequency points for each FFT bin
|
| 52 |
+
std::vector<float> all_freqs(n_freqs);
|
| 53 |
+
for (int i = 0; i < n_freqs; ++i) {
|
| 54 |
+
all_freqs[i] = i * m_config.sampling_rate / (2.0f * (n_freqs - 1));
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// Mel points
|
| 58 |
+
float m_min = hz_to_mel(0.0f);
|
| 59 |
+
float m_max = hz_to_mel(m_config.sampling_rate / 2.0f);
|
| 60 |
+
std::vector<float> m_pts(n_mels + 2);
|
| 61 |
+
for (int i = 0; i < n_mels + 2; ++i) {
|
| 62 |
+
m_pts[i] = m_min + (m_max - m_min) * i / (n_mels + 1);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Convert to Hz
|
| 66 |
+
std::vector<float> f_pts(n_mels + 2);
|
| 67 |
+
for (int i = 0; i < n_mels + 2; ++i) {
|
| 68 |
+
f_pts[i] = mel_to_hz(m_pts[i]);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Build filterbank matrix [n_freqs, n_mels]
|
| 72 |
+
m_mel_basis.resize(n_freqs * n_mels, 0.0f);
|
| 73 |
+
|
| 74 |
+
for (int m = 0; m < n_mels; ++m) {
|
| 75 |
+
for (int k = 0; k < n_freqs; ++k) {
|
| 76 |
+
float freq = all_freqs[k];
|
| 77 |
+
float left = f_pts[m];
|
| 78 |
+
float center = f_pts[m + 1];
|
| 79 |
+
float right = f_pts[m + 2];
|
| 80 |
+
|
| 81 |
+
float val = 0.0f;
|
| 82 |
+
if (freq >= left && freq <= center) {
|
| 83 |
+
val = (freq - left) / (center - left);
|
| 84 |
+
} else if (freq >= center && freq <= right) {
|
| 85 |
+
val = (right - freq) / (right - center);
|
| 86 |
+
}
|
| 87 |
+
m_mel_basis[k * n_mels + m] = val;
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
int MelFilterBank::ComputeNumFrames(int num_samples, int hop_length) {
|
| 93 |
+
return (num_samples + hop_length / 2) / hop_length;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Radix-2 FFT (in-place, complex)
|
| 97 |
+
static void fft(std::vector<float>& real, std::vector<float>& imag, bool inverse = false) {
|
| 98 |
+
int n = (int)real.size();
|
| 99 |
+
// Bit-reversal permutation
|
| 100 |
+
for (int i = 1, j = 0; i < n; ++i) {
|
| 101 |
+
int bit = n >> 1;
|
| 102 |
+
for (; j & bit; bit >>= 1) j ^= bit;
|
| 103 |
+
j ^= bit;
|
| 104 |
+
if (i < j) {
|
| 105 |
+
std::swap(real[i], real[j]);
|
| 106 |
+
std::swap(imag[i], imag[j]);
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
// Butterfly
|
| 110 |
+
for (int len = 2; len <= n; len <<= 1) {
|
| 111 |
+
float angle = 2.0f * M_PI / len * (inverse ? 1.0f : -1.0f);
|
| 112 |
+
float w_real = std::cos(angle);
|
| 113 |
+
float w_imag = std::sin(angle);
|
| 114 |
+
for (int i = 0; i < n; i += len) {
|
| 115 |
+
float cur_real = 1.0f, cur_imag = 0.0f;
|
| 116 |
+
for (int j = 0; j < len / 2; ++j) {
|
| 117 |
+
int a = i + j;
|
| 118 |
+
int b = i + j + len / 2;
|
| 119 |
+
float t_real = cur_real * real[b] - cur_imag * imag[b];
|
| 120 |
+
float t_imag = cur_real * imag[b] + cur_imag * real[b];
|
| 121 |
+
real[b] = real[a] - t_real;
|
| 122 |
+
imag[b] = imag[a] - t_imag;
|
| 123 |
+
real[a] += t_real;
|
| 124 |
+
imag[a] += t_imag;
|
| 125 |
+
float next_real = cur_real * w_real - cur_imag * w_imag;
|
| 126 |
+
cur_imag = cur_real * w_imag + cur_imag * w_real;
|
| 127 |
+
cur_real = next_real;
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
if (inverse) {
|
| 132 |
+
for (int i = 0; i < n; ++i) {
|
| 133 |
+
real[i] /= n;
|
| 134 |
+
imag[i] /= n;
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
void MelFilterBank::ComputeSTFT(const std::vector<float>& samples,
|
| 140 |
+
std::vector<float>& spec_real,
|
| 141 |
+
std::vector<float>& spec_imag,
|
| 142 |
+
int& num_frames) {
|
| 143 |
+
int n_fft = m_config.n_fft;
|
| 144 |
+
int hop = m_config.hop_length;
|
| 145 |
+
int num_samples = static_cast<int>(samples.size());
|
| 146 |
+
|
| 147 |
+
num_frames = ComputeNumFrames(num_samples, hop);
|
| 148 |
+
int n_freqs = n_fft / 2 + 1;
|
| 149 |
+
|
| 150 |
+
spec_real.assign(num_frames * n_freqs, 0.0f);
|
| 151 |
+
spec_imag.assign(num_frames * n_freqs, 0.0f);
|
| 152 |
+
|
| 153 |
+
// Pad signal (center padding, matching Python torch.stft center=True)
|
| 154 |
+
int pad_amount = n_fft / 2;
|
| 155 |
+
std::vector<float> padded(pad_amount + num_samples + pad_amount, 0.0f);
|
| 156 |
+
std::copy(samples.begin(), samples.end(), padded.begin() + pad_amount);
|
| 157 |
+
|
| 158 |
+
// FFT workspace
|
| 159 |
+
std::vector<float> fft_real(n_fft), fft_imag(n_fft);
|
| 160 |
+
|
| 161 |
+
for (int frame = 0; frame < num_frames; ++frame) {
|
| 162 |
+
int start = frame * hop;
|
| 163 |
+
// Apply window and copy to FFT buffer
|
| 164 |
+
for (int n = 0; n < n_fft; ++n) {
|
| 165 |
+
fft_real[n] = padded[start + n] * m_window[n];
|
| 166 |
+
fft_imag[n] = 0.0f;
|
| 167 |
+
}
|
| 168 |
+
// Forward FFT
|
| 169 |
+
fft(fft_real, fft_imag, false);
|
| 170 |
+
// Extract first n_freqs bins
|
| 171 |
+
for (int k = 0; k < n_freqs; ++k) {
|
| 172 |
+
spec_real[frame * n_freqs + k] = fft_real[k];
|
| 173 |
+
spec_imag[frame * n_freqs + k] = fft_imag[k];
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
std::vector<float> MelFilterBank::Extract(const std::vector<float>& samples, int sample_rate) {
|
| 179 |
+
if (sample_rate != m_config.sampling_rate) {
|
| 180 |
+
printf("WARNING: sample_rate mismatch: expected %d, got %d\n",
|
| 181 |
+
m_config.sampling_rate, sample_rate);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
int num_frames;
|
| 185 |
+
std::vector<float> spec_real, spec_imag;
|
| 186 |
+
ComputeSTFT(samples, spec_real, spec_imag, num_frames);
|
| 187 |
+
|
| 188 |
+
int n_freqs = m_n_freqs;
|
| 189 |
+
int n_mels = m_config.n_mels;
|
| 190 |
+
|
| 191 |
+
// Compute magnitude spectrogram [num_frames, n_freqs]
|
| 192 |
+
std::vector<float> spec_mag(num_frames * n_freqs);
|
| 193 |
+
for (int i = 0; i < num_frames * n_freqs; ++i) {
|
| 194 |
+
spec_mag[i] = std::sqrt(spec_real[i] * spec_real[i] + spec_imag[i] * spec_imag[i]);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// Apply mel filterbank: mel[frame, m] = sum_freq(spec[frame, freq] * basis[freq, m])
|
| 198 |
+
std::vector<float> mel(num_frames * n_mels, 0.0f);
|
| 199 |
+
for (int f = 0; f < num_frames; ++f) {
|
| 200 |
+
for (int m = 0; m < n_mels; ++m) {
|
| 201 |
+
float sum = 0.0f;
|
| 202 |
+
for (int k = 0; k < n_freqs; ++k) {
|
| 203 |
+
sum += spec_mag[f * n_freqs + k] * m_mel_basis[k * n_mels + m];
|
| 204 |
+
}
|
| 205 |
+
mel[f * n_mels + m] = std::log(std::max(sum, 1e-7f));
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
// Expected num_frames
|
| 210 |
+
int expected_frames = ComputeNumFrames(static_cast<int>(samples.size()), m_config.hop_length);
|
| 211 |
+
if (num_frames > expected_frames) {
|
| 212 |
+
// Truncate
|
| 213 |
+
std::vector<float> truncated(expected_frames * n_mels);
|
| 214 |
+
std::copy(mel.begin(), mel.begin() + expected_frames * n_mels, truncated.begin());
|
| 215 |
+
return truncated;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
return mel;
|
| 219 |
+
}
|
cpp/src/fbank.hpp
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* FBANK: Mel filterbank feature extraction for prompt audio.
|
| 5 |
+
*
|
| 6 |
+
* Extracts log-mel spectrogram features from audio waveform, matching the
|
| 7 |
+
* Python LocalVocosFbank behavior.
|
| 8 |
+
**************************************************************************************************/
|
| 9 |
+
|
| 10 |
+
#pragma once
|
| 11 |
+
|
| 12 |
+
#include <vector>
|
| 13 |
+
#include <cmath>
|
| 14 |
+
#include <cstdint>
|
| 15 |
+
|
| 16 |
+
class MelFilterBank {
|
| 17 |
+
public:
|
| 18 |
+
struct Config {
|
| 19 |
+
int sampling_rate;
|
| 20 |
+
int n_mels;
|
| 21 |
+
int n_fft;
|
| 22 |
+
int hop_length;
|
| 23 |
+
|
| 24 |
+
Config() : sampling_rate(24000), n_mels(100), n_fft(1024), hop_length(256) {}
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
MelFilterBank();
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* Initialize the filterbank with given config.
|
| 31 |
+
*/
|
| 32 |
+
int Init(const Config& config = Config());
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* Extract log-mel features from float32 audio samples [num_samples].
|
| 36 |
+
* Returns features of shape [num_frames, n_mels].
|
| 37 |
+
*/
|
| 38 |
+
std::vector<float> Extract(const std::vector<float>& samples, int sample_rate);
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Get the number of frames for given number of samples.
|
| 42 |
+
*/
|
| 43 |
+
static int ComputeNumFrames(int num_samples, int hop_length);
|
| 44 |
+
|
| 45 |
+
const Config& GetConfig() const { return m_config; }
|
| 46 |
+
|
| 47 |
+
private:
|
| 48 |
+
Config m_config;
|
| 49 |
+
|
| 50 |
+
// Mel filterbank matrix [n_freqs, n_mels]
|
| 51 |
+
std::vector<float> m_mel_basis;
|
| 52 |
+
int m_n_freqs;
|
| 53 |
+
|
| 54 |
+
// Hann window
|
| 55 |
+
std::vector<float> m_window;
|
| 56 |
+
|
| 57 |
+
void CreateMelFilterbank();
|
| 58 |
+
void CreateWindow();
|
| 59 |
+
|
| 60 |
+
// Simple STFT
|
| 61 |
+
void ComputeSTFT(const std::vector<float>& samples,
|
| 62 |
+
std::vector<float>& spec_real,
|
| 63 |
+
std::vector<float>& spec_imag,
|
| 64 |
+
int& num_frames);
|
| 65 |
+
};
|
cpp/src/pinyin_table.hpp
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
cpp/src/tokenizer.cpp
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Tokenizer implementation. Uses pinyin_table.hpp (auto-generated from pypinyin.pinyin_dict)
|
| 5 |
+
* for Chinese character → pinyin token mapping, matching Python LocalEmiliaTokenizer output.
|
| 6 |
+
**************************************************************************************************/
|
| 7 |
+
|
| 8 |
+
#include "tokenizer.hpp"
|
| 9 |
+
#include "src/pinyin_table.hpp"
|
| 10 |
+
|
| 11 |
+
#include <fstream>
|
| 12 |
+
#include <sstream>
|
| 13 |
+
#include <regex>
|
| 14 |
+
#include <cctype>
|
| 15 |
+
#include <algorithm>
|
| 16 |
+
|
| 17 |
+
Tokenizer::Tokenizer() : m_pad_id(0), m_loaded(false) {}
|
| 18 |
+
|
| 19 |
+
int Tokenizer::Load(const std::string& token_file) {
|
| 20 |
+
std::ifstream file(token_file);
|
| 21 |
+
if (!file.is_open()) {
|
| 22 |
+
printf("Failed to open token file: %s\n", token_file.c_str());
|
| 23 |
+
return -1;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
m_token2id.clear();
|
| 27 |
+
std::string line;
|
| 28 |
+
while (std::getline(file, line)) {
|
| 29 |
+
if (line.empty()) continue;
|
| 30 |
+
// Format: token\tid
|
| 31 |
+
size_t tab_pos = line.rfind('\t');
|
| 32 |
+
if (tab_pos == std::string::npos) continue;
|
| 33 |
+
|
| 34 |
+
std::string token = line.substr(0, tab_pos);
|
| 35 |
+
int id = std::stoi(line.substr(tab_pos + 1));
|
| 36 |
+
m_token2id[token] = id;
|
| 37 |
+
}
|
| 38 |
+
file.close();
|
| 39 |
+
|
| 40 |
+
// Build reverse mapping
|
| 41 |
+
int max_id = 0;
|
| 42 |
+
for (auto& kv : m_token2id) {
|
| 43 |
+
if (kv.second > max_id) max_id = kv.second;
|
| 44 |
+
}
|
| 45 |
+
m_id2token.resize(max_id + 1);
|
| 46 |
+
for (auto& kv : m_token2id) {
|
| 47 |
+
m_id2token[kv.second] = kv.first;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
auto it = m_token2id.find("_");
|
| 51 |
+
if (it != m_token2id.end()) {
|
| 52 |
+
m_pad_id = it->second;
|
| 53 |
+
} else {
|
| 54 |
+
m_pad_id = 0;
|
| 55 |
+
printf("WARNING: pad token '_' not found in token file, using 0 as pad_id\n");
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
m_loaded = true;
|
| 59 |
+
printf("Loaded %zu tokens from %s, pad_id=%d\n", m_token2id.size(), token_file.c_str(), m_pad_id);
|
| 60 |
+
return 0;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
int Tokenizer::TokenToId(const std::string& token) const {
|
| 64 |
+
auto it = m_token2id.find(token);
|
| 65 |
+
return (it != m_token2id.end()) ? it->second : m_pad_id;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
bool Tokenizer::IsChinese(char c) {
|
| 69 |
+
return (c >= 0x4E00 && c <= 0x9FFF);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
bool Tokenizer::IsAlphabet(char c) {
|
| 73 |
+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
std::string Tokenizer::MapPunctuation(const std::string& text) {
|
| 77 |
+
// Map full-width Chinese punctuation to ASCII equivalents.
|
| 78 |
+
// UTF-8 code points:
|
| 79 |
+
// \xEF\xBC\x8C = U+FF0C (,) \xEF\xBC\x9A = U+FF1A (:)
|
| 80 |
+
// \xE3\x80\x82 = U+3002 (。) \xE3\x80\x81 = U+3001 (、)
|
| 81 |
+
// \xEF\xBC\x81 = U+FF01 (!) \xE2\x80\x9C = U+201C (")
|
| 82 |
+
// \xEF\xBC\x9F = U+FF1F (?) \xE2\x80\x9D = U+201D (")
|
| 83 |
+
// \xEF\xBC\x9B = U+FF1B (;) \xE2\x80\x98 = U+2018 (')
|
| 84 |
+
// \xE2\x80\xA6 = U+2026 (…) \xE2\x80\x99 = U+2019 (')
|
| 85 |
+
// \xE2\x8B\xAF = U+22EF (⋯)
|
| 86 |
+
static const std::pair<std::string, std::string> replacements[] = {
|
| 87 |
+
{"\xEF\xBC\x8C", ","}, // ,
|
| 88 |
+
{"\xE3\x80\x82", "."}, // 。
|
| 89 |
+
{"\xEF\xBC\x81", "!"}, // !
|
| 90 |
+
{"\xEF\xBC\x9F", "?"}, // ?
|
| 91 |
+
{"\xEF\xBC\x9B", ";"}, // ;
|
| 92 |
+
{"\xEF\xBC\x9A", ":"}, // :
|
| 93 |
+
{"\xE3\x80\x81", ","}, // 、
|
| 94 |
+
{"\xE2\x80\x9C", "\""}, // "
|
| 95 |
+
{"\xE2\x80\x9D", "\""}, // "
|
| 96 |
+
{"\xE2\x80\x98", "'"}, // '
|
| 97 |
+
{"\xE2\x80\x99", "'"}, // '
|
| 98 |
+
{"\xE2\x80\xA6", "..."}, // …
|
| 99 |
+
{"\xE2\x8B\xAF", "..."}, // ⋯
|
| 100 |
+
};
|
| 101 |
+
std::string result = text;
|
| 102 |
+
for (auto& rep : replacements) {
|
| 103 |
+
size_t pos = 0;
|
| 104 |
+
while ((pos = result.find(rep.first, pos)) != std::string::npos) {
|
| 105 |
+
result.replace(pos, rep.first.length(), rep.second);
|
| 106 |
+
pos += rep.second.length();
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
return result;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
std::vector<std::pair<std::string, std::string>>
|
| 113 |
+
Tokenizer::GetSegments(const std::string& text) const {
|
| 114 |
+
std::vector<std::pair<std::string, std::string>> segments;
|
| 115 |
+
if (text.empty()) return segments;
|
| 116 |
+
|
| 117 |
+
std::string current;
|
| 118 |
+
std::string current_lang;
|
| 119 |
+
|
| 120 |
+
auto char_lang = [](char c) -> std::string {
|
| 121 |
+
if (IsChinese(c)) return "zh";
|
| 122 |
+
if (IsAlphabet(c)) return "en";
|
| 123 |
+
return "other";
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
for (size_t i = 0; i < text.size(); ) {
|
| 127 |
+
// Handle UTF-8 multi-byte characters
|
| 128 |
+
size_t char_len = 1;
|
| 129 |
+
unsigned char c = static_cast<unsigned char>(text[i]);
|
| 130 |
+
if ((c & 0x80) == 0) char_len = 1;
|
| 131 |
+
else if ((c & 0xE0) == 0xC0) char_len = 2;
|
| 132 |
+
else if ((c & 0xF0) == 0xE0) char_len = 3;
|
| 133 |
+
else if ((c & 0xF8) == 0xF0) char_len = 4;
|
| 134 |
+
|
| 135 |
+
std::string ch = text.substr(i, char_len);
|
| 136 |
+
std::string lang;
|
| 137 |
+
|
| 138 |
+
if (char_len > 1) {
|
| 139 |
+
lang = "zh"; // Assume multi-byte UTF-8 is Chinese
|
| 140 |
+
} else if (IsAlphabet(ch[0])) {
|
| 141 |
+
lang = "en";
|
| 142 |
+
} else {
|
| 143 |
+
lang = "other";
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
if (current.empty()) {
|
| 147 |
+
current = ch;
|
| 148 |
+
current_lang = lang;
|
| 149 |
+
} else if (current_lang == "other" || lang == "other" || current_lang == lang) {
|
| 150 |
+
current += ch;
|
| 151 |
+
if (current_lang == "other" && lang != "other") current_lang = lang;
|
| 152 |
+
} else {
|
| 153 |
+
segments.push_back({current, current_lang});
|
| 154 |
+
current = ch;
|
| 155 |
+
current_lang = lang;
|
| 156 |
+
}
|
| 157 |
+
i += char_len;
|
| 158 |
+
}
|
| 159 |
+
if (!current.empty()) {
|
| 160 |
+
segments.push_back({current, current_lang});
|
| 161 |
+
}
|
| 162 |
+
return segments;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
std::vector<std::string> Tokenizer::TokenizeZh(const std::string& text) const {
|
| 166 |
+
// Character-by-character pinyin tokenization using pinyin_table.hpp.
|
| 167 |
+
// Matches Python LocalEmiliaTokenizer (without jieba word segmentation).
|
| 168 |
+
std::vector<std::string> tokens;
|
| 169 |
+
std::string normalized = MapPunctuation(text);
|
| 170 |
+
|
| 171 |
+
for (size_t i = 0; i < normalized.size(); ) {
|
| 172 |
+
unsigned char c = static_cast<unsigned char>(normalized[i]);
|
| 173 |
+
size_t char_len = 1;
|
| 174 |
+
if ((c & 0x80) == 0) char_len = 1;
|
| 175 |
+
else if ((c & 0xE0) == 0xC0) char_len = 2;
|
| 176 |
+
else if ((c & 0xF0) == 0xE0) char_len = 3;
|
| 177 |
+
else if ((c & 0xF8) == 0xF0) char_len = 4;
|
| 178 |
+
|
| 179 |
+
std::string ch = normalized.substr(i, char_len);
|
| 180 |
+
i += char_len;
|
| 181 |
+
|
| 182 |
+
// Punctuation or ASCII
|
| 183 |
+
if (char_len == 1) {
|
| 184 |
+
std::string mapped = MapPunctuation(ch);
|
| 185 |
+
if (mapped != ch && m_token2id.find(mapped) != m_token2id.end()) {
|
| 186 |
+
tokens.push_back(mapped);
|
| 187 |
+
continue;
|
| 188 |
+
}
|
| 189 |
+
if (m_token2id.find(ch) != m_token2id.end()) {
|
| 190 |
+
tokens.push_back(ch);
|
| 191 |
+
continue;
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// Chinese character: lookup pinyin token IDs via generated table
|
| 196 |
+
if (char_len == 3) {
|
| 197 |
+
uint32_t cp = ((unsigned char)ch[0] & 0x0F) << 12
|
| 198 |
+
| ((unsigned char)ch[1] & 0x3F) << 6
|
| 199 |
+
| ((unsigned char)ch[2] & 0x3F);
|
| 200 |
+
auto ids = pinyin_lookup(cp);
|
| 201 |
+
for (int tid : ids) {
|
| 202 |
+
if (tid >= 0 && tid < (int)m_id2token.size() && !m_id2token[tid].empty()) {
|
| 203 |
+
tokens.push_back(m_id2token[tid]);
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
return tokens;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
std::vector<std::string> Tokenizer::TokenizeEn(const std::string& text) const {
|
| 212 |
+
// Simplified English tokenization: character-by-character phoneme mapping
|
| 213 |
+
// In production, use the Python tokenizer with piper_phonemize
|
| 214 |
+
std::vector<std::string> tokens;
|
| 215 |
+
std::string lower = text;
|
| 216 |
+
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
| 217 |
+
lower = MapPunctuation(lower);
|
| 218 |
+
|
| 219 |
+
for (size_t i = 0; i < lower.size(); ++i) {
|
| 220 |
+
char c = lower[i];
|
| 221 |
+
if (c == ' ') continue;
|
| 222 |
+
if (ispunct(c)) {
|
| 223 |
+
// Map common punctuation
|
| 224 |
+
switch (c) {
|
| 225 |
+
case ',': tokens.push_back(","); break;
|
| 226 |
+
case '.': tokens.push_back("."); break;
|
| 227 |
+
case '!': tokens.push_back("!"); break;
|
| 228 |
+
case '?': tokens.push_back("?"); break;
|
| 229 |
+
case ';': tokens.push_back(";"); break;
|
| 230 |
+
case ':': tokens.push_back(":"); break;
|
| 231 |
+
case '\'': tokens.push_back("'"); break;
|
| 232 |
+
case '"': tokens.push_back("\""); break;
|
| 233 |
+
default: break;
|
| 234 |
+
}
|
| 235 |
+
continue;
|
| 236 |
+
}
|
| 237 |
+
// Output character as-is (phoneme-based tokens)
|
| 238 |
+
std::string token(1, c);
|
| 239 |
+
if (m_token2id.find(token) != m_token2id.end()) {
|
| 240 |
+
tokens.push_back(token);
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
return tokens;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
std::vector<int> Tokenizer::TextToTokenIds(const std::string& text) const {
|
| 247 |
+
if (!m_loaded) return {};
|
| 248 |
+
|
| 249 |
+
std::string normalized = MapPunctuation(text);
|
| 250 |
+
auto segments = GetSegments(normalized);
|
| 251 |
+
|
| 252 |
+
std::vector<int> token_ids;
|
| 253 |
+
for (auto& seg : segments) {
|
| 254 |
+
std::vector<std::string> tokens;
|
| 255 |
+
if (seg.second == "zh") {
|
| 256 |
+
tokens = TokenizeZh(seg.first);
|
| 257 |
+
} else if (seg.second == "en") {
|
| 258 |
+
tokens = TokenizeEn(seg.first);
|
| 259 |
+
}
|
| 260 |
+
// "other" segments: try individual chars as tokens
|
| 261 |
+
else {
|
| 262 |
+
for (char c : seg.first) {
|
| 263 |
+
std::string token(1, c);
|
| 264 |
+
if (m_token2id.find(token) != m_token2id.end()) {
|
| 265 |
+
token_ids.push_back(m_token2id.at(token));
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
continue;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
for (auto& token : tokens) {
|
| 272 |
+
auto it = m_token2id.find(token);
|
| 273 |
+
if (it != m_token2id.end()) {
|
| 274 |
+
token_ids.push_back(it->second);
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
return token_ids;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
std::vector<std::vector<int>> Tokenizer::TextsToTokenIds(const std::vector<std::string>& texts) const {
|
| 282 |
+
std::vector<std::vector<int>> result;
|
| 283 |
+
result.reserve(texts.size());
|
| 284 |
+
for (auto& text : texts) {
|
| 285 |
+
result.push_back(TextToTokenIds(text));
|
| 286 |
+
}
|
| 287 |
+
return result;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
void Tokenizer::BuildCatTokens(const std::vector<int>& prompt_tokens,
|
| 291 |
+
const std::vector<int>& text_tokens,
|
| 292 |
+
int max_tokens,
|
| 293 |
+
std::vector<int32_t>& out_cat_tokens) const {
|
| 294 |
+
out_cat_tokens.assign(max_tokens, static_cast<int32_t>(m_pad_id));
|
| 295 |
+
|
| 296 |
+
size_t pos = 0;
|
| 297 |
+
for (size_t i = 0; i < prompt_tokens.size() && pos < (size_t)max_tokens; ++i) {
|
| 298 |
+
out_cat_tokens[pos++] = static_cast<int32_t>(prompt_tokens[i]);
|
| 299 |
+
}
|
| 300 |
+
for (size_t i = 0; i < text_tokens.size() && pos < (size_t)max_tokens; ++i) {
|
| 301 |
+
out_cat_tokens[pos++] = static_cast<int32_t>(text_tokens[i]);
|
| 302 |
+
}
|
| 303 |
+
if (pos < (size_t)max_tokens) {
|
| 304 |
+
out_cat_tokens[pos] = static_cast<int32_t>(m_pad_id);
|
| 305 |
+
}
|
| 306 |
+
}
|
cpp/src/tokenizer.hpp
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Tokenizer: loads tokens.txt and maps token strings to IDs.
|
| 5 |
+
*
|
| 6 |
+
* For board deployment, tokenization should ideally be done on a host machine
|
| 7 |
+
* and token IDs passed directly. This class provides basic tokenization
|
| 8 |
+
* capability for convenience.
|
| 9 |
+
**************************************************************************************************/
|
| 10 |
+
|
| 11 |
+
#pragma once
|
| 12 |
+
|
| 13 |
+
#include <string>
|
| 14 |
+
#include <vector>
|
| 15 |
+
#include <unordered_map>
|
| 16 |
+
#include <cstdint>
|
| 17 |
+
|
| 18 |
+
class Tokenizer {
|
| 19 |
+
public:
|
| 20 |
+
Tokenizer();
|
| 21 |
+
|
| 22 |
+
/**
|
| 23 |
+
* Load tokens.txt file. Format: token\tid per line.
|
| 24 |
+
*/
|
| 25 |
+
int Load(const std::string& token_file);
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* Convert a single text to token IDs using simplified rules.
|
| 29 |
+
* For production use, pre-compute token IDs with the Python tokenizer.
|
| 30 |
+
*/
|
| 31 |
+
std::vector<int> TextToTokenIds(const std::string& text) const;
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* Convert multiple texts to token IDs.
|
| 35 |
+
*/
|
| 36 |
+
std::vector<std::vector<int>> TextsToTokenIds(const std::vector<std::string>& texts) const;
|
| 37 |
+
|
| 38 |
+
/**
|
| 39 |
+
* Get the pad token ID ("_" token).
|
| 40 |
+
*/
|
| 41 |
+
int GetPadId() const { return m_pad_id; }
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Get vocabulary size.
|
| 45 |
+
*/
|
| 46 |
+
int GetVocabSize() const { return static_cast<int>(m_token2id.size()); }
|
| 47 |
+
|
| 48 |
+
/**
|
| 49 |
+
* Lookup a single token. Returns pad_id if not found.
|
| 50 |
+
*/
|
| 51 |
+
int TokenToId(const std::string& token) const;
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* Build concatenated token array [prompt_tokens + text_tokens + pad] padded to max_tokens.
|
| 55 |
+
*/
|
| 56 |
+
void BuildCatTokens(const std::vector<int>& prompt_tokens,
|
| 57 |
+
const std::vector<int>& text_tokens,
|
| 58 |
+
int max_tokens,
|
| 59 |
+
std::vector<int32_t>& out_cat_tokens) const;
|
| 60 |
+
|
| 61 |
+
bool IsLoaded() const { return m_loaded; }
|
| 62 |
+
|
| 63 |
+
private:
|
| 64 |
+
std::unordered_map<std::string, int> m_token2id;
|
| 65 |
+
std::vector<std::string> m_id2token;
|
| 66 |
+
int m_pad_id;
|
| 67 |
+
bool m_loaded;
|
| 68 |
+
|
| 69 |
+
// Simplified text→tokens for Chinese (char-by-char pinyin-like tokens)
|
| 70 |
+
std::vector<std::string> TokenizeZh(const std::string& text) const;
|
| 71 |
+
// Simplified text→tokens for English (basic phoneme rules)
|
| 72 |
+
std::vector<std::string> TokenizeEn(const std::string& text) const;
|
| 73 |
+
// Split text into language segments
|
| 74 |
+
std::vector<std::pair<std::string, std::string>> GetSegments(const std::string& text) const;
|
| 75 |
+
|
| 76 |
+
static bool IsChinese(char c);
|
| 77 |
+
static bool IsAlphabet(char c);
|
| 78 |
+
static std::string MapPunctuation(const std::string& text);
|
| 79 |
+
};
|
cpp/src/vocoder.cpp
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Vocoder implementation using kissfft for fast IRFFT.
|
| 5 |
+
**************************************************************************************************/
|
| 6 |
+
|
| 7 |
+
#include "src/vocoder.hpp"
|
| 8 |
+
|
| 9 |
+
#include <cmath>
|
| 10 |
+
#include <cstring>
|
| 11 |
+
#include <cstdio>
|
| 12 |
+
#include <algorithm>
|
| 13 |
+
|
| 14 |
+
#ifndef M_PI
|
| 15 |
+
#define M_PI 3.14159265358979323846
|
| 16 |
+
#endif
|
| 17 |
+
|
| 18 |
+
Vocoder::Vocoder() : m_has_init(false), m_n_freqs(0) {}
|
| 19 |
+
Vocoder::~Vocoder() {
|
| 20 |
+
m_session.reset();
|
| 21 |
+
if (m_fft_cfg) { rnn_fft_free(m_fft_cfg, 0); m_fft_cfg = nullptr; }
|
| 22 |
+
m_ifft_cfg = nullptr; // shared with m_fft_cfg
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
int Vocoder::Init(const Config& cfg) {
|
| 26 |
+
m_cfg = cfg;
|
| 27 |
+
m_n_freqs = cfg.n_fft / 2 + 1; // 513
|
| 28 |
+
|
| 29 |
+
BuildWindow();
|
| 30 |
+
|
| 31 |
+
// Init kissfft once
|
| 32 |
+
m_fft_cfg = rnn_fft_alloc(cfg.n_fft, NULL, NULL, 0);
|
| 33 |
+
if (!m_fft_cfg) {
|
| 34 |
+
printf("Failed to init kissfft for vocoder\n");
|
| 35 |
+
return -1;
|
| 36 |
+
}
|
| 37 |
+
m_ifft_cfg = m_fft_cfg;
|
| 38 |
+
|
| 39 |
+
// Load vocos_full.axmodel
|
| 40 |
+
m_session = std::make_unique<EngineWrapper>();
|
| 41 |
+
if (m_session->Init(cfg.model_path.c_str()) != 0) {
|
| 42 |
+
printf("Failed to load vocoder model: %s\n", cfg.model_path.c_str());
|
| 43 |
+
return -1;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
m_has_init = true;
|
| 47 |
+
printf("Vocoder initialized: n_fft=%d, hop=%d, n_mels=%d\n",
|
| 48 |
+
cfg.n_fft, cfg.hop_length, cfg.n_mels);
|
| 49 |
+
return 0;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
void Vocoder::BuildWindow() {
|
| 53 |
+
int n = m_cfg.n_fft;
|
| 54 |
+
m_window.resize(n);
|
| 55 |
+
m_window_sq.resize(n);
|
| 56 |
+
for (int i = 0; i < n; ++i) {
|
| 57 |
+
m_window[i] = 0.5f * (1.0f - std::cos(2.0f * M_PI * i / (n - 1)));
|
| 58 |
+
m_window_sq[i] = m_window[i] * m_window[i];
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
int Vocoder::Decode(const std::vector<float>& mel, int T, float feat_scale, std::vector<float>& audio) {
|
| 63 |
+
if (!m_has_init || mel.empty()) return -1;
|
| 64 |
+
|
| 65 |
+
int feat_dim = m_cfg.n_mels; // 100
|
| 66 |
+
int n_fft = m_cfg.n_fft; // 1024
|
| 67 |
+
int n_freqs = m_n_freqs; // 513
|
| 68 |
+
int hop = m_cfg.hop_length; // 256
|
| 69 |
+
|
| 70 |
+
// 1. Undo feat_scale and transpose to [1, n_mels, T_model]
|
| 71 |
+
float inv_scale = (feat_scale != 0.0f) ? (1.0f / feat_scale) : 1.0f;
|
| 72 |
+
int T_model = 620;
|
| 73 |
+
std::vector<float> mel_padded(T_model * feat_dim, 0.0f);
|
| 74 |
+
for (int t = 0; t < T; ++t) {
|
| 75 |
+
for (int c = 0; c < feat_dim; ++c) {
|
| 76 |
+
mel_padded[c * T_model + t] = mel[t * feat_dim + c] * inv_scale;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// 2. Run vocos_full.axmodel on NPU
|
| 81 |
+
int input_idx = m_session->GetInputIndex("mel");
|
| 82 |
+
if (input_idx < 0) input_idx = 0;
|
| 83 |
+
m_session->SetInput(mel_padded.data(), input_idx);
|
| 84 |
+
|
| 85 |
+
if (m_session->RunSync() != 0) {
|
| 86 |
+
printf("Vocoder NPU inference failed\n");
|
| 87 |
+
return -1;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
int real_idx = m_session->GetOutputIndex("real");
|
| 91 |
+
int imag_idx = m_session->GetOutputIndex("imag");
|
| 92 |
+
if (real_idx < 0) real_idx = 0;
|
| 93 |
+
if (imag_idx < 0) imag_idx = 1;
|
| 94 |
+
|
| 95 |
+
int spec_size = m_session->GetOutputSize(real_idx);
|
| 96 |
+
std::vector<float> real_spec(spec_size / sizeof(float));
|
| 97 |
+
std::vector<float> imag_spec(spec_size / sizeof(float));
|
| 98 |
+
m_session->GetOutput(real_spec.data(), real_idx);
|
| 99 |
+
m_session->GetOutput(imag_spec.data(), imag_idx);
|
| 100 |
+
|
| 101 |
+
// 3. kissfft IRFFT + window + overlap-add
|
| 102 |
+
int out_len = (T - 1) * hop + n_fft;
|
| 103 |
+
audio.assign(out_len, 0.0f);
|
| 104 |
+
std::vector<float> envelope(out_len, 0.0f);
|
| 105 |
+
|
| 106 |
+
std::vector<kiss_fft_cpx> cx_in(n_fft), cx_out(n_fft);
|
| 107 |
+
|
| 108 |
+
for (int t = 0; t < T; ++t) {
|
| 109 |
+
int spec_offset = t * n_freqs;
|
| 110 |
+
|
| 111 |
+
// Build full Hermitian spectrum from [0..N/2]
|
| 112 |
+
cx_in[0].r = real_spec[spec_offset + 0];
|
| 113 |
+
cx_in[0].i = 0.0f;
|
| 114 |
+
for (int k = 1; k < n_freqs - 1; ++k) {
|
| 115 |
+
cx_in[k].r = real_spec[spec_offset + k];
|
| 116 |
+
cx_in[k].i = imag_spec[spec_offset + k];
|
| 117 |
+
cx_in[n_fft - k].r = real_spec[spec_offset + k];
|
| 118 |
+
cx_in[n_fft - k].i = -imag_spec[spec_offset + k];
|
| 119 |
+
}
|
| 120 |
+
cx_in[n_freqs - 1].r = real_spec[spec_offset + (n_freqs - 1)];
|
| 121 |
+
cx_in[n_freqs - 1].i = 0.0f; // Nyquist imag must be 0
|
| 122 |
+
|
| 123 |
+
rnn_ifft(m_ifft_cfg, cx_in.data(), cx_out.data(), 0);
|
| 124 |
+
|
| 125 |
+
// Window + overlap-add
|
| 126 |
+
int pos = t * hop;
|
| 127 |
+
for (int n = 0; n < n_fft; ++n) {
|
| 128 |
+
float sample = (cx_out[n].r / n_fft) * m_window[n];
|
| 129 |
+
int p = pos + n;
|
| 130 |
+
if (p < out_len) {
|
| 131 |
+
audio[p] += sample;
|
| 132 |
+
envelope[p] += m_window_sq[n];
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// 4. Normalize by window envelope
|
| 138 |
+
for (int i = 0; i < out_len; ++i) {
|
| 139 |
+
if (envelope[i] > 1e-10f) {
|
| 140 |
+
audio[i] /= envelope[i];
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
// 5. Center trim (n_fft/2 from each side, matching torch.istft center=True)
|
| 145 |
+
int pad = n_fft / 2;
|
| 146 |
+
int trim_len = out_len - 2 * pad;
|
| 147 |
+
if (trim_len > 0) {
|
| 148 |
+
std::vector<float> trimmed(audio.begin() + pad, audio.begin() + pad + trim_len);
|
| 149 |
+
audio = std::move(trimmed);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return 0;
|
| 153 |
+
}
|
cpp/src/vocoder.hpp
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Vocoder: mel features → audio via vocos_full.axmodel + IRFFT + overlap-add.
|
| 5 |
+
**************************************************************************************************/
|
| 6 |
+
|
| 7 |
+
#pragma once
|
| 8 |
+
|
| 9 |
+
#include <vector>
|
| 10 |
+
#include <string>
|
| 11 |
+
#include <memory>
|
| 12 |
+
|
| 13 |
+
#include "src/EngineWrapper.hpp"
|
| 14 |
+
#include "kiss_fft.h"
|
| 15 |
+
|
| 16 |
+
class Vocoder {
|
| 17 |
+
public:
|
| 18 |
+
struct Config {
|
| 19 |
+
std::string model_path;
|
| 20 |
+
int n_fft = 1024;
|
| 21 |
+
int hop_length = 256;
|
| 22 |
+
int win_length = 1024;
|
| 23 |
+
int n_mels = 100;
|
| 24 |
+
int sample_rate = 24000;
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
Vocoder();
|
| 28 |
+
~Vocoder();
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Initialize: load vocos_full.axmodel and pre-compute IRFFT basis.
|
| 32 |
+
*/
|
| 33 |
+
int Init(const Config& cfg);
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* Decode mel features to audio waveform.
|
| 37 |
+
* @param mel Mel features [T, n_mels] (flat, row-major)
|
| 38 |
+
* @param T Number of mel frames
|
| 39 |
+
* @param feat_scale Feature scaling factor to undo (Python: features/feat_scale before vocoder)
|
| 40 |
+
* @param audio Output audio samples (24kHz float32)
|
| 41 |
+
*/
|
| 42 |
+
int Decode(const std::vector<float>& mel, int T, float feat_scale, std::vector<float>& audio);
|
| 43 |
+
|
| 44 |
+
bool HasInit() const { return m_has_init; }
|
| 45 |
+
|
| 46 |
+
private:
|
| 47 |
+
Config m_cfg;
|
| 48 |
+
bool m_has_init;
|
| 49 |
+
std::unique_ptr<EngineWrapper> m_session;
|
| 50 |
+
|
| 51 |
+
// kissfft configs (shared forward/inverse twiddles)
|
| 52 |
+
kiss_fft_state* m_fft_cfg = nullptr;
|
| 53 |
+
kiss_fft_state* m_ifft_cfg = nullptr;
|
| 54 |
+
|
| 55 |
+
// Hann window
|
| 56 |
+
std::vector<float> m_window;
|
| 57 |
+
|
| 58 |
+
// Window squared for envelope normalization
|
| 59 |
+
std::vector<float> m_window_sq;
|
| 60 |
+
|
| 61 |
+
int m_n_freqs;
|
| 62 |
+
|
| 63 |
+
void BuildWindow();
|
| 64 |
+
|
| 65 |
+
static void Fft(std::vector<float>& real, std::vector<float>& imag, bool inverse);
|
| 66 |
+
};
|
cpp/src/wav_writer.hpp
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Simple WAV file writer for float32 mono audio.
|
| 5 |
+
**************************************************************************************************/
|
| 6 |
+
|
| 7 |
+
#pragma once
|
| 8 |
+
|
| 9 |
+
#include <vector>
|
| 10 |
+
#include <string>
|
| 11 |
+
#include <fstream>
|
| 12 |
+
#include <cstdint>
|
| 13 |
+
#include <cstring>
|
| 14 |
+
|
| 15 |
+
class WavWriter {
|
| 16 |
+
public:
|
| 17 |
+
/**
|
| 18 |
+
* Write float32 mono audio samples to a WAV file.
|
| 19 |
+
*
|
| 20 |
+
* @param path Output file path
|
| 21 |
+
* @param samples Float32 audio samples in range [-1.0, 1.0]
|
| 22 |
+
* @param sample_rate Sample rate in Hz (e.g., 24000)
|
| 23 |
+
* @return true on success
|
| 24 |
+
*/
|
| 25 |
+
static bool Write(const std::string& path,
|
| 26 |
+
const std::vector<float>& samples,
|
| 27 |
+
int sample_rate) {
|
| 28 |
+
std::ofstream file(path, std::ios::binary);
|
| 29 |
+
if (!file.is_open()) return false;
|
| 30 |
+
|
| 31 |
+
int num_samples = static_cast<int>(samples.size());
|
| 32 |
+
int byte_rate = sample_rate * 2; // 16-bit mono
|
| 33 |
+
int data_size = num_samples * 2;
|
| 34 |
+
int chunk_size = 36 + data_size;
|
| 35 |
+
|
| 36 |
+
// RIFF header
|
| 37 |
+
Write4CC(file, "RIFF");
|
| 38 |
+
WriteU32LE(file, chunk_size);
|
| 39 |
+
Write4CC(file, "WAVE");
|
| 40 |
+
|
| 41 |
+
// fmt chunk
|
| 42 |
+
Write4CC(file, "fmt ");
|
| 43 |
+
WriteU32LE(file, 16); // chunk size
|
| 44 |
+
WriteU16LE(file, 1); // PCM format
|
| 45 |
+
WriteU16LE(file, 1); // mono
|
| 46 |
+
WriteU32LE(file, sample_rate);
|
| 47 |
+
WriteU32LE(file, byte_rate);
|
| 48 |
+
WriteU16LE(file, 2); // block align
|
| 49 |
+
WriteU16LE(file, 16); // bits per sample
|
| 50 |
+
|
| 51 |
+
// data chunk
|
| 52 |
+
Write4CC(file, "data");
|
| 53 |
+
WriteU32LE(file, data_size);
|
| 54 |
+
|
| 55 |
+
// Convert float32 to int16 (with clipping)
|
| 56 |
+
for (float sample : samples) {
|
| 57 |
+
// Clamp to [-1, 1]
|
| 58 |
+
if (sample > 1.0f) sample = 1.0f;
|
| 59 |
+
if (sample < -1.0f) sample = -1.0f;
|
| 60 |
+
int16_t val = static_cast<int16_t>(sample * 32767.0f);
|
| 61 |
+
WriteU16LE(file, static_cast<uint16_t>(val));
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
file.close();
|
| 65 |
+
return true;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
private:
|
| 69 |
+
static void WriteU16LE(std::ofstream& f, uint16_t v) {
|
| 70 |
+
f.put(v & 0xFF);
|
| 71 |
+
f.put((v >> 8) & 0xFF);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
static void WriteU32LE(std::ofstream& f, uint32_t v) {
|
| 75 |
+
f.put(v & 0xFF);
|
| 76 |
+
f.put((v >> 8) & 0xFF);
|
| 77 |
+
f.put((v >> 16) & 0xFF);
|
| 78 |
+
f.put((v >> 24) & 0xFF);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
static void Write4CC(std::ofstream& f, const char* s) {
|
| 82 |
+
f.write(s, 4);
|
| 83 |
+
}
|
| 84 |
+
};
|
cpp/src/zipvoice_engine.cpp
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* ZipVoiceEngine implementation.
|
| 5 |
+
**************************************************************************************************/
|
| 6 |
+
|
| 7 |
+
#include "zipvoice_engine.hpp"
|
| 8 |
+
|
| 9 |
+
#include <fstream>
|
| 10 |
+
#include <sstream>
|
| 11 |
+
#include <cmath>
|
| 12 |
+
#include <cstring>
|
| 13 |
+
#include <algorithm>
|
| 14 |
+
#include <sys/time.h>
|
| 15 |
+
|
| 16 |
+
#ifndef M_PI
|
| 17 |
+
#define M_PI 3.14159265358979323846
|
| 18 |
+
#endif
|
| 19 |
+
|
| 20 |
+
// Simple JSON value parser (no external dependency)
|
| 21 |
+
// Only parses the simple structures needed by manifest.json and runtime_config.json
|
| 22 |
+
namespace {
|
| 23 |
+
|
| 24 |
+
std::string json_get_string(const std::string& json, const std::string& key) {
|
| 25 |
+
std::string search = "\"" + key + "\"";
|
| 26 |
+
size_t pos = json.find(search);
|
| 27 |
+
if (pos == std::string::npos) return "";
|
| 28 |
+
|
| 29 |
+
pos = json.find(':', pos + search.length());
|
| 30 |
+
if (pos == std::string::npos) return "";
|
| 31 |
+
|
| 32 |
+
pos = json.find('"', pos + 1);
|
| 33 |
+
if (pos == std::string::npos) return "";
|
| 34 |
+
|
| 35 |
+
size_t end = json.find('"', pos + 1);
|
| 36 |
+
if (end == std::string::npos) return "";
|
| 37 |
+
|
| 38 |
+
return json.substr(pos + 1, end - pos - 1);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
int json_get_int(const std::string& json, const std::string& key, int default_val = 0) {
|
| 42 |
+
std::string search = "\"" + key + "\"";
|
| 43 |
+
size_t pos = json.find(search);
|
| 44 |
+
if (pos == std::string::npos) return default_val;
|
| 45 |
+
|
| 46 |
+
pos = json.find(':', pos + search.length());
|
| 47 |
+
if (pos == std::string::npos) return default_val;
|
| 48 |
+
|
| 49 |
+
// Skip whitespace
|
| 50 |
+
pos++;
|
| 51 |
+
while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++;
|
| 52 |
+
if (pos >= json.length()) return default_val;
|
| 53 |
+
|
| 54 |
+
// Parse number
|
| 55 |
+
std::string num_str;
|
| 56 |
+
while (pos < json.length() && (std::isdigit(json[pos]) || json[pos] == '-' || json[pos] == '.')) {
|
| 57 |
+
num_str += json[pos];
|
| 58 |
+
pos++;
|
| 59 |
+
}
|
| 60 |
+
if (num_str.empty()) return default_val;
|
| 61 |
+
|
| 62 |
+
// Check if float
|
| 63 |
+
if (num_str.find('.') != std::string::npos) {
|
| 64 |
+
return static_cast<int>(std::stof(num_str));
|
| 65 |
+
}
|
| 66 |
+
return std::stoi(num_str);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
float json_get_float(const std::string& json, const std::string& key, float default_val = 0.0f) {
|
| 70 |
+
std::string search = "\"" + key + "\"";
|
| 71 |
+
size_t pos = json.find(search);
|
| 72 |
+
if (pos == std::string::npos) return default_val;
|
| 73 |
+
|
| 74 |
+
pos = json.find(':', pos + search.length());
|
| 75 |
+
if (pos == std::string::npos) return default_val;
|
| 76 |
+
|
| 77 |
+
pos++;
|
| 78 |
+
while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++;
|
| 79 |
+
if (pos >= json.length()) return default_val;
|
| 80 |
+
|
| 81 |
+
std::string num_str;
|
| 82 |
+
while (pos < json.length() && (std::isdigit(json[pos]) || json[pos] == '-' || json[pos] == '.')) {
|
| 83 |
+
num_str += json[pos];
|
| 84 |
+
pos++;
|
| 85 |
+
}
|
| 86 |
+
if (num_str.empty()) return default_val;
|
| 87 |
+
return std::stof(num_str);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
std::vector<std::string> json_get_string_array(const std::string& json, const std::string& key) {
|
| 91 |
+
std::vector<std::string> result;
|
| 92 |
+
std::string search = "\"" + key + "\"";
|
| 93 |
+
size_t pos = json.find(search);
|
| 94 |
+
if (pos == std::string::npos) return result;
|
| 95 |
+
|
| 96 |
+
pos = json.find('[', pos + search.length());
|
| 97 |
+
if (pos == std::string::npos) return result;
|
| 98 |
+
|
| 99 |
+
size_t end = json.find(']', pos);
|
| 100 |
+
if (end == std::string::npos) return result;
|
| 101 |
+
|
| 102 |
+
std::string array_str = json.substr(pos + 1, end - pos - 1);
|
| 103 |
+
|
| 104 |
+
size_t start = 0;
|
| 105 |
+
while (start < array_str.length()) {
|
| 106 |
+
size_t q1 = array_str.find('"', start);
|
| 107 |
+
if (q1 == std::string::npos) break;
|
| 108 |
+
size_t q2 = array_str.find('"', q1 + 1);
|
| 109 |
+
if (q2 == std::string::npos) break;
|
| 110 |
+
result.push_back(array_str.substr(q1 + 1, q2 - q1 - 1));
|
| 111 |
+
start = q2 + 1;
|
| 112 |
+
}
|
| 113 |
+
return result;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
std::string read_file_content(const std::string& path) {
|
| 117 |
+
std::ifstream file(path);
|
| 118 |
+
if (!file.is_open()) return "";
|
| 119 |
+
std::stringstream ss;
|
| 120 |
+
ss << file.rdbuf();
|
| 121 |
+
return ss.str();
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
} // anonymous namespace
|
| 125 |
+
|
| 126 |
+
ZipVoiceEngine::ZipVoiceEngine()
|
| 127 |
+
: m_has_init(false), m_decoder_seq_len(0), m_decoder_has_padding_mask(false) {}
|
| 128 |
+
|
| 129 |
+
ZipVoiceEngine::~ZipVoiceEngine() {
|
| 130 |
+
m_sessions.clear();
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
double ZipVoiceEngine::GetCurrentTimeMs() {
|
| 134 |
+
struct timeval tv;
|
| 135 |
+
gettimeofday(&tv, nullptr);
|
| 136 |
+
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
int ZipVoiceEngine::Init(const std::string& model_dir) {
|
| 140 |
+
if (LoadConfig(model_dir) != 0) return -1;
|
| 141 |
+
if (LoadManifest(model_dir) != 0) return -1;
|
| 142 |
+
if (LoadModels(model_dir) != 0) return -1;
|
| 143 |
+
if (LoadDecoderMetadata() != 0) return -1;
|
| 144 |
+
|
| 145 |
+
m_has_init = true;
|
| 146 |
+
printf("ZipVoiceEngine initialized: max_tokens=%d, max_feat_len=%d, feat_dim=%d, num_step=%d\n",
|
| 147 |
+
m_config.max_tokens, m_config.max_feat_len, m_config.feat_dim, m_config.num_step);
|
| 148 |
+
return 0;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
int ZipVoiceEngine::LoadConfig(const std::string& model_dir) {
|
| 152 |
+
std::string config_path = model_dir + "/runtime_config.json";
|
| 153 |
+
std::string json = read_file_content(config_path);
|
| 154 |
+
if (!json.empty()) {
|
| 155 |
+
m_config.max_tokens = json_get_int(json, "max_tokens", 384);
|
| 156 |
+
m_config.max_feat_len = json_get_int(json, "max_feat_len", 1024);
|
| 157 |
+
m_config.feat_dim = json_get_int(json, "feat_dim", 100);
|
| 158 |
+
m_config.sampling_rate = json_get_int(json, "sampling_rate", 24000);
|
| 159 |
+
m_config.hop_length = json_get_int(json, "hop_length", 256);
|
| 160 |
+
m_config.num_step = json_get_int(json, "num_step", 10);
|
| 161 |
+
m_config.t_shift = json_get_float(json, "t_shift", 0.5f);
|
| 162 |
+
}
|
| 163 |
+
m_config.model_dir = model_dir;
|
| 164 |
+
return 0;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
int ZipVoiceEngine::LoadManifest(const std::string& model_dir) {
|
| 168 |
+
std::string manifest_path = model_dir + "/decoder4_split_manifest.json";
|
| 169 |
+
std::string json = read_file_content(manifest_path);
|
| 170 |
+
if (json.empty()) {
|
| 171 |
+
printf("Failed to read manifest: %s\n", manifest_path.c_str());
|
| 172 |
+
return -1;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// Parse encoder info
|
| 176 |
+
m_encoder_info.name = json_get_string(json, "name");
|
| 177 |
+
// Need to parse the nested "encoder" object
|
| 178 |
+
size_t enc_pos = json.find("\"encoder\"");
|
| 179 |
+
if (enc_pos != std::string::npos) {
|
| 180 |
+
size_t obj_start = json.find('{', enc_pos);
|
| 181 |
+
size_t obj_end = json.find('}', obj_start);
|
| 182 |
+
if (obj_start != std::string::npos && obj_end != std::string::npos) {
|
| 183 |
+
std::string enc_json = json.substr(obj_start, obj_end - obj_start + 1);
|
| 184 |
+
m_encoder_info.name = json_get_string(enc_json, "name");
|
| 185 |
+
m_encoder_info.file = json_get_string(enc_json, "file");
|
| 186 |
+
m_encoder_info.inputs = json_get_string_array(enc_json, "inputs");
|
| 187 |
+
m_encoder_info.outputs = json_get_string_array(enc_json, "outputs");
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// Parse decoder_parts array
|
| 192 |
+
size_t dec_pos = json.find("\"decoder_parts\"");
|
| 193 |
+
if (dec_pos != std::string::npos) {
|
| 194 |
+
size_t arr_start = json.find('[', dec_pos);
|
| 195 |
+
if (arr_start != std::string::npos) {
|
| 196 |
+
m_decoder_parts.clear();
|
| 197 |
+
size_t pos = arr_start + 1;
|
| 198 |
+
int depth = 0;
|
| 199 |
+
std::string part_json;
|
| 200 |
+
for (; pos < json.length(); ++pos) {
|
| 201 |
+
if (json[pos] == '{') depth++;
|
| 202 |
+
if (depth > 0) part_json += json[pos];
|
| 203 |
+
if (json[pos] == '}') {
|
| 204 |
+
depth--;
|
| 205 |
+
if (depth == 0) {
|
| 206 |
+
// Parse this part
|
| 207 |
+
ModelInfo info;
|
| 208 |
+
info.name = json_get_string(part_json, "name");
|
| 209 |
+
info.file = json_get_string(part_json, "file");
|
| 210 |
+
info.inputs = json_get_string_array(part_json, "inputs");
|
| 211 |
+
info.outputs = json_get_string_array(part_json, "outputs");
|
| 212 |
+
m_decoder_parts.push_back(info);
|
| 213 |
+
part_json.clear();
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
printf("Manifest: encoder=%s, decoder_parts=%zu\n",
|
| 221 |
+
m_encoder_info.name.c_str(), m_decoder_parts.size());
|
| 222 |
+
for (size_t i = 0; i < m_decoder_parts.size(); ++i) {
|
| 223 |
+
printf(" part%zu: %s (%s) in=%zu out=%zu\n", i,
|
| 224 |
+
m_decoder_parts[i].name.c_str(), m_decoder_parts[i].file.c_str(),
|
| 225 |
+
m_decoder_parts[i].inputs.size(), m_decoder_parts[i].outputs.size());
|
| 226 |
+
}
|
| 227 |
+
return 0;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
int ZipVoiceEngine::LoadModels(const std::string& model_dir) {
|
| 231 |
+
// Load encoder
|
| 232 |
+
std::string enc_path = model_dir + "/" + m_encoder_info.file;
|
| 233 |
+
auto enc = std::make_unique<EngineWrapper>();
|
| 234 |
+
if (enc->Init(enc_path.c_str()) != 0) {
|
| 235 |
+
printf("Failed to load encoder: %s\n", enc_path.c_str());
|
| 236 |
+
return -1;
|
| 237 |
+
}
|
| 238 |
+
m_sessions[m_encoder_info.name] = std::move(enc);
|
| 239 |
+
|
| 240 |
+
// Load decoder parts
|
| 241 |
+
for (auto& part : m_decoder_parts) {
|
| 242 |
+
std::string path = model_dir + "/" + part.file;
|
| 243 |
+
auto sess = std::make_unique<EngineWrapper>();
|
| 244 |
+
if (sess->Init(path.c_str()) != 0) {
|
| 245 |
+
printf("Failed to load decoder part: %s\n", path.c_str());
|
| 246 |
+
return -1;
|
| 247 |
+
}
|
| 248 |
+
m_sessions[part.name] = std::move(sess);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
printf("Loaded %zu models\n", m_sessions.size());
|
| 252 |
+
return 0;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
int ZipVoiceEngine::LoadDecoderMetadata() {
|
| 256 |
+
if (m_decoder_parts.empty()) return -1;
|
| 257 |
+
|
| 258 |
+
auto& part0 = m_decoder_parts[0];
|
| 259 |
+
auto it = m_sessions.find(part0.name);
|
| 260 |
+
if (it == m_sessions.end()) return -1;
|
| 261 |
+
|
| 262 |
+
auto& sess = it->second;
|
| 263 |
+
|
| 264 |
+
// Check if padding_mask is an input
|
| 265 |
+
int pad_idx = sess->GetInputIndex("padding_mask");
|
| 266 |
+
m_decoder_has_padding_mask = (pad_idx >= 0);
|
| 267 |
+
|
| 268 |
+
// Get sequence length from x input shape
|
| 269 |
+
m_decoder_seq_len = m_config.max_feat_len;
|
| 270 |
+
|
| 271 |
+
// Try to get seq_len from model metadata
|
| 272 |
+
// The shape info is in the AX engine io_info; for simplicity we use config value
|
| 273 |
+
|
| 274 |
+
printf("Decoder metadata: seq_len=%d, has_padding_mask=%d\n",
|
| 275 |
+
m_decoder_seq_len, m_decoder_has_padding_mask ? 1 : 0);
|
| 276 |
+
return 0;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
std::vector<float> ZipVoiceEngine::GetTimesteps(int num_step, float t_shift) const {
|
| 280 |
+
std::vector<float> ts(num_step + 1);
|
| 281 |
+
for (int i = 0; i <= num_step; ++i) {
|
| 282 |
+
float t = static_cast<float>(i) / num_step;
|
| 283 |
+
ts[i] = t_shift * t / (1.0f + (t_shift - 1.0f) * t);
|
| 284 |
+
}
|
| 285 |
+
return ts;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
int ZipVoiceEngine::RunEncoder(const std::vector<int32_t>& cat_tokens,
|
| 289 |
+
std::vector<float>& out_encoded) {
|
| 290 |
+
auto it = m_sessions.find(m_encoder_info.name);
|
| 291 |
+
if (it == m_sessions.end()) return -1;
|
| 292 |
+
|
| 293 |
+
auto& sess = it->second;
|
| 294 |
+
|
| 295 |
+
// Set input
|
| 296 |
+
int input_idx = sess->GetInputIndex(m_encoder_info.inputs[0].c_str());
|
| 297 |
+
if (input_idx < 0) input_idx = 0;
|
| 298 |
+
|
| 299 |
+
// Need to copy to non-const buffer for the engine API
|
| 300 |
+
std::vector<int32_t> input_copy = cat_tokens;
|
| 301 |
+
sess->SetInput(input_copy.data(), input_idx);
|
| 302 |
+
|
| 303 |
+
// Run
|
| 304 |
+
if (sess->RunSync() != 0) return -1;
|
| 305 |
+
|
| 306 |
+
// Get output
|
| 307 |
+
int output_idx = sess->GetOutputIndex(m_encoder_info.outputs[0].c_str());
|
| 308 |
+
if (output_idx < 0) output_idx = 0;
|
| 309 |
+
|
| 310 |
+
int output_size = sess->GetOutputSize(output_idx);
|
| 311 |
+
out_encoded.resize(output_size / sizeof(float));
|
| 312 |
+
sess->GetOutput(out_encoded.data(), output_idx);
|
| 313 |
+
|
| 314 |
+
return 0;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
int ZipVoiceEngine::DurationExpand(const std::vector<float>& encoded,
|
| 318 |
+
int prompt_tokens_len,
|
| 319 |
+
int text_tokens_len,
|
| 320 |
+
int prompt_features_len,
|
| 321 |
+
float speed,
|
| 322 |
+
std::vector<float>& out_text_condition,
|
| 323 |
+
int& out_features_len) {
|
| 324 |
+
int total_tokens_len = prompt_tokens_len + text_tokens_len;
|
| 325 |
+
if (total_tokens_len <= 0) return -1;
|
| 326 |
+
|
| 327 |
+
// Compute target features length
|
| 328 |
+
int features_len = static_cast<int>(
|
| 329 |
+
std::ceil(static_cast<float>(prompt_features_len) / prompt_tokens_len
|
| 330 |
+
* total_tokens_len / speed)
|
| 331 |
+
);
|
| 332 |
+
if (features_len > m_config.max_feat_len) {
|
| 333 |
+
features_len = m_config.max_feat_len;
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
int feat_dim = m_config.feat_dim;
|
| 337 |
+
|
| 338 |
+
// encoded shape: [1, max_tokens, feat_dim] (flat array)
|
| 339 |
+
// Extract the no-pad portion
|
| 340 |
+
int token_dur = features_len / total_tokens_len;
|
| 341 |
+
int max_tokens = m_config.max_tokens;
|
| 342 |
+
|
| 343 |
+
// out_text_condition: [1, features_len, feat_dim]
|
| 344 |
+
out_text_condition.assign(features_len * feat_dim, 0.0f);
|
| 345 |
+
|
| 346 |
+
// Repeat each token embedding token_dur times
|
| 347 |
+
for (int t = 0; t < total_tokens_len; ++t) {
|
| 348 |
+
int base_idx = t * feat_dim;
|
| 349 |
+
for (int d = 0; d < token_dur; ++d) {
|
| 350 |
+
int frame_idx = t * token_dur + d;
|
| 351 |
+
if (frame_idx >= features_len) break;
|
| 352 |
+
int dst_idx = frame_idx * feat_dim;
|
| 353 |
+
std::copy(encoded.begin() + base_idx,
|
| 354 |
+
encoded.begin() + base_idx + feat_dim,
|
| 355 |
+
out_text_condition.begin() + dst_idx);
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// Fill residual frames with last token embedding
|
| 360 |
+
int filled = total_tokens_len * token_dur;
|
| 361 |
+
int residual = features_len - filled;
|
| 362 |
+
if (residual > 0) {
|
| 363 |
+
int last_base = total_tokens_len * feat_dim;
|
| 364 |
+
for (int d = 0; d < residual; ++d) {
|
| 365 |
+
int dst_idx = (filled + d) * feat_dim;
|
| 366 |
+
std::copy(encoded.begin() + last_base,
|
| 367 |
+
encoded.begin() + last_base + feat_dim,
|
| 368 |
+
out_text_condition.begin() + dst_idx);
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
out_features_len = features_len;
|
| 373 |
+
return 0;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
int ZipVoiceEngine::RunDecoderPart(const ModelInfo& part,
|
| 377 |
+
std::map<std::string, std::vector<float>>& values,
|
| 378 |
+
const std::vector<uint8_t>* padding_mask_data,
|
| 379 |
+
std::vector<uint8_t>* padding_mask2_out) {
|
| 380 |
+
auto it = m_sessions.find(part.name);
|
| 381 |
+
if (it == m_sessions.end()) return -1;
|
| 382 |
+
auto& sess = it->second;
|
| 383 |
+
|
| 384 |
+
// Set inputs in order
|
| 385 |
+
for (size_t i = 0; i < part.inputs.size(); ++i) {
|
| 386 |
+
const std::string& expected_name = part.inputs[i];
|
| 387 |
+
|
| 388 |
+
int input_idx = sess->GetInputIndex(expected_name.c_str());
|
| 389 |
+
if (input_idx < 0) input_idx = static_cast<int>(i);
|
| 390 |
+
|
| 391 |
+
// padding_mask: use uint8 data from caller (part0 only)
|
| 392 |
+
if (expected_name == "padding_mask") {
|
| 393 |
+
if (padding_mask_data) {
|
| 394 |
+
sess->SetInput((void*)padding_mask_data->data(), input_idx);
|
| 395 |
+
continue;
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
// padding_mask2: use raw uint8 data from part0's output
|
| 399 |
+
if (expected_name == "padding_mask2") {
|
| 400 |
+
if (padding_mask2_out && !padding_mask2_out->empty()) {
|
| 401 |
+
sess->SetInput((void*)padding_mask2_out->data(), input_idx);
|
| 402 |
+
continue;
|
| 403 |
+
}
|
| 404 |
+
printf("ERROR: padding_mask2 not available for '%s'\n", part.name.c_str());
|
| 405 |
+
return -1;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
// Find the value in float map
|
| 409 |
+
auto val_it = values.find(expected_name);
|
| 410 |
+
if (val_it == values.end()) {
|
| 411 |
+
printf("Missing input '%s' for model '%s'\n",
|
| 412 |
+
expected_name.c_str(), part.name.c_str());
|
| 413 |
+
return -1;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
sess->SetInput((void*)val_it->second.data(), input_idx);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
// Run
|
| 420 |
+
if (sess->RunSync() != 0) return -1;
|
| 421 |
+
|
| 422 |
+
// Get outputs
|
| 423 |
+
for (size_t i = 0; i < part.outputs.size(); ++i) {
|
| 424 |
+
const std::string& name = part.outputs[i];
|
| 425 |
+
|
| 426 |
+
int output_idx = sess->GetOutputIndex(name.c_str());
|
| 427 |
+
if (output_idx < 0) output_idx = static_cast<int>(i);
|
| 428 |
+
|
| 429 |
+
int size = sess->GetOutputSize(output_idx);
|
| 430 |
+
if (size <= 0) {
|
| 431 |
+
printf("Invalid output size for '%s' in '%s'\n",
|
| 432 |
+
name.c_str(), part.name.c_str());
|
| 433 |
+
return -1;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
// padding_mask2 is uint8 — read raw bytes, don't convert to float
|
| 437 |
+
if (name == "padding_mask2" && padding_mask2_out) {
|
| 438 |
+
padding_mask2_out->resize(size);
|
| 439 |
+
sess->GetOutput(padding_mask2_out->data(), output_idx);
|
| 440 |
+
continue;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
std::vector<float> output_data(size / sizeof(float));
|
| 444 |
+
sess->GetOutput(output_data.data(), output_idx);
|
| 445 |
+
values[name] = std::move(output_data);
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
return 0;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
int ZipVoiceEngine::Sample(const std::vector<int32_t>& cat_tokens,
|
| 452 |
+
int prompt_tokens_len,
|
| 453 |
+
int text_tokens_len,
|
| 454 |
+
const std::vector<float>& prompt_features,
|
| 455 |
+
int prompt_features_len,
|
| 456 |
+
float speed,
|
| 457 |
+
float guidance_scale,
|
| 458 |
+
int seed,
|
| 459 |
+
std::vector<float>& out_features,
|
| 460 |
+
Timing& out_timing) {
|
| 461 |
+
double t_total_start = GetCurrentTimeMs();
|
| 462 |
+
|
| 463 |
+
// 1. Run encoder
|
| 464 |
+
double t_start = GetCurrentTimeMs();
|
| 465 |
+
std::vector<float> encoded;
|
| 466 |
+
if (RunEncoder(cat_tokens, encoded) != 0) return -1;
|
| 467 |
+
out_timing.encoder_time_sec = static_cast<float>(GetCurrentTimeMs() - t_start) / 1000.0f;
|
| 468 |
+
|
| 469 |
+
// encoded shape: [max_tokens * feat_dim] (flattened, batch=1)
|
| 470 |
+
int feat_dim = m_config.feat_dim;
|
| 471 |
+
|
| 472 |
+
// 2. Duration expand
|
| 473 |
+
t_start = GetCurrentTimeMs();
|
| 474 |
+
std::vector<float> text_condition;
|
| 475 |
+
int features_len;
|
| 476 |
+
if (DurationExpand(encoded, prompt_tokens_len, text_tokens_len,
|
| 477 |
+
prompt_features_len, speed,
|
| 478 |
+
text_condition, features_len) != 0) return -1;
|
| 479 |
+
out_timing.duration_expand_time_sec = static_cast<float>(GetCurrentTimeMs() - t_start) / 1000.0f;
|
| 480 |
+
|
| 481 |
+
// 3. Prepare decoder inputs
|
| 482 |
+
int seq_len = m_decoder_seq_len > 0 ? m_decoder_seq_len : m_config.max_feat_len;
|
| 483 |
+
|
| 484 |
+
if (features_len > seq_len) {
|
| 485 |
+
printf("features_len=%d exceeds decoder seq_len=%d\n", features_len, seq_len);
|
| 486 |
+
return -1;
|
| 487 |
+
}
|
| 488 |
+
if (prompt_features_len > seq_len) {
|
| 489 |
+
printf("prompt_features_len=%d exceeds decoder seq_len=%d\n", prompt_features_len, seq_len);
|
| 490 |
+
return -1;
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
// text_cond_padded: [1, seq_len, feat_dim] (zeros, filled up to features_len)
|
| 494 |
+
std::vector<float> text_cond_padded(seq_len * feat_dim, 0.0f);
|
| 495 |
+
std::copy(text_condition.begin(),
|
| 496 |
+
text_condition.begin() + features_len * feat_dim,
|
| 497 |
+
text_cond_padded.begin());
|
| 498 |
+
|
| 499 |
+
// speech_cond_padded: [1, seq_len, feat_dim]
|
| 500 |
+
std::vector<float> speech_cond_padded(seq_len * feat_dim, 0.0f);
|
| 501 |
+
std::copy(prompt_features.begin(),
|
| 502 |
+
prompt_features.begin() + prompt_features_len * feat_dim,
|
| 503 |
+
speech_cond_padded.begin());
|
| 504 |
+
|
| 505 |
+
// padding_mask: [1, seq_len] as uint8_t (bool), NOT float32!
|
| 506 |
+
// Python: np.zeros((1, seq_len), dtype=np.bool_)
|
| 507 |
+
std::vector<uint8_t> padding_mask(seq_len, 0);
|
| 508 |
+
for (int i = features_len; i < seq_len; ++i) {
|
| 509 |
+
padding_mask[i] = 1;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
// x: random init [1, seq_len, feat_dim]
|
| 513 |
+
std::vector<float> x(seq_len * feat_dim, 0.0f);
|
| 514 |
+
// Simple LCG random
|
| 515 |
+
uint32_t rng_state = static_cast<uint32_t>(seed > 0 ? seed : 42);
|
| 516 |
+
for (int i = 0; i < features_len * feat_dim; ++i) {
|
| 517 |
+
rng_state = rng_state * 1103515245 + 12345;
|
| 518 |
+
// Box-Muller for normal distribution
|
| 519 |
+
float u1 = static_cast<float>(rng_state & 0x7FFFFFFF) / 0x7FFFFFFF;
|
| 520 |
+
rng_state = rng_state * 1103515245 + 12345;
|
| 521 |
+
float u2 = static_cast<float>(rng_state & 0x7FFFFFFF) / 0x7FFFFFFF;
|
| 522 |
+
x[i] = std::sqrt(-2.0f * std::log(std::max(u1, 1e-10f)))
|
| 523 |
+
* std::cos(2.0f * M_PI * u2);
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
// Time steps
|
| 527 |
+
std::vector<float> timesteps = GetTimesteps(m_config.num_step, m_config.t_shift);
|
| 528 |
+
|
| 529 |
+
// Guidance scale as single float array
|
| 530 |
+
std::vector<float> gs = { guidance_scale };
|
| 531 |
+
|
| 532 |
+
// 4. Flow-matching decoder
|
| 533 |
+
double t_dec_total = 0.0;
|
| 534 |
+
for (int step = 0; step < m_config.num_step; ++step) {
|
| 535 |
+
double t_step_start = GetCurrentTimeMs();
|
| 536 |
+
|
| 537 |
+
// Prepare inputs for this step
|
| 538 |
+
std::map<std::string, std::vector<float>> values;
|
| 539 |
+
|
| 540 |
+
// t: scalar
|
| 541 |
+
std::vector<float> t_val = { timesteps[step] };
|
| 542 |
+
values["t"] = t_val;
|
| 543 |
+
|
| 544 |
+
// x: [1, seq_len, feat_dim]
|
| 545 |
+
values["x"] = x;
|
| 546 |
+
|
| 547 |
+
// text_condition
|
| 548 |
+
values["text_condition"] = text_cond_padded;
|
| 549 |
+
|
| 550 |
+
// speech_condition
|
| 551 |
+
values["speech_condition"] = speech_cond_padded;
|
| 552 |
+
|
| 553 |
+
// guidance_scale: scalar
|
| 554 |
+
values["guidance_scale"] = gs;
|
| 555 |
+
|
| 556 |
+
// padding_mask is passed separately as uint8_t (bool), not in values map
|
| 557 |
+
std::vector<uint8_t> padding_mask2_data; // populated by part0, used by parts 1-3
|
| 558 |
+
|
| 559 |
+
// Run all decoder parts in sequence
|
| 560 |
+
for (auto& part : m_decoder_parts) {
|
| 561 |
+
if (RunDecoderPart(part, values, &padding_mask, &padding_mask2_data) != 0) return -1;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
t_dec_total += GetCurrentTimeMs() - t_step_start;
|
| 565 |
+
|
| 566 |
+
// Get v (velocity) from last part output
|
| 567 |
+
const std::string& final_output_name = m_decoder_parts.back().outputs[0];
|
| 568 |
+
auto vit = values.find(final_output_name);
|
| 569 |
+
if (vit == values.end()) {
|
| 570 |
+
printf("Missing final output '%s'\n", final_output_name.c_str());
|
| 571 |
+
return -1;
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
const std::vector<float>& v = vit->second;
|
| 575 |
+
|
| 576 |
+
// Euler step: x = x + v * dt
|
| 577 |
+
float dt = timesteps[step + 1] - timesteps[step];
|
| 578 |
+
for (size_t i = 0; i < x.size(); ++i) {
|
| 579 |
+
x[i] += v[i] * dt;
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
// Zero out padding region
|
| 583 |
+
for (int i = features_len * feat_dim; i < seq_len * feat_dim; ++i) {
|
| 584 |
+
x[i] = 0.0f;
|
| 585 |
+
}
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
out_timing.decoder_time_sec = static_cast<float>(t_dec_total) / 1000.0f;
|
| 589 |
+
|
| 590 |
+
// 5. Extract generated features (excluding prompt region)
|
| 591 |
+
int generated_frames = features_len - prompt_features_len;
|
| 592 |
+
if (generated_frames <= 0) {
|
| 593 |
+
generated_frames = features_len;
|
| 594 |
+
out_features.assign(x.begin(), x.begin() + features_len * feat_dim);
|
| 595 |
+
} else {
|
| 596 |
+
int offset = prompt_features_len * feat_dim;
|
| 597 |
+
out_features.assign(x.begin() + offset,
|
| 598 |
+
x.begin() + features_len * feat_dim);
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
out_timing.generated_frames = generated_frames;
|
| 602 |
+
out_timing.features_len = features_len;
|
| 603 |
+
out_timing.total_time_sec = static_cast<float>(GetCurrentTimeMs() - t_total_start) / 1000.0f;
|
| 604 |
+
|
| 605 |
+
printf(" encoder: %.3f s dur_expand: %.3f s decoder(%d steps): %.3f s (avg %.3f ms/step) total: %.3f s\n",
|
| 606 |
+
out_timing.encoder_time_sec,
|
| 607 |
+
out_timing.duration_expand_time_sec,
|
| 608 |
+
m_config.num_step,
|
| 609 |
+
out_timing.decoder_time_sec,
|
| 610 |
+
out_timing.decoder_time_sec / m_config.num_step * 1000.0f,
|
| 611 |
+
out_timing.total_time_sec);
|
| 612 |
+
|
| 613 |
+
return 0;
|
| 614 |
+
}
|
cpp/src/zipvoice_engine.hpp
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* ZipVoiceEngine: the core TTS inference engine.
|
| 5 |
+
*
|
| 6 |
+
* Manages encoder + split-decoder4 axmodel inference, duration expansion,
|
| 7 |
+
* and flow-matching sampling. Mirrors the Python Decoder4ZipVoiceBoardRuntime.
|
| 8 |
+
*
|
| 9 |
+
* Model pipeline:
|
| 10 |
+
* 1. encoder.axmodel: cat_tokens[n] → encoded[n, feat_dim]
|
| 11 |
+
* 2. Duration expand: repeat tokens → text_condition[1, seq_len, feat_dim]
|
| 12 |
+
* 3. decoder_part0..3: flow-matching ODE solver (Euler, num_step steps)
|
| 13 |
+
* part0: t, x, text_cond, speech_cond, guidance_scale, padding_mask
|
| 14 |
+
* → decoder_hidden_p0, time_hidden, padding_mask2, cfg_scale
|
| 15 |
+
* part1: decoder_hidden_p0, time_hidden, padding_mask2 → decoder_hidden_p1
|
| 16 |
+
* part2: decoder_hidden_p1, time_hidden, padding_mask2 → decoder_hidden_p2
|
| 17 |
+
* part3: decoder_hidden_p2, time_hidden, padding_mask2, cfg_scale → v
|
| 18 |
+
* x = x + v * dt
|
| 19 |
+
**************************************************************************************************/
|
| 20 |
+
|
| 21 |
+
#pragma once
|
| 22 |
+
|
| 23 |
+
#include <string>
|
| 24 |
+
#include <vector>
|
| 25 |
+
#include <map>
|
| 26 |
+
#include <memory>
|
| 27 |
+
#include <cstdint>
|
| 28 |
+
|
| 29 |
+
#include "EngineWrapper.hpp"
|
| 30 |
+
|
| 31 |
+
class ZipVoiceEngine {
|
| 32 |
+
public:
|
| 33 |
+
struct Config {
|
| 34 |
+
std::string model_dir;
|
| 35 |
+
int max_feat_len = 1024;
|
| 36 |
+
int max_tokens = 384;
|
| 37 |
+
int feat_dim = 100;
|
| 38 |
+
int sampling_rate = 24000;
|
| 39 |
+
int hop_length = 256;
|
| 40 |
+
int num_step = 10;
|
| 41 |
+
float t_shift = 0.5f;
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
struct ModelInfo {
|
| 45 |
+
std::string name;
|
| 46 |
+
std::string file;
|
| 47 |
+
std::vector<std::string> inputs;
|
| 48 |
+
std::vector<std::string> outputs;
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
struct Timing {
|
| 52 |
+
float encoder_time_sec = 0.0f;
|
| 53 |
+
float duration_expand_time_sec = 0.0f;
|
| 54 |
+
float decoder_time_sec = 0.0f;
|
| 55 |
+
float total_time_sec = 0.0f;
|
| 56 |
+
int generated_frames = 0;
|
| 57 |
+
int features_len = 0;
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
ZipVoiceEngine();
|
| 61 |
+
~ZipVoiceEngine();
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* Initialize from a model directory containing:
|
| 65 |
+
* - runtime_config.json
|
| 66 |
+
* - decoder4_split_manifest.json
|
| 67 |
+
* - encoder.axmodel
|
| 68 |
+
* - decoder_part0.axmodel .. decoder_part3.axmodel
|
| 69 |
+
*/
|
| 70 |
+
int Init(const std::string& model_dir);
|
| 71 |
+
|
| 72 |
+
/**
|
| 73 |
+
* Run encoder: cat_tokens[1, max_tokens] → encoded[1, max_tokens, feat_dim].
|
| 74 |
+
* out_encoded should be pre-allocated with size: 1 * max_tokens * feat_dim floats.
|
| 75 |
+
*/
|
| 76 |
+
int RunEncoder(const std::vector<int32_t>& cat_tokens, std::vector<float>& out_encoded);
|
| 77 |
+
|
| 78 |
+
/**
|
| 79 |
+
* Duration expand: token-level features → frame-level features.
|
| 80 |
+
*/
|
| 81 |
+
int DurationExpand(const std::vector<float>& encoded,
|
| 82 |
+
int prompt_tokens_len,
|
| 83 |
+
int text_tokens_len,
|
| 84 |
+
int prompt_features_len,
|
| 85 |
+
float speed,
|
| 86 |
+
std::vector<float>& out_text_condition,
|
| 87 |
+
int& out_features_len);
|
| 88 |
+
|
| 89 |
+
/**
|
| 90 |
+
* Full sampling: encoder → duration_expand → decoder flow-matching.
|
| 91 |
+
*
|
| 92 |
+
* @param cat_tokens [max_tokens] int32 token IDs
|
| 93 |
+
* @param prompt_tokens_len number of prompt tokens
|
| 94 |
+
* @param text_tokens_len number of text tokens
|
| 95 |
+
* @param prompt_features [prompt_frames, feat_dim] mel features from prompt wav
|
| 96 |
+
* @param prompt_features_len number of prompt feature frames
|
| 97 |
+
* @param speed speed factor (1.0 = normal)
|
| 98 |
+
* @param guidance_scale classifier-free guidance scale
|
| 99 |
+
* @param seed random seed
|
| 100 |
+
* @param out_features output mel features [generated_frames, feat_dim]
|
| 101 |
+
* @param out_timing timing breakdown
|
| 102 |
+
*/
|
| 103 |
+
int Sample(const std::vector<int32_t>& cat_tokens,
|
| 104 |
+
int prompt_tokens_len,
|
| 105 |
+
int text_tokens_len,
|
| 106 |
+
const std::vector<float>& prompt_features,
|
| 107 |
+
int prompt_features_len,
|
| 108 |
+
float speed,
|
| 109 |
+
float guidance_scale,
|
| 110 |
+
int seed,
|
| 111 |
+
std::vector<float>& out_features,
|
| 112 |
+
Timing& out_timing);
|
| 113 |
+
|
| 114 |
+
const Config& GetConfig() const { return m_config; }
|
| 115 |
+
bool HasInit() const { return m_has_init; }
|
| 116 |
+
|
| 117 |
+
private:
|
| 118 |
+
Config m_config;
|
| 119 |
+
bool m_has_init;
|
| 120 |
+
|
| 121 |
+
ModelInfo m_encoder_info;
|
| 122 |
+
std::vector<ModelInfo> m_decoder_parts;
|
| 123 |
+
|
| 124 |
+
// Model sessions (one per axmodel)
|
| 125 |
+
std::map<std::string, std::unique_ptr<EngineWrapper>> m_sessions;
|
| 126 |
+
|
| 127 |
+
// Decoder metadata
|
| 128 |
+
int m_decoder_seq_len;
|
| 129 |
+
bool m_decoder_has_padding_mask;
|
| 130 |
+
|
| 131 |
+
int LoadConfig(const std::string& model_dir);
|
| 132 |
+
int LoadManifest(const std::string& model_dir);
|
| 133 |
+
int LoadModels(const std::string& model_dir);
|
| 134 |
+
int LoadDecoderMetadata();
|
| 135 |
+
|
| 136 |
+
std::vector<float> GetTimesteps(int num_step, float t_shift) const;
|
| 137 |
+
|
| 138 |
+
int RunDecoderPart(const ModelInfo& part,
|
| 139 |
+
std::map<std::string, std::vector<float>>& values,
|
| 140 |
+
const std::vector<uint8_t>* padding_mask_data = nullptr,
|
| 141 |
+
std::vector<uint8_t>* padding_mask2_out = nullptr);
|
| 142 |
+
|
| 143 |
+
static double GetCurrentTimeMs();
|
| 144 |
+
};
|
cpp/third_party/kissfft/_kiss_fft_guts.h
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*Copyright (c) 2003-2004, Mark Borgerding
|
| 2 |
+
|
| 3 |
+
All rights reserved.
|
| 4 |
+
|
| 5 |
+
Redistribution and use in source and binary forms, with or without
|
| 6 |
+
modification, are permitted provided that the following conditions are met:
|
| 7 |
+
|
| 8 |
+
* Redistributions of source code must retain the above copyright notice,
|
| 9 |
+
this list of conditions and the following disclaimer.
|
| 10 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 11 |
+
this list of conditions and the following disclaimer in the
|
| 12 |
+
documentation and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 15 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 16 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 17 |
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
| 18 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 19 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 20 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 21 |
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 22 |
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 23 |
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 24 |
+
POSSIBILITY OF SUCH DAMAGE.*/
|
| 25 |
+
|
| 26 |
+
#ifndef KISS_FFT_GUTS_H
|
| 27 |
+
#define KISS_FFT_GUTS_H
|
| 28 |
+
|
| 29 |
+
#define MIN(a,b) ((a)<(b) ? (a):(b))
|
| 30 |
+
#define MAX(a,b) ((a)>(b) ? (a):(b))
|
| 31 |
+
|
| 32 |
+
/* kiss_fft.h
|
| 33 |
+
defines kiss_fft_scalar as either short or a float type
|
| 34 |
+
and defines
|
| 35 |
+
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
|
| 36 |
+
#include "kiss_fft.h"
|
| 37 |
+
|
| 38 |
+
/*
|
| 39 |
+
Explanation of macros dealing with complex math:
|
| 40 |
+
|
| 41 |
+
C_MUL(m,a,b) : m = a*b
|
| 42 |
+
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise
|
| 43 |
+
C_SUB( res, a,b) : res = a - b
|
| 44 |
+
C_SUBFROM( res , a) : res -= a
|
| 45 |
+
C_ADDTO( res , a) : res += a
|
| 46 |
+
* */
|
| 47 |
+
#ifdef FIXED_POINT
|
| 48 |
+
#include "arch.h"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
#define SAMP_MAX 2147483647
|
| 52 |
+
#define TWID_MAX 32767
|
| 53 |
+
#define TRIG_UPSCALE 1
|
| 54 |
+
|
| 55 |
+
#define SAMP_MIN -SAMP_MAX
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# define S_MUL(a,b) MULT16_32_Q15(b, a)
|
| 59 |
+
|
| 60 |
+
# define C_MUL(m,a,b) \
|
| 61 |
+
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
| 62 |
+
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0)
|
| 63 |
+
|
| 64 |
+
# define C_MULC(m,a,b) \
|
| 65 |
+
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
|
| 66 |
+
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0)
|
| 67 |
+
|
| 68 |
+
# define C_MULBYSCALAR( c, s ) \
|
| 69 |
+
do{ (c).r = S_MUL( (c).r , s ) ;\
|
| 70 |
+
(c).i = S_MUL( (c).i , s ) ; }while(0)
|
| 71 |
+
|
| 72 |
+
# define DIVSCALAR(x,k) \
|
| 73 |
+
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 )
|
| 74 |
+
|
| 75 |
+
# define C_FIXDIV(c,div) \
|
| 76 |
+
do { DIVSCALAR( (c).r , div); \
|
| 77 |
+
DIVSCALAR( (c).i , div); }while (0)
|
| 78 |
+
|
| 79 |
+
#define C_ADD( res, a,b)\
|
| 80 |
+
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
|
| 81 |
+
}while(0)
|
| 82 |
+
#define C_SUB( res, a,b)\
|
| 83 |
+
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
|
| 84 |
+
}while(0)
|
| 85 |
+
#define C_ADDTO( res , a)\
|
| 86 |
+
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
|
| 87 |
+
}while(0)
|
| 88 |
+
|
| 89 |
+
#define C_SUBFROM( res , a)\
|
| 90 |
+
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
|
| 91 |
+
}while(0)
|
| 92 |
+
|
| 93 |
+
#if defined(OPUS_ARM_INLINE_ASM)
|
| 94 |
+
#include "arm/kiss_fft_armv4.h"
|
| 95 |
+
#endif
|
| 96 |
+
|
| 97 |
+
#if defined(OPUS_ARM_INLINE_EDSP)
|
| 98 |
+
#include "arm/kiss_fft_armv5e.h"
|
| 99 |
+
#endif
|
| 100 |
+
#if defined(MIPSr1_ASM)
|
| 101 |
+
#include "mips/kiss_fft_mipsr1.h"
|
| 102 |
+
#endif
|
| 103 |
+
|
| 104 |
+
#else /* not FIXED_POINT*/
|
| 105 |
+
|
| 106 |
+
# define S_MUL(a,b) ( (a)*(b) )
|
| 107 |
+
#define C_MUL(m,a,b) \
|
| 108 |
+
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
|
| 109 |
+
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
|
| 110 |
+
#define C_MULC(m,a,b) \
|
| 111 |
+
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
|
| 112 |
+
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0)
|
| 113 |
+
|
| 114 |
+
#define C_MUL4(m,a,b) C_MUL(m,a,b)
|
| 115 |
+
|
| 116 |
+
# define C_FIXDIV(c,div) /* NOOP */
|
| 117 |
+
# define C_MULBYSCALAR( c, s ) \
|
| 118 |
+
do{ (c).r *= (s);\
|
| 119 |
+
(c).i *= (s); }while(0)
|
| 120 |
+
#endif
|
| 121 |
+
|
| 122 |
+
#ifndef CHECK_OVERFLOW_OP
|
| 123 |
+
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */
|
| 124 |
+
#endif
|
| 125 |
+
|
| 126 |
+
#ifndef C_ADD
|
| 127 |
+
#define C_ADD( res, a,b)\
|
| 128 |
+
do { \
|
| 129 |
+
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
|
| 130 |
+
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
|
| 131 |
+
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
|
| 132 |
+
}while(0)
|
| 133 |
+
#define C_SUB( res, a,b)\
|
| 134 |
+
do { \
|
| 135 |
+
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
|
| 136 |
+
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
|
| 137 |
+
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
|
| 138 |
+
}while(0)
|
| 139 |
+
#define C_ADDTO( res , a)\
|
| 140 |
+
do { \
|
| 141 |
+
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
|
| 142 |
+
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
|
| 143 |
+
(res).r += (a).r; (res).i += (a).i;\
|
| 144 |
+
}while(0)
|
| 145 |
+
|
| 146 |
+
#define C_SUBFROM( res , a)\
|
| 147 |
+
do {\
|
| 148 |
+
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
|
| 149 |
+
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
|
| 150 |
+
(res).r -= (a).r; (res).i -= (a).i; \
|
| 151 |
+
}while(0)
|
| 152 |
+
#endif /* C_ADD defined */
|
| 153 |
+
|
| 154 |
+
#ifdef FIXED_POINT
|
| 155 |
+
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
|
| 156 |
+
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/
|
| 157 |
+
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase))
|
| 158 |
+
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase))
|
| 159 |
+
# define HALF_OF(x) ((x)>>1)
|
| 160 |
+
#elif defined(USE_SIMD)
|
| 161 |
+
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
|
| 162 |
+
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
|
| 163 |
+
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f))
|
| 164 |
+
#else
|
| 165 |
+
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
|
| 166 |
+
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
|
| 167 |
+
# define HALF_OF(x) ((x)*.5f)
|
| 168 |
+
#endif
|
| 169 |
+
|
| 170 |
+
#define kf_cexp(x,phase) \
|
| 171 |
+
do{ \
|
| 172 |
+
(x)->r = KISS_FFT_COS(phase);\
|
| 173 |
+
(x)->i = KISS_FFT_SIN(phase);\
|
| 174 |
+
}while(0)
|
| 175 |
+
|
| 176 |
+
#define kf_cexp2(x,phase) \
|
| 177 |
+
do{ \
|
| 178 |
+
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
|
| 179 |
+
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
|
| 180 |
+
}while(0)
|
| 181 |
+
|
| 182 |
+
#endif /* KISS_FFT_GUTS_H */
|
cpp/third_party/kissfft/arch.h
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Copyright (c) 2003-2008 Jean-Marc Valin
|
| 2 |
+
Copyright (c) 2007-2008 CSIRO
|
| 3 |
+
Copyright (c) 2007-2009 Xiph.Org Foundation
|
| 4 |
+
Written by Jean-Marc Valin */
|
| 5 |
+
/**
|
| 6 |
+
@file arch.h
|
| 7 |
+
@brief Various architecture definitions for CELT
|
| 8 |
+
*/
|
| 9 |
+
/*
|
| 10 |
+
Redistribution and use in source and binary forms, with or without
|
| 11 |
+
modification, are permitted provided that the following conditions
|
| 12 |
+
are met:
|
| 13 |
+
|
| 14 |
+
- Redistributions of source code must retain the above copyright
|
| 15 |
+
notice, this list of conditions and the following disclaimer.
|
| 16 |
+
|
| 17 |
+
- Redistributions in binary form must reproduce the above copyright
|
| 18 |
+
notice, this list of conditions and the following disclaimer in the
|
| 19 |
+
documentation and/or other materials provided with the distribution.
|
| 20 |
+
|
| 21 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 22 |
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 23 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 24 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
| 25 |
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
| 26 |
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
| 27 |
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
| 28 |
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
| 29 |
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
| 30 |
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 31 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 32 |
+
*/
|
| 33 |
+
|
| 34 |
+
#ifndef ARCH_H
|
| 35 |
+
#define ARCH_H
|
| 36 |
+
|
| 37 |
+
#include "opus_types.h"
|
| 38 |
+
#include "common.h"
|
| 39 |
+
|
| 40 |
+
# if !defined(__GNUC_PREREQ)
|
| 41 |
+
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
| 42 |
+
# define __GNUC_PREREQ(_maj,_min) \
|
| 43 |
+
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
| 44 |
+
# else
|
| 45 |
+
# define __GNUC_PREREQ(_maj,_min) 0
|
| 46 |
+
# endif
|
| 47 |
+
# endif
|
| 48 |
+
|
| 49 |
+
#define CELT_SIG_SCALE 32768.f
|
| 50 |
+
|
| 51 |
+
#define celt_fatal(str) _celt_fatal(str, __FILE__, __LINE__);
|
| 52 |
+
#ifdef ENABLE_ASSERTIONS
|
| 53 |
+
#include <stdio.h>
|
| 54 |
+
#include <stdlib.h>
|
| 55 |
+
#ifdef __GNUC__
|
| 56 |
+
__attribute__((noreturn))
|
| 57 |
+
#endif
|
| 58 |
+
static OPUS_INLINE void _celt_fatal(const char *str, const char *file, int line)
|
| 59 |
+
{
|
| 60 |
+
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str);
|
| 61 |
+
abort();
|
| 62 |
+
}
|
| 63 |
+
#define celt_assert(cond) {if (!(cond)) {celt_fatal("assertion failed: " #cond);}}
|
| 64 |
+
#define celt_assert2(cond, message) {if (!(cond)) {celt_fatal("assertion failed: " #cond "\n" message);}}
|
| 65 |
+
#else
|
| 66 |
+
#define celt_assert(cond)
|
| 67 |
+
#define celt_assert2(cond, message)
|
| 68 |
+
#endif
|
| 69 |
+
|
| 70 |
+
#define IMUL32(a,b) ((a)*(b))
|
| 71 |
+
|
| 72 |
+
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */
|
| 73 |
+
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
|
| 74 |
+
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */
|
| 75 |
+
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
|
| 76 |
+
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */
|
| 77 |
+
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */
|
| 78 |
+
#define UADD32(a,b) ((a)+(b))
|
| 79 |
+
#define USUB32(a,b) ((a)-(b))
|
| 80 |
+
|
| 81 |
+
/* Set this if opus_int64 is a native type of the CPU. */
|
| 82 |
+
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
|
| 83 |
+
(which can be ILP32 for x32) and Win64 (which is LLP64). */
|
| 84 |
+
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64)
|
| 85 |
+
#define OPUS_FAST_INT64 1
|
| 86 |
+
#else
|
| 87 |
+
#define OPUS_FAST_INT64 0
|
| 88 |
+
#endif
|
| 89 |
+
|
| 90 |
+
#define PRINT_MIPS(file)
|
| 91 |
+
|
| 92 |
+
#ifdef FIXED_POINT
|
| 93 |
+
|
| 94 |
+
typedef opus_int16 opus_val16;
|
| 95 |
+
typedef opus_int32 opus_val32;
|
| 96 |
+
typedef opus_int64 opus_val64;
|
| 97 |
+
|
| 98 |
+
typedef opus_val32 celt_sig;
|
| 99 |
+
typedef opus_val16 celt_norm;
|
| 100 |
+
typedef opus_val32 celt_ener;
|
| 101 |
+
|
| 102 |
+
#define Q15ONE 32767
|
| 103 |
+
|
| 104 |
+
#define SIG_SHIFT 12
|
| 105 |
+
/* Safe saturation value for 32-bit signals. Should be less than
|
| 106 |
+
2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/
|
| 107 |
+
#define SIG_SAT (300000000)
|
| 108 |
+
|
| 109 |
+
#define NORM_SCALING 16384
|
| 110 |
+
|
| 111 |
+
#define DB_SHIFT 10
|
| 112 |
+
|
| 113 |
+
#define EPSILON 1
|
| 114 |
+
#define VERY_SMALL 0
|
| 115 |
+
#define VERY_LARGE16 ((opus_val16)32767)
|
| 116 |
+
#define Q15_ONE ((opus_val16)32767)
|
| 117 |
+
|
| 118 |
+
#define SCALEIN(a) (a)
|
| 119 |
+
#define SCALEOUT(a) (a)
|
| 120 |
+
|
| 121 |
+
#define ABS16(x) ((x) < 0 ? (-(x)) : (x))
|
| 122 |
+
#define ABS32(x) ((x) < 0 ? (-(x)) : (x))
|
| 123 |
+
|
| 124 |
+
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) {
|
| 125 |
+
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
#ifdef FIXED_DEBUG
|
| 129 |
+
#include "fixed_debug.h"
|
| 130 |
+
#else
|
| 131 |
+
|
| 132 |
+
#include "fixed_generic.h"
|
| 133 |
+
|
| 134 |
+
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR
|
| 135 |
+
#include "arm/fixed_arm64.h"
|
| 136 |
+
#elif OPUS_ARM_INLINE_EDSP
|
| 137 |
+
#include "arm/fixed_armv5e.h"
|
| 138 |
+
#elif defined (OPUS_ARM_INLINE_ASM)
|
| 139 |
+
#include "arm/fixed_armv4.h"
|
| 140 |
+
#elif defined (BFIN_ASM)
|
| 141 |
+
#include "fixed_bfin.h"
|
| 142 |
+
#elif defined (TI_C5X_ASM)
|
| 143 |
+
#include "fixed_c5x.h"
|
| 144 |
+
#elif defined (TI_C6X_ASM)
|
| 145 |
+
#include "fixed_c6x.h"
|
| 146 |
+
#endif
|
| 147 |
+
|
| 148 |
+
#endif
|
| 149 |
+
|
| 150 |
+
#else /* FIXED_POINT */
|
| 151 |
+
|
| 152 |
+
typedef float opus_val16;
|
| 153 |
+
typedef float opus_val32;
|
| 154 |
+
typedef float opus_val64;
|
| 155 |
+
|
| 156 |
+
typedef float celt_sig;
|
| 157 |
+
typedef float celt_norm;
|
| 158 |
+
typedef float celt_ener;
|
| 159 |
+
|
| 160 |
+
#ifdef FLOAT_APPROX
|
| 161 |
+
/* This code should reliably detect NaN/inf even when -ffast-math is used.
|
| 162 |
+
Assumes IEEE 754 format. */
|
| 163 |
+
static OPUS_INLINE int celt_isnan(float x)
|
| 164 |
+
{
|
| 165 |
+
union {float f; opus_uint32 i;} in;
|
| 166 |
+
in.f = x;
|
| 167 |
+
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0;
|
| 168 |
+
}
|
| 169 |
+
#else
|
| 170 |
+
#ifdef __FAST_MATH__
|
| 171 |
+
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input
|
| 172 |
+
#endif
|
| 173 |
+
#define celt_isnan(x) ((x)!=(x))
|
| 174 |
+
#endif
|
| 175 |
+
|
| 176 |
+
#define Q15ONE 1.0f
|
| 177 |
+
|
| 178 |
+
#define NORM_SCALING 1.f
|
| 179 |
+
|
| 180 |
+
#define EPSILON 1e-15f
|
| 181 |
+
#define VERY_SMALL 1e-30f
|
| 182 |
+
#define VERY_LARGE16 1e15f
|
| 183 |
+
#define Q15_ONE ((opus_val16)1.f)
|
| 184 |
+
|
| 185 |
+
/* This appears to be the same speed as C99's fabsf() but it's more portable. */
|
| 186 |
+
#define ABS16(x) ((float)fabs(x))
|
| 187 |
+
#define ABS32(x) ((float)fabs(x))
|
| 188 |
+
|
| 189 |
+
#define QCONST16(x,bits) (x)
|
| 190 |
+
#define QCONST32(x,bits) (x)
|
| 191 |
+
|
| 192 |
+
#define NEG16(x) (-(x))
|
| 193 |
+
#define NEG32(x) (-(x))
|
| 194 |
+
#define NEG32_ovflw(x) (-(x))
|
| 195 |
+
#define EXTRACT16(x) (x)
|
| 196 |
+
#define EXTEND32(x) (x)
|
| 197 |
+
#define SHR16(a,shift) (a)
|
| 198 |
+
#define SHL16(a,shift) (a)
|
| 199 |
+
#define SHR32(a,shift) (a)
|
| 200 |
+
#define SHL32(a,shift) (a)
|
| 201 |
+
#define PSHR32(a,shift) (a)
|
| 202 |
+
#define VSHR32(a,shift) (a)
|
| 203 |
+
|
| 204 |
+
#define PSHR(a,shift) (a)
|
| 205 |
+
#define SHR(a,shift) (a)
|
| 206 |
+
#define SHL(a,shift) (a)
|
| 207 |
+
#define SATURATE(x,a) (x)
|
| 208 |
+
#define SATURATE16(x) (x)
|
| 209 |
+
|
| 210 |
+
#define ROUND16(a,shift) (a)
|
| 211 |
+
#define SROUND16(a,shift) (a)
|
| 212 |
+
#define HALF16(x) (.5f*(x))
|
| 213 |
+
#define HALF32(x) (.5f*(x))
|
| 214 |
+
|
| 215 |
+
#define ADD16(a,b) ((a)+(b))
|
| 216 |
+
#define SUB16(a,b) ((a)-(b))
|
| 217 |
+
#define ADD32(a,b) ((a)+(b))
|
| 218 |
+
#define SUB32(a,b) ((a)-(b))
|
| 219 |
+
#define ADD32_ovflw(a,b) ((a)+(b))
|
| 220 |
+
#define SUB32_ovflw(a,b) ((a)-(b))
|
| 221 |
+
#define MULT16_16_16(a,b) ((a)*(b))
|
| 222 |
+
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b))
|
| 223 |
+
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b))
|
| 224 |
+
|
| 225 |
+
#define MULT16_32_Q15(a,b) ((a)*(b))
|
| 226 |
+
#define MULT16_32_Q16(a,b) ((a)*(b))
|
| 227 |
+
|
| 228 |
+
#define MULT32_32_Q31(a,b) ((a)*(b))
|
| 229 |
+
|
| 230 |
+
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
|
| 231 |
+
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b))
|
| 232 |
+
|
| 233 |
+
#define MULT16_16_Q11_32(a,b) ((a)*(b))
|
| 234 |
+
#define MULT16_16_Q11(a,b) ((a)*(b))
|
| 235 |
+
#define MULT16_16_Q13(a,b) ((a)*(b))
|
| 236 |
+
#define MULT16_16_Q14(a,b) ((a)*(b))
|
| 237 |
+
#define MULT16_16_Q15(a,b) ((a)*(b))
|
| 238 |
+
#define MULT16_16_P15(a,b) ((a)*(b))
|
| 239 |
+
#define MULT16_16_P13(a,b) ((a)*(b))
|
| 240 |
+
#define MULT16_16_P14(a,b) ((a)*(b))
|
| 241 |
+
#define MULT16_32_P16(a,b) ((a)*(b))
|
| 242 |
+
|
| 243 |
+
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b))
|
| 244 |
+
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b))
|
| 245 |
+
|
| 246 |
+
#define SCALEIN(a) ((a)*CELT_SIG_SCALE)
|
| 247 |
+
#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE))
|
| 248 |
+
|
| 249 |
+
#define SIG2WORD16(x) (x)
|
| 250 |
+
|
| 251 |
+
#endif /* !FIXED_POINT */
|
| 252 |
+
|
| 253 |
+
#ifndef GLOBAL_STACK_SIZE
|
| 254 |
+
#ifdef FIXED_POINT
|
| 255 |
+
#define GLOBAL_STACK_SIZE 120000
|
| 256 |
+
#else
|
| 257 |
+
#define GLOBAL_STACK_SIZE 120000
|
| 258 |
+
#endif
|
| 259 |
+
#endif
|
| 260 |
+
|
| 261 |
+
#endif /* ARCH_H */
|
cpp/third_party/kissfft/common.h
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
#ifndef COMMON_H
|
| 4 |
+
#define COMMON_H
|
| 5 |
+
|
| 6 |
+
#include "stdlib.h"
|
| 7 |
+
#include "string.h"
|
| 8 |
+
|
| 9 |
+
#define RNN_INLINE inline
|
| 10 |
+
#define OPUS_INLINE inline
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
/** RNNoise wrapper for malloc(). To do your own dynamic allocation, all you need t
|
| 14 |
+
o do is replace this function and rnnoise_free */
|
| 15 |
+
#ifndef OVERRIDE_RNNOISE_ALLOC
|
| 16 |
+
static RNN_INLINE void *rnnoise_alloc (size_t size)
|
| 17 |
+
{
|
| 18 |
+
return malloc(size);
|
| 19 |
+
}
|
| 20 |
+
#endif
|
| 21 |
+
|
| 22 |
+
/** RNNoise wrapper for free(). To do your own dynamic allocation, all you need to do is replace this function and rnnoise_alloc */
|
| 23 |
+
#ifndef OVERRIDE_RNNOISE_FREE
|
| 24 |
+
static RNN_INLINE void rnnoise_free (void *ptr)
|
| 25 |
+
{
|
| 26 |
+
free(ptr);
|
| 27 |
+
}
|
| 28 |
+
#endif
|
| 29 |
+
|
| 30 |
+
/** Copy n elements from src to dst. The 0* term provides compile-time type checking */
|
| 31 |
+
#ifndef OVERRIDE_RNN_COPY
|
| 32 |
+
#define RNN_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
|
| 33 |
+
#endif
|
| 34 |
+
|
| 35 |
+
/** Copy n elements from src to dst, allowing overlapping regions. The 0* term
|
| 36 |
+
provides compile-time type checking */
|
| 37 |
+
#ifndef OVERRIDE_RNN_MOVE
|
| 38 |
+
#define RNN_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
|
| 39 |
+
#endif
|
| 40 |
+
|
| 41 |
+
/** Set n elements of dst to zero */
|
| 42 |
+
#ifndef OVERRIDE_RNN_CLEAR
|
| 43 |
+
#define RNN_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst))))
|
| 44 |
+
#endif
|
| 45 |
+
|
| 46 |
+
# if !defined(OPUS_GNUC_PREREQ)
|
| 47 |
+
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
|
| 48 |
+
# define OPUS_GNUC_PREREQ(_maj,_min) \
|
| 49 |
+
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
|
| 50 |
+
# else
|
| 51 |
+
# define OPUS_GNUC_PREREQ(_maj,_min) 0
|
| 52 |
+
# endif
|
| 53 |
+
# endif
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
#endif
|
cpp/third_party/kissfft/kiss_fft.c
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*Copyright (c) 2003-2004, Mark Borgerding
|
| 2 |
+
Lots of modifications by Jean-Marc Valin
|
| 3 |
+
Copyright (c) 2005-2007, Xiph.Org Foundation
|
| 4 |
+
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
|
| 5 |
+
|
| 6 |
+
All rights reserved.
|
| 7 |
+
|
| 8 |
+
Redistribution and use in source and binary forms, with or without
|
| 9 |
+
modification, are permitted provided that the following conditions are met:
|
| 10 |
+
|
| 11 |
+
* Redistributions of source code must retain the above copyright notice,
|
| 12 |
+
this list of conditions and the following disclaimer.
|
| 13 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 14 |
+
this list of conditions and the following disclaimer in the
|
| 15 |
+
documentation and/or other materials provided with the distribution.
|
| 16 |
+
|
| 17 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 18 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 19 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 20 |
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
| 21 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 22 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 23 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 24 |
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 25 |
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 26 |
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 27 |
+
POSSIBILITY OF SUCH DAMAGE.*/
|
| 28 |
+
|
| 29 |
+
/* This code is originally from Mark Borgerding's KISS-FFT but has been
|
| 30 |
+
heavily modified to better suit Opus */
|
| 31 |
+
|
| 32 |
+
#ifndef SKIP_CONFIG_H
|
| 33 |
+
# ifdef HAVE_CONFIG_H
|
| 34 |
+
# include "config.h"
|
| 35 |
+
# endif
|
| 36 |
+
#endif
|
| 37 |
+
|
| 38 |
+
#include "_kiss_fft_guts.h"
|
| 39 |
+
#define CUSTOM_MODES
|
| 40 |
+
|
| 41 |
+
/* The guts header contains all the multiplication and addition macros that are defined for
|
| 42 |
+
complex numbers. It also declares the kf_ internal functions.
|
| 43 |
+
*/
|
| 44 |
+
|
| 45 |
+
static void kf_bfly2(
|
| 46 |
+
kiss_fft_cpx * Fout,
|
| 47 |
+
int m,
|
| 48 |
+
int N
|
| 49 |
+
)
|
| 50 |
+
{
|
| 51 |
+
kiss_fft_cpx * Fout2;
|
| 52 |
+
int i;
|
| 53 |
+
(void)m;
|
| 54 |
+
#ifdef CUSTOM_MODES
|
| 55 |
+
if (m==1)
|
| 56 |
+
{
|
| 57 |
+
celt_assert(m==1);
|
| 58 |
+
for (i=0;i<N;i++)
|
| 59 |
+
{
|
| 60 |
+
kiss_fft_cpx t;
|
| 61 |
+
Fout2 = Fout + 1;
|
| 62 |
+
t = *Fout2;
|
| 63 |
+
C_SUB( *Fout2 , *Fout , t );
|
| 64 |
+
C_ADDTO( *Fout , t );
|
| 65 |
+
Fout += 2;
|
| 66 |
+
}
|
| 67 |
+
} else
|
| 68 |
+
#endif
|
| 69 |
+
{
|
| 70 |
+
opus_val16 tw;
|
| 71 |
+
tw = QCONST16(0.7071067812f, 15);
|
| 72 |
+
/* We know that m==4 here because the radix-2 is just after a radix-4 */
|
| 73 |
+
celt_assert(m==4);
|
| 74 |
+
for (i=0;i<N;i++)
|
| 75 |
+
{
|
| 76 |
+
kiss_fft_cpx t;
|
| 77 |
+
Fout2 = Fout + 4;
|
| 78 |
+
t = Fout2[0];
|
| 79 |
+
C_SUB( Fout2[0] , Fout[0] , t );
|
| 80 |
+
C_ADDTO( Fout[0] , t );
|
| 81 |
+
|
| 82 |
+
t.r = S_MUL(ADD32_ovflw(Fout2[1].r, Fout2[1].i), tw);
|
| 83 |
+
t.i = S_MUL(SUB32_ovflw(Fout2[1].i, Fout2[1].r), tw);
|
| 84 |
+
C_SUB( Fout2[1] , Fout[1] , t );
|
| 85 |
+
C_ADDTO( Fout[1] , t );
|
| 86 |
+
|
| 87 |
+
t.r = Fout2[2].i;
|
| 88 |
+
t.i = -Fout2[2].r;
|
| 89 |
+
C_SUB( Fout2[2] , Fout[2] , t );
|
| 90 |
+
C_ADDTO( Fout[2] , t );
|
| 91 |
+
|
| 92 |
+
t.r = S_MUL(SUB32_ovflw(Fout2[3].i, Fout2[3].r), tw);
|
| 93 |
+
t.i = S_MUL(NEG32_ovflw(ADD32_ovflw(Fout2[3].i, Fout2[3].r)), tw);
|
| 94 |
+
C_SUB( Fout2[3] , Fout[3] , t );
|
| 95 |
+
C_ADDTO( Fout[3] , t );
|
| 96 |
+
Fout += 8;
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
static void kf_bfly4(
|
| 102 |
+
kiss_fft_cpx * Fout,
|
| 103 |
+
const size_t fstride,
|
| 104 |
+
const kiss_fft_state *st,
|
| 105 |
+
int m,
|
| 106 |
+
int N,
|
| 107 |
+
int mm
|
| 108 |
+
)
|
| 109 |
+
{
|
| 110 |
+
int i;
|
| 111 |
+
|
| 112 |
+
if (m==1)
|
| 113 |
+
{
|
| 114 |
+
/* Degenerate case where all the twiddles are 1. */
|
| 115 |
+
for (i=0;i<N;i++)
|
| 116 |
+
{
|
| 117 |
+
kiss_fft_cpx scratch0, scratch1;
|
| 118 |
+
|
| 119 |
+
C_SUB( scratch0 , *Fout, Fout[2] );
|
| 120 |
+
C_ADDTO(*Fout, Fout[2]);
|
| 121 |
+
C_ADD( scratch1 , Fout[1] , Fout[3] );
|
| 122 |
+
C_SUB( Fout[2], *Fout, scratch1 );
|
| 123 |
+
C_ADDTO( *Fout , scratch1 );
|
| 124 |
+
C_SUB( scratch1 , Fout[1] , Fout[3] );
|
| 125 |
+
|
| 126 |
+
Fout[1].r = ADD32_ovflw(scratch0.r, scratch1.i);
|
| 127 |
+
Fout[1].i = SUB32_ovflw(scratch0.i, scratch1.r);
|
| 128 |
+
Fout[3].r = SUB32_ovflw(scratch0.r, scratch1.i);
|
| 129 |
+
Fout[3].i = ADD32_ovflw(scratch0.i, scratch1.r);
|
| 130 |
+
Fout+=4;
|
| 131 |
+
}
|
| 132 |
+
} else {
|
| 133 |
+
int j;
|
| 134 |
+
kiss_fft_cpx scratch[6];
|
| 135 |
+
const kiss_twiddle_cpx *tw1,*tw2,*tw3;
|
| 136 |
+
const int m2=2*m;
|
| 137 |
+
const int m3=3*m;
|
| 138 |
+
kiss_fft_cpx * Fout_beg = Fout;
|
| 139 |
+
for (i=0;i<N;i++)
|
| 140 |
+
{
|
| 141 |
+
Fout = Fout_beg + i*mm;
|
| 142 |
+
tw3 = tw2 = tw1 = st->twiddles;
|
| 143 |
+
/* m is guaranteed to be a multiple of 4. */
|
| 144 |
+
for (j=0;j<m;j++)
|
| 145 |
+
{
|
| 146 |
+
C_MUL(scratch[0],Fout[m] , *tw1 );
|
| 147 |
+
C_MUL(scratch[1],Fout[m2] , *tw2 );
|
| 148 |
+
C_MUL(scratch[2],Fout[m3] , *tw3 );
|
| 149 |
+
|
| 150 |
+
C_SUB( scratch[5] , *Fout, scratch[1] );
|
| 151 |
+
C_ADDTO(*Fout, scratch[1]);
|
| 152 |
+
C_ADD( scratch[3] , scratch[0] , scratch[2] );
|
| 153 |
+
C_SUB( scratch[4] , scratch[0] , scratch[2] );
|
| 154 |
+
C_SUB( Fout[m2], *Fout, scratch[3] );
|
| 155 |
+
tw1 += fstride;
|
| 156 |
+
tw2 += fstride*2;
|
| 157 |
+
tw3 += fstride*3;
|
| 158 |
+
C_ADDTO( *Fout , scratch[3] );
|
| 159 |
+
|
| 160 |
+
Fout[m].r = ADD32_ovflw(scratch[5].r, scratch[4].i);
|
| 161 |
+
Fout[m].i = SUB32_ovflw(scratch[5].i, scratch[4].r);
|
| 162 |
+
Fout[m3].r = SUB32_ovflw(scratch[5].r, scratch[4].i);
|
| 163 |
+
Fout[m3].i = ADD32_ovflw(scratch[5].i, scratch[4].r);
|
| 164 |
+
++Fout;
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
#ifndef RADIX_TWO_ONLY
|
| 172 |
+
|
| 173 |
+
static void kf_bfly3(
|
| 174 |
+
kiss_fft_cpx * Fout,
|
| 175 |
+
const size_t fstride,
|
| 176 |
+
const kiss_fft_state *st,
|
| 177 |
+
int m,
|
| 178 |
+
int N,
|
| 179 |
+
int mm
|
| 180 |
+
)
|
| 181 |
+
{
|
| 182 |
+
int i;
|
| 183 |
+
size_t k;
|
| 184 |
+
const size_t m2 = 2*m;
|
| 185 |
+
const kiss_twiddle_cpx *tw1,*tw2;
|
| 186 |
+
kiss_fft_cpx scratch[5];
|
| 187 |
+
kiss_twiddle_cpx epi3;
|
| 188 |
+
|
| 189 |
+
kiss_fft_cpx * Fout_beg = Fout;
|
| 190 |
+
#ifdef FIXED_POINT
|
| 191 |
+
/*epi3.r = -16384;*/ /* Unused */
|
| 192 |
+
epi3.i = -28378;
|
| 193 |
+
#else
|
| 194 |
+
epi3 = st->twiddles[fstride*m];
|
| 195 |
+
#endif
|
| 196 |
+
for (i=0;i<N;i++)
|
| 197 |
+
{
|
| 198 |
+
Fout = Fout_beg + i*mm;
|
| 199 |
+
tw1=tw2=st->twiddles;
|
| 200 |
+
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
|
| 201 |
+
k=m;
|
| 202 |
+
do {
|
| 203 |
+
|
| 204 |
+
C_MUL(scratch[1],Fout[m] , *tw1);
|
| 205 |
+
C_MUL(scratch[2],Fout[m2] , *tw2);
|
| 206 |
+
|
| 207 |
+
C_ADD(scratch[3],scratch[1],scratch[2]);
|
| 208 |
+
C_SUB(scratch[0],scratch[1],scratch[2]);
|
| 209 |
+
tw1 += fstride;
|
| 210 |
+
tw2 += fstride*2;
|
| 211 |
+
|
| 212 |
+
Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r));
|
| 213 |
+
Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i));
|
| 214 |
+
|
| 215 |
+
C_MULBYSCALAR( scratch[0] , epi3.i );
|
| 216 |
+
|
| 217 |
+
C_ADDTO(*Fout,scratch[3]);
|
| 218 |
+
|
| 219 |
+
Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i);
|
| 220 |
+
Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r);
|
| 221 |
+
|
| 222 |
+
Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i);
|
| 223 |
+
Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r);
|
| 224 |
+
|
| 225 |
+
++Fout;
|
| 226 |
+
} while(--k);
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
#ifndef OVERRIDE_kf_bfly5
|
| 232 |
+
static void kf_bfly5(
|
| 233 |
+
kiss_fft_cpx * Fout,
|
| 234 |
+
const size_t fstride,
|
| 235 |
+
const kiss_fft_state *st,
|
| 236 |
+
int m,
|
| 237 |
+
int N,
|
| 238 |
+
int mm
|
| 239 |
+
)
|
| 240 |
+
{
|
| 241 |
+
kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4;
|
| 242 |
+
int i, u;
|
| 243 |
+
kiss_fft_cpx scratch[13];
|
| 244 |
+
const kiss_twiddle_cpx *tw;
|
| 245 |
+
kiss_twiddle_cpx ya,yb;
|
| 246 |
+
kiss_fft_cpx * Fout_beg = Fout;
|
| 247 |
+
|
| 248 |
+
#ifdef FIXED_POINT
|
| 249 |
+
ya.r = 10126;
|
| 250 |
+
ya.i = -31164;
|
| 251 |
+
yb.r = -26510;
|
| 252 |
+
yb.i = -19261;
|
| 253 |
+
#else
|
| 254 |
+
ya = st->twiddles[fstride*m];
|
| 255 |
+
yb = st->twiddles[fstride*2*m];
|
| 256 |
+
#endif
|
| 257 |
+
tw=st->twiddles;
|
| 258 |
+
|
| 259 |
+
for (i=0;i<N;i++)
|
| 260 |
+
{
|
| 261 |
+
Fout = Fout_beg + i*mm;
|
| 262 |
+
Fout0=Fout;
|
| 263 |
+
Fout1=Fout0+m;
|
| 264 |
+
Fout2=Fout0+2*m;
|
| 265 |
+
Fout3=Fout0+3*m;
|
| 266 |
+
Fout4=Fout0+4*m;
|
| 267 |
+
|
| 268 |
+
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
|
| 269 |
+
for ( u=0; u<m; ++u ) {
|
| 270 |
+
scratch[0] = *Fout0;
|
| 271 |
+
|
| 272 |
+
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);
|
| 273 |
+
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);
|
| 274 |
+
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);
|
| 275 |
+
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);
|
| 276 |
+
|
| 277 |
+
C_ADD( scratch[7],scratch[1],scratch[4]);
|
| 278 |
+
C_SUB( scratch[10],scratch[1],scratch[4]);
|
| 279 |
+
C_ADD( scratch[8],scratch[2],scratch[3]);
|
| 280 |
+
C_SUB( scratch[9],scratch[2],scratch[3]);
|
| 281 |
+
|
| 282 |
+
Fout0->r = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r));
|
| 283 |
+
Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i));
|
| 284 |
+
|
| 285 |
+
scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r)));
|
| 286 |
+
scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r)));
|
| 287 |
+
|
| 288 |
+
scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i));
|
| 289 |
+
scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i)));
|
| 290 |
+
|
| 291 |
+
C_SUB(*Fout1,scratch[5],scratch[6]);
|
| 292 |
+
C_ADD(*Fout4,scratch[5],scratch[6]);
|
| 293 |
+
|
| 294 |
+
scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r)));
|
| 295 |
+
scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r)));
|
| 296 |
+
scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i));
|
| 297 |
+
scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i));
|
| 298 |
+
|
| 299 |
+
C_ADD(*Fout2,scratch[11],scratch[12]);
|
| 300 |
+
C_SUB(*Fout3,scratch[11],scratch[12]);
|
| 301 |
+
|
| 302 |
+
++Fout0;++Fout1;++Fout2;++Fout3;++Fout4;
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
#endif /* OVERRIDE_kf_bfly5 */
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
#endif
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
#ifdef CUSTOM_MODES
|
| 313 |
+
|
| 314 |
+
static
|
| 315 |
+
void compute_bitrev_table(
|
| 316 |
+
int Fout,
|
| 317 |
+
opus_int32 *f,
|
| 318 |
+
const size_t fstride,
|
| 319 |
+
int in_stride,
|
| 320 |
+
opus_int16 * factors,
|
| 321 |
+
const kiss_fft_state *st
|
| 322 |
+
)
|
| 323 |
+
{
|
| 324 |
+
const int p=*factors++; /* the radix */
|
| 325 |
+
const int m=*factors++; /* stage's fft length/p */
|
| 326 |
+
|
| 327 |
+
/*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/
|
| 328 |
+
if (m==1)
|
| 329 |
+
{
|
| 330 |
+
int j;
|
| 331 |
+
for (j=0;j<p;j++)
|
| 332 |
+
{
|
| 333 |
+
*f = Fout+j;
|
| 334 |
+
f += fstride*in_stride;
|
| 335 |
+
}
|
| 336 |
+
} else {
|
| 337 |
+
int j;
|
| 338 |
+
for (j=0;j<p;j++)
|
| 339 |
+
{
|
| 340 |
+
compute_bitrev_table( Fout , f, fstride*p, in_stride, factors,st);
|
| 341 |
+
f += fstride*in_stride;
|
| 342 |
+
Fout += m;
|
| 343 |
+
}
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
/* facbuf is populated by p1,m1,p2,m2, ...
|
| 348 |
+
where
|
| 349 |
+
p[i] * m[i] = m[i-1]
|
| 350 |
+
m0 = n */
|
| 351 |
+
static
|
| 352 |
+
int kf_factor(int n,opus_int16 * facbuf)
|
| 353 |
+
{
|
| 354 |
+
int p=4;
|
| 355 |
+
int i;
|
| 356 |
+
int stages=0;
|
| 357 |
+
int nbak = n;
|
| 358 |
+
|
| 359 |
+
/*factor out powers of 4, powers of 2, then any remaining primes */
|
| 360 |
+
do {
|
| 361 |
+
while (n % p) {
|
| 362 |
+
switch (p) {
|
| 363 |
+
case 4: p = 2; break;
|
| 364 |
+
case 2: p = 3; break;
|
| 365 |
+
default: p += 2; break;
|
| 366 |
+
}
|
| 367 |
+
if (p>32000 || (opus_int32)p*(opus_int32)p > n)
|
| 368 |
+
p = n; /* no more factors, skip to end */
|
| 369 |
+
}
|
| 370 |
+
n /= p;
|
| 371 |
+
#ifdef RADIX_TWO_ONLY
|
| 372 |
+
if (p!=2 && p != 4)
|
| 373 |
+
#else
|
| 374 |
+
if (p>5)
|
| 375 |
+
#endif
|
| 376 |
+
{
|
| 377 |
+
return 0;
|
| 378 |
+
}
|
| 379 |
+
facbuf[2*stages] = p;
|
| 380 |
+
if (p==2 && stages > 1)
|
| 381 |
+
{
|
| 382 |
+
facbuf[2*stages] = 4;
|
| 383 |
+
facbuf[2] = 2;
|
| 384 |
+
}
|
| 385 |
+
stages++;
|
| 386 |
+
} while (n > 1);
|
| 387 |
+
n = nbak;
|
| 388 |
+
/* Reverse the order to get the radix 4 at the end, so we can use the
|
| 389 |
+
fast degenerate case. It turns out that reversing the order also
|
| 390 |
+
improves the noise behaviour. */
|
| 391 |
+
for (i=0;i<stages/2;i++)
|
| 392 |
+
{
|
| 393 |
+
int tmp;
|
| 394 |
+
tmp = facbuf[2*i];
|
| 395 |
+
facbuf[2*i] = facbuf[2*(stages-i-1)];
|
| 396 |
+
facbuf[2*(stages-i-1)] = tmp;
|
| 397 |
+
}
|
| 398 |
+
for (i=0;i<stages;i++)
|
| 399 |
+
{
|
| 400 |
+
n /= facbuf[2*i];
|
| 401 |
+
facbuf[2*i+1] = n;
|
| 402 |
+
}
|
| 403 |
+
return 1;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
static void compute_twiddles(kiss_twiddle_cpx *twiddles, int nfft)
|
| 407 |
+
{
|
| 408 |
+
int i;
|
| 409 |
+
#ifdef FIXED_POINT
|
| 410 |
+
for (i=0;i<nfft;++i) {
|
| 411 |
+
opus_val32 phase = -i;
|
| 412 |
+
kf_cexp2(twiddles+i, DIV32(SHL32(phase,17),nfft));
|
| 413 |
+
}
|
| 414 |
+
#else
|
| 415 |
+
for (i=0;i<nfft;++i) {
|
| 416 |
+
const double pi=3.14159265358979323846264338327;
|
| 417 |
+
double phase = ( -2*pi /nfft ) * i;
|
| 418 |
+
kf_cexp(twiddles+i, phase );
|
| 419 |
+
}
|
| 420 |
+
#endif
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
int rnn_fft_alloc_arch_c(kiss_fft_state *st) {
|
| 424 |
+
(void)st;
|
| 425 |
+
return 0;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
/*
|
| 429 |
+
*
|
| 430 |
+
* Allocates all necessary storage space for the fft and ifft.
|
| 431 |
+
* The return value is a contiguous block of memory. As such,
|
| 432 |
+
* It can be freed with free().
|
| 433 |
+
* */
|
| 434 |
+
kiss_fft_state *rnn_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem,
|
| 435 |
+
const kiss_fft_state *base, int arch)
|
| 436 |
+
{
|
| 437 |
+
kiss_fft_state *st=NULL;
|
| 438 |
+
size_t memneeded = sizeof(struct kiss_fft_state); /* twiddle factors*/
|
| 439 |
+
|
| 440 |
+
if ( lenmem==NULL ) {
|
| 441 |
+
st = ( kiss_fft_state*)KISS_FFT_MALLOC( memneeded );
|
| 442 |
+
}else{
|
| 443 |
+
if (mem != NULL && *lenmem >= memneeded)
|
| 444 |
+
st = (kiss_fft_state*)mem;
|
| 445 |
+
*lenmem = memneeded;
|
| 446 |
+
}
|
| 447 |
+
if (st) {
|
| 448 |
+
opus_int32 *bitrev;
|
| 449 |
+
kiss_twiddle_cpx *twiddles;
|
| 450 |
+
|
| 451 |
+
st->nfft=nfft;
|
| 452 |
+
#ifdef FIXED_POINT
|
| 453 |
+
st->scale_shift = celt_ilog2(st->nfft);
|
| 454 |
+
if (st->nfft == 1<<st->scale_shift)
|
| 455 |
+
st->scale = Q15ONE;
|
| 456 |
+
else
|
| 457 |
+
st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift);
|
| 458 |
+
#else
|
| 459 |
+
st->scale = 1.f/nfft;
|
| 460 |
+
#endif
|
| 461 |
+
if (base != NULL)
|
| 462 |
+
{
|
| 463 |
+
st->twiddles = base->twiddles;
|
| 464 |
+
st->shift = 0;
|
| 465 |
+
while (st->shift < 32 && nfft<<st->shift != base->nfft)
|
| 466 |
+
st->shift++;
|
| 467 |
+
if (st->shift>=32)
|
| 468 |
+
goto fail;
|
| 469 |
+
} else {
|
| 470 |
+
st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft);
|
| 471 |
+
compute_twiddles(twiddles, nfft);
|
| 472 |
+
st->shift = -1;
|
| 473 |
+
}
|
| 474 |
+
if (!kf_factor(nfft,st->factors))
|
| 475 |
+
{
|
| 476 |
+
goto fail;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
/* bitrev */
|
| 480 |
+
st->bitrev = bitrev = (opus_int32*)KISS_FFT_MALLOC(sizeof(opus_int32)*nfft);
|
| 481 |
+
if (st->bitrev==NULL)
|
| 482 |
+
goto fail;
|
| 483 |
+
compute_bitrev_table(0, bitrev, 1,1, st->factors,st);
|
| 484 |
+
|
| 485 |
+
/* Initialize architecture specific fft parameters */
|
| 486 |
+
if (rnn_fft_alloc_arch(st, arch))
|
| 487 |
+
goto fail;
|
| 488 |
+
}
|
| 489 |
+
return st;
|
| 490 |
+
fail:
|
| 491 |
+
rnn_fft_free(st, arch);
|
| 492 |
+
return NULL;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
kiss_fft_state *rnn_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch)
|
| 496 |
+
{
|
| 497 |
+
return rnn_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch);
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
void rnn_fft_free_arch_c(kiss_fft_state *st) {
|
| 501 |
+
(void)st;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
void rnn_fft_free(const kiss_fft_state *cfg, int arch)
|
| 505 |
+
{
|
| 506 |
+
if (cfg)
|
| 507 |
+
{
|
| 508 |
+
rnn_fft_free_arch((kiss_fft_state *)cfg, arch);
|
| 509 |
+
opus_free((opus_int32*)cfg->bitrev);
|
| 510 |
+
if (cfg->shift < 0)
|
| 511 |
+
opus_free((kiss_twiddle_cpx*)cfg->twiddles);
|
| 512 |
+
opus_free((kiss_fft_state*)cfg);
|
| 513 |
+
}
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
#endif /* CUSTOM_MODES */
|
| 517 |
+
|
| 518 |
+
void rnn_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout)
|
| 519 |
+
{
|
| 520 |
+
int m2, m;
|
| 521 |
+
int p;
|
| 522 |
+
int L;
|
| 523 |
+
int fstride[MAXFACTORS];
|
| 524 |
+
int i;
|
| 525 |
+
int shift;
|
| 526 |
+
|
| 527 |
+
/* st->shift can be -1 */
|
| 528 |
+
shift = st->shift>0 ? st->shift : 0;
|
| 529 |
+
|
| 530 |
+
fstride[0] = 1;
|
| 531 |
+
L=0;
|
| 532 |
+
do {
|
| 533 |
+
p = st->factors[2*L];
|
| 534 |
+
m = st->factors[2*L+1];
|
| 535 |
+
fstride[L+1] = fstride[L]*p;
|
| 536 |
+
L++;
|
| 537 |
+
} while(m!=1);
|
| 538 |
+
m = st->factors[2*L-1];
|
| 539 |
+
for (i=L-1;i>=0;i--)
|
| 540 |
+
{
|
| 541 |
+
if (i!=0)
|
| 542 |
+
m2 = st->factors[2*i-1];
|
| 543 |
+
else
|
| 544 |
+
m2 = 1;
|
| 545 |
+
switch (st->factors[2*i])
|
| 546 |
+
{
|
| 547 |
+
case 2:
|
| 548 |
+
kf_bfly2(fout, m, fstride[i]);
|
| 549 |
+
break;
|
| 550 |
+
case 4:
|
| 551 |
+
kf_bfly4(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
| 552 |
+
break;
|
| 553 |
+
#ifndef RADIX_TWO_ONLY
|
| 554 |
+
case 3:
|
| 555 |
+
kf_bfly3(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
| 556 |
+
break;
|
| 557 |
+
case 5:
|
| 558 |
+
kf_bfly5(fout,fstride[i]<<shift,st,m, fstride[i], m2);
|
| 559 |
+
break;
|
| 560 |
+
#endif
|
| 561 |
+
}
|
| 562 |
+
m = m2;
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
void rnn_fft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
|
| 567 |
+
{
|
| 568 |
+
int i;
|
| 569 |
+
opus_val16 scale;
|
| 570 |
+
#ifdef FIXED_POINT
|
| 571 |
+
/* Allows us to scale with MULT16_32_Q16(), which is faster than
|
| 572 |
+
MULT16_32_Q15() on ARM. */
|
| 573 |
+
int scale_shift = st->scale_shift-1;
|
| 574 |
+
#endif
|
| 575 |
+
scale = st->scale;
|
| 576 |
+
|
| 577 |
+
celt_assert2 (fin != fout, "In-place FFT not supported");
|
| 578 |
+
/* Bit-reverse the input */
|
| 579 |
+
for (i=0;i<st->nfft;i++)
|
| 580 |
+
{
|
| 581 |
+
kiss_fft_cpx x = fin[i];
|
| 582 |
+
fout[st->bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift);
|
| 583 |
+
fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift);
|
| 584 |
+
}
|
| 585 |
+
rnn_fft_impl(st, fout);
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
void rnn_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
|
| 590 |
+
{
|
| 591 |
+
int i;
|
| 592 |
+
celt_assert2 (fin != fout, "In-place FFT not supported");
|
| 593 |
+
/* Bit-reverse the input */
|
| 594 |
+
for (i=0;i<st->nfft;i++)
|
| 595 |
+
fout[st->bitrev[i]] = fin[i];
|
| 596 |
+
for (i=0;i<st->nfft;i++)
|
| 597 |
+
fout[i].i = -fout[i].i;
|
| 598 |
+
rnn_fft_impl(st, fout);
|
| 599 |
+
for (i=0;i<st->nfft;i++)
|
| 600 |
+
fout[i].i = -fout[i].i;
|
| 601 |
+
}
|
cpp/third_party/kissfft/kiss_fft.h
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*Copyright (c) 2003-2004, Mark Borgerding
|
| 2 |
+
Lots of modifications by Jean-Marc Valin
|
| 3 |
+
Copyright (c) 2005-2007, Xiph.Org Foundation
|
| 4 |
+
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
|
| 5 |
+
|
| 6 |
+
All rights reserved.
|
| 7 |
+
|
| 8 |
+
Redistribution and use in source and binary forms, with or without
|
| 9 |
+
modification, are permitted provided that the following conditions are met:
|
| 10 |
+
|
| 11 |
+
* Redistributions of source code must retain the above copyright notice,
|
| 12 |
+
this list of conditions and the following disclaimer.
|
| 13 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 14 |
+
this list of conditions and the following disclaimer in the
|
| 15 |
+
documentation and/or other materials provided with the distribution.
|
| 16 |
+
|
| 17 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 18 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 19 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 20 |
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
| 21 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 22 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 23 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 24 |
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 25 |
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 26 |
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 27 |
+
POSSIBILITY OF SUCH DAMAGE.*/
|
| 28 |
+
|
| 29 |
+
#ifndef KISS_FFT_H
|
| 30 |
+
#define KISS_FFT_H
|
| 31 |
+
|
| 32 |
+
#include <stdlib.h>
|
| 33 |
+
#include <math.h>
|
| 34 |
+
#include "arch.h"
|
| 35 |
+
|
| 36 |
+
#include <stdlib.h>
|
| 37 |
+
#define opus_alloc(x) malloc(x)
|
| 38 |
+
#define opus_free(x) free(x)
|
| 39 |
+
|
| 40 |
+
#ifdef __cplusplus
|
| 41 |
+
extern "C" {
|
| 42 |
+
#endif
|
| 43 |
+
|
| 44 |
+
#ifdef USE_SIMD
|
| 45 |
+
# include <xmmintrin.h>
|
| 46 |
+
# define kiss_fft_scalar __m128
|
| 47 |
+
#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes)
|
| 48 |
+
#else
|
| 49 |
+
#define KISS_FFT_MALLOC opus_alloc
|
| 50 |
+
#endif
|
| 51 |
+
|
| 52 |
+
#ifdef FIXED_POINT
|
| 53 |
+
#include "arch.h"
|
| 54 |
+
|
| 55 |
+
# define kiss_fft_scalar opus_int32
|
| 56 |
+
# define kiss_twiddle_scalar opus_int16
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
#else
|
| 60 |
+
# ifndef kiss_fft_scalar
|
| 61 |
+
/* default is float */
|
| 62 |
+
# define kiss_fft_scalar float
|
| 63 |
+
# define kiss_twiddle_scalar float
|
| 64 |
+
# define KF_SUFFIX _celt_single
|
| 65 |
+
# endif
|
| 66 |
+
#endif
|
| 67 |
+
|
| 68 |
+
typedef struct {
|
| 69 |
+
kiss_fft_scalar r;
|
| 70 |
+
kiss_fft_scalar i;
|
| 71 |
+
}kiss_fft_cpx;
|
| 72 |
+
|
| 73 |
+
typedef struct {
|
| 74 |
+
kiss_twiddle_scalar r;
|
| 75 |
+
kiss_twiddle_scalar i;
|
| 76 |
+
}kiss_twiddle_cpx;
|
| 77 |
+
|
| 78 |
+
#define MAXFACTORS 8
|
| 79 |
+
/* e.g. an fft of length 128 has 4 factors
|
| 80 |
+
as far as kissfft is concerned
|
| 81 |
+
4*4*4*2
|
| 82 |
+
*/
|
| 83 |
+
|
| 84 |
+
typedef struct arch_fft_state{
|
| 85 |
+
int is_supported;
|
| 86 |
+
void *priv;
|
| 87 |
+
} arch_fft_state;
|
| 88 |
+
|
| 89 |
+
typedef struct kiss_fft_state{
|
| 90 |
+
int nfft;
|
| 91 |
+
opus_val16 scale;
|
| 92 |
+
#ifdef FIXED_POINT
|
| 93 |
+
int scale_shift;
|
| 94 |
+
#endif
|
| 95 |
+
int shift;
|
| 96 |
+
opus_int16 factors[2*MAXFACTORS];
|
| 97 |
+
const opus_int32 *bitrev;
|
| 98 |
+
const kiss_twiddle_cpx *twiddles;
|
| 99 |
+
arch_fft_state *arch_fft;
|
| 100 |
+
} kiss_fft_state;
|
| 101 |
+
|
| 102 |
+
#if defined(HAVE_ARM_NE10)
|
| 103 |
+
#include "arm/fft_arm.h"
|
| 104 |
+
#endif
|
| 105 |
+
|
| 106 |
+
/*typedef struct kiss_fft_state* kiss_fft_cfg;*/
|
| 107 |
+
|
| 108 |
+
/**
|
| 109 |
+
* opus_fft_alloc
|
| 110 |
+
*
|
| 111 |
+
* Initialize a FFT (or IFFT) algorithm's cfg/state buffer.
|
| 112 |
+
*
|
| 113 |
+
* typical usage: kiss_fft_cfg mycfg=opus_fft_alloc(1024,0,NULL,NULL);
|
| 114 |
+
*
|
| 115 |
+
* The return value from fft_alloc is a cfg buffer used internally
|
| 116 |
+
* by the fft routine or NULL.
|
| 117 |
+
*
|
| 118 |
+
* If lenmem is NULL, then opus_fft_alloc will allocate a cfg buffer using malloc.
|
| 119 |
+
* The returned value should be free()d when done to avoid memory leaks.
|
| 120 |
+
*
|
| 121 |
+
* The state can be placed in a user supplied buffer 'mem':
|
| 122 |
+
* If lenmem is not NULL and mem is not NULL and *lenmem is large enough,
|
| 123 |
+
* then the function places the cfg in mem and the size used in *lenmem
|
| 124 |
+
* and returns mem.
|
| 125 |
+
*
|
| 126 |
+
* If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),
|
| 127 |
+
* then the function returns NULL and places the minimum cfg
|
| 128 |
+
* buffer size in *lenmem.
|
| 129 |
+
* */
|
| 130 |
+
|
| 131 |
+
kiss_fft_state *rnn_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch);
|
| 132 |
+
|
| 133 |
+
kiss_fft_state *rnn_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch);
|
| 134 |
+
|
| 135 |
+
/**
|
| 136 |
+
* opus_fft(cfg,in_out_buf)
|
| 137 |
+
*
|
| 138 |
+
* Perform an FFT on a complex input buffer.
|
| 139 |
+
* for a forward FFT,
|
| 140 |
+
* fin should be f[0] , f[1] , ... ,f[nfft-1]
|
| 141 |
+
* fout will be F[0] , F[1] , ... ,F[nfft-1]
|
| 142 |
+
* Note that each element is complex and can be accessed like
|
| 143 |
+
f[k].r and f[k].i
|
| 144 |
+
* */
|
| 145 |
+
void rnn_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
|
| 146 |
+
void rnn_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
|
| 147 |
+
|
| 148 |
+
void rnn_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
|
| 149 |
+
void rnn_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
|
| 150 |
+
|
| 151 |
+
void rnn_fft_free(const kiss_fft_state *cfg, int arch);
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
void rnn_fft_free_arch_c(kiss_fft_state *st);
|
| 155 |
+
int rnn_fft_alloc_arch_c(kiss_fft_state *st);
|
| 156 |
+
|
| 157 |
+
#if !defined(OVERRIDE_OPUS_FFT)
|
| 158 |
+
/* Is run-time CPU detection enabled on this platform? */
|
| 159 |
+
#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10))
|
| 160 |
+
|
| 161 |
+
extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(
|
| 162 |
+
kiss_fft_state *st);
|
| 163 |
+
|
| 164 |
+
#define opus_fft_alloc_arch(_st, arch) \
|
| 165 |
+
((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
|
| 166 |
+
|
| 167 |
+
extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(
|
| 168 |
+
kiss_fft_state *st);
|
| 169 |
+
#define opus_fft_free_arch(_st, arch) \
|
| 170 |
+
((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
|
| 171 |
+
|
| 172 |
+
extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
| 173 |
+
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
|
| 174 |
+
#define opus_fft(_cfg, _fin, _fout, arch) \
|
| 175 |
+
((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
|
| 176 |
+
|
| 177 |
+
extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
|
| 178 |
+
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
|
| 179 |
+
#define opus_ifft(_cfg, _fin, _fout, arch) \
|
| 180 |
+
((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
|
| 181 |
+
|
| 182 |
+
#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
|
| 183 |
+
|
| 184 |
+
#define rnn_fft_alloc_arch(_st, arch) \
|
| 185 |
+
((void)(arch), rnn_fft_alloc_arch_c(_st))
|
| 186 |
+
|
| 187 |
+
#define rnn_fft_free_arch(_st, arch) \
|
| 188 |
+
((void)(arch), rnn_fft_free_arch_c(_st))
|
| 189 |
+
|
| 190 |
+
#define rnn_fft(_cfg, _fin, _fout, arch) \
|
| 191 |
+
((void)(arch), rnn_fft_c(_cfg, _fin, _fout))
|
| 192 |
+
|
| 193 |
+
#define rnn_ifft(_cfg, _fin, _fout, arch) \
|
| 194 |
+
((void)(arch), rnn_ifft_c(_cfg, _fin, _fout))
|
| 195 |
+
|
| 196 |
+
#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
|
| 197 |
+
#endif /* end if !defined(OVERRIDE_OPUS_FFT) */
|
| 198 |
+
|
| 199 |
+
#ifdef __cplusplus
|
| 200 |
+
}
|
| 201 |
+
#endif
|
| 202 |
+
|
| 203 |
+
#endif
|
cpp/third_party/kissfft/opus_types.h
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
|
| 2 |
+
/* Modified by Jean-Marc Valin */
|
| 3 |
+
/*
|
| 4 |
+
Redistribution and use in source and binary forms, with or without
|
| 5 |
+
modification, are permitted provided that the following conditions
|
| 6 |
+
are met:
|
| 7 |
+
|
| 8 |
+
- Redistributions of source code must retain the above copyright
|
| 9 |
+
notice, this list of conditions and the following disclaimer.
|
| 10 |
+
|
| 11 |
+
- Redistributions in binary form must reproduce the above copyright
|
| 12 |
+
notice, this list of conditions and the following disclaimer in the
|
| 13 |
+
documentation and/or other materials provided with the distribution.
|
| 14 |
+
|
| 15 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 16 |
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 17 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 18 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
| 19 |
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
| 20 |
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
| 21 |
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
| 22 |
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
| 23 |
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
| 24 |
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 25 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 26 |
+
*/
|
| 27 |
+
/* opus_types.h based on ogg_types.h from libogg */
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
@file opus_types.h
|
| 31 |
+
@brief Opus reference implementation types
|
| 32 |
+
*/
|
| 33 |
+
#ifndef OPUS_TYPES_H
|
| 34 |
+
#define OPUS_TYPES_H
|
| 35 |
+
|
| 36 |
+
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
|
| 37 |
+
#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
|
| 38 |
+
#include <stdint.h>
|
| 39 |
+
|
| 40 |
+
typedef int16_t opus_int16;
|
| 41 |
+
typedef uint16_t opus_uint16;
|
| 42 |
+
typedef int32_t opus_int32;
|
| 43 |
+
typedef uint32_t opus_uint32;
|
| 44 |
+
#elif defined(_WIN32)
|
| 45 |
+
|
| 46 |
+
# if defined(__CYGWIN__)
|
| 47 |
+
# include <_G_config.h>
|
| 48 |
+
typedef _G_int32_t opus_int32;
|
| 49 |
+
typedef _G_uint32_t opus_uint32;
|
| 50 |
+
typedef _G_int16 opus_int16;
|
| 51 |
+
typedef _G_uint16 opus_uint16;
|
| 52 |
+
# elif defined(__MINGW32__)
|
| 53 |
+
typedef short opus_int16;
|
| 54 |
+
typedef unsigned short opus_uint16;
|
| 55 |
+
typedef int opus_int32;
|
| 56 |
+
typedef unsigned int opus_uint32;
|
| 57 |
+
# elif defined(__MWERKS__)
|
| 58 |
+
typedef int opus_int32;
|
| 59 |
+
typedef unsigned int opus_uint32;
|
| 60 |
+
typedef short opus_int16;
|
| 61 |
+
typedef unsigned short opus_uint16;
|
| 62 |
+
# else
|
| 63 |
+
/* MSVC/Borland */
|
| 64 |
+
typedef __int32 opus_int32;
|
| 65 |
+
typedef unsigned __int32 opus_uint32;
|
| 66 |
+
typedef __int16 opus_int16;
|
| 67 |
+
typedef unsigned __int16 opus_uint16;
|
| 68 |
+
# endif
|
| 69 |
+
|
| 70 |
+
#elif defined(__MACOS__)
|
| 71 |
+
|
| 72 |
+
# include <sys/types.h>
|
| 73 |
+
typedef SInt16 opus_int16;
|
| 74 |
+
typedef UInt16 opus_uint16;
|
| 75 |
+
typedef SInt32 opus_int32;
|
| 76 |
+
typedef UInt32 opus_uint32;
|
| 77 |
+
|
| 78 |
+
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
|
| 79 |
+
|
| 80 |
+
# include <sys/types.h>
|
| 81 |
+
typedef int16_t opus_int16;
|
| 82 |
+
typedef u_int16_t opus_uint16;
|
| 83 |
+
typedef int32_t opus_int32;
|
| 84 |
+
typedef u_int32_t opus_uint32;
|
| 85 |
+
|
| 86 |
+
#elif defined(__BEOS__)
|
| 87 |
+
|
| 88 |
+
/* Be */
|
| 89 |
+
# include <inttypes.h>
|
| 90 |
+
typedef int16 opus_int16;
|
| 91 |
+
typedef u_int16 opus_uint16;
|
| 92 |
+
typedef int32_t opus_int32;
|
| 93 |
+
typedef u_int32_t opus_uint32;
|
| 94 |
+
|
| 95 |
+
#elif defined (__EMX__)
|
| 96 |
+
|
| 97 |
+
/* OS/2 GCC */
|
| 98 |
+
typedef short opus_int16;
|
| 99 |
+
typedef unsigned short opus_uint16;
|
| 100 |
+
typedef int opus_int32;
|
| 101 |
+
typedef unsigned int opus_uint32;
|
| 102 |
+
|
| 103 |
+
#elif defined (DJGPP)
|
| 104 |
+
|
| 105 |
+
/* DJGPP */
|
| 106 |
+
typedef short opus_int16;
|
| 107 |
+
typedef unsigned short opus_uint16;
|
| 108 |
+
typedef int opus_int32;
|
| 109 |
+
typedef unsigned int opus_uint32;
|
| 110 |
+
|
| 111 |
+
#elif defined(R5900)
|
| 112 |
+
|
| 113 |
+
/* PS2 EE */
|
| 114 |
+
typedef int opus_int32;
|
| 115 |
+
typedef unsigned opus_uint32;
|
| 116 |
+
typedef short opus_int16;
|
| 117 |
+
typedef unsigned short opus_uint16;
|
| 118 |
+
|
| 119 |
+
#elif defined(__SYMBIAN32__)
|
| 120 |
+
|
| 121 |
+
/* Symbian GCC */
|
| 122 |
+
typedef signed short opus_int16;
|
| 123 |
+
typedef unsigned short opus_uint16;
|
| 124 |
+
typedef signed int opus_int32;
|
| 125 |
+
typedef unsigned int opus_uint32;
|
| 126 |
+
|
| 127 |
+
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
|
| 128 |
+
|
| 129 |
+
typedef short opus_int16;
|
| 130 |
+
typedef unsigned short opus_uint16;
|
| 131 |
+
typedef long opus_int32;
|
| 132 |
+
typedef unsigned long opus_uint32;
|
| 133 |
+
|
| 134 |
+
#elif defined(CONFIG_TI_C6X)
|
| 135 |
+
|
| 136 |
+
typedef short opus_int16;
|
| 137 |
+
typedef unsigned short opus_uint16;
|
| 138 |
+
typedef int opus_int32;
|
| 139 |
+
typedef unsigned int opus_uint32;
|
| 140 |
+
|
| 141 |
+
#else
|
| 142 |
+
|
| 143 |
+
/* Give up, take a reasonable guess */
|
| 144 |
+
typedef short opus_int16;
|
| 145 |
+
typedef unsigned short opus_uint16;
|
| 146 |
+
typedef int opus_int32;
|
| 147 |
+
typedef unsigned int opus_uint32;
|
| 148 |
+
|
| 149 |
+
#endif
|
| 150 |
+
|
| 151 |
+
#define opus_int int /* used for counters etc; at least 16 bits */
|
| 152 |
+
#define opus_int64 long long
|
| 153 |
+
#define opus_int8 signed char
|
| 154 |
+
|
| 155 |
+
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
|
| 156 |
+
#define opus_uint64 unsigned long long
|
| 157 |
+
#define opus_uint8 unsigned char
|
| 158 |
+
|
| 159 |
+
#endif /* OPUS_TYPES_H */
|
cpp/toolchains/aarch64-none-linux-gnu.toolchain.cmake
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AX650 / AX630C aarch64 cross-compile toolchain
|
| 2 |
+
#
|
| 3 |
+
# Download:
|
| 4 |
+
# wget https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
|
| 5 |
+
# tar -xf gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz
|
| 6 |
+
#
|
| 7 |
+
# Set TOOLCHAIN_DIR: export TOOLCHAIN_DIR=/path/to/gcc-arm-...
|
| 8 |
+
# or pass: cmake -DTOOLCHAIN_DIR=/path/to/...
|
| 9 |
+
|
| 10 |
+
if(NOT DEFINED TOOLCHAIN_DIR)
|
| 11 |
+
set(TOOLCHAIN_DIR "$ENV{HOME}/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu")
|
| 12 |
+
endif()
|
| 13 |
+
|
| 14 |
+
set(CMAKE_SYSTEM_NAME Linux)
|
| 15 |
+
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
| 16 |
+
|
| 17 |
+
set(CMAKE_C_COMPILER "${TOOLCHAIN_DIR}/bin/aarch64-none-linux-gnu-gcc")
|
| 18 |
+
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_DIR}/bin/aarch64-none-linux-gnu-g++")
|
| 19 |
+
|
| 20 |
+
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
| 21 |
+
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
| 22 |
+
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
cpp/utils/checker.h
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Utility: checker macros
|
| 5 |
+
*
|
| 6 |
+
* Adapted from melotts.axera-main
|
| 7 |
+
**************************************************************************************************/
|
| 8 |
+
|
| 9 |
+
#pragma once
|
| 10 |
+
|
| 11 |
+
#include "utils/logger.h"
|
| 12 |
+
|
| 13 |
+
#define CHECK_PTR(p) \
|
| 14 |
+
do { \
|
| 15 |
+
if (!p) { \
|
| 16 |
+
ALOGE("%s nil pointer\n", #p); \
|
| 17 |
+
return -1; \
|
| 18 |
+
} \
|
| 19 |
+
} while (0)
|
| 20 |
+
|
| 21 |
+
#define CHECK_INITED(p) \
|
| 22 |
+
do { \
|
| 23 |
+
if (!p->HasInit()) { \
|
| 24 |
+
ALOGE("%s has not init\n", #p); \
|
| 25 |
+
return -1; \
|
| 26 |
+
} \
|
| 27 |
+
} while (0)
|
cpp/utils/io.hpp
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Utility: AX engine I/O helpers
|
| 5 |
+
*
|
| 6 |
+
* Adapted from melotts.axera-main
|
| 7 |
+
**************************************************************************************************/
|
| 8 |
+
|
| 9 |
+
#pragma once
|
| 10 |
+
|
| 11 |
+
#include <cstdio>
|
| 12 |
+
#include <cstring>
|
| 13 |
+
#include <vector>
|
| 14 |
+
#include <array>
|
| 15 |
+
#include <string>
|
| 16 |
+
#include <fstream>
|
| 17 |
+
#include <cstdint>
|
| 18 |
+
#include <cstdlib>
|
| 19 |
+
|
| 20 |
+
#include "utils/checker.h"
|
| 21 |
+
#include "ax_sys_api.h"
|
| 22 |
+
#include "ax_engine_type.h"
|
| 23 |
+
|
| 24 |
+
#define IO_CMM_ALIGN_SIZE 128
|
| 25 |
+
|
| 26 |
+
namespace utils {
|
| 27 |
+
|
| 28 |
+
typedef enum {
|
| 29 |
+
IO_BUFFER_STRATEGY_DEFAULT,
|
| 30 |
+
IO_BUFFER_STRATEGY_CACHED
|
| 31 |
+
} IO_BUFFER_STRATEGY_T;
|
| 32 |
+
|
| 33 |
+
static inline void brief_io_info(const std::string& strModel, const AX_ENGINE_IO_INFO_T* io_info) {
|
| 34 |
+
auto describe_shape_type = [](AX_ENGINE_TENSOR_LAYOUT_T type) -> const char* {
|
| 35 |
+
switch (type) {
|
| 36 |
+
case AX_ENGINE_TENSOR_LAYOUT_NHWC: return "NHWC";
|
| 37 |
+
case AX_ENGINE_TENSOR_LAYOUT_NCHW: return "NCHW";
|
| 38 |
+
default: return "unknown";
|
| 39 |
+
}
|
| 40 |
+
};
|
| 41 |
+
auto describe_data_type = [](AX_ENGINE_DATA_TYPE_T type) -> const char* {
|
| 42 |
+
switch (type) {
|
| 43 |
+
case AX_ENGINE_DT_UINT8: return "uint8";
|
| 44 |
+
case AX_ENGINE_DT_UINT16: return "uint16";
|
| 45 |
+
case AX_ENGINE_DT_FLOAT32: return "float32";
|
| 46 |
+
case AX_ENGINE_DT_SINT16: return "sint16";
|
| 47 |
+
case AX_ENGINE_DT_SINT8: return "sint8";
|
| 48 |
+
case AX_ENGINE_DT_SINT32: return "sint32";
|
| 49 |
+
case AX_ENGINE_DT_UINT32: return "uint32";
|
| 50 |
+
case AX_ENGINE_DT_FLOAT64: return "float64";
|
| 51 |
+
default: return "unknown";
|
| 52 |
+
}
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
printf("Model: %s\n", strModel.c_str());
|
| 56 |
+
for (uint32_t i = 0; i < io_info->nInputSize; ++i) {
|
| 57 |
+
auto& input = io_info->pInputs[i];
|
| 58 |
+
printf(" Input[%d]: %s size=%u\n", i, input.pName, input.nSize);
|
| 59 |
+
}
|
| 60 |
+
for (uint32_t i = 0; i < io_info->nOutputSize; ++i) {
|
| 61 |
+
auto& output = io_info->pOutputs[i];
|
| 62 |
+
printf(" Output[%d]: %s size=%u\n", i, output.pName, output.nSize);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
static inline AX_S32 alloc_engine_buffer(const std::string& token, const std::string& appendix,
|
| 67 |
+
size_t index, const AX_ENGINE_IOMETA_T* pMeta,
|
| 68 |
+
AX_ENGINE_IO_BUFFER_T* pBuf,
|
| 69 |
+
IO_BUFFER_STRATEGY_T eStrategy = IO_BUFFER_STRATEGY_DEFAULT) {
|
| 70 |
+
memset(pBuf, 0, sizeof(AX_ENGINE_IO_BUFFER_T));
|
| 71 |
+
pBuf->nSize = pMeta->nSize;
|
| 72 |
+
|
| 73 |
+
const std::string token_name = "zipvoice_" + token + appendix + std::to_string(index);
|
| 74 |
+
|
| 75 |
+
AX_S32 ret;
|
| 76 |
+
if (eStrategy == IO_BUFFER_STRATEGY_CACHED) {
|
| 77 |
+
ret = AX_SYS_MemAllocCached((AX_U64*)&pBuf->phyAddr, &pBuf->pVirAddr,
|
| 78 |
+
pBuf->nSize, IO_CMM_ALIGN_SIZE,
|
| 79 |
+
(const AX_S8*)token_name.c_str());
|
| 80 |
+
} else {
|
| 81 |
+
ret = AX_SYS_MemAlloc((AX_U64*)&pBuf->phyAddr, &pBuf->pVirAddr,
|
| 82 |
+
pBuf->nSize, IO_CMM_ALIGN_SIZE,
|
| 83 |
+
(const AX_S8*)token_name.c_str());
|
| 84 |
+
}
|
| 85 |
+
return ret;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
static inline AX_S32 free_engine_buffer(AX_ENGINE_IO_BUFFER_T* pBuf) {
|
| 89 |
+
if (pBuf->phyAddr == 0) {
|
| 90 |
+
delete[] reinterpret_cast<uint8_t*>(pBuf->pVirAddr);
|
| 91 |
+
} else {
|
| 92 |
+
AX_SYS_MemFree(pBuf->phyAddr, pBuf->pVirAddr);
|
| 93 |
+
}
|
| 94 |
+
pBuf->phyAddr = 0;
|
| 95 |
+
pBuf->pVirAddr = nullptr;
|
| 96 |
+
return 0;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
static inline void free_io_index(AX_ENGINE_IO_BUFFER_T* io_buf, size_t index) {
|
| 100 |
+
free_engine_buffer(io_buf + index);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
static inline void free_io(AX_ENGINE_IO_T &io) {
|
| 104 |
+
for (size_t j = 0; j < io.nInputSize; ++j) {
|
| 105 |
+
free_io_index(io.pInputs, j);
|
| 106 |
+
}
|
| 107 |
+
for (size_t j = 0; j < io.nOutputSize; ++j) {
|
| 108 |
+
free_io_index(io.pOutputs, j);
|
| 109 |
+
}
|
| 110 |
+
delete[] io.pInputs;
|
| 111 |
+
delete[] io.pOutputs;
|
| 112 |
+
io.pInputs = nullptr;
|
| 113 |
+
io.pOutputs = nullptr;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
static inline int prepare_io(const std::string& token, const AX_ENGINE_IO_INFO_T* info,
|
| 117 |
+
AX_ENGINE_IO_T &io, IO_BUFFER_STRATEGY_T strategy) {
|
| 118 |
+
memset(&io, 0, sizeof(io));
|
| 119 |
+
|
| 120 |
+
io.pInputs = new AX_ENGINE_IO_BUFFER_T[info->nInputSize];
|
| 121 |
+
if (!io.pInputs) return -1;
|
| 122 |
+
memset(io.pInputs, 0x00, sizeof(AX_ENGINE_IO_BUFFER_T) * info->nInputSize);
|
| 123 |
+
io.nInputSize = info->nInputSize;
|
| 124 |
+
|
| 125 |
+
for (AX_U32 i = 0; i < info->nInputSize; ++i) {
|
| 126 |
+
auto meta = info->pInputs[i];
|
| 127 |
+
auto buffer = &io.pInputs[i];
|
| 128 |
+
int ret = alloc_engine_buffer(token, "_input_", i, &meta, buffer, strategy);
|
| 129 |
+
if (ret != 0) {
|
| 130 |
+
for (AX_U32 j = 0; j < i; ++j) free_io_index(io.pInputs, j);
|
| 131 |
+
delete[] io.pInputs;
|
| 132 |
+
io.pInputs = nullptr;
|
| 133 |
+
return ret;
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
io.pOutputs = new AX_ENGINE_IO_BUFFER_T[info->nOutputSize];
|
| 138 |
+
if (!io.pOutputs) {
|
| 139 |
+
delete[] io.pInputs;
|
| 140 |
+
io.pInputs = nullptr;
|
| 141 |
+
return -1;
|
| 142 |
+
}
|
| 143 |
+
memset(io.pOutputs, 0x00, sizeof(AX_ENGINE_IO_BUFFER_T) * info->nOutputSize);
|
| 144 |
+
io.nOutputSize = info->nOutputSize;
|
| 145 |
+
|
| 146 |
+
for (size_t i = 0; i < info->nOutputSize; ++i) {
|
| 147 |
+
auto meta = info->pOutputs[i];
|
| 148 |
+
auto buffer = &io.pOutputs[i];
|
| 149 |
+
int ret = alloc_engine_buffer(token, "_output_", i, &meta, buffer, strategy);
|
| 150 |
+
if (ret != 0) {
|
| 151 |
+
for (AX_U32 j = 0; j < info->nInputSize; ++j) free_io_index(io.pInputs, j);
|
| 152 |
+
delete[] io.pInputs;
|
| 153 |
+
io.pInputs = nullptr;
|
| 154 |
+
for (size_t k = 0; k < i; ++k) free_io_index(io.pOutputs, k);
|
| 155 |
+
delete[] io.pOutputs;
|
| 156 |
+
io.pOutputs = nullptr;
|
| 157 |
+
return ret;
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
return 0;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
static inline AX_S32 push_io_input(void* input, int index, AX_ENGINE_IO_T& io) {
|
| 164 |
+
AX_ENGINE_IO_BUFFER_T* pBuf = &io.pInputs[index];
|
| 165 |
+
memcpy(pBuf->pVirAddr, input, pBuf->nSize);
|
| 166 |
+
return 0;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
static inline AX_S32 push_io_output(void* output, int index, AX_ENGINE_IO_T& io) {
|
| 170 |
+
AX_ENGINE_IO_BUFFER_T* pBuf = &io.pOutputs[index];
|
| 171 |
+
memcpy(output, pBuf->pVirAddr, pBuf->nSize);
|
| 172 |
+
return 0;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
static inline bool read_file(const char* path, std::vector<char>& data) {
|
| 176 |
+
std::fstream fs(path, std::ios::in | std::ios::binary);
|
| 177 |
+
if (!fs.is_open()) return false;
|
| 178 |
+
|
| 179 |
+
fs.seekg(std::ios::end);
|
| 180 |
+
auto fs_end = fs.tellg();
|
| 181 |
+
fs.seekg(std::ios::beg);
|
| 182 |
+
auto fs_beg = fs.tellg();
|
| 183 |
+
|
| 184 |
+
auto file_size = static_cast<size_t>(fs_end - fs_beg);
|
| 185 |
+
auto vector_size = data.size();
|
| 186 |
+
data.reserve(vector_size + file_size);
|
| 187 |
+
data.insert(data.end(), std::istreambuf_iterator<char>(fs), std::istreambuf_iterator<char>());
|
| 188 |
+
fs.close();
|
| 189 |
+
return true;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
static inline bool read_file(const char* path, AX_VOID **pModelBufferVirAddr,
|
| 193 |
+
AX_U64 &u64ModelBufferPhyAddr, AX_U32 &nModelBufferSize) {
|
| 194 |
+
std::fstream fs(path, std::ios::in | std::ios::binary);
|
| 195 |
+
if (!fs.is_open()) return false;
|
| 196 |
+
|
| 197 |
+
fs.seekg(0, std::ios::end);
|
| 198 |
+
int file_size = fs.tellg();
|
| 199 |
+
fs.seekg(0, std::ios::beg);
|
| 200 |
+
|
| 201 |
+
nModelBufferSize = (AX_U32)file_size;
|
| 202 |
+
AX_SYS_MemAlloc(&u64ModelBufferPhyAddr, pModelBufferVirAddr, nModelBufferSize,
|
| 203 |
+
0x100, (AX_S8 *)"ZIPVOICE-MODEL");
|
| 204 |
+
|
| 205 |
+
if (!pModelBufferVirAddr || (u64ModelBufferPhyAddr == 0)) return false;
|
| 206 |
+
|
| 207 |
+
fs.read((AX_CHAR *)*pModelBufferVirAddr, nModelBufferSize);
|
| 208 |
+
fs.close();
|
| 209 |
+
return true;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
} // namespace utils
|
cpp/utils/logger.h
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Utility: logger
|
| 5 |
+
*
|
| 6 |
+
* Adapted from melotts.axera-main
|
| 7 |
+
**************************************************************************************************/
|
| 8 |
+
|
| 9 |
+
#pragma once
|
| 10 |
+
|
| 11 |
+
#include <cstdio>
|
| 12 |
+
|
| 13 |
+
#ifdef __cplusplus
|
| 14 |
+
extern "C" {
|
| 15 |
+
#endif
|
| 16 |
+
|
| 17 |
+
typedef enum {
|
| 18 |
+
SKEL_LOG_MIN = -1,
|
| 19 |
+
SKEL_LOG_EMERGENCY = 0,
|
| 20 |
+
SKEL_LOG_ALERT = 1,
|
| 21 |
+
SKEL_LOG_CRITICAL = 2,
|
| 22 |
+
SKEL_LOG_ERROR = 3,
|
| 23 |
+
SKEL_LOG_WARN = 4,
|
| 24 |
+
SKEL_LOG_NOTICE = 5,
|
| 25 |
+
SKEL_LOG_INFO = 6,
|
| 26 |
+
SKEL_LOG_DEBUG = 7,
|
| 27 |
+
SKEL_LOG_MAX
|
| 28 |
+
} SKEL_LOG_LEVEL_E;
|
| 29 |
+
|
| 30 |
+
static SKEL_LOG_LEVEL_E log_level = SKEL_LOG_INFO;
|
| 31 |
+
|
| 32 |
+
#if 1
|
| 33 |
+
#define MACRO_BLACK "\033[1;30;30m"
|
| 34 |
+
#define MACRO_RED "\033[1;30;31m"
|
| 35 |
+
#define MACRO_GREEN "\033[1;30;32m"
|
| 36 |
+
#define MACRO_YELLOW "\033[1;30;33m"
|
| 37 |
+
#define MACRO_BLUE "\033[1;30;34m"
|
| 38 |
+
#define MACRO_PURPLE "\033[1;30;35m"
|
| 39 |
+
#define MACRO_WHITE "\033[1;30;37m"
|
| 40 |
+
#define MACRO_END "\033[0m"
|
| 41 |
+
#else
|
| 42 |
+
#define MACRO_BLACK
|
| 43 |
+
#define MACRO_RED
|
| 44 |
+
#define MACRO_GREEN
|
| 45 |
+
#define MACRO_YELLOW
|
| 46 |
+
#define MACRO_BLUE
|
| 47 |
+
#define MACRO_PURPLE
|
| 48 |
+
#define MACRO_WHITE
|
| 49 |
+
#define MACRO_END
|
| 50 |
+
#endif
|
| 51 |
+
|
| 52 |
+
#define ALOGE(fmt, ...) printf(MACRO_RED "[E][%32s][%4d]: " fmt MACRO_END "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
| 53 |
+
#define ALOGW(fmt, ...) if (log_level >= SKEL_LOG_WARN) \
|
| 54 |
+
printf(MACRO_YELLOW "[W][%32s][%4d]: " fmt MACRO_END "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
| 55 |
+
#define ALOGI(fmt, ...) if (log_level >= SKEL_LOG_INFO) \
|
| 56 |
+
printf(MACRO_GREEN "[I][%32s][%4d]: " fmt MACRO_END "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
| 57 |
+
#define ALOGD(fmt, ...) if (log_level >= SKEL_LOG_DEBUG) \
|
| 58 |
+
printf(MACRO_WHITE "[D][%32s][%4d]: " fmt MACRO_END "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
| 59 |
+
#define ALOGN(fmt, ...) if (log_level >= SKEL_LOG_NOTICE) \
|
| 60 |
+
printf(MACRO_PURPLE "[N][%32s][%4d]: " fmt MACRO_END "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
| 61 |
+
|
| 62 |
+
#ifdef __cplusplus
|
| 63 |
+
}
|
| 64 |
+
#endif
|
cpp/vocoder/vocos_full.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70a7a897f6beb0b6e8536413a0ea6ee2132bec1257e51208e80eca506bda4c40
|
| 3 |
+
size 15413002
|
cpp/zipvoice.cpp
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************************************
|
| 2 |
+
* ZipVoice AXERA C++ Port
|
| 3 |
+
*
|
| 4 |
+
* Main entry: Command-line TTS inference using ZipVoice encoder + split-decoder4
|
| 5 |
+
* axmodels on AXERA NPU boards.
|
| 6 |
+
*
|
| 7 |
+
* Pipeline:
|
| 8 |
+
* 1. Load tokenizer (tokens.txt)
|
| 9 |
+
* 2. Load prompt audio → extract mel filterbank features
|
| 10 |
+
* 3. Tokenize text → build cat_tokens
|
| 11 |
+
* 4. Encoder (encoder.axmodel) → encoded features
|
| 12 |
+
* 5. Duration expand
|
| 13 |
+
* 6. Decoder4 (decoder_part0..3.axmodel) → flow-matching → mel features
|
| 14 |
+
* 7. Save mel features as raw float32 binary (can be decoded with Python vocoder)
|
| 15 |
+
*
|
| 16 |
+
* Usage:
|
| 17 |
+
* ./zipvoice_axera \
|
| 18 |
+
* --model-dir ../models/zipvoice_ax650 \
|
| 19 |
+
* --token-file ../resources/zipvoice_hf/zipvoice/tokens.txt \
|
| 20 |
+
* --prompt-wav ../assets/moss_prompts/zh_1_4p5s.wav \
|
| 21 |
+
* --prompt-text "你好,欢迎使用语音合成系统" \
|
| 22 |
+
* --text "这是要合成的目标文本" \
|
| 23 |
+
* --output-feat output_mel.bin
|
| 24 |
+
*
|
| 25 |
+
* Requirements: AX650 board with axengine SDK
|
| 26 |
+
*
|
| 27 |
+
* Based on ZipVoice.AXERA Python inference and melotts.axera-main C++ patterns.
|
| 28 |
+
**************************************************************************************************/
|
| 29 |
+
|
| 30 |
+
#include <cstdio>
|
| 31 |
+
#include <cstdlib>
|
| 32 |
+
#include <cstring>
|
| 33 |
+
#include <string>
|
| 34 |
+
#include <vector>
|
| 35 |
+
#include <cmath>
|
| 36 |
+
#include <algorithm>
|
| 37 |
+
#include <sys/time.h>
|
| 38 |
+
#include <unistd.h>
|
| 39 |
+
#include <sys/wait.h>
|
| 40 |
+
|
| 41 |
+
#include "ax_sys_api.h"
|
| 42 |
+
#include "src/cmdline.hpp"
|
| 43 |
+
#include "src/EngineWrapper.hpp"
|
| 44 |
+
#include "src/tokenizer.hpp"
|
| 45 |
+
#include "src/fbank.hpp"
|
| 46 |
+
#include "src/zipvoice_engine.hpp"
|
| 47 |
+
#include "src/vocoder.hpp"
|
| 48 |
+
#include "src/wav_writer.hpp"
|
| 49 |
+
|
| 50 |
+
static double get_current_time_ms() {
|
| 51 |
+
struct timeval tv;
|
| 52 |
+
gettimeofday(&tv, nullptr);
|
| 53 |
+
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Persistent Python daemon for fast tokenization (imports jieba/pypinyin once at startup).
|
| 57 |
+
// Communication via pipe: C++ writes commands to daemon's stdin, reads responses from stdout.
|
| 58 |
+
struct PyDaemon {
|
| 59 |
+
int pid = -1;
|
| 60 |
+
FILE* write_pipe = nullptr;
|
| 61 |
+
FILE* read_pipe = nullptr;
|
| 62 |
+
|
| 63 |
+
~PyDaemon() { Stop(); }
|
| 64 |
+
|
| 65 |
+
bool Start(const std::string& repo_dir, const std::string& daemon_script) {
|
| 66 |
+
int pipe_to_cpp[2], pipe_to_py[2];
|
| 67 |
+
if (pipe(pipe_to_cpp) != 0 || pipe(pipe_to_py) != 0) return false;
|
| 68 |
+
|
| 69 |
+
pid = fork();
|
| 70 |
+
if (pid < 0) { close(pipe_to_cpp[0]); close(pipe_to_cpp[1]); close(pipe_to_py[0]); close(pipe_to_py[1]); return false; }
|
| 71 |
+
|
| 72 |
+
if (pid == 0) {
|
| 73 |
+
// Child: Python daemon
|
| 74 |
+
close(pipe_to_cpp[0]); // close read end of cpp-pipe
|
| 75 |
+
close(pipe_to_py[1]); // close write end of py-pipe
|
| 76 |
+
dup2(pipe_to_py[0], STDIN_FILENO);
|
| 77 |
+
dup2(pipe_to_cpp[1], STDOUT_FILENO);
|
| 78 |
+
close(pipe_to_cpp[1]); close(pipe_to_py[0]);
|
| 79 |
+
execlp("python3", "python3", "-u", daemon_script.c_str(), repo_dir.c_str(), nullptr);
|
| 80 |
+
_exit(1);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Parent
|
| 84 |
+
close(pipe_to_cpp[1]); // close write end
|
| 85 |
+
close(pipe_to_py[0]); // close read end
|
| 86 |
+
write_pipe = fdopen(pipe_to_py[1], "w");
|
| 87 |
+
read_pipe = fdopen(pipe_to_cpp[0], "r");
|
| 88 |
+
if (!write_pipe || !read_pipe) { Stop(); return false; }
|
| 89 |
+
setlinebuf(write_pipe);
|
| 90 |
+
|
| 91 |
+
// Wait for READY signal
|
| 92 |
+
char buf[256];
|
| 93 |
+
if (!fgets(buf, sizeof(buf), read_pipe) || strncmp(buf, "READY", 5) != 0) {
|
| 94 |
+
printf("PyDaemon: failed to start (%s)\n", buf ? buf : "no response");
|
| 95 |
+
Stop(); return false;
|
| 96 |
+
}
|
| 97 |
+
printf("PyDaemon: started\n");
|
| 98 |
+
return true;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
void Stop() {
|
| 102 |
+
if (write_pipe) { fprintf(write_pipe, "quit\n"); fflush(write_pipe); fclose(write_pipe); write_pipe = nullptr; }
|
| 103 |
+
if (read_pipe) { fclose(read_pipe); read_pipe = nullptr; }
|
| 104 |
+
if (pid > 0) { waitpid(pid, nullptr, 0); pid = -1; }
|
| 105 |
+
}
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
static int DaemonCountTokens(PyDaemon& py_daemon, const std::string& prompt_file,
|
| 109 |
+
const std::string& text_file, int* prompt_len, int* text_len) {
|
| 110 |
+
if (!py_daemon.write_pipe || !py_daemon.read_pipe) return -1;
|
| 111 |
+
fprintf(py_daemon.write_pipe, "count\t%s\t%s\n", prompt_file.c_str(), text_file.c_str());
|
| 112 |
+
fflush(py_daemon.write_pipe);
|
| 113 |
+
char buf[256];
|
| 114 |
+
if (fgets(buf, sizeof(buf), py_daemon.read_pipe) && sscanf(buf, "COUNT %d %d", prompt_len, text_len) == 2) {
|
| 115 |
+
return 0;
|
| 116 |
+
}
|
| 117 |
+
return -1;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
static int DaemonTokenize(PyDaemon& py_daemon, const std::string& prompt_file,
|
| 121 |
+
const std::string& text_file, int max_tokens,
|
| 122 |
+
const std::string& output_bin, int* prompt_len, int* text_len) {
|
| 123 |
+
if (!py_daemon.write_pipe || !py_daemon.read_pipe) return -1;
|
| 124 |
+
fprintf(py_daemon.write_pipe, "tokenize\t%s\t%s\t%d\t%s\n",
|
| 125 |
+
prompt_file.c_str(), text_file.c_str(), max_tokens, output_bin.c_str());
|
| 126 |
+
fflush(py_daemon.write_pipe);
|
| 127 |
+
char buf[256];
|
| 128 |
+
if (fgets(buf, sizeof(buf), py_daemon.read_pipe)) {
|
| 129 |
+
if (sscanf(buf, "TOKENS %d %d", prompt_len, text_len) == 2) return 0;
|
| 130 |
+
if (strncmp(buf, "ERROR", 5) == 0) {
|
| 131 |
+
printf("PyDaemon: %s", buf);
|
| 132 |
+
return -2;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
return -1;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/**
|
| 139 |
+
* Read float32 WAV file and return samples.
|
| 140 |
+
* Supports 16-bit PCM and 32-bit float WAV files with basic header parsing.
|
| 141 |
+
*/
|
| 142 |
+
static int ReadWavFile(const std::string& path, std::vector<float>& samples, int& sample_rate) {
|
| 143 |
+
std::ifstream file(path, std::ios::binary);
|
| 144 |
+
if (!file.is_open()) {
|
| 145 |
+
printf("Failed to open: %s\n", path.c_str());
|
| 146 |
+
return -1;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Read RIFF header
|
| 150 |
+
char riff[5] = {};
|
| 151 |
+
file.read(riff, 4);
|
| 152 |
+
if (std::strncmp(riff, "RIFF", 4) != 0) {
|
| 153 |
+
printf("Not a valid WAV file: %s\n", path.c_str());
|
| 154 |
+
return -1;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
uint32_t file_size;
|
| 158 |
+
file.read(reinterpret_cast<char*>(&file_size), 4);
|
| 159 |
+
|
| 160 |
+
char wave[5] = {};
|
| 161 |
+
file.read(wave, 4);
|
| 162 |
+
if (std::strncmp(wave, "WAVE", 4) != 0) {
|
| 163 |
+
printf("Not a valid WAV file: %s\n", path.c_str());
|
| 164 |
+
return -1;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Parse chunks
|
| 168 |
+
int num_channels = 1;
|
| 169 |
+
int bits_per_sample = 16;
|
| 170 |
+
sample_rate = 24000;
|
| 171 |
+
uint32_t data_size = 0;
|
| 172 |
+
|
| 173 |
+
while (file.good()) {
|
| 174 |
+
char chunk_id[5] = {};
|
| 175 |
+
file.read(chunk_id, 4);
|
| 176 |
+
uint32_t chunk_size;
|
| 177 |
+
file.read(reinterpret_cast<char*>(&chunk_size), 4);
|
| 178 |
+
|
| 179 |
+
if (std::strncmp(chunk_id, "fmt ", 4) == 0) {
|
| 180 |
+
uint16_t audio_format, num_ch, bps;
|
| 181 |
+
uint32_t sr, byte_rate;
|
| 182 |
+
uint16_t block_align;
|
| 183 |
+
|
| 184 |
+
file.read(reinterpret_cast<char*>(&audio_format), 2);
|
| 185 |
+
file.read(reinterpret_cast<char*>(&num_ch), 2);
|
| 186 |
+
file.read(reinterpret_cast<char*>(&sr), 4);
|
| 187 |
+
file.read(reinterpret_cast<char*>(&byte_rate), 4);
|
| 188 |
+
file.read(reinterpret_cast<char*>(&block_align), 2);
|
| 189 |
+
file.read(reinterpret_cast<char*>(&bps), 2);
|
| 190 |
+
|
| 191 |
+
num_channels = num_ch;
|
| 192 |
+
sample_rate = sr;
|
| 193 |
+
bits_per_sample = bps;
|
| 194 |
+
|
| 195 |
+
// Skip remaining fmt bytes
|
| 196 |
+
if (chunk_size > 16) {
|
| 197 |
+
file.seekg(chunk_size - 16, std::ios::cur);
|
| 198 |
+
}
|
| 199 |
+
} else if (std::strncmp(chunk_id, "data", 4) == 0) {
|
| 200 |
+
data_size = chunk_size;
|
| 201 |
+
break;
|
| 202 |
+
} else {
|
| 203 |
+
// Skip unknown chunk
|
| 204 |
+
file.seekg(chunk_size, std::ios::cur);
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
if (data_size == 0) {
|
| 209 |
+
printf("No data chunk found in WAV: %s\n", path.c_str());
|
| 210 |
+
return -1;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// Read audio data
|
| 214 |
+
int num_samples = data_size / (bits_per_sample / 8) / num_channels;
|
| 215 |
+
|
| 216 |
+
if (bits_per_sample == 16) {
|
| 217 |
+
std::vector<int16_t> raw(num_samples * num_channels);
|
| 218 |
+
file.read(reinterpret_cast<char*>(raw.data()), data_size);
|
| 219 |
+
|
| 220 |
+
samples.resize(num_samples);
|
| 221 |
+
for (int i = 0; i < num_samples; ++i) {
|
| 222 |
+
// Take first channel only
|
| 223 |
+
samples[i] = raw[i * num_channels] / 32768.0f;
|
| 224 |
+
}
|
| 225 |
+
} else if (bits_per_sample == 32) {
|
| 226 |
+
// Assume float32
|
| 227 |
+
samples.resize(num_samples * num_channels);
|
| 228 |
+
file.read(reinterpret_cast<char*>(samples.data()), data_size);
|
| 229 |
+
|
| 230 |
+
// Extract first channel
|
| 231 |
+
std::vector<float> mono(num_samples);
|
| 232 |
+
for (int i = 0; i < num_samples; ++i) {
|
| 233 |
+
mono[i] = samples[i * num_channels];
|
| 234 |
+
}
|
| 235 |
+
samples = std::move(mono);
|
| 236 |
+
} else {
|
| 237 |
+
printf("Unsupported bit depth: %d\n", bits_per_sample);
|
| 238 |
+
return -1;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
printf("Read WAV: %s, sr=%d, ch=%d, samples=%d\n",
|
| 242 |
+
path.c_str(), sample_rate, num_channels, num_samples);
|
| 243 |
+
return 0;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
/**
|
| 247 |
+
* Simple linear resampling.
|
| 248 |
+
*/
|
| 249 |
+
static std::vector<float> ResampleLinear(const std::vector<float>& samples,
|
| 250 |
+
int orig_sr, int target_sr) {
|
| 251 |
+
if (orig_sr == target_sr) return samples;
|
| 252 |
+
|
| 253 |
+
int old_len = static_cast<int>(samples.size());
|
| 254 |
+
int new_len = std::max(1, static_cast<int>(
|
| 255 |
+
std::round(static_cast<double>(old_len) * target_sr / orig_sr)));
|
| 256 |
+
|
| 257 |
+
std::vector<float> result(new_len);
|
| 258 |
+
for (int i = 0; i < new_len; ++i) {
|
| 259 |
+
double pos = static_cast<double>(i) * (old_len - 1) / (new_len - 1);
|
| 260 |
+
int idx = static_cast<int>(pos);
|
| 261 |
+
double frac = pos - idx;
|
| 262 |
+
|
| 263 |
+
if (idx + 1 < old_len) {
|
| 264 |
+
result[i] = static_cast<float>(
|
| 265 |
+
samples[idx] * (1.0 - frac) + samples[idx + 1] * frac);
|
| 266 |
+
} else {
|
| 267 |
+
result[i] = samples[old_len - 1];
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
return result;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
/**
|
| 274 |
+
* Compute RMS of audio samples.
|
| 275 |
+
*/
|
| 276 |
+
static float ComputeRms(const std::vector<float>& samples) {
|
| 277 |
+
if (samples.empty()) return 0.0f;
|
| 278 |
+
float sum_sq = 0.0f;
|
| 279 |
+
for (float s : samples) sum_sq += s * s;
|
| 280 |
+
return std::sqrt(sum_sq / samples.size());
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
/**
|
| 284 |
+
* RMS normalize audio.
|
| 285 |
+
*/
|
| 286 |
+
static void RmsNormalize(std::vector<float>& samples, float target_rms) {
|
| 287 |
+
float rms = ComputeRms(samples);
|
| 288 |
+
if (rms < target_rms && rms > 1e-10f) {
|
| 289 |
+
float gain = target_rms / rms;
|
| 290 |
+
for (float& s : samples) s *= gain;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
static bool IsUtf8Lead(unsigned char c) {
|
| 295 |
+
return (c & 0xC0) != 0x80;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
static std::vector<std::string> SplitUtf8Chars(const std::string& text) {
|
| 299 |
+
std::vector<std::string> chars;
|
| 300 |
+
for (size_t i = 0; i < text.size();) {
|
| 301 |
+
unsigned char c = (unsigned char)text[i];
|
| 302 |
+
size_t len = 1;
|
| 303 |
+
if ((c & 0x80) == 0) len = 1;
|
| 304 |
+
else if ((c & 0xE0) == 0xC0) len = 2;
|
| 305 |
+
else if ((c & 0xF0) == 0xE0) len = 3;
|
| 306 |
+
else if ((c & 0xF8) == 0xF0) len = 4;
|
| 307 |
+
chars.push_back(text.substr(i, len));
|
| 308 |
+
i += len;
|
| 309 |
+
}
|
| 310 |
+
return chars;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
static bool IsChineseUtf8Char(const std::string& ch) {
|
| 314 |
+
if (ch.size() != 3) return false;
|
| 315 |
+
unsigned char b0 = (unsigned char)ch[0];
|
| 316 |
+
unsigned char b1 = (unsigned char)ch[1];
|
| 317 |
+
unsigned char b2 = (unsigned char)ch[2];
|
| 318 |
+
uint32_t cp = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F);
|
| 319 |
+
return cp >= 0x4E00 && cp <= 0x9FFF;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
static bool IsSplitPunct(const std::string& ch) {
|
| 323 |
+
static const char* puncts[] = {
|
| 324 |
+
".", "!", "?", ";", ",", ":",
|
| 325 |
+
"。", "!", "?", ";", ",", "、", ":"
|
| 326 |
+
};
|
| 327 |
+
for (auto* p : puncts) if (ch == p) return true;
|
| 328 |
+
return false;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
static std::string TrimAsciiSpaces(const std::string& s) {
|
| 332 |
+
size_t start = 0, end = s.size();
|
| 333 |
+
while (start < end && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r')) start++;
|
| 334 |
+
while (end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n' || s[end-1] == '\r')) end--;
|
| 335 |
+
return s.substr(start, end - start);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
static std::string JoinUnits(const std::string& left, const std::string& right) {
|
| 339 |
+
if (left.empty()) return TrimAsciiSpaces(right);
|
| 340 |
+
std::string r = TrimAsciiSpaces(right);
|
| 341 |
+
if (r.empty()) return TrimAsciiSpaces(left);
|
| 342 |
+
auto left_chars = SplitUtf8Chars(left);
|
| 343 |
+
auto right_chars = SplitUtf8Chars(r);
|
| 344 |
+
bool zh_boundary = (!left_chars.empty() && IsChineseUtf8Char(left_chars.back())) ||
|
| 345 |
+
(!right_chars.empty() && IsChineseUtf8Char(right_chars.front()));
|
| 346 |
+
return zh_boundary ? (TrimAsciiSpaces(left) + r) : (TrimAsciiSpaces(left) + " " + r);
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
static std::vector<std::string> SplitUnitsCpp(const std::string& text) {
|
| 350 |
+
std::vector<std::string> units;
|
| 351 |
+
auto chars = SplitUtf8Chars(TrimAsciiSpaces(text));
|
| 352 |
+
std::string current;
|
| 353 |
+
for (const auto& ch : chars) {
|
| 354 |
+
current += ch;
|
| 355 |
+
if (IsSplitPunct(ch)) {
|
| 356 |
+
std::string t = TrimAsciiSpaces(current);
|
| 357 |
+
if (!t.empty()) units.push_back(t);
|
| 358 |
+
current.clear();
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
current = TrimAsciiSpaces(current);
|
| 362 |
+
if (!current.empty()) units.push_back(current);
|
| 363 |
+
if (units.empty() && !text.empty()) units.push_back(TrimAsciiSpaces(text));
|
| 364 |
+
return units;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
static int TokenCountCpp(Tokenizer& tokenizer, const std::string& text) {
|
| 368 |
+
return (int)tokenizer.TextToTokenIds(text).size();
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
struct SegmentInfoCpp {
|
| 372 |
+
std::string text;
|
| 373 |
+
int text_tokens = 0;
|
| 374 |
+
int raw_features_len = 0;
|
| 375 |
+
int features_len = 0;
|
| 376 |
+
int generated_frames = 0;
|
| 377 |
+
};
|
| 378 |
+
|
| 379 |
+
static std::vector<std::string> SplitLongUnitCpp(Tokenizer& tokenizer, const std::string& unit, int max_text_tokens) {
|
| 380 |
+
if (TokenCountCpp(tokenizer, unit) <= max_text_tokens) return {unit};
|
| 381 |
+
|
| 382 |
+
std::vector<std::string> chunks;
|
| 383 |
+
if (unit.find(' ') != std::string::npos) {
|
| 384 |
+
std::stringstream ss(unit);
|
| 385 |
+
std::string piece, current;
|
| 386 |
+
while (ss >> piece) {
|
| 387 |
+
std::string candidate = JoinUnits(current, piece);
|
| 388 |
+
if (!current.empty() && TokenCountCpp(tokenizer, candidate) > max_text_tokens) {
|
| 389 |
+
chunks.push_back(current);
|
| 390 |
+
current = piece;
|
| 391 |
+
} else {
|
| 392 |
+
current = candidate;
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
if (!current.empty()) chunks.push_back(current);
|
| 396 |
+
return chunks;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
auto chars = SplitUtf8Chars(unit);
|
| 400 |
+
std::string current;
|
| 401 |
+
for (const auto& ch : chars) {
|
| 402 |
+
std::string candidate = current + ch;
|
| 403 |
+
if (!current.empty() && TokenCountCpp(tokenizer, candidate) > max_text_tokens) {
|
| 404 |
+
chunks.push_back(current);
|
| 405 |
+
current = ch;
|
| 406 |
+
} else {
|
| 407 |
+
current = candidate;
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
if (!current.empty()) chunks.push_back(current);
|
| 411 |
+
return chunks;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
static SegmentInfoCpp EstimateSegmentCpp(Tokenizer& tokenizer, const std::string& text,
|
| 415 |
+
int prompt_frames, int prompt_tokens_len,
|
| 416 |
+
float speed, int max_feat_len) {
|
| 417 |
+
SegmentInfoCpp s;
|
| 418 |
+
s.text = text;
|
| 419 |
+
s.text_tokens = TokenCountCpp(tokenizer, text);
|
| 420 |
+
s.raw_features_len = (int)std::ceil((double)prompt_frames / prompt_tokens_len * (prompt_tokens_len + s.text_tokens) / speed);
|
| 421 |
+
s.features_len = std::min(s.raw_features_len, max_feat_len);
|
| 422 |
+
s.generated_frames = s.features_len - prompt_frames;
|
| 423 |
+
if (s.generated_frames <= 0) s.generated_frames = s.features_len;
|
| 424 |
+
return s;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
static std::vector<SegmentInfoCpp> BuildSegmentsCpp(Tokenizer& tokenizer, const std::string& text,
|
| 428 |
+
int prompt_frames, int prompt_tokens_len,
|
| 429 |
+
float speed, int max_feat_len,
|
| 430 |
+
int max_text_tokens, int min_generated_frames,
|
| 431 |
+
int max_generated_frames, double max_raw_feat_ratio) {
|
| 432 |
+
auto raw_units = SplitUnitsCpp(text);
|
| 433 |
+
std::vector<std::string> units;
|
| 434 |
+
for (const auto& u : raw_units) {
|
| 435 |
+
auto split = SplitLongUnitCpp(tokenizer, u, max_text_tokens);
|
| 436 |
+
units.insert(units.end(), split.begin(), split.end());
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
std::vector<SegmentInfoCpp> segments;
|
| 440 |
+
std::string current;
|
| 441 |
+
for (const auto& unit : units) {
|
| 442 |
+
std::string candidate = JoinUnits(current, unit);
|
| 443 |
+
auto cand = EstimateSegmentCpp(tokenizer, candidate, prompt_frames, prompt_tokens_len, speed, max_feat_len);
|
| 444 |
+
bool raw_too_long = cand.raw_features_len > (int)(max_feat_len * max_raw_feat_ratio);
|
| 445 |
+
bool too_long = cand.text_tokens > max_text_tokens || cand.generated_frames > max_generated_frames || raw_too_long;
|
| 446 |
+
if (!current.empty() && too_long) {
|
| 447 |
+
segments.push_back(EstimateSegmentCpp(tokenizer, current, prompt_frames, prompt_tokens_len, speed, max_feat_len));
|
| 448 |
+
current = unit;
|
| 449 |
+
} else {
|
| 450 |
+
current = candidate;
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
if (!current.empty()) segments.push_back(EstimateSegmentCpp(tokenizer, current, prompt_frames, prompt_tokens_len, speed, max_feat_len));
|
| 454 |
+
|
| 455 |
+
if (segments.size() >= 2 && segments.back().generated_frames < min_generated_frames) {
|
| 456 |
+
std::string merged_text = JoinUnits(segments[segments.size()-2].text, segments.back().text);
|
| 457 |
+
auto merged = EstimateSegmentCpp(tokenizer, merged_text, prompt_frames, prompt_tokens_len, speed, max_feat_len);
|
| 458 |
+
bool raw_ok = merged.raw_features_len <= (int)(max_feat_len * max_raw_feat_ratio);
|
| 459 |
+
if (merged.text_tokens <= max_text_tokens && merged.generated_frames <= max_generated_frames && raw_ok) {
|
| 460 |
+
segments[segments.size()-2] = merged;
|
| 461 |
+
segments.pop_back();
|
| 462 |
+
}
|
| 463 |
+
}
|
| 464 |
+
return segments;
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
static std::vector<std::string> SplitLongUnitCppDaemon(PyDaemon& py_daemon,
|
| 468 |
+
const std::string& prompt_text,
|
| 469 |
+
const std::string& unit,
|
| 470 |
+
int max_text_tokens) {
|
| 471 |
+
std::string tmp_prompt = "/tmp/zipvoice_prompt_count.txt";
|
| 472 |
+
std::string tmp_text = "/tmp/zipvoice_text_count.txt";
|
| 473 |
+
{
|
| 474 |
+
std::ofstream pf(tmp_prompt); pf << prompt_text; pf.close();
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
auto count_for = [&](const std::string& txt) -> int {
|
| 478 |
+
std::ofstream tf(tmp_text); tf << txt; tf.close();
|
| 479 |
+
int p = 0, t = 0;
|
| 480 |
+
if (DaemonCountTokens(py_daemon, tmp_prompt, tmp_text, &p, &t) != 0) return 1000000;
|
| 481 |
+
return t;
|
| 482 |
+
};
|
| 483 |
+
|
| 484 |
+
if (count_for(unit) <= max_text_tokens) return {unit};
|
| 485 |
+
|
| 486 |
+
std::vector<std::string> chunks;
|
| 487 |
+
if (unit.find(' ') != std::string::npos) {
|
| 488 |
+
std::stringstream ss(unit);
|
| 489 |
+
std::string piece, current;
|
| 490 |
+
while (ss >> piece) {
|
| 491 |
+
std::string candidate = JoinUnits(current, piece);
|
| 492 |
+
if (!current.empty() && count_for(candidate) > max_text_tokens) {
|
| 493 |
+
chunks.push_back(current);
|
| 494 |
+
current = piece;
|
| 495 |
+
} else {
|
| 496 |
+
current = candidate;
|
| 497 |
+
}
|
| 498 |
+
}
|
| 499 |
+
if (!current.empty()) chunks.push_back(current);
|
| 500 |
+
return chunks;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
auto chars = SplitUtf8Chars(unit);
|
| 504 |
+
std::string current;
|
| 505 |
+
for (const auto& ch : chars) {
|
| 506 |
+
std::string candidate = current + ch;
|
| 507 |
+
if (!current.empty() && count_for(candidate) > max_text_tokens) {
|
| 508 |
+
chunks.push_back(current);
|
| 509 |
+
current = ch;
|
| 510 |
+
} else {
|
| 511 |
+
current = candidate;
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
if (!current.empty()) chunks.push_back(current);
|
| 515 |
+
return chunks;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
static std::vector<SegmentInfoCpp> BuildSegmentsCppDaemon(PyDaemon& py_daemon,
|
| 519 |
+
const std::string& prompt_text,
|
| 520 |
+
const std::string& text,
|
| 521 |
+
int prompt_frames,
|
| 522 |
+
int prompt_tokens_len,
|
| 523 |
+
float speed,
|
| 524 |
+
int max_feat_len,
|
| 525 |
+
int max_text_tokens,
|
| 526 |
+
int min_generated_frames,
|
| 527 |
+
int max_generated_frames,
|
| 528 |
+
double max_raw_feat_ratio) {
|
| 529 |
+
std::string tmp_prompt = "/tmp/zipvoice_prompt_count.txt";
|
| 530 |
+
std::string tmp_text = "/tmp/zipvoice_text_count.txt";
|
| 531 |
+
{
|
| 532 |
+
std::ofstream pf(tmp_prompt); pf << prompt_text; pf.close();
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
auto token_count_daemon = [&](const std::string& txt) -> int {
|
| 536 |
+
std::ofstream tf(tmp_text); tf << txt; tf.close();
|
| 537 |
+
int p = 0, t = 0;
|
| 538 |
+
if (DaemonCountTokens(py_daemon, tmp_prompt, tmp_text, &p, &t) != 0) return 1000000;
|
| 539 |
+
return t;
|
| 540 |
+
};
|
| 541 |
+
auto estimate_segment = [&](const std::string& seg_text) -> SegmentInfoCpp {
|
| 542 |
+
SegmentInfoCpp s;
|
| 543 |
+
s.text = seg_text;
|
| 544 |
+
s.text_tokens = token_count_daemon(seg_text);
|
| 545 |
+
s.raw_features_len = (int)std::ceil((double)prompt_frames / prompt_tokens_len * (prompt_tokens_len + s.text_tokens) / speed);
|
| 546 |
+
s.features_len = std::min(s.raw_features_len, max_feat_len);
|
| 547 |
+
s.generated_frames = s.features_len - prompt_frames;
|
| 548 |
+
if (s.generated_frames <= 0) s.generated_frames = s.features_len;
|
| 549 |
+
return s;
|
| 550 |
+
};
|
| 551 |
+
|
| 552 |
+
auto raw_units = SplitUnitsCpp(text);
|
| 553 |
+
std::vector<std::string> units;
|
| 554 |
+
for (const auto& u : raw_units) {
|
| 555 |
+
auto split = SplitLongUnitCppDaemon(py_daemon, prompt_text, u, max_text_tokens);
|
| 556 |
+
units.insert(units.end(), split.begin(), split.end());
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
std::vector<SegmentInfoCpp> segments;
|
| 560 |
+
std::string current;
|
| 561 |
+
for (const auto& unit : units) {
|
| 562 |
+
std::string candidate = JoinUnits(current, unit);
|
| 563 |
+
auto cand = estimate_segment(candidate);
|
| 564 |
+
bool raw_too_long = cand.raw_features_len > (int)(max_feat_len * max_raw_feat_ratio);
|
| 565 |
+
bool too_long = cand.text_tokens > max_text_tokens || cand.generated_frames > max_generated_frames || raw_too_long;
|
| 566 |
+
if (!current.empty() && too_long) {
|
| 567 |
+
segments.push_back(estimate_segment(current));
|
| 568 |
+
current = unit;
|
| 569 |
+
} else {
|
| 570 |
+
current = candidate;
|
| 571 |
+
}
|
| 572 |
+
}
|
| 573 |
+
if (!current.empty()) segments.push_back(estimate_segment(current));
|
| 574 |
+
|
| 575 |
+
if (segments.size() >= 2 && segments.back().generated_frames < min_generated_frames) {
|
| 576 |
+
std::string merged_text = JoinUnits(segments[segments.size()-2].text, segments.back().text);
|
| 577 |
+
auto merged = estimate_segment(merged_text);
|
| 578 |
+
bool raw_ok = merged.raw_features_len <= (int)(max_feat_len * max_raw_feat_ratio);
|
| 579 |
+
if (merged.text_tokens <= max_text_tokens && merged.generated_frames <= max_generated_frames && raw_ok) {
|
| 580 |
+
segments[segments.size()-2] = merged;
|
| 581 |
+
segments.pop_back();
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
return segments;
|
| 585 |
+
}
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
/**
|
| 589 |
+
* Save features as raw float32 binary (compatible with numpy .fromfile).
|
| 590 |
+
*/
|
| 591 |
+
static bool SaveFeaturesBin(const std::string& path,
|
| 592 |
+
const std::vector<float>& features,
|
| 593 |
+
int num_frames, int feat_dim) {
|
| 594 |
+
std::ofstream file(path, std::ios::binary);
|
| 595 |
+
if (!file.is_open()) return false;
|
| 596 |
+
file.write(reinterpret_cast<const char*>(features.data()),
|
| 597 |
+
num_frames * feat_dim * sizeof(float));
|
| 598 |
+
file.close();
|
| 599 |
+
printf("Saved features: %s [%d, %d]\n", path.c_str(), num_frames, feat_dim);
|
| 600 |
+
return true;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
int main(int argc, char** argv) {
|
| 604 |
+
// --- Command line parsing ---
|
| 605 |
+
cmdline::parser cmd;
|
| 606 |
+
|
| 607 |
+
cmd.add<std::string>("model-dir", 'm', "Model directory containing axmodels and configs",
|
| 608 |
+
true, "");
|
| 609 |
+
cmd.add<std::string>("token-file", 't', "Path to tokens.txt",
|
| 610 |
+
false, "");
|
| 611 |
+
cmd.add<std::string>("prompt-wav", 'w', "Prompt audio WAV file", true, "");
|
| 612 |
+
cmd.add<std::string>("prompt-text", 'p', "Prompt text (for tokenization; optional with --cat-tokens-file)", false, "");
|
| 613 |
+
cmd.add<std::string>("text", 's', "Text to synthesize", false, "");
|
| 614 |
+
cmd.add<std::string>("text-file", 'f', "UTF-8 text file to synthesize", false, "");
|
| 615 |
+
cmd.add<std::string>("output-wav", 'o', "Output WAV file path",
|
| 616 |
+
false, "output.wav");
|
| 617 |
+
cmd.add<std::string>("output-feat", 0, "Output mel features as raw float32 binary (optional)",
|
| 618 |
+
false, "");
|
| 619 |
+
cmd.add<std::string>("repo-dir", 0, "Repo root dir. When set, auto-decode mel to WAV via Python vocoder (deprecated, use --vocoder-model).",
|
| 620 |
+
false, "");
|
| 621 |
+
cmd.add<std::string>("vocoder-model", 0, "Path to vocos_full.axmodel for C++ vocoder. When set, all-C++ pipeline, no Python.",
|
| 622 |
+
false, "");
|
| 623 |
+
cmd.add<std::string>("cat-tokens-file", 0, "Pre-computed cat_tokens int32 binary (from export_tokens.py). "
|
| 624 |
+
"When set, skips built-in tokenizer.",
|
| 625 |
+
false, "");
|
| 626 |
+
cmd.add<int>("prompt-tokens-len", 0, "Number of prompt tokens (required with --cat-tokens-file)", false, 0);
|
| 627 |
+
cmd.add<int>("text-tokens-len", 0, "Number of text tokens (required with --cat-tokens-file)", false, 0);
|
| 628 |
+
|
| 629 |
+
cmd.add<int>("max-tokens", 0, "Max token sequence length", false, 384);
|
| 630 |
+
cmd.add<int>("max-feat-len", 0, "Max feature sequence length", false, 1024);
|
| 631 |
+
cmd.add<int>("num-step", 0, "Number of flow-matching steps", false, 10);
|
| 632 |
+
cmd.add<float>("speed", 0, "Speech speed factor", false, 1.0f);
|
| 633 |
+
cmd.add<float>("guidance-scale", 0, "CFG guidance scale", false, 1.0f);
|
| 634 |
+
cmd.add<float>("t-shift", 0, "Time shift for flow scheduler", false, 0.5f);
|
| 635 |
+
cmd.add<float>("feat-scale", 0, "Feature scaling factor", false, 0.1f);
|
| 636 |
+
cmd.add<float>("target-rms", 0, "Target RMS for audio normalization", false, 0.1f);
|
| 637 |
+
cmd.add<int>("seed", 0, "Random seed", false, 42);
|
| 638 |
+
cmd.add<int>("min-generated-frames", 0, "Minimum generated frames per segment",
|
| 639 |
+
false, 360);
|
| 640 |
+
cmd.add<int>("max-generated-frames", 0, "Maximum generated frames per segment",
|
| 641 |
+
false, 620);
|
| 642 |
+
|
| 643 |
+
cmd.parse_check(argc, argv);
|
| 644 |
+
|
| 645 |
+
auto model_dir = cmd.get<std::string>("model-dir");
|
| 646 |
+
auto token_file = cmd.get<std::string>("token-file");
|
| 647 |
+
auto prompt_wav = cmd.get<std::string>("prompt-wav");
|
| 648 |
+
auto prompt_text = cmd.get<std::string>("prompt-text");
|
| 649 |
+
auto text = cmd.get<std::string>("text");
|
| 650 |
+
auto text_file = cmd.get<std::string>("text-file");
|
| 651 |
+
auto output_wav = cmd.get<std::string>("output-wav");
|
| 652 |
+
auto output_feat = cmd.get<std::string>("output-feat");
|
| 653 |
+
auto repo_dir = cmd.get<std::string>("repo-dir");
|
| 654 |
+
auto vocoder_model = cmd.get<std::string>("vocoder-model");
|
| 655 |
+
auto cat_tokens_file = cmd.get<std::string>("cat-tokens-file");
|
| 656 |
+
int prompt_tokens_len_cmd = cmd.get<int>("prompt-tokens-len");
|
| 657 |
+
int text_tokens_len_cmd = cmd.get<int>("text-tokens-len");
|
| 658 |
+
|
| 659 |
+
int max_tokens = cmd.get<int>("max-tokens");
|
| 660 |
+
int max_feat_len = cmd.get<int>("max-feat-len");
|
| 661 |
+
int num_step = cmd.get<int>("num-step");
|
| 662 |
+
float speed = cmd.get<float>("speed");
|
| 663 |
+
float guidance_scale = cmd.get<float>("guidance-scale");
|
| 664 |
+
float t_shift = cmd.get<float>("t-shift");
|
| 665 |
+
float feat_scale = cmd.get<float>("feat-scale");
|
| 666 |
+
float target_rms = cmd.get<float>("target-rms");
|
| 667 |
+
int seed = cmd.get<int>("seed");
|
| 668 |
+
|
| 669 |
+
// Load target text (not required when using pre-computed tokens)
|
| 670 |
+
std::string target_text = "(from cat-tokens-file)";
|
| 671 |
+
if (!cat_tokens_file.empty()) {
|
| 672 |
+
// Text not needed; pre-computed tokens are used
|
| 673 |
+
} else if (!text.empty()) {
|
| 674 |
+
target_text = text;
|
| 675 |
+
} else if (!text_file.empty()) {
|
| 676 |
+
std::ifstream file(text_file);
|
| 677 |
+
if (!file.is_open()) {
|
| 678 |
+
printf("Failed to open text file: %s\n", text_file.c_str());
|
| 679 |
+
return -1;
|
| 680 |
+
}
|
| 681 |
+
std::stringstream ss;
|
| 682 |
+
ss << file.rdbuf();
|
| 683 |
+
target_text = ss.str();
|
| 684 |
+
} else {
|
| 685 |
+
printf("ERROR: Either --text, --text-file, or --cat-tokens-file is required\n");
|
| 686 |
+
return -1;
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
// --- Load tokenizer first (needed for Python-aligned segmentation) ---
|
| 690 |
+
double t_tokenizer = get_current_time_ms();
|
| 691 |
+
Tokenizer tokenizer;
|
| 692 |
+
if (!token_file.empty()) {
|
| 693 |
+
if (tokenizer.Load(token_file) != 0) {
|
| 694 |
+
printf("Failed to load tokenizer\n");
|
| 695 |
+
return -1;
|
| 696 |
+
}
|
| 697 |
+
printf("Tokenizer load: %.0f ms\n", get_current_time_ms() - t_tokenizer);
|
| 698 |
+
} else {
|
| 699 |
+
printf("WARNING: No token file provided. Token IDs must be pre-computed.\n");
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
// Normalize whitespace only (Python load_text behavior)
|
| 703 |
+
{
|
| 704 |
+
std::string normalized;
|
| 705 |
+
bool last_was_space = false;
|
| 706 |
+
for (char c : target_text) {
|
| 707 |
+
if (c == '\n' || c == '\r' || c == '\t') c = ' ';
|
| 708 |
+
if (c == ' ') { if (!last_was_space) normalized += c; last_was_space = true; }
|
| 709 |
+
else { normalized += c; last_was_space = false; }
|
| 710 |
+
}
|
| 711 |
+
while (!normalized.empty() && normalized.back() == ' ') normalized.pop_back();
|
| 712 |
+
target_text = normalized;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
// --- Start Python daemon early (needed for English long-text segmentation) ---
|
| 716 |
+
PyDaemon py_daemon;
|
| 717 |
+
if (!repo_dir.empty()) {
|
| 718 |
+
std::string daemon_path = repo_dir + "/cpp/scripts/py_daemon.py";
|
| 719 |
+
py_daemon.Start(repo_dir, daemon_path);
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
// Python-aligned long-text segmentation (text_processing.build_segments)
|
| 723 |
+
std::vector<std::string> sentences;
|
| 724 |
+
{
|
| 725 |
+
int prompt_tokens_len_actual = 0;
|
| 726 |
+
if (!repo_dir.empty()) {
|
| 727 |
+
// English: get true prompt token length from daemon
|
| 728 |
+
std::string tmp_prompt = "/tmp/zipvoice_prompt_seg.txt";
|
| 729 |
+
std::string tmp_dummy = "/tmp/zipvoice_dummy_seg.txt";
|
| 730 |
+
{ std::ofstream pf(tmp_prompt); pf << prompt_text; pf.close(); }
|
| 731 |
+
{ std::ofstream df(tmp_dummy); df << "x"; df.close(); }
|
| 732 |
+
int p = 0, t = 0;
|
| 733 |
+
if (DaemonCountTokens(py_daemon, tmp_prompt, tmp_dummy, &p, &t) != 0) {
|
| 734 |
+
printf("ERROR: failed to count prompt tokens via daemon\n");
|
| 735 |
+
return -1;
|
| 736 |
+
}
|
| 737 |
+
prompt_tokens_len_actual = p;
|
| 738 |
+
} else {
|
| 739 |
+
// Chinese: C++ tokenizer
|
| 740 |
+
prompt_tokens_len_actual = TokenCountCpp(tokenizer, prompt_text);
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
int max_text_tokens = max_tokens - prompt_tokens_len_actual - 1;
|
| 744 |
+
int min_generated_frames = 360;
|
| 745 |
+
int max_generated_frames = 620;
|
| 746 |
+
double max_raw_feat_ratio = 1.2;
|
| 747 |
+
if (!repo_dir.empty()) {
|
| 748 |
+
// English path: use Python daemon token counts inside build_segments
|
| 749 |
+
auto segments = BuildSegmentsCppDaemon(py_daemon, prompt_text, target_text,
|
| 750 |
+
422, prompt_tokens_len_actual,
|
| 751 |
+
speed, max_feat_len,
|
| 752 |
+
max_text_tokens,
|
| 753 |
+
min_generated_frames,
|
| 754 |
+
max_generated_frames,
|
| 755 |
+
max_raw_feat_ratio);
|
| 756 |
+
for (const auto& seg : segments) sentences.push_back(seg.text);
|
| 757 |
+
} else {
|
| 758 |
+
// Chinese path: use C++ tokenizer token counts
|
| 759 |
+
auto segments = BuildSegmentsCpp(tokenizer, target_text,
|
| 760 |
+
422, prompt_tokens_len_actual,
|
| 761 |
+
speed, max_feat_len,
|
| 762 |
+
max_text_tokens,
|
| 763 |
+
min_generated_frames,
|
| 764 |
+
max_generated_frames,
|
| 765 |
+
max_raw_feat_ratio);
|
| 766 |
+
for (const auto& seg : segments) sentences.push_back(seg.text);
|
| 767 |
+
}
|
| 768 |
+
}
|
| 769 |
+
|
| 770 |
+
printf("After build_segments: %zu segments\n", sentences.size());
|
| 771 |
+
|
| 772 |
+
printf("========================================\n");
|
| 773 |
+
printf("ZipVoice AXERA C++ Inference\n");
|
| 774 |
+
printf("========================================\n");
|
| 775 |
+
printf("model-dir: %s\n", model_dir.c_str());
|
| 776 |
+
printf("token-file: %s\n", token_file.c_str());
|
| 777 |
+
printf("prompt-wav: %s\n", prompt_wav.c_str());
|
| 778 |
+
printf("prompt-text: %s\n", prompt_text.c_str());
|
| 779 |
+
printf("target-text: %s\n", target_text.c_str());
|
| 780 |
+
printf("output-wav: %s\n", output_wav.c_str());
|
| 781 |
+
printf("max-tokens: %d\n", max_tokens);
|
| 782 |
+
printf("max-feat-len: %d\n", max_feat_len);
|
| 783 |
+
printf("num-step: %d\n", num_step);
|
| 784 |
+
printf("speed: %.2f\n", speed);
|
| 785 |
+
printf("guidance-scale:%.2f\n", guidance_scale);
|
| 786 |
+
printf("t-shift: %.2f\n", t_shift);
|
| 787 |
+
printf("seed: %d\n", seed);
|
| 788 |
+
printf("========================================\n");
|
| 789 |
+
|
| 790 |
+
// --- Init AX system ---
|
| 791 |
+
double t_total_start = get_current_time_ms();
|
| 792 |
+
int ret = AX_SYS_Init();
|
| 793 |
+
if (0 != ret) {
|
| 794 |
+
fprintf(stderr, "AX_SYS_Init failed! ret = 0x%x\n", ret);
|
| 795 |
+
return -1;
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
AX_ENGINE_NPU_ATTR_T npu_attr;
|
| 799 |
+
memset(&npu_attr, 0, sizeof(npu_attr));
|
| 800 |
+
npu_attr.eHardMode = static_cast<AX_ENGINE_NPU_MODE_T>(0);
|
| 801 |
+
ret = AX_ENGINE_Init(&npu_attr);
|
| 802 |
+
if (0 != ret) {
|
| 803 |
+
fprintf(stderr, "AX_ENGINE_Init failed{0x%8x}.\n", ret);
|
| 804 |
+
return -1;
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
// tokenizer already loaded above for Python-aligned segmentation
|
| 808 |
+
|
| 809 |
+
// --- Load prompt audio & extract features ---
|
| 810 |
+
double t_feat_start = get_current_time_ms();
|
| 811 |
+
std::vector<float> prompt_audio;
|
| 812 |
+
int prompt_sr;
|
| 813 |
+
if (ReadWavFile(prompt_wav, prompt_audio, prompt_sr) != 0) {
|
| 814 |
+
return -1;
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
// Resample to 24kHz if needed
|
| 818 |
+
std::vector<float> prompt_resampled = ResampleLinear(prompt_audio, prompt_sr, 24000);
|
| 819 |
+
|
| 820 |
+
// RMS normalize
|
| 821 |
+
float prompt_rms = ComputeRms(prompt_resampled);
|
| 822 |
+
RmsNormalize(prompt_resampled, target_rms);
|
| 823 |
+
|
| 824 |
+
// Extract mel filterbank features
|
| 825 |
+
MelFilterBank fbank;
|
| 826 |
+
MelFilterBank::Config fbank_cfg;
|
| 827 |
+
fbank_cfg.sampling_rate = 24000;
|
| 828 |
+
fbank_cfg.n_mels = 100;
|
| 829 |
+
fbank_cfg.n_fft = 1024;
|
| 830 |
+
fbank_cfg.hop_length = 256;
|
| 831 |
+
fbank.Init(fbank_cfg);
|
| 832 |
+
|
| 833 |
+
std::vector<float> prompt_mel = fbank.Extract(prompt_resampled, 24000);
|
| 834 |
+
int prompt_frames = MelFilterBank::ComputeNumFrames(
|
| 835 |
+
static_cast<int>(prompt_resampled.size()), 256);
|
| 836 |
+
|
| 837 |
+
// Scale features
|
| 838 |
+
for (float& v : prompt_mel) v *= feat_scale;
|
| 839 |
+
|
| 840 |
+
printf("Prompt features: %d frames x %d mels (%.0f ms)\n",
|
| 841 |
+
prompt_frames, fbank_cfg.n_mels,
|
| 842 |
+
get_current_time_ms() - t_feat_start);
|
| 843 |
+
double t_fbank_ms = get_current_time_ms() - t_feat_start;
|
| 844 |
+
|
| 845 |
+
// ---- RTF timing starts here (excludes one-time init) ----
|
| 846 |
+
double t_rtf_start = get_current_time_ms();
|
| 847 |
+
|
| 848 |
+
// --- Initialize ZipVoice engine (once, before sentence loop) ---
|
| 849 |
+
double t_load_start = get_current_time_ms();
|
| 850 |
+
ZipVoiceEngine engine;
|
| 851 |
+
|
| 852 |
+
if (engine.Init(model_dir) != 0) {
|
| 853 |
+
printf("Failed to initialize ZipVoice engine\n");
|
| 854 |
+
return -1;
|
| 855 |
+
}
|
| 856 |
+
printf("Engine load: %.0f ms\n", get_current_time_ms() - t_load_start);
|
| 857 |
+
|
| 858 |
+
// --- Load vocoder axmodel (once, before sentence loop) ---
|
| 859 |
+
Vocoder vocoder;
|
| 860 |
+
if (!vocoder_model.empty()) {
|
| 861 |
+
double t_vocoder_load = get_current_time_ms();
|
| 862 |
+
Vocoder::Config vcfg;
|
| 863 |
+
vcfg.model_path = vocoder_model;
|
| 864 |
+
if (vocoder.Init(vcfg) != 0) {
|
| 865 |
+
printf("ERROR: Failed to load vocoder model\n");
|
| 866 |
+
return -1;
|
| 867 |
+
}
|
| 868 |
+
printf("Vocoder load: %.0f ms\n", get_current_time_ms() - t_vocoder_load);
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
// --- Process sentences ---
|
| 872 |
+
std::vector<float> all_audio;
|
| 873 |
+
double total_npu_ms = 0.0;
|
| 874 |
+
double total_vocoder_ms = 0.0;
|
| 875 |
+
int silence_samples = (int)(24000 * 0.14); // 140ms silence between sentences (matching Python)
|
| 876 |
+
std::vector<float> silence(silence_samples, 0.0f);
|
| 877 |
+
|
| 878 |
+
for (size_t si = 0; si < sentences.size(); ++si) {
|
| 879 |
+
std::string target_text = sentences[si];
|
| 880 |
+
if (sentences.size() > 1) printf("\n--- Sentence %zu/%zu: %s ---\n", si+1, sentences.size(), target_text.substr(0, 60).c_str());
|
| 881 |
+
|
| 882 |
+
double t_tokenize = get_current_time_ms();
|
| 883 |
+
|
| 884 |
+
std::vector<int> prompt_tokens;
|
| 885 |
+
std::vector<int> text_tokens;
|
| 886 |
+
int prompt_tokens_len = 0;
|
| 887 |
+
int text_tokens_len = 0;
|
| 888 |
+
|
| 889 |
+
std::string actual_cat_tokens_file = cat_tokens_file;
|
| 890 |
+
std::string tmp_tokens_file;
|
| 891 |
+
|
| 892 |
+
if (!cat_tokens_file.empty()) {
|
| 893 |
+
// Use pre-computed cat_tokens file
|
| 894 |
+
prompt_tokens_len = prompt_tokens_len_cmd;
|
| 895 |
+
text_tokens_len = text_tokens_len_cmd;
|
| 896 |
+
if (prompt_tokens_len <= 0 || text_tokens_len <= 0) {
|
| 897 |
+
printf("ERROR: --prompt-tokens-len and --text-tokens-len required with --cat-tokens-file\n");
|
| 898 |
+
return -1;
|
| 899 |
+
}
|
| 900 |
+
printf("Using pre-computed tokens: %s (prompt=%d, text=%d)\n",
|
| 901 |
+
cat_tokens_file.c_str(), prompt_tokens_len, text_tokens_len);
|
| 902 |
+
} else if (tokenizer.IsLoaded()) {
|
| 903 |
+
// Fast: use built-in C++ tokenizer (requires --token-file)
|
| 904 |
+
prompt_tokens = tokenizer.TextToTokenIds(prompt_text);
|
| 905 |
+
text_tokens = tokenizer.TextToTokenIds(target_text);
|
| 906 |
+
prompt_tokens_len = (int)prompt_tokens.size();
|
| 907 |
+
text_tokens_len = (int)text_tokens.size();
|
| 908 |
+
printf("C++ tokenizer: prompt=%d, text=%d (%.0f ms)\n",
|
| 909 |
+
prompt_tokens_len, text_tokens_len, get_current_time_ms() - t_tokenize);
|
| 910 |
+
} else if (!repo_dir.empty()) {
|
| 911 |
+
// Fast: use persistent Python daemon (imports done once at startup)
|
| 912 |
+
std::string tmp_prompt = "/tmp/zipvoice_prompt.txt";
|
| 913 |
+
std::string tmp_target = "/tmp/zipvoice_target.txt";
|
| 914 |
+
tmp_tokens_file = "/tmp/zipvoice_cat_tokens.bin";
|
| 915 |
+
{
|
| 916 |
+
std::ofstream pf(tmp_prompt); pf << prompt_text; pf.close();
|
| 917 |
+
std::ofstream tf(tmp_target); tf << target_text; tf.close();
|
| 918 |
+
}
|
| 919 |
+
if (DaemonTokenize(py_daemon, tmp_prompt, tmp_target, max_tokens,
|
| 920 |
+
tmp_tokens_file, &prompt_tokens_len, &text_tokens_len) == 0) {
|
| 921 |
+
actual_cat_tokens_file = tmp_tokens_file;
|
| 922 |
+
printf("Python tokenizer: prompt=%d, text=%d (%.0f ms)\n",
|
| 923 |
+
prompt_tokens_len, text_tokens_len, get_current_time_ms() - t_tokenize);
|
| 924 |
+
} else {
|
| 925 |
+
printf("ERROR: Python daemon tokenization failed\n");
|
| 926 |
+
return -1;
|
| 927 |
+
}
|
| 928 |
+
} else {
|
| 929 |
+
printf("ERROR: Provide --token-file, --cat-tokens-file, or --repo-dir\n");
|
| 930 |
+
return -1;
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
// Build or load cat_tokens [prompt + text + pad]
|
| 934 |
+
std::vector<int32_t> cat_tokens;
|
| 935 |
+
if (!actual_cat_tokens_file.empty()) {
|
| 936 |
+
// Read int32 binary
|
| 937 |
+
std::ifstream ctf(actual_cat_tokens_file, std::ios::binary);
|
| 938 |
+
if (!ctf.is_open()) {
|
| 939 |
+
printf("Failed to open: %s\n", actual_cat_tokens_file.c_str());
|
| 940 |
+
return -1;
|
| 941 |
+
}
|
| 942 |
+
ctf.seekg(0, std::ios::end);
|
| 943 |
+
size_t file_size = ctf.tellg();
|
| 944 |
+
ctf.seekg(0, std::ios::beg);
|
| 945 |
+
cat_tokens.resize(file_size / sizeof(int32_t));
|
| 946 |
+
ctf.read(reinterpret_cast<char*>(cat_tokens.data()), file_size);
|
| 947 |
+
ctf.close();
|
| 948 |
+
printf("Loaded %zu cat_tokens\n", cat_tokens.size());
|
| 949 |
+
if ((int)cat_tokens.size() > max_tokens) {
|
| 950 |
+
max_tokens = (int)cat_tokens.size();
|
| 951 |
+
}
|
| 952 |
+
} else if (tokenizer.IsLoaded()) {
|
| 953 |
+
tokenizer.BuildCatTokens(prompt_tokens, text_tokens, max_tokens, cat_tokens);
|
| 954 |
+
} else {
|
| 955 |
+
printf("ERROR: Cannot build cat_tokens\n");
|
| 956 |
+
return -1;
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
int feat_dim = fbank_cfg.n_mels;
|
| 960 |
+
|
| 961 |
+
ZipVoiceEngine::Timing timing;
|
| 962 |
+
std::vector<float> output_features;
|
| 963 |
+
|
| 964 |
+
if (engine.Sample(cat_tokens,
|
| 965 |
+
prompt_tokens_len,
|
| 966 |
+
text_tokens_len,
|
| 967 |
+
prompt_mel,
|
| 968 |
+
prompt_frames,
|
| 969 |
+
speed,
|
| 970 |
+
guidance_scale,
|
| 971 |
+
seed,
|
| 972 |
+
output_features,
|
| 973 |
+
timing) != 0) {
|
| 974 |
+
printf("Inference failed!\n");
|
| 975 |
+
return -1;
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
double t_infer_end = get_current_time_ms();
|
| 979 |
+
|
| 980 |
+
int generated_frames = timing.generated_frames;
|
| 981 |
+
total_npu_ms += timing.total_time_sec * 1000.0;
|
| 982 |
+
|
| 983 |
+
// --- Save mel features ---
|
| 984 |
+
if (!output_feat.empty()) {
|
| 985 |
+
SaveFeaturesBin(output_feat, output_features, generated_frames, feat_dim);
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
// --- Decode mel to WAV ---
|
| 989 |
+
float audio_sec = 0.0f;
|
| 990 |
+
if (!vocoder_model.empty()) {
|
| 991 |
+
// C++ vocoder (axmodel): fast, all NPU
|
| 992 |
+
printf("\nDecoding via C++ vocoder...\n");
|
| 993 |
+
double t_vocoder_start = get_current_time_ms();
|
| 994 |
+
|
| 995 |
+
std::vector<float> audio;
|
| 996 |
+
if (vocoder.Decode(output_features, generated_frames, feat_scale, audio) != 0) {
|
| 997 |
+
printf("ERROR: Vocoder decode failed\n");
|
| 998 |
+
return -1;
|
| 999 |
+
}
|
| 1000 |
+
|
| 1001 |
+
double t_vocoder_decode = get_current_time_ms() - t_vocoder_start;
|
| 1002 |
+
audio_sec = audio.size() / 24000.0f;
|
| 1003 |
+
|
| 1004 |
+
// RMS normalize (matching Python vocoder_decode_loaded)
|
| 1005 |
+
RmsNormalize(audio, target_rms);
|
| 1006 |
+
if (prompt_rms < target_rms) {
|
| 1007 |
+
float scale = prompt_rms / target_rms;
|
| 1008 |
+
for (float& s : audio) s *= scale;
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
// Accumulate audio + silence between sentences
|
| 1012 |
+
if (!all_audio.empty()) all_audio.insert(all_audio.end(), silence.begin(), silence.end());
|
| 1013 |
+
all_audio.insert(all_audio.end(), audio.begin(), audio.end());
|
| 1014 |
+
total_vocoder_ms += t_vocoder_decode;
|
| 1015 |
+
|
| 1016 |
+
double t_rtf_ms = get_current_time_ms() - t_rtf_start;
|
| 1017 |
+
double t_npu_ms = timing.total_time_sec * 1000.0;
|
| 1018 |
+
|
| 1019 |
+
} else if (!repo_dir.empty()) {
|
| 1020 |
+
// Python vocoder (deprecated fallback)
|
| 1021 |
+
printf("\nDecoding mel to WAV via Python vocoder...\n");
|
| 1022 |
+
double t_vocoder_start = get_current_time_ms();
|
| 1023 |
+
char cmdline[2048];
|
| 1024 |
+
snprintf(cmdline, sizeof(cmdline),
|
| 1025 |
+
"python3 -c \""
|
| 1026 |
+
"import numpy as np, soundfile as sf, sys;"
|
| 1027 |
+
"sys.path.insert(0, '%s');"
|
| 1028 |
+
"from scripts.common_infer import load_vocoder, vocoder_decode_loaded;"
|
| 1029 |
+
"feat = np.fromfile('%s', dtype=np.float32).reshape(1, -1, 100);"
|
| 1030 |
+
"v = load_vocoder('%s');"
|
| 1031 |
+
"audio = vocoder_decode_loaded(v, feat, feat_scale=%.2f, target_rms=%.2f, prompt_rms=%.4f);"
|
| 1032 |
+
"sf.write('%s', audio, 24000);"
|
| 1033 |
+
"print('AUDIO_SEC=%%f' %% (len(audio)/24000))"
|
| 1034 |
+
"\"",
|
| 1035 |
+
repo_dir.c_str(), output_feat.c_str(), repo_dir.c_str(),
|
| 1036 |
+
feat_scale, target_rms, prompt_rms,
|
| 1037 |
+
output_wav.c_str());
|
| 1038 |
+
FILE* fp = popen(cmdline, "r");
|
| 1039 |
+
if (fp) {
|
| 1040 |
+
char buf[256];
|
| 1041 |
+
while (fgets(buf, sizeof(buf), fp)) {
|
| 1042 |
+
printf("%s", buf);
|
| 1043 |
+
sscanf(buf, "AUDIO_SEC=%f", &audio_sec);
|
| 1044 |
+
}
|
| 1045 |
+
pclose(fp);
|
| 1046 |
+
}
|
| 1047 |
+
double t_vocoder = get_current_time_ms() - t_vocoder_start;
|
| 1048 |
+
double t_rtf_ms = get_current_time_ms() - t_rtf_start;
|
| 1049 |
+
double t_npu_ms = timing.total_time_sec * 1000.0;
|
| 1050 |
+
printf(" NPU inference: %.0f ms vocoder: %.0f ms\n", t_npu_ms, t_vocoder);
|
| 1051 |
+
if (audio_sec > 0.0f) {
|
| 1052 |
+
printf(" RTF (NPU only): %.4f (%.3f s / audio %.2f s)\n",
|
| 1053 |
+
t_npu_ms / 1000.0 / audio_sec, t_npu_ms / 1000.0, audio_sec);
|
| 1054 |
+
}
|
| 1055 |
+
total_vocoder_ms += t_vocoder;
|
| 1056 |
+
} else {
|
| 1057 |
+
printf("\nTo decode mel to WAV, add --vocoder-model <path/to/vocos_full.axmodel>\n");
|
| 1058 |
+
}
|
| 1059 |
+
} // end for each sentence
|
| 1060 |
+
|
| 1061 |
+
// Write concatenated audio
|
| 1062 |
+
if (!all_audio.empty()) {
|
| 1063 |
+
if (!WavWriter::Write(output_wav, all_audio, 24000)) {
|
| 1064 |
+
printf("ERROR: Failed to write WAV\n");
|
| 1065 |
+
return -1;
|
| 1066 |
+
}
|
| 1067 |
+
float total_audio_sec = all_audio.size() / 24000.0f;
|
| 1068 |
+
printf("\n========================================\n");
|
| 1069 |
+
printf("Long-text synthesis complete\n");
|
| 1070 |
+
printf(" Segments: %zu\n", sentences.size());
|
| 1071 |
+
printf(" Audio duration: %.2f s\n", total_audio_sec);
|
| 1072 |
+
printf(" NPU total: %.3f s\n", total_npu_ms / 1000.0);
|
| 1073 |
+
printf(" Vocoder total: %.3f s\n", total_vocoder_ms / 1000.0);
|
| 1074 |
+
printf(" RTF (NPU only): %.4f\n", total_npu_ms / 1000.0 / total_audio_sec);
|
| 1075 |
+
printf(" RTF (end-to-end):%.4f\n", (total_npu_ms + total_vocoder_ms) / 1000.0 / total_audio_sec);
|
| 1076 |
+
printf(" Saved: %s\n", output_wav.c_str());
|
| 1077 |
+
printf("========================================\n");
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
printf("\nDone!\n");
|
| 1081 |
+
return 0;
|
| 1082 |
+
}
|