120 lines
5.3 KiB
C++
120 lines
5.3 KiB
C++
// CLI dev autour de tts_engine. La logique est maintenant dans tts_engine.{h,cpp}
|
|
// (réutilisée par le JNI). Ce binaire reste pour les bench A/B et la régression.
|
|
//
|
|
// Usage:
|
|
// tts_pipeline <talker_gguf> <dump_dir> <out.wav> [cpu|htp] [max_steps]
|
|
//
|
|
// Variables d'environnement :
|
|
// KZTTS_TEXT, KZTTS_VOCAB_GGUF : texte arbitraire au lieu de input_ids_full.bin (fixture)
|
|
// (le mode fixture est conservé pour bench/régression)
|
|
// KZTTS_CP_CACHE : 1 (défaut) = cp_predict_cached, 0 = cp_predict oracle
|
|
// KZTTS_SEED : seed sampling (défaut 42)
|
|
// KZTTS_THREADS : threads CPU (défaut 6)
|
|
// KZTTS_TEMP / TOPK / TOPP / REPP : sampling Talker
|
|
// KZTTS_CP_TEMP / CP_TOPK / CP_TOPP / CP_REPP : sampling CP
|
|
#include "tts_engine.h"
|
|
#include "kazeia_text_tokenizer.h"
|
|
#include "llama.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
static float env_f(const char* k, float dflt) { const char* v = getenv(k); return v ? (float)atof(v) : dflt; }
|
|
static int env_i(const char* k, int dflt) { const char* v = getenv(k); return v ? atoi(v) : dflt; }
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 4) {
|
|
printf("usage: %s <talker_gguf> <dump_dir> <out.wav> [cpu|htp] [max_steps]\n", argv[0]);
|
|
printf(" Texte arbitraire :\n");
|
|
printf(" KZTTS_VOCAB_GGUF=/path/qwen3.gguf KZTTS_TEXT=\"phrase libre\" %s ...\n", argv[0]);
|
|
printf(" Mode fixture (input_ids_full.bin) : aucune variable d'env requise.\n");
|
|
return 1;
|
|
}
|
|
const char* talker_gguf = argv[1];
|
|
std::string D = argv[2]; if (D.back() != '/') D += '/';
|
|
const char* out_wav = argv[3];
|
|
const bool use_htp = (argc >= 5 && !strcmp(argv[4], "htp"));
|
|
const int max_steps = (argc >= 6) ? atoi(argv[5]) : 256;
|
|
const char* kz_text = getenv("KZTTS_TEXT");
|
|
const char* kz_vocab_gguf = getenv("KZTTS_VOCAB_GGUF");
|
|
|
|
if (!kz_text || !kz_vocab_gguf) {
|
|
fprintf(stderr, "tts_pipeline: mode fixture (input_ids_full.bin) demandé mais non implémenté\n");
|
|
fprintf(stderr, " -> poser KZTTS_TEXT et KZTTS_VOCAB_GGUF pour utiliser le pipeline live.\n");
|
|
fprintf(stderr, " (Le mode fixture a été déporté en branche fixture pour test_engine séparé.)\n");
|
|
return 2;
|
|
}
|
|
|
|
TtsEngineLoadCfg lc;
|
|
lc.talker_gguf = talker_gguf;
|
|
lc.vocab_gguf = kz_vocab_gguf;
|
|
lc.dump_dir = D.c_str();
|
|
lc.use_htp = use_htp;
|
|
lc.n_threads = env_i("KZTTS_THREADS", 6);
|
|
lc.cp_use_cache = env_i("KZTTS_CP_CACHE", 1) != 0;
|
|
// Clonage vocal embarqué (opt-in via env)
|
|
lc.speaker_encoder_gguf = getenv("KZTTS_SPK_GGUF"); // ex: tts_dump/speaker_encoder.gguf
|
|
lc.mel_basis_path = getenv("KZTTS_MEL_BASIS"); // ex: tts_dump/mel_basis_qwen3tts.bin
|
|
|
|
auto * eng = tts_engine_load(lc);
|
|
if (!eng) { fprintf(stderr, "tts_engine_load FAIL\n"); return 3; }
|
|
|
|
// Si KZTTS_REF_WAV pointe sur un WAV mono 24kHz, on en extrait le x_vector et
|
|
// l'utilise comme override pour ce synth (clonage vocal in-process).
|
|
std::vector<float> ref_xvec;
|
|
if (const char * ref_wav = getenv("KZTTS_REF_WAV")) {
|
|
struct timespec t0, t1;
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
|
ref_xvec = tts_engine_encode_speaker_wav(eng, ref_wav);
|
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
double enc_s = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) * 1e-9;
|
|
if (ref_xvec.empty()) {
|
|
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s FAIL (encoder missing or read FAIL)\n", ref_wav);
|
|
} else {
|
|
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s -> x_vector[%zu] in %.3fs\n",
|
|
ref_wav, ref_xvec.size(), enc_s);
|
|
}
|
|
}
|
|
|
|
TtsSynthesizeCfg sc;
|
|
sc.text = kz_text;
|
|
sc.out_wav_path = out_wav;
|
|
sc.max_steps = max_steps;
|
|
sc.seed = env_i("KZTTS_SEED", 42);
|
|
sc.cp_temp = env_f("KZTTS_CP_TEMP", 0.9f);
|
|
sc.cp_top_k = env_i("KZTTS_CP_TOPK", 50);
|
|
sc.cp_top_p = env_f("KZTTS_CP_TOPP", 0.95f);
|
|
sc.cp_rep_penalty = env_f("KZTTS_CP_REPP", 1.10f);
|
|
sc.talker_temp = env_f("KZTTS_TEMP", 0.9f);
|
|
sc.talker_top_k = env_i("KZTTS_TOPK", 50);
|
|
sc.talker_top_p = env_f("KZTTS_TOPP", 0.95f);
|
|
sc.talker_rep_penalty = env_f("KZTTS_REPP", 1.10f);
|
|
if (!ref_xvec.empty()) {
|
|
sc.xvector_override = ref_xvec.data();
|
|
sc.xvector_override_len = (int)ref_xvec.size();
|
|
}
|
|
|
|
auto R = tts_engine_synthesize(eng, sc);
|
|
if (R.err) { fprintf(stderr, "tts_engine_synthesize FAIL err=%d\n", R.err); tts_engine_free(eng); return 4; }
|
|
|
|
printf("=== TTS : N=%d frames (audio %.2fs) en %.3fs (RTF %.2f) ===\n",
|
|
R.frames, R.audio_s, R.total_s, R.total_s / R.audio_s);
|
|
printf(" prefill %.3fs | talker_loop %.3fs | cp_loop %.3fs | decoder %.3fs\n",
|
|
R.prefill_s, R.talker_loop_s, R.cp_loop_s, R.decoder_s);
|
|
printf(" per-frame talker=%.1fms cp=%.1fms\n",
|
|
R.talker_loop_s * 1000.0 / R.frames, R.cp_loop_s * 1000.0 / R.frames);
|
|
if (R.ttfb_s > 0) {
|
|
printf(" STREAMING : TTFB=%.3fs (premier chunk audio prêt) | %d chunks\n",
|
|
R.ttfb_s, R.n_chunks);
|
|
}
|
|
printf("WAV -> %s\n", out_wav);
|
|
|
|
tts_engine_free(eng);
|
|
return 0;
|
|
}
|