#!/usr/bin/env python3 """Duelo honesto entre dos configuraciones de voz. Por que existe esto: la primera comparacion uso UNA frase, y la segunda tres. Con esos tamaƱos se concluyo que el ajuste nuevo mejoraba... y al regenerar la cache entera salio peor. XTTS muestrea en cada generacion (do_sample, temperatura 0,65), asi que dos pasadas de la MISMA frase con la MISMA configuracion ya dan numeros distintos. Comparar pocas muestras es leer ruido. Aqui cada configuracion genera las mismas N frases R veces, en el mismo proceso y el mismo dispositivo, y se comparan las medias. Ademas se mide la dispersion DENTRO de cada configuracion: si la diferencia entre las dos es menor que lo que varia una consigo misma, no hay diferencia que defender. python duelo_ajustes.py [cuda|cpu] [repeticiones] """ import json import os import statistics import sys import tempfile import torch DISPOSITIVO = sys.argv[1] if len(sys.argv) > 1 else "cuda" REPES = int(sys.argv[2]) if len(sys.argv) > 2 else 3 BASE = os.path.dirname(os.path.abspath(__file__)) REF = os.path.join(BASE, "ref") CATALOGO = os.path.join(BASE, "..", "newelle", "data", "acciones", "acciones.json") os.environ.setdefault("COQUI_TOS_AGREED", "1") LARGO = dict(gpt_cond_len=30, gpt_cond_chunk_len=6, max_ref_length=30) VIEJA = dict( nombre="vieja (3 refs, cond 6s)", refs=[os.path.join(REF, n) for n in ("jarvis_lento_b.wav", "jarvis_lento_a.wav", "jarvis_ref.wav")], cond=dict(gpt_cond_len=6, gpt_cond_chunk_len=6, max_ref_length=30), ) NUEVA = dict( nombre="nueva (1 ref sesion, cond 30s)", refs=[os.path.join(REF, "jarvis_sesion.wav")], cond=LARGO, ) GEN = dict(temperature=0.65, repetition_penalty=2.0, speed=1.0) def frases(n=8): with open(CATALOGO, encoding="utf-8") as f: d = json.load(f) acc = d.get("acciones", d if isinstance(d, list) else []) return sorted({a["acuse"] for a in acc if a.get("acuse")})[:n] def main(): from TTS.api import TTS import soundfile as sf api = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(DISPOSITIVO) modelo = api.synthesizer.tts_model def emb(ruta): _, e = modelo.get_conditioning_latents(audio_path=[ruta], **LARGO) return e patron = emb(os.path.join(REF, "jarvis_lento_b.wav")) def parecido(ruta): return float(torch.nn.functional.cosine_similarity( patron.squeeze().float(), emb(ruta).squeeze().float(), dim=0)) textos = frases() print(f" {len(textos)} frases x {REPES} repeticiones por configuracion\n") resumen = {} with tempfile.TemporaryDirectory() as tmp: for cfg in (VIEJA, NUEVA): refs = [r for r in cfg["refs"] if os.path.exists(r)] gpt, hablante = modelo.get_conditioning_latents( audio_path=refs, **cfg["cond"]) puntos = [] for i, texto in enumerate(textos): for r in range(REPES): out = modelo.inference( text=texto, language="es", gpt_cond_latent=gpt, speaker_embedding=hablante, enable_text_splitting=True, **GEN) d = os.path.join(tmp, f"{id(cfg)}_{i}_{r}.wav") sf.write(d, out["wav"], 24000) puntos.append(parecido(d)) resumen[cfg["nombre"]] = puntos print(f" {cfg['nombre']:32s} media {statistics.mean(puntos):.4f}" f" desv {statistics.pstdev(puntos):.4f}" f" ({len(puntos)} muestras)", flush=True) a, b = list(resumen.values()) ma, mb = statistics.mean(a), statistics.mean(b) ruido = (statistics.pstdev(a) + statistics.pstdev(b)) / 2 print(f"\n diferencia entre configuraciones: {abs(mb-ma):.4f}") print(f" variacion dentro de cada una: {ruido:.4f}") if abs(mb - ma) < ruido: print("\n => la diferencia es MENOR que el ruido: no hay mejora que defender") else: gana = list(resumen)[1] if mb > ma else list(resumen)[0] print(f"\n => gana: {gana}") if __name__ == "__main__": main()