Skip to main content

use_frequency/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive frequency helpers.
3//!
4//! The crate keeps frequency calculations narrow: finite positive Hertz values,
5//! periods, angular frequency, and Nyquist limits.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_frequency::{Frequency, nyquist_frequency};
11//!
12//! let frequency = Frequency::new(440.0).unwrap();
13//!
14//! assert!(frequency.period_seconds() > 0.0);
15//! assert_eq!(nyquist_frequency(48_000.0).unwrap(), 24_000.0);
16//! ```
17
18use std::f64::consts::PI;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct Frequency {
22    pub hz: f64,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum FrequencyError {
27    InvalidFrequency,
28    InvalidSampleRate,
29}
30
31fn validate_positive(value: f64, error: FrequencyError) -> Result<f64, FrequencyError> {
32    if !value.is_finite() || value <= 0.0 {
33        Err(error)
34    } else {
35        Ok(value)
36    }
37}
38
39impl Frequency {
40    pub fn new(hz: f64) -> Result<Self, FrequencyError> {
41        Ok(Self {
42            hz: validate_positive(hz, FrequencyError::InvalidFrequency)?,
43        })
44    }
45
46    #[must_use]
47    pub fn hz(&self) -> f64 {
48        self.hz
49    }
50
51    #[must_use]
52    pub fn period_seconds(&self) -> f64 {
53        1.0 / self.hz
54    }
55
56    #[must_use]
57    pub fn angular_frequency(&self) -> f64 {
58        2.0 * PI * self.hz
59    }
60}
61
62pub fn period_seconds(hz: f64) -> Result<f64, FrequencyError> {
63    Ok(1.0 / validate_positive(hz, FrequencyError::InvalidFrequency)?)
64}
65
66pub fn angular_frequency(hz: f64) -> Result<f64, FrequencyError> {
67    Ok(2.0 * PI * validate_positive(hz, FrequencyError::InvalidFrequency)?)
68}
69
70pub fn nyquist_frequency(sample_rate_hz: f64) -> Result<f64, FrequencyError> {
71    Ok(validate_positive(sample_rate_hz, FrequencyError::InvalidSampleRate)? / 2.0)
72}
73
74#[cfg(test)]
75mod tests {
76    use std::f64::consts::PI;
77
78    use super::{Frequency, FrequencyError, angular_frequency, nyquist_frequency, period_seconds};
79
80    #[test]
81    fn constructs_frequency_and_reports_values() {
82        let frequency = Frequency::new(4.0).unwrap();
83
84        assert_eq!(frequency.hz(), 4.0);
85        assert_eq!(frequency.period_seconds(), 0.25);
86        assert_eq!(frequency.angular_frequency(), 8.0 * PI);
87    }
88
89    #[test]
90    fn computes_free_frequency_helpers() {
91        assert_eq!(period_seconds(2.0).unwrap(), 0.5);
92        assert_eq!(angular_frequency(2.0).unwrap(), 4.0 * PI);
93        assert_eq!(nyquist_frequency(48_000.0).unwrap(), 24_000.0);
94    }
95
96    #[test]
97    fn rejects_invalid_frequencies() {
98        assert_eq!(Frequency::new(0.0), Err(FrequencyError::InvalidFrequency));
99        assert_eq!(
100            period_seconds(f64::NAN),
101            Err(FrequencyError::InvalidFrequency)
102        );
103    }
104
105    #[test]
106    fn rejects_invalid_sample_rates() {
107        assert_eq!(
108            nyquist_frequency(0.0),
109            Err(FrequencyError::InvalidSampleRate)
110        );
111        assert_eq!(
112            nyquist_frequency(f64::INFINITY),
113            Err(FrequencyError::InvalidSampleRate)
114        );
115    }
116}