1#![forbid(unsafe_code)]
2#[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}