autoace / src /fusion.py
edwixx's picture
Deploy AutoAce voice tone dashboard
c3eb7ea verified
Raw
History Blame Contribute Delete
3.27 kB
from .features import AcousticFeatures
from .schema import AudioQuality, EmotionalIntensity, EmotionalTone, NoiseSeverity, Result
def _intensity(rms_std: float, ser_margin: float | None) -> EmotionalIntensity:
margin = ser_margin or 0.0
if rms_std >= 0.085 or margin >= 0.5:
return EmotionalIntensity.high
if rms_std >= 0.03 or margin >= 0.15:
return EmotionalIntensity.medium
return EmotionalIntensity.low
def _tone(
ser_top: str | None, ser_margin: float | None, intensity: EmotionalIntensity
) -> EmotionalTone:
margin = ser_margin or 0.0
if ser_top is None or margin < 0.15:
return EmotionalTone.neutral
if ser_top == "hap":
return EmotionalTone.satisfied
if ser_top == "neu":
return EmotionalTone.neutral
if ser_top == "sad":
return EmotionalTone.distressed if intensity == EmotionalIntensity.high else EmotionalTone.upset
if ser_top == "ang":
if intensity == EmotionalIntensity.high:
return EmotionalTone.upset
if intensity == EmotionalIntensity.medium:
return EmotionalTone.frustrated
return EmotionalTone.neutral
return EmotionalTone.neutral
def _noise_severity(snr_gap_db: float | None) -> NoiseSeverity:
if snr_gap_db is None:
return NoiseSeverity.none
if snr_gap_db >= 18:
return NoiseSeverity.none
if snr_gap_db >= 11:
return NoiseSeverity.low
if snr_gap_db >= 4:
return NoiseSeverity.medium
return NoiseSeverity.high
def _audio_quality(clipping_ratio: float, rms_mean: float) -> AudioQuality:
if clipping_ratio > 0.02 or rms_mean < 0.002:
return AudioQuality.severely_impaired
if clipping_ratio > 0.002 or rms_mean < 0.01:
return AudioQuality.slightly_impaired
return AudioQuality.clear
def fuse(
feat: AcousticFeatures,
snr_gap_db: float | None,
ser_categorical: list[dict] | None,
dimensional_emotion: dict | None,
noise_type: str,
overlap_s: float | None,
longest_gap_s: float,
) -> Result:
ser_top = ser_categorical[0]["label"] if ser_categorical else None
ser_margin = (
ser_categorical[0]["score"] - ser_categorical[1]["score"]
if ser_categorical and len(ser_categorical) > 1
else None
)
intensity = _intensity(feat.rms_std, ser_margin)
tone = _tone(ser_top, ser_margin, intensity)
severity = _noise_severity(snr_gap_db)
present = severity != NoiseSeverity.none
quality = _audio_quality(feat.clipping_ratio, feat.rms_mean)
overlap_present = (overlap_s or 0.0) >= 2.0
long_silence = longest_gap_s > 15.0
confidence = 0.5 + (ser_margin or 0.0)
if ser_categorical is None:
confidence -= 0.15
if dimensional_emotion is None:
confidence -= 0.05
confidence = max(0.35, min(0.95, confidence))
return Result(
emotional_tone=tone,
emotional_intensity=intensity,
background_noise_present=present,
background_noise_type=noise_type if present else "",
background_noise_severity=severity,
audio_quality=quality,
speaker_overlap_present=overlap_present,
long_silence_present=long_silence,
confidence=round(confidence, 2),
)