HY-2012 commited on
Commit
92264aa
·
verified ·
1 Parent(s): d5df948

Upload the cpp version

Browse files
.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/en_long_paragraph.wav filter=lfs diff=lfs merge=lfs -text
54
  outputs/en_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
55
- outputs/en_sentence.wav filter=lfs diff=lfs merge=lfs -text
56
  outputs/en_sentence_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
57
- outputs/zh_long_paragraph.wav filter=lfs diff=lfs merge=lfs -text
58
  outputs/zh_long_paragraph_distill_ax650.wav filter=lfs diff=lfs merge=lfs -text
59
- outputs/zh_sentence.wav filter=lfs diff=lfs merge=lfs -text
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
+ }