Skip to main content

use_signal_energy/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive signal-energy helpers.
3//!
4//! The crate provides a few explicit helpers for energy, mean power, RMS power,
5//! and decibel conversions.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_signal_energy::{decibels_from_amplitude, mean_power, signal_energy};
11//!
12//! let samples = [1.0, -1.0, 1.0, -1.0];
13//!
14//! assert_eq!(signal_energy(&samples), Some(4.0));
15//! assert_eq!(mean_power(&samples), Some(1.0));
16//! assert_eq!(decibels_from_amplitude(2.0, 1.0).unwrap(), 20.0 * 2.0_f64.log10());
17//! ```
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum EnergyError {
21    InvalidPower,
22    InvalidReferencePower,
23    InvalidAmplitude,
24    InvalidReferenceAmplitude,
25}
26
27fn validated_samples(samples: &[f64]) -> Option<&[f64]> {
28    if samples.is_empty() || samples.iter().any(|sample| !sample.is_finite()) {
29        None
30    } else {
31        Some(samples)
32    }
33}
34
35pub fn signal_energy(samples: &[f64]) -> Option<f64> {
36    Some(
37        validated_samples(samples)?
38            .iter()
39            .map(|sample| sample * sample)
40            .sum(),
41    )
42}
43
44pub fn mean_power(samples: &[f64]) -> Option<f64> {
45    let samples = validated_samples(samples)?;
46    Some(signal_energy(samples)? / samples.len() as f64)
47}
48
49pub fn rms_power(samples: &[f64]) -> Option<f64> {
50    Some(mean_power(samples)?.sqrt())
51}
52
53pub fn decibels_from_power(power: f64, reference_power: f64) -> Result<f64, EnergyError> {
54    if !power.is_finite() || power <= 0.0 {
55        return Err(EnergyError::InvalidPower);
56    }
57
58    if !reference_power.is_finite() || reference_power <= 0.0 {
59        return Err(EnergyError::InvalidReferencePower);
60    }
61
62    Ok(10.0 * (power / reference_power).log10())
63}
64
65pub fn decibels_from_amplitude(
66    amplitude: f64,
67    reference_amplitude: f64,
68) -> Result<f64, EnergyError> {
69    if !amplitude.is_finite() || amplitude <= 0.0 {
70        return Err(EnergyError::InvalidAmplitude);
71    }
72
73    if !reference_amplitude.is_finite() || reference_amplitude <= 0.0 {
74        return Err(EnergyError::InvalidReferenceAmplitude);
75    }
76
77    Ok(20.0 * (amplitude / reference_amplitude).log10())
78}
79
80#[cfg(test)]
81mod tests {
82    use super::{
83        EnergyError, decibels_from_amplitude, decibels_from_power, mean_power, rms_power,
84        signal_energy,
85    };
86
87    #[test]
88    fn computes_energy_and_power_helpers() {
89        let samples = [1.0, -1.0, 1.0, -1.0];
90
91        assert_eq!(signal_energy(&samples), Some(4.0));
92        assert_eq!(mean_power(&samples), Some(1.0));
93        assert_eq!(rms_power(&samples), Some(1.0));
94    }
95
96    #[test]
97    fn computes_decibel_conversions() {
98        assert_eq!(decibels_from_power(10.0, 1.0).unwrap(), 10.0);
99        assert!(
100            (decibels_from_amplitude(2.0, 1.0).unwrap() - 6.020_599_913_279_624).abs() < 1.0e-12
101        );
102    }
103
104    #[test]
105    fn rejects_empty_and_invalid_samples() {
106        assert_eq!(signal_energy(&[]), None);
107        assert_eq!(mean_power(&[1.0, f64::NAN]), None);
108    }
109
110    #[test]
111    fn rejects_invalid_decibel_inputs() {
112        assert_eq!(
113            decibels_from_power(0.0, 1.0),
114            Err(EnergyError::InvalidPower)
115        );
116        assert_eq!(
117            decibels_from_power(1.0, 0.0),
118            Err(EnergyError::InvalidReferencePower)
119        );
120        assert_eq!(
121            decibels_from_amplitude(0.0, 1.0),
122            Err(EnergyError::InvalidAmplitude)
123        );
124        assert_eq!(
125            decibels_from_amplitude(1.0, f64::INFINITY),
126            Err(EnergyError::InvalidReferenceAmplitude)
127        );
128    }
129}