From 3be42af9bbb5b239dff08b24e52f635dce02cbca Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 13 Aug 2026 19:54:50 +0200 Subject: [PATCH] tts-server: normalizza i numeri interi 5+ cifre in parole italiane (Qwen3-TTS li leggeva cifra per cifra) --- src/text-normalize.h | 123 +++++++++++++++++++++++++++++++++++++++++++ tools/tts-server.cpp | 5 +- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/text-normalize.h diff --git a/src/text-normalize.h b/src/text-normalize.h new file mode 100644 index 0000000..13b6e65 --- /dev/null +++ b/src/text-normalize.h @@ -0,0 +1,123 @@ +// text-normalize.h: normalizzazione testo per TTS. +// Qwen3-TTS legge i numeri interi di 5+ cifre cifra per cifra +// ("12345" -> "un, due, tre, quattro, cinque"). Questa funzione li +// converte in parole italiane prima della sintesi. +#pragma once + +#include +#include + +namespace text_normalize { + +static bool is_alnum_utf8(char c) { + unsigned char u = (unsigned char) c; + return (u >= '0' && u <= '9') || (u >= 'A' && u <= 'Z') || (u >= 'a' && u <= 'z') || u >= 0x80; +} + +// Converte un intero (0..999.999.999.999) in parole italiane. +static std::string number_to_words(uint64_t n) { + if (n == 0) return "zero"; + + static const char * units[] = {"", "uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto", "nove"}; + static const char * teens[] = {"dieci", "undici", "dodici", "tredici", "quattordici", "quindici", "sedici", + "diciassette", "diciotto", "diciannove"}; + static const char * tens[] = {"", "", "venti", "trenta", "quaranta", "cinquanta", "sessanta", "settanta", + "ottanta", "novanta"}; + + // Converte un gruppo di 3 cifre (0-999). + auto group3 = [&](int g) -> std::string { + if (g == 0) return ""; + std::string out; + int h = g / 100, rest = g % 100; + if (h > 0) { + out += (h == 1) ? "cento" : (std::string(units[h]) + "cento"); + } + if (rest >= 10 && rest < 20) { + out += teens[rest - 10]; + } else { + int t = rest / 10, u = rest % 10; + if (t > 1) { + std::string dec = tens[t]; + if (u == 1 || u == 8) { + dec.pop_back(); // venti+uno=ventuno, venti+otto=ventotto + out += dec + units[u]; + } else if (u == 3) { + out += dec + "tr\xc3\xa9"; // ventitré (accento) + } else if (u > 0) { + out += dec + units[u]; + } else { + out += dec; + } + } else if (t == 1) { + out += teens[u]; + } else if (u > 0) { + out += units[u]; + } + } + return out; + }; + + static const char * scale_singular[] = {"", "mille", "un milione", "un miliardo"}; + static const char * scale_plural[] = {"", "mila", "milioni", "miliardi"}; + + int groups[4] = {0, 0, 0, 0}; + uint64_t v = n; + for (int i = 0; i < 4 && v > 0; i++) { + groups[i] = (int) (v % 1000); + v /= 1000; + } + + std::string out; + bool first = true; + for (int i = 3; i >= 0; i--) { + int g = groups[i]; + if (g == 0) continue; + std::string words = group3(g); + if (i > 0) { + if (g == 1) { + words = scale_singular[i]; + } else { + words += " " + std::string(scale_plural[i]); + } + } + if (!first) out += " "; + out += words; + first = false; + } + return out; +} + +// Normalizza i numeri interi di 5+ cifre in parole italiane. +// Non tocca: date, orari, decimali, codici, numeri < 5 cifre, numeri > 12 cifre. +static void normalize_numbers(std::string & s) { + std::string out; + out.reserve(s.size() + 32); + size_t i = 0; + const size_t n = s.size(); + while (i < n) { + unsigned char c = (unsigned char) s[i]; + if (c >= '0' && c <= '9') { + size_t j = i; + while (j < n && s[j] >= '0' && s[j] <= '9') j++; + size_t len = j - i; + bool left_ok = (i == 0) || !is_alnum_utf8(s[i - 1]); + bool right_ok = (j == n) || !is_alnum_utf8(s[j]); + if (len >= 5 && len <= 12 && left_ok && right_ok) { + uint64_t val = 0; + for (size_t k = i; k < j; k++) { + val = val * 10 + (uint64_t) (s[k] - '0'); + } + out += number_to_words(val); + } else { + out.append(s, i, len); + } + i = j; + } else { + out += s[i]; + i++; + } + } + s.swap(out); +} + +} // namespace text_normalize diff --git a/tools/tts-server.cpp b/tools/tts-server.cpp index 2e0ccbd..4bdb1f4 100644 --- a/tools/tts-server.cpp +++ b/tools/tts-server.cpp @@ -7,6 +7,7 @@ #include "qwen.h" #include "rvq-file.h" +#include "text-normalize.h" #include "version.h" #include @@ -222,7 +223,9 @@ int main(int argc, char ** argv) { be.synthesize = [q, &lang](const tts_request & req, const tts_sink & sink, std::string & err) -> int { struct qt_tts_params p; qt_tts_default_params(&p); - p.text = req.input.c_str(); + std::string normalized = req.input; + text_normalize::normalize_numbers(normalized); + p.text = normalized.c_str(); p.lang = lang.c_str(); // Copy the registered voice latents out under the lock: the