sva_samples/measure/
stereo.rs1use crate::measure::envelope::rms;
4
5pub const RAIL_DB: f64 = 160.0;
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct StereoFrame {
9 pub t_secs: f64,
10 pub correlation: f64,
11 pub mid_rms: f64,
12 pub side_rms: f64,
13 pub width: f64,
14 pub balance_db: f64,
15 pub mono_db: f64,
16}
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct StereoImage {
20 pub channels: usize,
21 pub overall: StereoFrame,
22 pub frames: Vec<StereoFrame>,
23}
24
25pub fn analyze(
26 planes: &[&[f64]],
27 channels: usize,
28 sample_rate: f64,
29 start_secs: f64,
30 frame_secs: f64,
31) -> StereoImage {
32 let (l, r) = (planes[0], planes[1]);
33 let stride = ((frame_secs * sample_rate).round() as usize).max(1);
34 let frames = l
35 .chunks(stride)
36 .zip(r.chunks(stride))
37 .enumerate()
38 .map(|(n, (li, ri))| measure(li, ri, start_secs + (n * stride) as f64 / sample_rate))
39 .collect();
40 StereoImage {
41 channels,
42 overall: measure(l, r, start_secs),
43 frames,
44 }
45}
46
47fn measure(l: &[f64], r: &[f64], t_secs: f64) -> StereoFrame {
48 let mid: Vec<f64> = l.iter().zip(r).map(|(a, b)| (a + b) / 2.0).collect();
49 let side: Vec<f64> = l.iter().zip(r).map(|(a, b)| (a - b) / 2.0).collect();
50 let (mid_rms, side_rms) = (rms(&mid), rms(&side));
51 let (l_rms, r_rms) = (rms(l), rms(r));
52 StereoFrame {
53 t_secs,
54 correlation: pearson(l, r),
55 mid_rms,
56 side_rms,
57 width: ratio(side_rms, mid_rms),
58 balance_db: db(r_rms, l_rms),
59 mono_db: db(mid_rms, (l_rms + r_rms) / 2.0),
60 }
61}
62
63fn ratio(a: f64, b: f64) -> f64 {
65 match () {
66 () if b > 0.0 => a / b,
67 () if a > 0.0 => f64::INFINITY,
68 () => 0.0,
69 }
70}
71
72fn db(a: f64, b: f64) -> f64 {
74 match ratio(a, b) {
75 r if r > 0.0 => (20.0 * r.log10()).clamp(-RAIL_DB, RAIL_DB),
76 _ if a > 0.0 => RAIL_DB,
77 _ if b > 0.0 => -RAIL_DB,
78 _ => 0.0,
79 }
80}
81
82fn pearson(l: &[f64], r: &[f64]) -> f64 {
84 let n = l.len().min(r.len());
85 if n == 0 {
86 return 1.0;
87 }
88 let mean = |v: &[f64]| v[..n].iter().copied().sum::<f64>() / n as f64;
89 let (lm, rm) = (mean(l), mean(r));
90 let (mut cov, mut lv, mut rv) = (0.0, 0.0, 0.0);
91 for i in 0..n {
92 let (a, b) = (l[i] - lm, r[i] - rm);
93 cov += a * b;
94 lv += a * a;
95 rv += b * b;
96 }
97 if lv <= 0.0 || rv <= 0.0 {
98 return 1.0;
99 }
100 (cov / (lv * rv).sqrt()).clamp(-1.0, 1.0)
101}