Skip to main content

use_sample/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive signal sample helpers.
3//!
4//! The crate intentionally keeps sample handling explicit: finite sample values,
5//! simple counting, and duration calculations from a sample rate.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_sample::{Sample, duration_seconds};
11//!
12//! let sample = Sample::new(-0.25).unwrap();
13//!
14//! assert_eq!(sample.abs(), 0.25);
15//! assert_eq!(duration_seconds(480, 48_000.0).unwrap(), 0.01);
16//! ```
17
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct Sample {
20    pub value: f64,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SampleError {
25    InvalidValue,
26    InvalidSampleRate,
27}
28
29impl Sample {
30    pub fn new(value: f64) -> Result<Self, SampleError> {
31        if !value.is_finite() {
32            return Err(SampleError::InvalidValue);
33        }
34
35        Ok(Self { value })
36    }
37
38    #[must_use]
39    pub fn value(&self) -> f64 {
40        self.value
41    }
42
43    #[must_use]
44    pub fn abs(&self) -> f64 {
45        self.value.abs()
46    }
47
48    #[must_use]
49    pub fn is_silent(&self) -> bool {
50        self.value == 0.0
51    }
52}
53
54pub fn validate_samples(samples: &[f64]) -> Result<(), SampleError> {
55    if samples.iter().all(|sample| sample.is_finite()) {
56        Ok(())
57    } else {
58        Err(SampleError::InvalidValue)
59    }
60}
61
62#[must_use]
63pub fn sample_count(samples: &[f64]) -> usize {
64    samples.len()
65}
66
67pub fn duration_seconds(sample_count: usize, sample_rate_hz: f64) -> Result<f64, SampleError> {
68    if !sample_rate_hz.is_finite() || sample_rate_hz <= 0.0 {
69        return Err(SampleError::InvalidSampleRate);
70    }
71
72    Ok(sample_count as f64 / sample_rate_hz)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::{duration_seconds, sample_count, validate_samples, Sample, SampleError};
78
79    #[test]
80    fn constructs_sample_and_reports_properties() {
81        let sample = Sample::new(-0.25).unwrap();
82
83        assert_eq!(sample.value(), -0.25);
84        assert_eq!(sample.abs(), 0.25);
85        assert!(!sample.is_silent());
86    }
87
88    #[test]
89    fn validates_finite_samples_and_counts_them() {
90        let values = [-1.0, 0.0, 1.0];
91
92        assert_eq!(validate_samples(&values), Ok(()));
93        assert_eq!(sample_count(&values), 3);
94    }
95
96    #[test]
97    fn accepts_empty_sample_slices() {
98        assert_eq!(validate_samples(&[]), Ok(()));
99        assert_eq!(sample_count(&[]), 0);
100        assert_eq!(duration_seconds(0, 48_000.0).unwrap(), 0.0);
101    }
102
103    #[test]
104    fn rejects_invalid_sample_values() {
105        assert_eq!(Sample::new(f64::NAN), Err(SampleError::InvalidValue));
106        assert_eq!(
107            validate_samples(&[0.0, f64::INFINITY]),
108            Err(SampleError::InvalidValue)
109        );
110    }
111
112    #[test]
113    fn rejects_invalid_sample_rates() {
114        assert_eq!(
115            duration_seconds(100, 0.0),
116            Err(SampleError::InvalidSampleRate)
117        );
118        assert_eq!(
119            duration_seconds(100, f64::NAN),
120            Err(SampleError::InvalidSampleRate)
121        );
122    }
123}