firewheel_core/
clock.rs

1use core::ops::{Add, AddAssign, Sub, SubAssign};
2
3use crate::node::ProcInfo;
4
5/// When a particular audio event should occur.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum EventDelay {
8    /// The event should happen when the clock reaches the given time in
9    /// seconds.
10    ///
11    /// Note, this clock is not perfectly accurate, but it does correctly
12    /// account for any output underflows that may occur.
13    ///
14    /// The value is an absolute time, *NOT* a delta time. Use the context's
15    /// `clock_now` method to get the current time of the clock.
16    DelayUntilSeconds(ClockSeconds),
17
18    /// The event should happen when the clock reaches the given time in
19    /// samples (of a single channel of audio).
20    ///
21    /// This is more accurate than `DelayUntilSeconds`, but it does not
22    /// account for any output underflows that may occur. This clock is
23    /// ideal for syncing events to a custom musical transport.
24    ///
25    /// The value is an absolute time, *NOT* a delta time. Use the context's
26    /// `clock_samples` to get the current time of the clock.
27    DelayUntilSamples(ClockSamples),
28
29    /// The event should happen when the musical clock reaches the given
30    /// musical time.
31    ///
32    /// Like `DelayUntilSamples`, this is very accurate, but note it also
33    /// does not account for any output underflows that may occur.
34    DelayUntilMusical(MusicalTime),
35}
36
37impl EventDelay {
38    pub fn elapsed_or_get(&self, proc_info: &ProcInfo) -> Option<Self> {
39        match self {
40            EventDelay::DelayUntilSeconds(seconds) => {
41                if *seconds <= proc_info.clock_seconds.start {
42                    None
43                } else {
44                    Some(*self)
45                }
46            }
47            EventDelay::DelayUntilSamples(samples) => {
48                if *samples <= proc_info.clock_samples {
49                    None
50                } else {
51                    Some(*self)
52                }
53            }
54            EventDelay::DelayUntilMusical(musical) => {
55                if let Some(transport) = &proc_info.transport_info {
56                    if transport.paused || *musical <= transport.musical_clock.start {
57                        None
58                    } else {
59                        Some(*self)
60                    }
61                } else {
62                    None
63                }
64            }
65        }
66    }
67
68    pub fn elapsed_on_frame(&self, proc_info: &ProcInfo, sample_rate: u32) -> Option<usize> {
69        match self {
70            EventDelay::DelayUntilSeconds(seconds) => {
71                if *seconds <= proc_info.clock_seconds.start {
72                    Some(0)
73                } else if *seconds >= proc_info.clock_seconds.end {
74                    None
75                } else {
76                    let frame = ((seconds.0 - proc_info.clock_seconds.start.0)
77                        * f64::from(sample_rate))
78                    .round() as usize;
79
80                    if frame >= proc_info.frames {
81                        None
82                    } else {
83                        Some(frame)
84                    }
85                }
86            }
87            EventDelay::DelayUntilSamples(samples) => {
88                if *samples <= proc_info.clock_samples {
89                    Some(0)
90                } else {
91                    let frame = samples.0 - proc_info.clock_samples.0;
92
93                    if frame >= proc_info.frames as i64 {
94                        None
95                    } else {
96                        Some(frame as usize)
97                    }
98                }
99            }
100            EventDelay::DelayUntilMusical(musical) => {
101                if let Some(transport) = &proc_info.transport_info {
102                    if transport.paused || *musical >= transport.musical_clock.end {
103                        None
104                    } else if *musical <= transport.musical_clock.start {
105                        Some(0)
106                    } else {
107                        let frame = transport.transport.musical_to_sample(*musical, sample_rate)
108                            - proc_info.clock_samples;
109
110                        if frame.0 >= proc_info.frames as i64 {
111                            None
112                        } else {
113                            Some(frame.0 as usize)
114                        }
115                    }
116                } else {
117                    None
118                }
119            }
120        }
121    }
122}
123
124/// An absolute clock time in units of seconds.
125#[repr(transparent)]
126#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
127pub struct ClockSeconds(pub f64);
128
129impl Add for ClockSeconds {
130    type Output = Self;
131    fn add(self, rhs: Self) -> Self::Output {
132        Self(self.0 + rhs.0)
133    }
134}
135
136impl Sub for ClockSeconds {
137    type Output = Self;
138    fn sub(self, rhs: Self) -> Self::Output {
139        Self(self.0 - rhs.0)
140    }
141}
142
143impl AddAssign for ClockSeconds {
144    fn add_assign(&mut self, rhs: Self) {
145        self.0 += rhs.0;
146    }
147}
148
149impl SubAssign for ClockSeconds {
150    fn sub_assign(&mut self, rhs: Self) {
151        self.0 -= rhs.0;
152    }
153}
154
155impl From<f64> for ClockSeconds {
156    fn from(value: f64) -> Self {
157        Self(value)
158    }
159}
160
161impl Into<f64> for ClockSeconds {
162    fn into(self) -> f64 {
163        self.0
164    }
165}
166
167/// An absolute clock time in units of samples (in a single channel of audio).
168#[repr(transparent)]
169#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
170pub struct ClockSamples(pub i64);
171
172impl ClockSamples {
173    pub const fn new(samples: i64) -> Self {
174        Self(samples)
175    }
176
177    pub fn from_secs_f64(seconds: f64, sample_rate: u32) -> Self {
178        let seconds_i64 = seconds.floor() as i64;
179        let fract_samples_i64 = (seconds.fract() * f64::from(sample_rate)).round() as i64;
180
181        Self((seconds_i64 * i64::from(sample_rate)) + fract_samples_i64)
182    }
183
184    /// (whole seconds, samples *after* whole seconds)
185    pub fn whole_seconds_and_fract(&self, sample_rate: u32) -> (i64, u32) {
186        let whole_seconds = self.0 / i64::from(sample_rate);
187        let fract_samples = self.0 % i64::from(sample_rate);
188
189        if fract_samples < 0 {
190            (
191                whole_seconds - 1,
192                sample_rate - (fract_samples.abs() as u32),
193            )
194        } else {
195            (whole_seconds, fract_samples as u32)
196        }
197    }
198
199    #[inline]
200    pub fn fract_second_samples(&self, sample_rate: u32) -> u32 {
201        (self.0 % i64::from(sample_rate)) as u32
202    }
203
204    pub fn as_secs_f64(&self, sample_rate: u32, sample_rate_recip: f64) -> f64 {
205        let whole_seconds = self.0 / i64::from(sample_rate);
206        let fract_samples = self.0 % i64::from(sample_rate);
207
208        whole_seconds as f64 + (fract_samples as f64 * sample_rate_recip)
209    }
210}
211
212impl Add for ClockSamples {
213    type Output = Self;
214    fn add(self, rhs: Self) -> Self::Output {
215        Self(self.0 + rhs.0)
216    }
217}
218
219impl Sub for ClockSamples {
220    type Output = Self;
221    fn sub(self, rhs: Self) -> Self::Output {
222        Self(self.0 - rhs.0)
223    }
224}
225
226impl AddAssign for ClockSamples {
227    fn add_assign(&mut self, rhs: Self) {
228        self.0 += rhs.0;
229    }
230}
231
232impl SubAssign for ClockSamples {
233    fn sub_assign(&mut self, rhs: Self) {
234        self.0 -= rhs.0;
235    }
236}
237
238impl From<i64> for ClockSamples {
239    fn from(value: i64) -> Self {
240        Self(value)
241    }
242}
243
244impl Into<i64> for ClockSamples {
245    fn into(self) -> i64 {
246        self.0
247    }
248}
249
250/// Musical time in units of beats.
251#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
252pub struct MusicalTime(pub f64);
253
254impl MusicalTime {
255    pub const fn new(beats: f64) -> Self {
256        Self(beats)
257    }
258
259    /// Convert to the corresponding time in samples.
260    pub fn to_sample_time(&self, seconds_per_beat: f64, sample_rate: u32) -> ClockSamples {
261        let secs_f64 = self.0 * seconds_per_beat;
262        ClockSamples::from_secs_f64(secs_f64, sample_rate)
263    }
264
265    /// Convert from the corresponding time in samples.
266    pub fn from_sample_time(
267        sample_time: ClockSamples,
268        beats_per_second: f64,
269        sample_rate: u32,
270        sample_rate_recip: f64,
271    ) -> Self {
272        let secs_f64 = sample_time.as_secs_f64(sample_rate, sample_rate_recip);
273        MusicalTime(secs_f64 * beats_per_second)
274    }
275}
276
277pub fn seconds_per_beat(beats_per_minute: f64) -> f64 {
278    60.0 / beats_per_minute
279}
280
281pub fn beats_per_second(beats_per_minute: f64) -> f64 {
282    beats_per_minute * (1.0 / 60.0)
283}
284
285impl Add for MusicalTime {
286    type Output = Self;
287    fn add(self, rhs: Self) -> Self::Output {
288        Self(self.0 + rhs.0)
289    }
290}
291
292impl Sub for MusicalTime {
293    type Output = Self;
294    fn sub(self, rhs: Self) -> Self::Output {
295        Self(self.0 - rhs.0)
296    }
297}
298
299impl AddAssign for MusicalTime {
300    fn add_assign(&mut self, rhs: Self) {
301        self.0 += rhs.0;
302    }
303}
304
305impl SubAssign for MusicalTime {
306    fn sub_assign(&mut self, rhs: Self) {
307        self.0 -= rhs.0;
308    }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq)]
312pub struct MusicalTransport {
313    beats_per_minute: f64,
314    seconds_per_beat: f64,
315    // TODO: Automated tempo?
316}
317
318impl MusicalTransport {
319    pub fn new(beats_per_minute: f64) -> Self {
320        Self {
321            beats_per_minute,
322            seconds_per_beat: seconds_per_beat(beats_per_minute),
323        }
324    }
325
326    pub fn beats_per_minute(&self) -> f64 {
327        self.beats_per_minute
328    }
329
330    pub fn seconds_per_beat(&self) -> f64 {
331        self.seconds_per_beat
332    }
333}
334
335impl MusicalTransport {
336    /// Convert from musical time the corresponding time in samples.
337    pub fn musical_to_sample(&self, musical: MusicalTime, sample_rate: u32) -> ClockSamples {
338        musical.to_sample_time(self.seconds_per_beat, sample_rate)
339    }
340
341    /// Convert from the time in samples to the corresponding musical time.
342    pub fn sample_to_musical(
343        &self,
344        sample_time: ClockSamples,
345        sample_rate: u32,
346        sample_rate_recip: f64,
347    ) -> MusicalTime {
348        MusicalTime::from_sample_time(
349            sample_time,
350            self.beats_per_minute * (1.0 / 60.0),
351            sample_rate,
352            sample_rate_recip,
353        )
354    }
355}