Skip to content

Commit 94a040e

Browse files
authored
Refactor ZipVoice C++ code (#2911)
This pull request introduces a significant refactoring of the ZipVoice C++ implementation, aiming to improve code structure, performance, and maintainability. The changes include the addition of a NormalDataGenerator for better random number generation, splitting the OfflineTtsZipvoiceModel into more modular encoder and decoder components, and optimizing the mel spectrogram computation.
1 parent 241cb9b commit 94a040e

6 files changed

Lines changed: 316 additions & 180 deletions

sherpa-onnx/csrc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set(sources
2626
keyword-spotter.cc
2727
lodr-fst.cc
2828
math.cc
29+
normal-data-generator.cc
2930
offline-canary-model-config.cc
3031
offline-canary-model.cc
3132
offline-ctc-fst-decoder-config.cc
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// sherpa-onnx/csrc/normal-data-generator.cc
2+
//
3+
// Copyright 2025 Xiaomi Corporation
4+
5+
// Written by ChatGPT
6+
7+
#include "sherpa-onnx/csrc/normal-data-generator.h"
8+
9+
#include <random>
10+
#include <thread>
11+
12+
namespace sherpa_onnx {
13+
14+
// Helper type hidden in translation unit
15+
struct RNGHolder {
16+
std::mt19937 rng;
17+
std::normal_distribution<float> dist;
18+
19+
RNGHolder()
20+
: rng([] {
21+
std::random_device rd;
22+
std::seed_seq seq{rd(),
23+
static_cast<unsigned>(std::hash<std::thread::id>{}(
24+
std::this_thread::get_id()))};
25+
return std::mt19937(seq);
26+
}()),
27+
dist() {}
28+
};
29+
30+
NormalDataGenerator::NormalDataGenerator(float mean /* = 0.0f */,
31+
float stddev /* = 1.0f */)
32+
: mean_(mean), stddev_(stddev) {}
33+
34+
void NormalDataGenerator::Fill(float *data, std::size_t size) const {
35+
// One RNGHolder per thread
36+
static thread_local RNGHolder holder;
37+
38+
holder.dist.param(
39+
std::normal_distribution<float>::param_type(mean_, stddev_));
40+
41+
for (std::size_t i = 0; i < size; ++i) {
42+
data[i] = holder.dist(holder.rng);
43+
}
44+
}
45+
46+
} // namespace sherpa_onnx
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// sherpa-onnx/csrc/normal-data-generator.h
2+
//
3+
// Copyright 2025 Xiaomi Corporation
4+
5+
// Written by ChatGPT
6+
#ifndef SHERPA_ONNX_CSRC_NORMAL_DATA_GENERATOR_H_
7+
#define SHERPA_ONNX_CSRC_NORMAL_DATA_GENERATOR_H_
8+
9+
#include <cstddef>
10+
11+
namespace sherpa_onnx {
12+
13+
class NormalDataGenerator {
14+
public:
15+
explicit NormalDataGenerator(float mean = 0.0f, float stddev = 1.0f);
16+
17+
// Fill pre-allocated memory
18+
void Fill(float *data, std::size_t size) const;
19+
20+
private:
21+
float mean_;
22+
float stddev_;
23+
};
24+
25+
} // namespace sherpa_onnx
26+
#endif // SHERPA_ONNX_CSRC_NORMAL_DATA_GENERATOR_H_

sherpa-onnx/csrc/offline-tts-zipvoice-impl.h

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
3333
model_(std::make_unique<OfflineTtsZipvoiceModel>(config.model)),
3434
vocoder_(Vocoder::Create(config.model)) {
3535
InitFrontend();
36+
37+
PostInit();
3638
}
3739

3840
template <typename Manager>
@@ -41,6 +43,8 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
4143
model_(std::make_unique<OfflineTtsZipvoiceModel>(mgr, config.model)),
4244
vocoder_(Vocoder::Create(mgr, config.model)) {
4345
InitFrontend(mgr);
46+
47+
PostInit();
4448
}
4549

4650
int32_t SampleRate() const override {
@@ -99,6 +103,33 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
99103
}
100104

101105
private:
106+
void PostInit() { InitMelBanks(); }
107+
108+
void InitMelBanks() {
109+
const auto &meta = model_->GetMetaData();
110+
int32_t sample_rate = meta.sample_rate;
111+
int32_t n_fft = meta.n_fft;
112+
int32_t hop_length = meta.hop_length;
113+
int32_t win_length = meta.window_length;
114+
int32_t num_mels = meta.num_mels;
115+
116+
knf::FrameExtractionOptions frame_opts;
117+
frame_opts.samp_freq = sample_rate;
118+
frame_opts.frame_length_ms = win_length * 1000 / sample_rate;
119+
frame_opts.frame_shift_ms = hop_length * 1000 / sample_rate;
120+
frame_opts.window_type = "hanning";
121+
122+
knf::MelBanksOptions mel_opts;
123+
mel_opts.num_bins = num_mels;
124+
mel_opts.low_freq = 0;
125+
mel_opts.high_freq = sample_rate / 2;
126+
mel_opts.is_librosa = true;
127+
mel_opts.use_slaney_mel_scale = false;
128+
mel_opts.norm = "";
129+
130+
mel_banks_ = std::make_unique<knf::MelBanks>(mel_opts, frame_opts, 1.0f);
131+
}
132+
102133
template <typename Manager>
103134
void InitFrontend(Manager *mgr) {
104135
frontend_ = std::make_unique<MatchaTtsLexicon>(
@@ -112,9 +143,9 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
112143
config_.model.zipvoice.data_dir, config_.model.debug, true);
113144
}
114145

115-
std::vector<int32_t> ComputeMelSpectrogram(
116-
const std::vector<float> &_samples, int32_t sample_rate,
117-
std::vector<float> *prompt_features) const {
146+
void ComputeMelSpectrogram(const std::vector<float> &_samples,
147+
int32_t sample_rate, float feat_scale,
148+
std::vector<float> *prompt_features) const {
118149
const auto &meta = model_->GetMetaData();
119150
if (sample_rate != meta.sample_rate) {
120151
SHERPA_ONNX_LOGE(
@@ -131,19 +162,18 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
131162
sample_rate, meta.sample_rate, lowpass_cutoff, lowpass_filter_width);
132163
std::vector<float> samples;
133164
resampler->Resample(_samples.data(), _samples.size(), true, &samples);
134-
return ComputeMelSpectrogram(samples, prompt_features);
135-
} else {
136-
// Use the original samples if the sample rate matches
137-
return ComputeMelSpectrogram(_samples, prompt_features);
165+
ComputeMelSpectrogram(samples, feat_scale, prompt_features);
166+
return;
138167
}
168+
169+
ComputeMelSpectrogram(_samples, feat_scale, prompt_features);
139170
}
140171

141-
std::vector<int32_t> ComputeMelSpectrogram(
142-
const std::vector<float> &samples,
143-
std::vector<float> *prompt_features) const {
172+
void ComputeMelSpectrogram(const std::vector<float> &samples,
173+
float feat_scale,
174+
std::vector<float> *prompt_features) const {
144175
const auto &meta = model_->GetMetaData();
145176

146-
int32_t sample_rate = meta.sample_rate;
147177
int32_t n_fft = meta.n_fft;
148178
int32_t hop_length = meta.hop_length;
149179
int32_t win_length = meta.window_length;
@@ -161,46 +191,23 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
161191
int32_t num_frames = stft_result.num_frames;
162192
int32_t fft_bins = n_fft / 2 + 1;
163193

164-
knf::FrameExtractionOptions frame_opts;
165-
frame_opts.samp_freq = sample_rate;
166-
frame_opts.frame_length_ms = win_length * 1000 / sample_rate;
167-
frame_opts.frame_shift_ms = hop_length * 1000 / sample_rate;
168-
frame_opts.window_type = "hanning";
169-
170-
knf::MelBanksOptions mel_opts;
171-
mel_opts.num_bins = num_mels;
172-
mel_opts.low_freq = 0;
173-
mel_opts.high_freq = sample_rate / 2;
174-
mel_opts.is_librosa = true;
175-
mel_opts.use_slaney_mel_scale = false;
176-
mel_opts.norm = "";
177-
178-
knf::MelBanks mel_banks(mel_opts, frame_opts, 1.0f);
194+
prompt_features->resize(num_frames * num_mels);
195+
float *p = prompt_features->data();
179196

180-
prompt_features->clear();
181-
prompt_features->reserve(num_frames * num_mels);
197+
std::vector<float> magnitude_spectrum(fft_bins);
182198

183-
for (int32_t i = 0; i < num_frames; ++i) {
184-
std::vector<float> magnitude_spectrum(fft_bins);
199+
for (int32_t i = 0; i < num_frames; ++i, p += num_mels) {
185200
for (int32_t k = 0; k < fft_bins; ++k) {
186201
float real = stft_result.real[i * fft_bins + k];
187202
float imag = stft_result.imag[i * fft_bins + k];
188203
magnitude_spectrum[k] = std::sqrt(real * real + imag * imag);
189204
}
190-
std::vector<float> mel_features(num_mels, 0.0f);
191-
mel_banks.Compute(magnitude_spectrum.data(), mel_features.data());
192-
for (auto &v : mel_features) {
193-
v = std::log(v + 1e-10f);
205+
206+
mel_banks_->Compute(magnitude_spectrum.data(), p);
207+
208+
for (int32_t j = 0; j < num_mels; ++j) {
209+
p[j] = std::log(p[j] + 1e-10f) * feat_scale;
194210
}
195-
// Instead of push_back a vector, push elements individually
196-
prompt_features->insert(prompt_features->end(), mel_features.begin(),
197-
mel_features.end());
198-
}
199-
if (num_frames == 0) {
200-
SHERPA_ONNX_LOGE("No frames extracted from the prompt audio");
201-
return {0, 0};
202-
} else {
203-
return {num_frames, num_mels};
204211
}
205212
}
206213

@@ -214,12 +221,14 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
214221

215222
std::array<int64_t, 2> tokens_shape = {1,
216223
static_cast<int64_t>(tokens.size())};
224+
217225
Ort::Value tokens_tensor = Ort::Value::CreateTensor(
218226
memory_info, const_cast<int64_t *>(tokens.data()), tokens.size(),
219227
tokens_shape.data(), tokens_shape.size());
220228

221229
std::array<int64_t, 2> prompt_tokens_shape = {
222230
1, static_cast<int64_t>(prompt_tokens.size())};
231+
223232
Ort::Value prompt_tokens_tensor = Ort::Value::CreateTensor(
224233
memory_info, const_cast<int64_t *>(prompt_tokens.data()),
225234
prompt_tokens.size(), prompt_tokens_shape.data(),
@@ -230,31 +239,32 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
230239

231240
// Scale prompt_samples
232241
std::vector<float> prompt_samples_scaled = prompt_samples;
233-
float prompt_rms = 0.0f;
242+
double prompt_rms = 0.0;
234243
double sum_sq = 0.0;
235244
// Compute RMS of prompt_samples
236245
for (float s : prompt_samples_scaled) {
237246
sum_sq += s * s;
238247
}
239248
prompt_rms = std::sqrt(sum_sq / prompt_samples_scaled.size());
240249
if (prompt_rms < target_rms && prompt_rms > 0.0f) {
241-
float scale = target_rms / static_cast<float>(prompt_rms);
250+
float scale = target_rms / prompt_rms;
242251
for (auto &s : prompt_samples_scaled) {
243252
s *= scale;
244253
}
245254
}
246255

247256
std::vector<float> prompt_features;
248-
auto res_shape = ComputeMelSpectrogram(prompt_samples_scaled, sample_rate,
249-
&prompt_features);
250257

251-
int32_t num_frames = res_shape[0];
252-
int32_t mel_dim = res_shape[1];
258+
int32_t mel_dim = model_->GetMetaData().num_mels;
253259

254-
if (feat_scale != 1.0f) {
255-
for (auto &item : prompt_features) {
256-
item *= feat_scale;
257-
}
260+
ComputeMelSpectrogram(prompt_samples_scaled, sample_rate, feat_scale,
261+
&prompt_features);
262+
263+
int32_t num_frames = prompt_features.size() / mel_dim;
264+
265+
if (num_frames == 0) {
266+
SHERPA_ONNX_LOGE("No frames extracted from the prompt audio");
267+
return {};
258268
}
259269

260270
std::array<int64_t, 3> shape = {1, num_frames, mel_dim};
@@ -268,16 +278,19 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
268278

269279
// Assume mel_shape = {1, T, C}
270280
std::vector<int64_t> mel_shape = mel.GetTensorTypeAndShapeInfo().GetShape();
271-
int64_t T = mel_shape[1], C = mel_shape[2];
281+
int64_t T = mel_shape[1];
282+
int64_t C = mel_shape[2];
272283

273-
float *mel_data = mel.GetTensorMutableData<float>();
284+
const float *mel_data = mel.GetTensorData<float>();
274285
std::vector<float> mel_permuted(C * T);
275286

287+
float inv_feat_scale = 1 / feat_scale;
288+
276289
for (int64_t c = 0; c < C; ++c) {
277290
for (int64_t t = 0; t < T; ++t) {
278291
int64_t src_idx = t * C + c; // src: [T, C] (row major)
279292
int64_t dst_idx = c * T + t; // dst: [C, T] (row major)
280-
mel_permuted[dst_idx] = mel_data[src_idx] / feat_scale;
293+
mel_permuted[dst_idx] = mel_data[src_idx] * inv_feat_scale;
281294
}
282295
}
283296

@@ -304,6 +317,8 @@ class OfflineTtsZipvoiceImpl : public OfflineTtsImpl {
304317
std::unique_ptr<OfflineTtsZipvoiceModel> model_;
305318
std::unique_ptr<Vocoder> vocoder_;
306319
std::unique_ptr<OfflineTtsFrontend> frontend_;
320+
321+
std::unique_ptr<knf::MelBanks> mel_banks_;
307322
};
308323

309324
} // namespace sherpa_onnx

sherpa-onnx/csrc/offline-tts-zipvoice-model-meta-data.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ struct OfflineTtsZipvoiceModelMetaData {
2222
int32_t window_length = 1024;
2323
int32_t num_mels = 100;
2424
int32_t use_espeak = 1;
25-
int32_t use_pinyin = 1;
2625
};
2726

2827
} // namespace sherpa_onnx

0 commit comments

Comments
 (0)