#!/usr/bin/env python3
"""Compara el original y el clon en lo que el oido nota, no solo en timbre.

La puntuacion de parecido que se venia usando es el coseno entre embeddings de
hablante: mide TIMBRE. Es ciega al tono y al ritmo, y una voz puede tener el
timbre correcto y aun asi "no parecerse nada" porque habla mas rapido, mas
agudo o mas plano que el original. Esto mide eso.

  - tono medio y rango (F0): si el clon habla medio tono por encima, se nota
    muchisimo mas que una diferencia de timbre
  - variacion del tono: un original expresivo contra un clon monotono suena a
    otra persona aunque el timbre coincida
  - velocidad: silabas por segundo, aproximada por picos de energia
  - brillo: centroide espectral, delata si el clon suena mas apagado

    python analiza_voz.py fichero.wav [fichero2.wav ...]
"""
import sys

import numpy as np


def carga(ruta, sr=22050):
    import librosa
    y, _ = librosa.load(ruta, sr=sr, mono=True)
    return y, sr


def analiza(ruta):
    import librosa
    y, sr = carga(ruta)

    # F0 con pyin, que es lento pero fiable en voz
    f0, sonoro, _ = librosa.pyin(
        y, sr=sr, fmin=50, fmax=400, frame_length=2048)
    f0v = f0[~np.isnan(f0)]

    # velocidad: picos del envelope de energia ~ silabas
    env = librosa.onset.onset_strength(y=y, sr=sr)
    picos = librosa.util.peak_pick(env, pre_max=3, post_max=3, pre_avg=3,
                                   post_avg=5, delta=0.2, wait=4)
    dur = len(y) / sr
    silabas_s = len(picos) / dur if dur else 0

    # brillo
    cent = float(np.mean(librosa.feature.spectral_centroid(y=y, sr=sr)))

    # proporcion de voz sonora: cuanto del audio es habla con tono
    prop = float(np.mean(sonoro)) if sonoro is not None else 0.0

    return {
        "dur": dur,
        "f0_medio": float(np.median(f0v)) if len(f0v) else 0.0,
        "f0_p10": float(np.percentile(f0v, 10)) if len(f0v) else 0.0,
        "f0_p90": float(np.percentile(f0v, 90)) if len(f0v) else 0.0,
        "f0_desv": float(np.std(f0v)) if len(f0v) else 0.0,
        "silabas_s": silabas_s,
        "brillo": cent,
        "sonoro": prop,
    }


def main():
    print(f"{'fichero':34s} {'dur':>6s} {'tono':>7s} {'rango':>11s} "
          f"{'varia':>7s} {'sil/s':>6s} {'brillo':>7s}")
    print("-" * 84)
    for ruta in sys.argv[1:]:
        try:
            a = analiza(ruta)
        except Exception as e:
            print(f"{ruta[:34]:34s} error: {e}")
            continue
        nombre = ruta.split("/")[-1][:34]
        print(f"{nombre:34s} {a['dur']:5.1f}s {a['f0_medio']:6.1f}Hz "
              f"{a['f0_p10']:4.0f}-{a['f0_p90']:<4.0f}Hz {a['f0_desv']:6.1f} "
              f"{a['silabas_s']:6.2f} {a['brillo']:6.0f}Hz")


if __name__ == "__main__":
    main()
