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