Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import librosa | |
| class ForensicsAnalyzer: | |
| """ | |
| Independent Audio Forensics Engine. | |
| Evaluates raw DSP features to compute Voice Naturalness and Audio Quality. | |
| Completely independent of deep learning models and classifier predictions. | |
| """ | |
| def analyze(waveform: np.ndarray, sr: int, ai_probability: float = 0.0) -> dict: | |
| # ai_probability is kept in the signature strictly for API compatibility. | |
| # It is NEVER referenced or used in any score, characteristic, or calculation. | |
| # --- 1. Multi-Window Analysis --- | |
| window_duration = 5 * sr | |
| if len(waveform) > 15 * sr: | |
| # Extract 3 representative windows | |
| w1 = waveform[:window_duration] | |
| mid_start = len(waveform) // 2 - window_duration // 2 | |
| w2 = waveform[mid_start : mid_start + window_duration] | |
| w3 = waveform[-window_duration:] | |
| windows = [w1, w2, w3] | |
| elif len(waveform) > window_duration: | |
| w1 = waveform[:window_duration] | |
| w2 = waveform[-window_duration:] | |
| windows = [w1, w2] | |
| else: | |
| windows = [waveform] | |
| metrics = { | |
| "pitch_std": [], | |
| "pitch_range": [], | |
| "mean_pitch": [], | |
| "pause_ratio": [], | |
| "speech_ratio": [], | |
| "sc_mean": [], | |
| "sb_mean": [], | |
| "rms_cons": [], | |
| "zcr_cons": [] | |
| } | |
| for w in windows: | |
| if len(w) == 0: | |
| continue | |
| # --- Adaptive Pause & Speech Detection --- | |
| # Using 20th percentile of RMS energy as adaptive baseline statistic | |
| rms = librosa.feature.rms(y=w)[0] | |
| if len(rms) > 0: | |
| p20 = np.percentile(rms, 20) | |
| p95 = np.percentile(rms, 95) | |
| # Adaptive silence threshold: p20 + 10% of dynamic range | |
| adaptive_threshold = p20 + 0.10 * max(0.0, p95 - p20) | |
| pause_ratio = float(np.mean(rms < adaptive_threshold)) | |
| speech_ratio = 1.0 - pause_ratio | |
| # RMS Consistency via percentile-based normalization | |
| # If dynamic range or variance is smooth, consistency is high | |
| rms_cv = np.std(rms) / (np.mean(rms) + 1e-6) | |
| rms_cons = float(np.clip(100.0 * np.exp(-1.5 * rms_cv), 0.0, 100.0)) | |
| else: | |
| adaptive_threshold = 0.0 | |
| pause_ratio = 1.0 | |
| speech_ratio = 0.0 | |
| rms_cons = 0.0 | |
| metrics["pause_ratio"].append(pause_ratio) | |
| metrics["speech_ratio"].append(speech_ratio) | |
| metrics["rms_cons"].append(rms_cons) | |
| # --- Pitch Dynamics --- | |
| try: | |
| f0 = librosa.yin(w, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'), sr=sr) | |
| min_len = min(len(f0), len(rms)) | |
| f0_trimmed = f0[:min_len] | |
| rms_trimmed = rms[:min_len] | |
| # Filter strictly for active speech frames above adaptive threshold | |
| valid_f0 = f0_trimmed[rms_trimmed >= adaptive_threshold] | |
| if len(valid_f0) > 5: | |
| f0_std = float(np.std(valid_f0)) | |
| f0_mean = float(np.mean(valid_f0)) | |
| # Pitch range: 90th percentile - 10th percentile to avoid spurious outlier jumps | |
| f0_range = float(np.percentile(valid_f0, 90) - np.percentile(valid_f0, 10)) | |
| metrics["pitch_std"].append(f0_std) | |
| metrics["pitch_range"].append(f0_range) | |
| metrics["mean_pitch"].append(f0_mean) | |
| else: | |
| metrics["pitch_std"].append(0.0) | |
| metrics["pitch_range"].append(0.0) | |
| metrics["mean_pitch"].append(0.0) | |
| except Exception: | |
| metrics["pitch_std"].append(0.0) | |
| metrics["pitch_range"].append(0.0) | |
| metrics["mean_pitch"].append(0.0) | |
| # --- Spectral & Temporal Quality --- | |
| sc = librosa.feature.spectral_centroid(y=w, sr=sr)[0] | |
| sb = librosa.feature.spectral_bandwidth(y=w, sr=sr)[0] | |
| zcr = librosa.feature.zero_crossing_rate(y=w)[0] | |
| metrics["sc_mean"].append(float(np.mean(sc))) | |
| metrics["sb_mean"].append(float(np.mean(sb))) | |
| # ZCR consistency: exponential decay normalization of zero-crossing variation | |
| zcr_cv = np.std(zcr) / (np.mean(zcr) + 1e-6) | |
| zcr_cons = float(np.clip(100.0 * np.exp(-1.0 * zcr_cv), 0.0, 100.0)) | |
| metrics["zcr_cons"].append(zcr_cons) | |
| # Safely average all windowed metrics | |
| avg_pitch_std = float(np.mean(metrics["pitch_std"])) if metrics["pitch_std"] else 0.0 | |
| avg_pitch_range = float(np.mean(metrics["pitch_range"])) if metrics["pitch_range"] else 0.0 | |
| avg_mean_pitch = float(np.mean(metrics["mean_pitch"])) if metrics["mean_pitch"] else 0.0 | |
| avg_pause_ratio = float(np.mean(metrics["pause_ratio"])) if metrics["pause_ratio"] else 0.0 | |
| avg_speech_ratio = float(np.mean(metrics["speech_ratio"])) if metrics["speech_ratio"] else 0.0 | |
| avg_sc_mean = float(np.mean(metrics["sc_mean"])) if metrics["sc_mean"] else 0.0 | |
| avg_sb_mean = float(np.mean(metrics["sb_mean"])) if metrics["sb_mean"] else 0.0 | |
| avg_rms_cons = float(np.mean(metrics["rms_cons"])) if metrics["rms_cons"] else 0.0 | |
| avg_zcr_cons = float(np.mean(metrics["zcr_cons"])) if metrics["zcr_cons"] else 0.0 | |
| # --- 2. PURE DSP SCORING: VOICE NATURALNESS --- | |
| # Depends strictly on: pitch variation, pitch range, pause ratio | |
| # Non-linear Gaussian/exponential normalization across full 0-100 spectrum | |
| # 1) Pitch variation score (ideal conversational speech std is ~35 Hz) | |
| pitch_std_score = float(np.clip(100.0 * np.exp(-((avg_pitch_std - 38.0) / 30.0)**2), 0.0, 100.0)) | |
| # 2) Pitch range score (ideal conversational range is ~80-160 Hz between 10th and 90th percentile) | |
| pitch_range_score = float(np.clip(100.0 * (1.0 - np.exp(-avg_pitch_range / 50.0)), 0.0, 100.0)) | |
| # 3) Pause ratio score (ideal conversational pause ratio is ~15% to 30%) | |
| pause_score = float(np.clip(100.0 * np.exp(-((avg_pause_ratio - 0.22) / 0.16)**2), 0.0, 100.0)) | |
| # Combine purely from DSP weights without artificial score clamping | |
| raw_naturalness = 0.40 * pitch_std_score + 0.35 * pitch_range_score + 0.25 * pause_score | |
| voice_naturalness_val = int(np.round(np.clip(raw_naturalness, 0.0, 100.0))) | |
| # --- 3. PURE DSP SCORING: AUDIO QUALITY --- | |
| # Depends strictly on: spectral centroid, spectral bandwidth, RMS consistency, ZCR consistency | |
| # 1) Spectral Centroid score (ideal voice clarity is ~1800-3000 Hz) | |
| sc_score = float(np.clip(100.0 * np.exp(-((avg_sc_mean - 2300.0) / 1400.0)**2), 0.0, 100.0)) | |
| # 2) Spectral Bandwidth score (square-root normalization for rich harmonic structure) | |
| sb_score = float(np.clip(100.0 * np.sqrt(min(avg_sb_mean, 2600.0) / 2600.0), 0.0, 100.0)) | |
| # Combine purely from DSP quality components | |
| raw_quality = 0.30 * sc_score + 0.30 * sb_score + 0.20 * avg_rms_cons + 0.20 * avg_zcr_cons | |
| audio_quality_val = int(np.round(np.clip(raw_quality, 0.0, 100.0))) | |
| # --- 4. DETECTED CHARACTERISTICS (DERIVED EXCLUSIVELY FROM DSP) --- | |
| characteristics = [] | |
| # Pitch evaluations | |
| if avg_pitch_std < 12.0: | |
| characteristics.append("⚠ Voice sounds flat or monotone") | |
| elif avg_pitch_std > 70.0: | |
| characteristics.append("⚠ Unnatural jumps in voice pitch") | |
| else: | |
| characteristics.append("✓ Lively and natural speaking voice") | |
| # Rhythm evaluations | |
| if avg_pause_ratio < 0.08: | |
| characteristics.append("⚠ Robotic speaking rhythm with no pauses") | |
| elif avg_pause_ratio > 0.45: | |
| characteristics.append("⚠ Unusually long awkward silences") | |
| else: | |
| characteristics.append("✓ Natural conversational breathing and pauses") | |
| # Spectral frequency evaluations | |
| if avg_sb_mean < 1100.0: | |
| characteristics.append("⚠ Audio sounds muffled or compressed") | |
| elif avg_sc_mean >= 1500.0: | |
| characteristics.append("✓ Crisp and clear recording quality") | |
| else: | |
| characteristics.append("⚠ Sound is muffled or lacks detail") | |
| # Recording consistency evaluations | |
| if avg_rms_cons >= 70.0 and avg_zcr_cons >= 70.0: | |
| characteristics.append("✓ Steady volume and clean recording") | |
| elif avg_rms_cons < 50.0: | |
| characteristics.append("⚠ Volume levels jump around during recording") | |
| characteristics = characteristics[:4] | |
| # --- 5. STRUCTURED RESULTS & CONFIDENCE --- | |
| # Determine confidence and reasons based strictly on DSP signal characteristics | |
| if voice_naturalness_val >= 75: | |
| nat_conf = "High" | |
| nat_reason = "Sounds like a real person talking naturally" | |
| elif voice_naturalness_val >= 50: | |
| nat_conf = "Medium" | |
| nat_reason = "Normal speaking rhythm and voice tone" | |
| else: | |
| nat_conf = "High" if len(windows) >= 2 else "Medium" | |
| nat_reason = "Voice sounds flat, robotic, or artificial" | |
| if audio_quality_val >= 75: | |
| qual_conf = "High" | |
| qual_reason = "Clear and steady microphone sound" | |
| elif audio_quality_val >= 50: | |
| qual_conf = "Medium" | |
| qual_reason = "Good audio clarity and balance" | |
| else: | |
| qual_conf = "Medium" | |
| qual_reason = "Muffled sound or uneven volume levels" | |
| structured_naturalness = { | |
| "score": voice_naturalness_val, | |
| "confidence": nat_conf, | |
| "reason": nat_reason | |
| } | |
| structured_quality = { | |
| "score": audio_quality_val, | |
| "confidence": qual_conf, | |
| "reason": qual_reason | |
| } | |
| # --- 6. ADVANCED ANALYSIS (COLLAPSED & UNDERSTANDABLE) --- | |
| advanced = { | |
| "Mean Pitch": f"{avg_mean_pitch:.1f} Hz", | |
| "Pitch Variation": f"{avg_pitch_std:.1f} Hz", | |
| "Spectral Profile": f"{int(avg_sc_mean)} Hz", | |
| "Recording Consistency": f"{int(avg_rms_cons)}%" | |
| } | |
| return { | |
| "voice_naturalness": structured_naturalness, | |
| "audio_quality": structured_quality, | |
| "characteristics": characteristics, | |
| "advanced": advanced | |
| } | |