1use num_rational::Ratio;
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4use crate::error::{DspError, DspResult};
5
6pub(crate) fn decimal_ratio(value: f64) -> Ratio<u64> {
10 let text = format!("{value}");
11 let (int, frac) = text.split_once('.').unwrap_or((&text, ""));
12 let digits = format!("{int}{frac}");
13 match (digits.parse::<u64>(), 10u64.checked_pow(frac.len() as u32)) {
14 (Ok(numer), Some(denom)) => Ratio::new(numer, denom),
15 _ => Ratio::<i64>::approximate_float(value)
16 .map(|r| Ratio::new(*r.numer() as u64, *r.denom() as u64))
17 .unwrap_or_else(|| Ratio::from_integer(value.round() as u64)),
18 }
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub struct SampleRate {
24 hz: Ratio<u64>,
25}
26
27impl SampleRate {
28 pub fn new(rate_hz: f64) -> DspResult<Self> {
31 if rate_hz <= 0.0 || rate_hz.is_nan() || rate_hz.is_infinite() {
32 return Err(DspError::InvalidSampleRate { rate_hz });
33 }
34 let hz = decimal_ratio(rate_hz);
35 Self::from_ratio(*hz.numer(), *hz.denom())
36 }
37
38 pub fn from_ratio(numer: u64, denom: u64) -> DspResult<Self> {
40 if numer == 0 || denom == 0 {
41 return Err(DspError::InvalidSampleRate { rate_hz: if denom == 0 { f64::NAN } else { 0.0 } });
42 }
43 Ok(Self { hz: Ratio::new(numer, denom) })
44 }
45
46 pub fn rate_hz(&self) -> f64 {
47 *self.hz.numer() as f64 / *self.hz.denom() as f64
48 }
49
50 pub fn as_ratio(&self) -> Ratio<u64> {
52 self.hz
53 }
54
55 pub fn nyquist_hz(&self) -> f64 {
56 self.rate_hz() / 2.0
57 }
58
59 pub fn sample_period_seconds(&self) -> f64 {
60 1.0 / self.rate_hz()
61 }
62
63 pub fn duration_for_samples(&self, num_samples: usize) -> Duration {
64 Duration::from_secs_f64(num_samples as f64 / self.rate_hz())
65 }
66
67 pub fn samples_for_duration(&self, duration: Duration) -> usize {
68 (duration.as_secs_f64() * self.rate_hz()).round() as usize
69 }
70}