use super::result::{AnalysisResult, AnalysisFlag};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisConfidence {
pub bpm_confidence: f32,
pub key_confidence: f32,
pub grid_stability: f32,
pub overall_confidence: f32,
pub flags: Vec<AnalysisFlag>,
}
pub fn compute_confidence(result: &AnalysisResult) -> AnalysisConfidence {
log::debug!("Computing confidence scores for analysis result");
let bpm_confidence = compute_bpm_confidence(result);
let key_confidence = compute_key_confidence(result);
let grid_stability = result.grid_stability.max(0.0).min(1.0);
let overall_confidence = if bpm_confidence > 0.0 && key_confidence > 0.0 {
(bpm_confidence * 0.4 + key_confidence * 0.3 + grid_stability * 0.3)
.max(0.0)
.min(1.0)
} else if bpm_confidence > 0.0 {
bpm_confidence * 0.6
} else if key_confidence > 0.0 {
key_confidence * 0.6
} else {
0.0
};
let mut flags = result.metadata.flags.clone();
if bpm_confidence < 0.3 {
flags.push(AnalysisFlag::MultimodalBpm);
}
if key_confidence < 0.2 {
flags.push(AnalysisFlag::WeakTonality);
}
if grid_stability < 0.3 {
flags.push(AnalysisFlag::TempoVariation);
}
log::debug!(
"Confidence scores: BPM={:.3}, Key={:.3}, Grid={:.3}, Overall={:.3}",
bpm_confidence,
key_confidence,
grid_stability,
overall_confidence
);
AnalysisConfidence {
bpm_confidence,
key_confidence,
grid_stability,
overall_confidence,
flags,
}
}
impl AnalysisConfidence {
pub fn is_high_confidence(&self) -> bool {
self.overall_confidence >= 0.7
}
pub fn is_low_confidence(&self) -> bool {
self.overall_confidence < 0.5
}
pub fn is_medium_confidence(&self) -> bool {
self.overall_confidence >= 0.5 && self.overall_confidence < 0.7
}
pub fn confidence_level(&self) -> &'static str {
if self.is_high_confidence() {
"High"
} else if self.is_low_confidence() {
"Low"
} else {
"Medium"
}
}
}
fn compute_bpm_confidence(result: &AnalysisResult) -> f32 {
if result.bpm <= 0.0 {
return 0.0;
}
let base_confidence = result.bpm_confidence.max(0.0).min(1.0);
let has_bpm_warning = result.metadata.confidence_warnings.iter()
.any(|w| w.contains("BPM"));
if has_bpm_warning {
base_confidence * 0.7
} else {
base_confidence
}
}
fn compute_key_confidence(result: &AnalysisResult) -> f32 {
if result.key_confidence <= 0.0 {
return 0.0;
}
let base_confidence = result.key_confidence.max(0.0).min(1.0);
let clarity_adjustment = if result.key_clarity < 0.2 {
0.6
} else if result.key_clarity < 0.5 {
0.85
} else {
1.0
};
let has_key_warning = result.metadata.confidence_warnings.iter()
.any(|w| w.contains("key") || w.contains("Key") || w.contains("tonality"));
let warning_adjustment = if has_key_warning {
0.7
} else {
1.0
};
base_confidence * clarity_adjustment * warning_adjustment
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::result::{AnalysisResult, AnalysisMetadata, BeatGrid, Key};
fn create_test_result(
bpm: f32,
bpm_confidence: f32,
key: Key,
key_confidence: f32,
key_clarity: f32,
grid_stability: f32,
) -> AnalysisResult {
AnalysisResult {
bpm,
bpm_confidence,
key,
key_confidence,
key_clarity,
beat_grid: BeatGrid {
downbeats: vec![],
beats: vec![],
bars: vec![],
},
grid_stability,
metadata: AnalysisMetadata {
duration_seconds: 30.0,
sample_rate: 44100,
processing_time_ms: 100.0,
algorithm_version: "0.1.0-alpha".to_string(),
onset_method_consensus: 1.0,
methods_used: vec![],
flags: vec![],
confidence_warnings: vec![],
tempogram_candidates: None,
tempogram_multi_res_triggered: None,
tempogram_multi_res_used: None,
tempogram_percussive_triggered: None,
tempogram_percussive_used: None,
},
}
}
#[test]
fn test_compute_confidence_all_good() {
let result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.8,
0.7, 0.85,
);
let confidence = compute_confidence(&result);
assert_eq!(confidence.bpm_confidence, 0.9);
assert_eq!(confidence.key_confidence, 0.8);
assert_eq!(confidence.grid_stability, 0.85);
assert!((confidence.overall_confidence - 0.855).abs() < 0.01);
}
#[test]
fn test_compute_confidence_bpm_failed() {
let result = create_test_result(
0.0,
0.0,
Key::Major(0),
0.8,
0.7, 0.85,
);
let confidence = compute_confidence(&result);
assert_eq!(confidence.bpm_confidence, 0.0);
assert_eq!(confidence.key_confidence, 0.8);
assert_eq!(confidence.grid_stability, 0.85);
assert!((confidence.overall_confidence - 0.48).abs() < 0.01);
}
#[test]
fn test_compute_confidence_key_failed() {
let result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.0,
0.0, 0.85,
);
let confidence = compute_confidence(&result);
assert_eq!(confidence.bpm_confidence, 0.9);
assert_eq!(confidence.key_confidence, 0.0);
assert_eq!(confidence.grid_stability, 0.85);
assert!((confidence.overall_confidence - 0.54).abs() < 0.01);
}
#[test]
fn test_compute_confidence_all_failed() {
let result = create_test_result(
0.0,
0.0,
Key::Major(0),
0.0,
0.0, 0.0,
);
let confidence = compute_confidence(&result);
assert_eq!(confidence.bpm_confidence, 0.0);
assert_eq!(confidence.key_confidence, 0.0);
assert_eq!(confidence.grid_stability, 0.0);
assert_eq!(confidence.overall_confidence, 0.0);
}
#[test]
fn test_compute_confidence_with_warnings() {
let mut result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.8,
0.7, 0.85,
);
result.metadata.confidence_warnings.push(
"BPM detection failed: insufficient onsets".to_string()
);
let confidence = compute_confidence(&result);
assert!(confidence.bpm_confidence < 0.9);
assert!(confidence.bpm_confidence > 0.0);
}
#[test]
fn test_compute_confidence_clamping() {
let result = create_test_result(
120.0,
1.5, Key::Major(0),
-0.5, 0.7, 2.0, );
let confidence = compute_confidence(&result);
assert!(confidence.bpm_confidence <= 1.0);
assert!(confidence.key_confidence >= 0.0);
assert!(confidence.grid_stability <= 1.0);
assert!(confidence.overall_confidence >= 0.0);
assert!(confidence.overall_confidence <= 1.0);
}
#[test]
fn test_confidence_helper_methods() {
let result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.8,
0.7,
0.85,
);
let confidence = compute_confidence(&result);
assert!(confidence.is_high_confidence());
assert!(!confidence.is_low_confidence());
assert!(!confidence.is_medium_confidence());
assert_eq!(confidence.confidence_level(), "High");
let low_result = create_test_result(
0.0,
0.0,
Key::Major(0),
0.0,
0.0,
0.0,
);
let low_confidence = compute_confidence(&low_result);
assert!(low_confidence.is_low_confidence());
assert!(!low_confidence.is_high_confidence());
assert_eq!(low_confidence.confidence_level(), "Low");
}
#[test]
fn test_key_clarity_adjustment() {
let high_clarity_result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.8,
0.7, 0.85,
);
let low_clarity_result = create_test_result(
120.0,
0.9,
Key::Major(0),
0.8,
0.1, 0.85,
);
let high_conf = compute_confidence(&high_clarity_result);
let low_conf = compute_confidence(&low_clarity_result);
assert!(low_conf.key_confidence < high_conf.key_confidence);
}
}