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