Skip to content

speaker_id

speaker_id

L'empreinte vocale du propriétaire — pour ne répondre qu'à LUI.

Demandé le 23 août 2026, après la garde d'adresse : « je veux absolument la reconnaissance de ma voix. » La garde par le nom filtre le film et le bruit ; elle ne filtre pas un tiers qui DIT « Diapason ». L'empreinte, si.

Tout est local : le modèle d'empreinte (ONNX, ~40 Mo) tourne sur cette machine, le profil vit dans ~/.diapason/voice_profile.npz, et rien ne part nulle part. Mesuré avant d'être retenu : deux voix distinctes se séparent nettement (même voix 0,65-0,82 de similarité, voix différentes 0,30-0,39, 11 ms par empreinte).

L'ENRÔLEMENT est silencieux : les premiers tours ADRESSÉS d'une session — le nom prononcé, ou la fenêtre d'engagement — nourrissent le profil. Au cinquième, le verrou s'arme. Pas de cérémonie : la voix qui dit « Diapason » est celle du propriétaire. diapason voice reset efface et réapprend.

Le seuil (0,45) penche vers ACCEPTER le propriétaire : un faux rejet rend l'assistant sourd à son maître — pire que l'état d'avant. La comparaison prend le MAXIMUM contre chaque échantillon enrôlé, plus robuste qu'un seul centroïde quand la voix varie (matin, rhume, distance du micro).

Classes

SpeakerVerifier

SpeakerVerifier(
    *,
    model_path: Path | None = None,
    profile_path: Path | None = None,
    seuil: float = SEUIL,
)

Empreintes et verdicts. Paresseux, verrouillé, jamais bloquant.

Source code in src/diapason/speech/speaker_id.py
def __init__(
    self,
    *,
    model_path: Path | None = None,
    profile_path: Path | None = None,
    seuil: float = SEUIL,
) -> None:
    self._model_path = model_path or _chemin_modele()
    self._profile_path = profile_path or _chemin_profil()
    self._seuil = seuil
    self._extracteur = None
    self._verrou = threading.Lock()
    self._profil: Optional[np.ndarray] = None  # (n, dim), lignes normées
    self._charger_profil()
Attributes
arme property
arme: bool

Le verrou n'agit qu'une fois le profil suffisamment nourri.

Methods:
verify
verify(
    pcm16: bytes, rate: int = 16000
) -> tuple[float, bool]

(score, est-le-propriétaire). Sans profil armé : (1.0, True).

Le doute — extraction impossible, tour trop court — profite au PROPRIÉTAIRE : un faux rejet rend l'assistant sourd à son maître.

Source code in src/diapason/speech/speaker_id.py
def verify(self, pcm16: bytes, rate: int = 16000) -> tuple[float, bool]:
    """(score, est-le-propriétaire). Sans profil armé : (1.0, True).

    Le doute — extraction impossible, tour trop court — profite au
    PROPRIÉTAIRE : un faux rejet rend l'assistant sourd à son maître.
    """
    if not self.arme:
        return 1.0, True
    v = self.embed(pcm16, rate)
    if v is None or self._profil is None:
        return 1.0, True
    score = float(np.max(self._profil @ v))
    return score, score >= self._seuil