pub fn compute_key_clarity(scores: &[(crate::analysis::result::Key, f32)]) -> f32 {
log::debug!("Computing key clarity from {} scores", scores.len());
if scores.is_empty() {
return 0.0;
}
if scores.len() < 2 {
return 0.0;
}
let score_values: Vec<f32> = scores.iter().map(|(_, score)| *score).collect();
let best_score = score_values[0];
let sum: f32 = score_values.iter().sum();
let average_score = sum / score_values.len() as f32;
let min_score = *score_values.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
let max_score = *score_values.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
let range = max_score - min_score;
if range > 1e-10 {
let clarity = (best_score - average_score) / range;
clarity.max(0.0).min(1.0)
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::result::Key;
#[test]
fn test_compute_key_clarity_empty() {
let clarity = compute_key_clarity(&[]);
assert_eq!(clarity, 0.0);
}
#[test]
fn test_compute_key_clarity_single() {
let scores = vec![(Key::Major(0), 0.8)];
let clarity = compute_key_clarity(&scores);
assert_eq!(clarity, 0.0);
}
#[test]
fn test_compute_key_clarity_high() {
let scores = vec![
(Key::Major(0), 0.9),
(Key::Major(1), 0.3),
(Key::Major(2), 0.3),
(Key::Major(3), 0.3),
];
let clarity = compute_key_clarity(&scores);
assert!(clarity > 0.5, "High clarity should be > 0.5");
}
#[test]
fn test_compute_key_clarity_low() {
let scores = vec![
(Key::Major(0), 0.5),
(Key::Major(1), 0.48),
(Key::Major(2), 0.49),
(Key::Major(3), 0.47),
];
let clarity = compute_key_clarity(&scores);
assert!(clarity < 0.5, "Low clarity should be < 0.5");
}
#[test]
fn test_compute_key_clarity_all_same() {
let scores = vec![
(Key::Major(0), 0.5),
(Key::Major(1), 0.5),
(Key::Major(2), 0.5),
];
let clarity = compute_key_clarity(&scores);
assert_eq!(clarity, 0.0);
}
#[test]
fn test_compute_key_clarity_clamped() {
let scores = vec![
(Key::Major(0), 1.0),
(Key::Major(1), 0.0),
];
let clarity = compute_key_clarity(&scores);
assert!(clarity >= 0.0 && clarity <= 1.0);
}
}