plato_midi_bridge/
style.rs1use crate::EisensteinLattice;
3
4#[derive(Debug, Clone)]
5pub struct StyleVector {
6 pub dims: [f64; 109],
7}
8
9impl StyleVector {
10 pub fn new(data: &[f64]) -> Self {
11 let mut dims = [0.0; 109];
12 let n = data.len().min(109);
13 dims[..n].copy_from_slice(&data[..n]);
14 StyleVector { dims }
15 }
16
17 pub fn cosine_similarity(&self, other: &StyleVector) -> f64 {
19 let dot: f64 = self.dims.iter().zip(other.dims.iter()).map(|(a, b)| a * b).sum();
20 let norm1: f64 = self.dims.iter().map(|a| a * a).sum::<f64>().sqrt();
21 let norm2: f64 = other.dims.iter().map(|a| a * a).sum::<f64>().sqrt();
22 if norm1 == 0.0 || norm2 == 0.0 { return 0.0; }
23 (dot / (norm1 * norm2)).clamp(0.0, 1.0)
24 }
25
26 pub fn euclidean_distance(&self, other: &StyleVector) -> f64 {
28 self.dims.iter().zip(other.dims.iter())
29 .map(|(a, b)| (a - b) * (a - b))
30 .sum::<f64>()
31 .sqrt()
32 }
33
34 pub fn to_5d(&self) -> [f64; 5] {
41 let pitch = self.dims[..48].iter().sum::<f64>() / 48.0 * 12.0;
42 let timing = self.dims[48..56].iter().sum::<f64>() / 8.0 * 100.0;
43 let velocity = self.dims[56..64].iter().sum::<f64>() / 8.0;
44 let articulation = 1.0 - (self.dims[64..72].iter().sum::<f64>() / 8.0);
45 let timbre = self.dims[72..80].iter().sum::<f64>() / 8.0;
46 [pitch, timing, velocity, articulation, timbre]
47 }
48
49 pub fn to_12d(&self) -> [f64; 12] {
51 let mut coupling = [0.0; 12];
52 for i in 0..12 {
53 coupling[i] = self.dims[i.min(108)];
54 }
55 coupling
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::EisensteinLattice;
63
64 #[test]
65 fn test_style_vector_creation() {
66 let data = vec![0.5; 109];
67 let v = StyleVector::new(&data);
68 assert!((v.dims[0] - 0.5).abs() < 1e-10);
69 }
70
71 #[test]
72 fn test_cosine_similarity_identical() {
73 let data: Vec<f64> = (0..109).map(|i| (i as f64) / 109.0).collect();
74 let v1 = StyleVector::new(&data);
75 let v2 = StyleVector::new(&data);
76 assert!((v1.cosine_similarity(&v2) - 1.0).abs() < 1e-10);
77 }
78
79 #[test]
80 fn test_5d_reduction() {
81 let data: Vec<f64> = (0..109).map(|i| (i as f64) / 50.0).collect();
82 let v = StyleVector::new(&data);
83 let d5 = v.to_5d();
84 assert_eq!(d5.len(), 5);
85 for val in &d5 {
86 assert!(val.is_finite());
87 }
88 }
89
90 #[test]
91 fn test_eisenstein_chamber_from_style() {
92 let data: Vec<f64> = (0..109).map(|i| if i < 12 { (i+1) as f64 / 12.0 } else { 0.0 }).collect();
93 let v = StyleVector::new(&data);
94 let coupling = v.to_12d();
95 let chamber = EisensteinLattice::chamber(&coupling);
96 assert!(chamber < 12);
97 }
98
99 #[test]
100 fn test_orthogonal_vectors_distant() {
101 let v1 = StyleVector::new(&[1.0; 109]);
102 let v2 = StyleVector::new(&[0.0; 109]);
103 let sim = v1.cosine_similarity(&v2);
104 assert!((sim - 0.0).abs() < 1e-10);
105 }
106}