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