Síntesis de voz en chino: normalización de texto e ingeniería
Este skill proviene de una mascota de escritorio con IA que habla constantemente (ver «Origen y mantenimiento» al final): respuestas en streaming de Claude que se entregan frase por frase al TTS local. Todas las reglas tienen pruebas y los números están medidos.
Regla de oro: la pronunciación se convierte en esta capa; el motor solo se encarga de la fonación. Si la pronunciación se deja al motor, al cambiar de motor cambia la pronunciación; incluso si se usa una biblioteca TN ya hecha, hay algunos casos que empeoran. Las bibliotecas existentes (wetext, PaddleSpeech) se usan como referencia y para complementar reglas.
Flujo de trabajo
Texto en streaming del modelo
→ ① Filtrado: eliminar decoraciones, emojis, símbolos de lista; bloques de código, tablas y enlaces se convierten en una frase (en streaming, debe reconocerlos incluso si están divididos)
→ ② Conversión a pronunciación: números, tiempos, unidades, símbolos, tabla de reemplazo de pronunciación (en streaming, mantener los límites)
→ ③ Segmentación de oraciones: el primer segmento se corta hasta la primera coma; después, si supera los 40 caracteres, se corta en una coma
→ ④ Síntesis: solo se adelanta unos 20 segundos a lo que se ha pronunciado; al interrumpir, se detiene realmente la inferencia
El orden no se puede cambiar: primero convertir a pronunciación, luego segmentar. Segmentar primero y limpiar después produce tres errores probados:
| Original | Resultado de segmentación primero | Problema |
|---|---|---|
下午14:30开会 | 下午14: / 30开会 | Los dos puntos se toman como coma, la hora se divide |
看这里:https://…/pull/7 就行 | https: / //…/pull/7 就行 | El enlace se parte en dos, la dirección se lee carácter por carácter |
大概3~5天 | 大概35天 | ~ se elimina como decoración, se lee «treinta y cinco días» |
El : de medio ancho solo se trata como coma si va seguido de un espacio en blanco.
En streaming, mantener los límites
Lo que se convierte correctamente en una oración completa puede no ser correcto en streaming. Con entrada carácter por carácter, «工作了26年» y «80年代» se leen como «èr liù nián» y «bā líng nián dài», porque los caracteres siguientes al número aún no han llegado; el segmento -10°C se lee en inglés como está.
- Esperar 2 caracteres más después de un número antes de soltarlo; las unidades separadas por un espacio también deben reconocerse (por ejemplo, «跑了 5 » se retiene hasta que llegue
km). - Al pasar al siguiente segmento, incluir los últimos caracteres del segmento anterior (para reconocer «了 26 年» hay que ver el «了» anterior).
- Retener hasta un máximo de 200 caracteres; para bloques de código y tablas, hasta 4000 caracteres; al llegar al límite, soltar.
- Cada prueba debe ejecutarse tanto con entrada carácter por carácter como con el bloque completo, y los resultados deben ser idénticos.
Referencia rápida de reglas (ver detalles en references/rules.md)
| Categoría | Ejemplo | Pronunciación |
|---|---|---|
| Año | 2026年, 2026届, 26年见 | Dígito a dígito: èr líng èr liù nián, èr líng èr liù jiè, èr liù nián jiàn |
| Cantidad de años | 工作了26年, 80年代, 26年前 | Como cantidad: èr shí liù nián, bā shí nián dài, èr shí liù nián qián |
| Número de teléfono / código | Teléfono, código de verificación, 编号27149, 房间1201 | Dígito a dígito, el 1 se lee «yāo», pausa en guiones y espacios |
| Número de cinco dígitos | 95588 / 价格 12999 | Sin clasificador, no termina en 0, no es precedido por dinero → número; de lo contrario, como cantidad |
| Lenguaje de Internet | 666, 2333, 520 solos / 666个赞, 花了888 | Dígito a dígito / como cantidad (solo se reconoce una lista fija) |
| 2 antes de unidad | 2个, 2L, 2℃ | Liǎng ge, liǎng shēng; excepto temperatura: èr shèshìdù |
| Unidades | 60km/h, 3kWh, 5min, 10mg | Liùshí gōnglǐ měi xiǎoshí, sān dù diàn, wǔ fēnzhōng, shí háokè |
| Unidades que no se convierten | 4g网络, 5A景区, 16GB | Se dejan como están para el motor |
| Rango, porcentaje | 3~5%, 3-5km, -1.5~2 | Bǎi fēn zhī sān dào wǔ, sān dào wǔ gōnglǐ, fù yī diǎn wǔ dào èr |
| Hora | 14:30, 13:00, 9:05 | Shísì diǎn bàn, shísān diǎn, jiǔ diǎn líng wǔ (formato 24 horas) |
| Fecha, fracción | 10/26 交 / 有7/12的观众 | Shí yuè èrshíliù hào / shí'èr fēn zhī qī (si precede «有, 占, 约» se trata como fracción) |
| Símbolos | #15, 2x, x3, 100w | Shíwǔ hào, liǎng bèi, chéng sān, yī bǎi wàn |
Usar los casos de prueba para verificar
references/golden-cases.json contiene 115 casos de bloque completo y 24 casos de streaming, todos verificados por el proyecto original. scripts/check-tn.mjs los usa para probar tu implementación:
node scripts/check-tn.mjs ./my-tn.mjs # Exporta el módulo normalize(text) y (opcionalmente) createStream()
node scripts/check-tn.mjs ./my-tn.mjs --only 号码 # Solo ver un grupo
- La comparación ignora espacios en blanco y signos de puntuación comunes, solo compara la pronunciación. Los casos con
noteson «de referencia», solo se informan sin contar como fallo. - Al escribir un nuevo TN, usarlo como línea de aceptación; al cambiar reglas, ejecutarlo para evitar arreglar algo y romper otra cosa.
- Las pronunciaciones controvertidas (por ejemplo, «这台电脑 12999» se trata como número) el proyecto original las acepta. Si tu producto quiere otra pronunciación, cambia el caso de prueba y escribe el motivo al lado.
Cómo depurar errores de pronunciación
Se dividen en cuatro categorías: no se escribió la pronunciación antes de leer; los homófonos los adivina el modelo; la grabación de referencia tiene problemas; aleatorio (con volver a muestrear basta).
Usa la misma voz y dos voces diferentes para leer la misma oración: si todas las voces se equivocan en el mismo carácter, añade una regla en la tabla de reemplazo de pronunciación; si solo una voz suena extraña, cambia de voz. Registra siempre el texto original y la pronunciación convertida en un log (jsonl) para poder consultarlo después.
Ingeniería (ver detalles en references/engines.md)
- Latencia de la primera frase: El primer segmento se corta hasta la primera coma. Con Matcha, 0.65 segundos después de que llegue el primer carácter se emite el primer audio.
- La mejora más efectiva es el caché: Frases no sintetizadas: CosyVoice primer bloque tarda 2.6–3 segundos; caché acertada: 6 milisegundos. Pre-sintetizar frases de relleno («嗯», «我想想») cuando el usuario empieza a hablar.
- Cancelar debe detener realmente la inferencia: Si se corta la conexión vLLM, se ejecuta
abort_request; de lo contrario, la siguiente frase espera 4.9 segundos (tras hacerlo, 0.8–1.7 segundos). - El inglés se omite silenciosamente: Algunos motores no leen
GitHub,PR,%; volver a verificar el inglés antes de actualizar dependencias. - Fuente de respaldo: Si la fuente principal no está lista o falla al empezar, se cambia a la de respaldo; si falla a mitad de la frase, no se relee. Que el servicio HTTP esté arriba no significa que el modelo esté cargado; ante un 503, reintentar.
Origen y mantenimiento
- Origen: Cirra (mascota de escritorio con IA, RedCarpG/cirra-deskpet) y su servicio de voz cirra-voice, octubre-noviembre de 2026.
- Código:
sidecar/src/tts/speech/(numbers.ts,units.ts,links.ts,filter.ts,overrides.ts,speech.test.ts),sidecar/src/tts/splitter.ts,sidecar/src/tts/pronunciations.ts. - Documentación:
docs/speech.md,docs/speech-tn.md(119 oraciones con triple contraste),docs/tts.md,docs/voices.md,docs/voice-chat.md,docs/voice-chat-2.md,docs/voice-install.md,docs/perf-checkup.md. - Experiencia:
lessons/chinese-tts.mden un repositorio privado.
- Código:
- Fuente:
skills/chinese-tts/en RedCarpG/redcarpg-skills. Lo que está en~/.claude/skills/es una copia instalada, no modificar directamente.references/golden-cases.jsonse extrajo el 2026-11-10 despeech.test.tsde Cirra con un script; no se sincronizan automáticamente. - Cómo modificar:
- Modificar en redcarpg-skills, mediante PR; aumentar la versión en
manifest.json, pasarnode scripts/validate.mjsy publicar (tagchinese-tts-v<versión>), luego reinstalar la copia local. Primero revisar silessons/chinese-tts.mdtiene nuevas entradas que incorporar. - Cuando cambien las reglas TN de Cirra, volver a extraer los casos de prueba (método: leer los pares
[original, pronunciación]del gruporeadNumbersy el grupo «流式» enspeech.test.ts), y usar un módulo adaptador con elsidecar/dist/tts/compilado de Cirra para ejecutarcheck-tn.mjs; deberían pasar todos. - Las cifras de los motores (latencia, tasa en tiempo real, VRAM) se midieron en octubre de 2026 en un ordenador con 8 GB de VRAM y CPU sin VNNI; si se cambia el hardware, hay que volver a medirlas.
- Modificar en redcarpg-skills, mediante PR; aumentar la versión en
- Registro de cambios:
- 2026-11-12 · 1.0.0, trasladado a redcarpg-skills y publicado públicamente. Se eliminaron rutas absolutas locales, las fuentes del proyecto se escribieron como enlaces; el contenido no cambió.
- 2026-11-10 · Cirra · Primera versión: flujo y orden, límites en streaming, referencia rápida de reglas y detalles, depuración de errores, ingeniería de motores; 139 casos de prueba y
check-tn.mjs(ejecutado con la implementación de Cirra, todos aprobados); dos prompts de prueba enevals/.