second_music_system/
posfloat.rs

1use std::{
2    cmp::Ordering,
3    fmt::{Debug, Display, Formatter, Result as FmtResult},
4    hash::{Hash, Hasher},
5    num::ParseFloatError,
6    ops::{Add, Deref, Div, Mul},
7    str::FromStr,
8};
9
10use super::SpeakerLayout;
11
12#[derive(Clone, Copy, PartialEq)]
13/// A finite, positive f32.
14///
15/// This is used in many places in SMS where a negative or infinite f32 would
16/// break things: timestamps, sample rates, etc. Zero would break some of these
17/// too, but less Interestingly.
18pub struct PosFloat(f32);
19
20impl PosFloat {
21    pub const HALF: PosFloat = PosFloat(0.5);
22    pub const ZERO: PosFloat = PosFloat(0.0);
23    pub const ONE: PosFloat = PosFloat(1.0);
24    pub const THOUSAND: PosFloat = PosFloat(1000.0);
25    pub const MILLION: PosFloat = PosFloat(1000000.0);
26    pub const BILLION: PosFloat = PosFloat(1000000.0);
27    pub const SECONDS_PER_MINUTE: PosFloat = PosFloat(60.0);
28    pub const SECONDS_PER_HOUR: PosFloat = PosFloat(3600.0);
29    pub const SECONDS_PER_DAY: PosFloat = PosFloat(86400.0);
30    /// Try to create a new PosFloat from an f32.
31    pub fn new(x: f32) -> Result<PosFloat, &'static str> {
32        if !x.is_finite() {
33            Err("PosFloat must be finite")
34        } else if !x.is_sign_positive() {
35            Err("PosFloat must be positive")
36        } else {
37            Ok(PosFloat(x))
38        }
39    }
40    /// Create a new PosFloat from an f32, which you *promise* is positive and
41    /// finite.
42    ///
43    /// # Safety
44    ///
45    /// `x` must be positive (sign bit of zero), and finite (exponent < max).
46    pub const unsafe fn new_unchecked(x: f32) -> PosFloat {
47        /* not available as const fn… */
48        /*
49        debug_assert!(x.is_finite(), "it wasn't finite");
50        debug_assert!(x.is_sign_positive(), "it wasn't positive");
51        */
52        PosFloat(x)
53    }
54    /// Create a new PosFloat from an f32. If it is non-finite or negative,
55    /// return zero.
56    pub fn new_clamped(x: f32) -> PosFloat {
57        if x.is_finite() && x.is_sign_positive() {
58            PosFloat(x)
59        } else {
60            PosFloat::ZERO
61        }
62    }
63    /// Interprets this `PosFloat` as a time in seconds, and converts it to an
64    /// integer number of sample frames at the given sample rate.
65    pub fn seconds_to_frames(&self, sample_rate: PosFloat) -> u64 {
66        (self.0 * sample_rate.0).floor() as u64
67    }
68    /// Interprets this `PosFloat` as a time in seconds, and converts it to an
69    /// potentially-non-whole-number of sample frames at the given sample rate.
70    pub fn seconds_to_frac_frames(&self, sample_rate: PosFloat) -> PosFloat {
71        PosFloat((self.0 * sample_rate.0).floor())
72    }
73    /// Interprets this `PosFloat` as a time in seconds, and converts it to an
74    /// integer number of samples for the given sample rate and speaker layout.
75    pub fn seconds_to_samples(
76        &self,
77        sample_rate: PosFloat,
78        speaker_layout: SpeakerLayout,
79    ) -> u64 {
80        self.seconds_to_frames(sample_rate)
81            * speaker_layout.get_num_channels() as u64
82    }
83    /// Subtract `differend` from ourselves and return the result. If the
84    /// result would have been zero (because `different` is greater than
85    /// `self`), return zero.
86    pub fn saturating_sub(&self, differend: PosFloat) -> PosFloat {
87        let ret = self.0 - differend.0;
88        if ret.is_sign_negative() || !ret.is_finite() {
89            PosFloat::ZERO
90        } else {
91            PosFloat(ret)
92        }
93    }
94}
95
96impl Display for PosFloat {
97    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
98        Display::fmt(&**self, f)
99    }
100}
101
102impl Debug for PosFloat {
103    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
104        Debug::fmt(&**self, f)
105    }
106}
107
108impl Eq for PosFloat {}
109
110impl PartialOrd for PosFloat {
111    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116// Same-sign finite f32s have a total ordering.
117impl Ord for PosFloat {
118    fn cmp(&self, other: &Self) -> Ordering {
119        self.0.to_bits().cmp(&other.0.to_bits())
120    }
121}
122
123impl Hash for PosFloat {
124    fn hash<H: Hasher>(&self, state: &mut H) {
125        debug_assert!(self.0.is_finite() && self.0.is_sign_positive());
126        self.0.to_bits().hash(state);
127    }
128}
129
130impl Deref for PosFloat {
131    type Target = f32;
132    fn deref(&self) -> &f32 {
133        &self.0
134    }
135}
136
137impl From<u8> for PosFloat {
138    fn from(value: u8) -> PosFloat {
139        PosFloat(value as f32)
140    }
141}
142
143impl From<u16> for PosFloat {
144    fn from(value: u16) -> PosFloat {
145        PosFloat(value as f32)
146    }
147}
148
149impl From<u32> for PosFloat {
150    fn from(value: u32) -> PosFloat {
151        PosFloat(value as f32)
152    }
153}
154
155impl From<u64> for PosFloat {
156    fn from(value: u64) -> PosFloat {
157        PosFloat(value as f32)
158    }
159}
160
161impl From<usize> for PosFloat {
162    fn from(value: usize) -> PosFloat {
163        PosFloat(value as f32)
164    }
165}
166
167pub enum TimePointFromStrError {
168    ParseFloatError(ParseFloatError),
169    NewTimePointError(&'static str),
170}
171
172impl From<ParseFloatError> for TimePointFromStrError {
173    fn from(e: ParseFloatError) -> Self {
174        Self::ParseFloatError(e)
175    }
176}
177
178impl From<&'static str> for TimePointFromStrError {
179    fn from(e: &'static str) -> Self {
180        Self::NewTimePointError(e)
181    }
182}
183
184impl FromStr for PosFloat {
185    type Err = TimePointFromStrError;
186    fn from_str(s: &str) -> Result<PosFloat, TimePointFromStrError> {
187        Ok(PosFloat::new(s.parse::<f32>()?)?)
188    }
189}
190
191impl Add<PosFloat> for PosFloat {
192    type Output = PosFloat;
193    fn add(self, rhs: PosFloat) -> PosFloat {
194        PosFloat(self.0 + rhs.0)
195    }
196}
197
198impl Div<PosFloat> for PosFloat {
199    type Output = PosFloat;
200    fn div(self, rhs: PosFloat) -> PosFloat {
201        PosFloat(self.0 / rhs.0)
202    }
203}
204
205impl Mul<PosFloat> for PosFloat {
206    type Output = PosFloat;
207    fn mul(self, rhs: PosFloat) -> PosFloat {
208        PosFloat(self.0 * rhs.0)
209    }
210}