Spaces:
Running
Running
File size: 1,444 Bytes
414dc55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | """Deterministic per-suspect voice assignment.
A suspect maps to a stable Supertonic speaker (M1-M5 for men, F1-F5 for women) and
prosody so they always sound the same - distinct cast voices from one on-device model.
"""
from __future__ import annotations
import hashlib
from ..schemas.suspect import Suspect, VoiceAssignment
def _seed(*parts: str) -> int:
return int.from_bytes(hashlib.sha256("|".join(parts).encode("utf-8")).digest()[:4], "big")
def assign_voice(suspect: Suspect, *, engine: str = "supertonic") -> VoiceAssignment:
"""Assign a stable, GENDER-MATCHED voice. The Supertonic provider maps speaker_id
0-4 to male voices (M1-M5) and 5-9 to female voices (F1-F5)."""
seed = _seed(suspect.sus_id, suspect.name)
gender = ((suspect.visual.gender if suspect.visual else "") or "").lower()
if gender.startswith("f"):
speaker_id = 5 + (seed % 5) # female voices F1-F5
elif gender.startswith("m"):
speaker_id = seed % 5 # male voices M1-M5
else:
speaker_id = seed % 10 # unknown -> any of the 10
# Calmer, slower delivery for high-composure suspects; edgier for evasive ones.
length_scale = round(0.95 + (seed % 20) / 100.0, 3) # 0.95 - 1.15
noise_w = round(0.70 + ((seed >> 8) % 20) / 100.0, 3) # 0.70 - 0.90
return VoiceAssignment(
engine=engine, speaker_id=speaker_id, length_scale=length_scale, noise_w=noise_w,
)
|