Skip to main content

sva_samples/measure/
sliding.rs

1// Concern: sliding windows over an envelope on the shared time base, why none fits, stats | Non-concern: what a window reads | IO: (held, rates, hop, window) -> spans
2
3use super::harmony::dissonance::Extreme;
4
5/// `least_secs` is twice the longest period read.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum Unread {
8    WindowUnderTwoPeriods {
9        window_secs: f64,
10        least_secs: f64,
11    },
12    TargetShorterThanWindow {
13        held_secs: f64,
14        window_secs: f64,
15        least_secs: f64,
16    },
17}
18
19pub fn fits(
20    held: usize,
21    sample_rate: f64,
22    window_secs: f64,
23    longest_period_secs: f64,
24) -> Result<(), Unread> {
25    let least_secs = 2.0 * longest_period_secs;
26    if window_secs < least_secs {
27        return Err(Unread::WindowUnderTwoPeriods {
28            window_secs,
29            least_secs,
30        });
31    }
32    let held_secs = held as f64 / sample_rate;
33    if held_secs < window_secs {
34        return Err(Unread::TargetShorterThanWindow {
35            held_secs,
36            window_secs,
37            least_secs,
38        });
39    }
40    Ok(())
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct Span {
45    pub t_secs: f64,
46    pub from_secs: f64,
47    pub to_secs: f64,
48    pub from: usize,
49    pub to: usize,
50}
51
52#[derive(Clone, Debug, PartialEq)]
53pub struct Spans {
54    pub hop_secs: f64,
55    pub window_secs: f64,
56    pub spans: Vec<Span>,
57}
58
59/// Centres step by `hop_secs` from the start as every framed reading steps; each window is
60/// clipped to the envelope's `len` samples at `rate`.
61pub fn spans(
62    held: usize,
63    sample_rate: f64,
64    start_secs: f64,
65    hop_secs: f64,
66    rate: f64,
67    window_secs: f64,
68    len: usize,
69) -> Spans {
70    let hop = ((hop_secs * sample_rate).round() as usize).max(1);
71    let size = (window_secs * rate).round() as usize;
72    Spans {
73        hop_secs: hop as f64 / sample_rate,
74        window_secs: size as f64 / rate,
75        spans: (0..held)
76            .step_by(hop)
77            .map(|c| {
78                let middle = (c as f64 * rate / sample_rate).round() as usize;
79                let from = middle.saturating_sub(size / 2);
80                let to = (middle + size - size / 2).min(len);
81                Span {
82                    t_secs: start_secs + c as f64 / sample_rate,
83                    from_secs: start_secs + from as f64 / rate,
84                    to_secs: start_secs + to as f64 / rate,
85                    from,
86                    to,
87                }
88            })
89            .collect(),
90    }
91}
92
93#[derive(Clone, Copy, Debug, PartialEq)]
94pub struct Stats {
95    pub mean: f64,
96    pub min: Extreme,
97    pub max: Extreme,
98}
99
100pub fn stats(values: impl Iterator<Item = (f64, f64)>) -> Option<Stats> {
101    let held: Vec<(f64, f64)> = values.collect();
102    let first = *held.first()?;
103    let pick = |better: fn(f64, f64) -> bool| {
104        let (t, value) = held
105            .iter()
106            .copied()
107            .fold(first, |a, b| if better(b.1, a.1) { b } else { a });
108        Extreme { value, t_secs: t }
109    };
110    Some(Stats {
111        mean: held.iter().map(|(_, v)| v).sum::<f64>() / held.len() as f64,
112        min: pick(|a, b| a < b),
113        max: pick(|a, b| a > b),
114    })
115}