Skip to main content

use_amplitude/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive amplitude helpers.
3//!
4//! The crate provides small slice-based helpers for common amplitude summaries.
5//! Empty slices and non-finite values return `None`.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_amplitude::{peak_amplitude, rms_amplitude};
11//!
12//! let samples = [-1.0, 0.0, 1.0];
13//!
14//! assert_eq!(peak_amplitude(&samples), Some(1.0));
15//! assert_eq!(rms_amplitude(&samples).unwrap(), (2.0_f64 / 3.0).sqrt());
16//! ```
17
18fn validated_samples(samples: &[f64]) -> Option<&[f64]> {
19    if samples.is_empty() || samples.iter().any(|sample| !sample.is_finite()) {
20        None
21    } else {
22        Some(samples)
23    }
24}
25
26pub fn peak_amplitude(samples: &[f64]) -> Option<f64> {
27    let mut values = validated_samples(samples)?.iter().copied();
28    let first = values.next()?.abs();
29
30    Some(values.fold(first, |peak, sample| peak.max(sample.abs())))
31}
32
33pub fn min_amplitude(samples: &[f64]) -> Option<f64> {
34    let mut values = validated_samples(samples)?.iter().copied();
35    let first = values.next()?;
36
37    Some(values.fold(first, f64::min))
38}
39
40pub fn max_amplitude(samples: &[f64]) -> Option<f64> {
41    let mut values = validated_samples(samples)?.iter().copied();
42    let first = values.next()?;
43
44    Some(values.fold(first, f64::max))
45}
46
47pub fn peak_to_peak_amplitude(samples: &[f64]) -> Option<f64> {
48    Some(max_amplitude(samples)? - min_amplitude(samples)?)
49}
50
51pub fn mean_amplitude(samples: &[f64]) -> Option<f64> {
52    let samples = validated_samples(samples)?;
53    let sum = samples.iter().sum::<f64>();
54
55    Some(sum / samples.len() as f64)
56}
57
58pub fn rms_amplitude(samples: &[f64]) -> Option<f64> {
59    let samples = validated_samples(samples)?;
60    let mean_square =
61        samples.iter().map(|sample| sample * sample).sum::<f64>() / samples.len() as f64;
62
63    Some(mean_square.sqrt())
64}
65
66#[cfg(test)]
67mod tests {
68    use super::{
69        max_amplitude, mean_amplitude, min_amplitude, peak_amplitude, peak_to_peak_amplitude,
70        rms_amplitude,
71    };
72
73    #[test]
74    fn computes_basic_amplitude_summaries() {
75        let samples = [-2.0, -0.5, 1.0, 3.0];
76
77        assert_eq!(peak_amplitude(&samples), Some(3.0));
78        assert_eq!(min_amplitude(&samples), Some(-2.0));
79        assert_eq!(max_amplitude(&samples), Some(3.0));
80        assert_eq!(peak_to_peak_amplitude(&samples), Some(5.0));
81        assert_eq!(mean_amplitude(&samples), Some(0.375));
82    }
83
84    #[test]
85    fn computes_rms_amplitude() {
86        let rms = rms_amplitude(&[-1.0, 0.0, 1.0]).unwrap();
87
88        assert!((rms - (2.0_f64 / 3.0).sqrt()).abs() < 1.0e-12);
89    }
90
91    #[test]
92    fn rejects_empty_inputs() {
93        assert_eq!(peak_amplitude(&[]), None);
94        assert_eq!(rms_amplitude(&[]), None);
95    }
96
97    #[test]
98    fn handles_single_values() {
99        assert_eq!(peak_amplitude(&[-0.5]), Some(0.5));
100        assert_eq!(peak_to_peak_amplitude(&[-0.5]), Some(0.0));
101        assert_eq!(mean_amplitude(&[-0.5]), Some(-0.5));
102    }
103
104    #[test]
105    fn rejects_non_finite_samples() {
106        assert_eq!(peak_amplitude(&[0.0, f64::NAN]), None);
107        assert_eq!(mean_amplitude(&[1.0, f64::INFINITY]), None);
108    }
109}