Skip to main content

sva_samples/measure/
envelope.rs

1// Concern: traces level over time as frames of RMS and peak across every channel | Non-concern: frequency content (spectrum.rs), choosing the frame length | IO: (planes, sample rate, frame) -> frames
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct EnvelopeFrame {
5    pub t_secs: f64,
6    pub rms: f64,
7    pub peak: f64,
8}
9
10pub fn trace(
11    planes: &[&[f64]],
12    sample_rate: f64,
13    start_secs: f64,
14    frame_secs: f64,
15) -> Vec<EnvelopeFrame> {
16    let stride = ((frame_secs * sample_rate).round() as usize).max(1);
17    let len = planes.iter().map(|p| p.len()).min().unwrap_or(0);
18    (0..len)
19        .step_by(stride)
20        .map(|from| {
21            let to = (from + stride).min(len);
22            let frame: Vec<&[f64]> = planes.iter().map(|p| &p[from..to]).collect();
23            EnvelopeFrame {
24                t_secs: start_secs + from as f64 / sample_rate,
25                rms: level(&frame),
26                peak: frame
27                    .iter()
28                    .flat_map(|p| p.iter())
29                    .fold(0f64, |a, &x| a.max(x.abs())),
30            }
31        })
32        .collect()
33}
34
35/// One RMS over every channel's samples together.
36pub fn level(planes: &[&[f64]]) -> f64 {
37    let count: usize = planes.iter().map(|p| p.len()).sum();
38    if count == 0 {
39        return 0.0;
40    }
41    let sum_sq: f64 = planes.iter().flat_map(|p| p.iter()).map(|&x| x * x).sum();
42    (sum_sq / count as f64).sqrt()
43}
44
45pub fn rms(samples: &[f64]) -> f64 {
46    level(&[samples])
47}