firewheel_core/clock/
transport.rs

1mod dynamic_transport;
2mod static_transport;
3
4use bevy_platform::sync::Arc;
5
6use core::{fmt::Debug, num::NonZeroU32, ops::Range};
7
8pub use dynamic_transport::{DynamicTransport, TransportKeyframe};
9pub use static_transport::StaticTransport;
10
11use crate::{
12    clock::{DurationSeconds, EventInstant, InstantMusical, InstantSamples, InstantSeconds},
13    diff::Notify,
14};
15
16#[derive(Debug, Clone, PartialEq)]
17#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
18pub enum MusicalTransport {
19    /// A musical transport with a single static tempo in beats per minute.
20    Static(StaticTransport),
21    /// A musical transport with multiple keyframes of tempo. The tempo
22    /// immediately jumps from one keyframe to another (the tempo is *NOT*
23    /// linearly interpolated between keyframes).
24    Dynamic(Arc<DynamicTransport>),
25}
26
27impl MusicalTransport {
28    /// Returns the beats per minute if this is of type [`MusicalTransport::Static`],
29    /// `None` otherwise.
30    pub fn beats_per_minute(&self) -> Option<f64> {
31        if let MusicalTransport::Static(s) = self {
32            Some(s.beats_per_minute)
33        } else {
34            None
35        }
36    }
37
38    /// Convert the time in musical beats to the corresponding time in seconds.
39    ///
40    /// * `musical` - The time in musical beats to convert.
41    /// * `transport_start` - The instant of the start of the transport (musical
42    /// time of `0`).
43    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
44    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
45    /// in speed, and a value greater than `1.0` means an increase in speed.
46    pub fn musical_to_seconds(
47        &self,
48        musical: InstantMusical,
49        transport_start: InstantSeconds,
50        speed_multiplier: f64,
51    ) -> InstantSeconds {
52        match self {
53            MusicalTransport::Static(t) => {
54                t.musical_to_seconds(musical, transport_start, speed_multiplier)
55            }
56            MusicalTransport::Dynamic(t) => {
57                t.musical_to_seconds(musical, transport_start, speed_multiplier)
58            }
59        }
60    }
61
62    /// Convert the time in musical beats to the corresponding time in samples.
63    ///
64    /// * `musical` - The time in musical beats to convert.
65    /// * `transport_start` - The instant of the start of the transport (musical
66    /// time of `0`).
67    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
68    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
69    /// in speed, and a value greater than `1.0` means an increase in speed.
70    /// * `sample_rate` - The sample rate of the stream.
71    pub fn musical_to_samples(
72        &self,
73        musical: InstantMusical,
74        transport_start: InstantSamples,
75        speed_multiplier: f64,
76        sample_rate: NonZeroU32,
77    ) -> InstantSamples {
78        match self {
79            MusicalTransport::Static(t) => {
80                t.musical_to_samples(musical, transport_start, speed_multiplier, sample_rate)
81            }
82            MusicalTransport::Dynamic(t) => {
83                t.musical_to_samples(musical, transport_start, speed_multiplier, sample_rate)
84            }
85        }
86    }
87
88    /// Convert the time in seconds to the corresponding time in musical beats.
89    ///
90    /// * `seconds` - The time in seconds to convert.
91    /// * `transport_start` - The instant of the start of the transport (musical
92    /// time of `0`).
93    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
94    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
95    /// in speed, and a value greater than `1.0` means an increase in speed.
96    /// * `sample_rate` - The sample rate of the stream.
97    pub fn seconds_to_musical(
98        &self,
99        seconds: InstantSeconds,
100        transport_start: InstantSeconds,
101        speed_multiplier: f64,
102    ) -> InstantMusical {
103        match self {
104            MusicalTransport::Static(t) => {
105                t.seconds_to_musical(seconds, transport_start, speed_multiplier)
106            }
107            MusicalTransport::Dynamic(t) => {
108                t.seconds_to_musical(seconds, transport_start, speed_multiplier)
109            }
110        }
111    }
112
113    /// Convert the time in samples to the corresponding time in musical beats.
114    ///
115    /// * `sample_time` - The time in samples to convert.
116    /// * `transport_start` - The instant of the start of the transport (musical
117    /// time of `0`).
118    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
119    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
120    /// in speed, and a value greater than `1.0` means an increase in speed.
121    /// * `sample_rate` - The sample rate of the stream.
122    /// * `sample_rate` - The reciprocal of the sample rate.
123    pub fn samples_to_musical(
124        &self,
125        sample_time: InstantSamples,
126        transport_start: InstantSamples,
127        speed_multiplier: f64,
128        sample_rate: NonZeroU32,
129        sample_rate_recip: f64,
130    ) -> InstantMusical {
131        match self {
132            MusicalTransport::Static(t) => t.samples_to_musical(
133                sample_time,
134                transport_start,
135                speed_multiplier,
136                sample_rate,
137                sample_rate_recip,
138            ),
139            MusicalTransport::Dynamic(t) => t.samples_to_musical(
140                sample_time,
141                transport_start,
142                speed_multiplier,
143                sample_rate,
144                sample_rate_recip,
145            ),
146        }
147    }
148
149    /// Return the musical time that occurs `delta_seconds` seconds after the
150    /// given `from` timestamp.
151    ///
152    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
153    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
154    /// in speed, and a value greater than `1.0` means an increase in speed.
155    pub fn delta_seconds_from(
156        &self,
157        from: InstantMusical,
158        delta_seconds: DurationSeconds,
159        speed_multiplier: f64,
160    ) -> InstantMusical {
161        match self {
162            MusicalTransport::Static(t) => {
163                t.delta_seconds_from(from, delta_seconds, speed_multiplier)
164            }
165            MusicalTransport::Dynamic(t) => {
166                t.delta_seconds_from(from, delta_seconds, speed_multiplier)
167            }
168        }
169    }
170
171    /// Return the tempo in beats per minute at the given musical time.
172    ///
173    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
174    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
175    /// in speed, and a value greater than `1.0` means an increase in speed.
176    pub fn bpm_at_musical(&self, musical: InstantMusical, speed_multiplier: f64) -> f64 {
177        match self {
178            MusicalTransport::Static(t) => t.bpm_at_musical(musical, speed_multiplier),
179            MusicalTransport::Dynamic(t) => t.bpm_at_musical(musical, speed_multiplier),
180        }
181    }
182
183    /// Return information about this transport for this processing block.
184    ///
185    /// * `frames` - The number of frames in this processing block.
186    /// * `playhead` - The current playhead of the transport at frame `0` in this
187    /// processing block.
188    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
189    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
190    /// in speed, and a value greater than `1.0` means an increase in speed.
191    /// * `sample_rate` - The sample rate of the stream.
192    pub fn proc_transport_info(
193        &self,
194        frames: usize,
195        playhead: InstantMusical,
196        speed_multiplier: f64,
197        sample_rate: NonZeroU32,
198    ) -> ProcTransportInfo {
199        match self {
200            MusicalTransport::Static(t) => t.proc_transport_info(frames, speed_multiplier),
201            MusicalTransport::Dynamic(t) => {
202                t.proc_transport_info(frames, playhead, speed_multiplier, sample_rate)
203            }
204        }
205    }
206
207    /// Return the instant the beginning of this transport (musical time of `0`)
208    /// occurs on.
209    ///
210    /// * `now` - The current time in samples.
211    /// * `playhead` - The current playhead of the transport.
212    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
213    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
214    /// in speed, and a value greater than `1.0` means an increase in speed.
215    /// * `sample_rate` - The sample rate of the stream.
216    pub fn transport_start(
217        &self,
218        now: InstantSamples,
219        playhead: InstantMusical,
220        speed_multiplier: f64,
221        sample_rate: NonZeroU32,
222    ) -> InstantSamples {
223        match self {
224            MusicalTransport::Static(t) => {
225                t.transport_start(now, playhead, speed_multiplier, sample_rate)
226            }
227            MusicalTransport::Dynamic(t) => {
228                t.transport_start(now, playhead, speed_multiplier, sample_rate)
229            }
230        }
231    }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq)]
235pub struct ProcTransportInfo {
236    /// The number of frames in this processing block that this information
237    /// lasts for before either the information changes, or the end of the
238    /// processing block is reached (whichever comes first).
239    pub frames: usize,
240
241    /// The beats per minute at the first frame of this process block.
242    pub beats_per_minute: f64,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq)]
246pub struct SpeedMultiplierKeyframe {
247    /// The multiplier for the playback speed. A value of `1.0` means no change
248    /// in speed, a value less than `1.0` means a decrease in speed, and a value
249    /// greater than `1.0` means an increase in speed.
250    ///
251    /// This can cause a panic if `multiplier <= 0.0`.
252    pub multiplier: f64,
253
254    /// The instant that this keyframe happens.
255    pub instant: EventInstant,
256}
257
258/// A multiplier for the speed of the transport.
259///
260/// A value of `1.0` means no change in speed, a value less than `1.0` means
261/// a decrease in speed, and a value greater than `1.0` means an increase in
262/// speed.
263#[derive(Debug, Clone, PartialEq)]
264pub enum TransportSpeed {
265    /// Set the mulitplier to a single static value.
266    Static {
267        /// The speed multiplier.
268        ///
269        /// This can cause a panic if `multiplier <= 0.0`.
270        multiplier: f64,
271        /// If this is `Some`, then the change will happen when the transport
272        /// reaches the given playhead.
273        ///
274        /// If this is `None`, then the change will happen as soon as the
275        /// processor receives the event.
276        start_at: Option<InstantMusical>,
277    },
278    /// Automate the speed multiplier values.
279    Automate {
280        /// The keyframes of animation.
281        ///
282        /// Note, the keyframes must be sorted by the event instant or else it
283        /// will not work correctly.
284        keyframes: Arc<Vec<SpeedMultiplierKeyframe>>,
285        /// If this is `Some`, then the change will happen when the transport
286        /// reaches the given playhead.
287        ///
288        /// If this is `None`, then the change will happen as soon as the
289        /// processor receives the event.
290        start_at: Option<InstantMusical>,
291    },
292}
293
294impl TransportSpeed {
295    /// Create a [`TransportSpeed`] with a single static value.
296    ///
297    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
298    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
299    /// in speed, and a value greater than `1.0` means an increase in speed.
300    /// * `change_at`: If this is `Some`, then the change will happen when the transport
301    /// reaches the given playhead. If this is `None`, then the change will happen as soon
302    /// as the processor receives the event.
303    pub const fn static_multiplier(multiplier: f64, change_at: Option<InstantMusical>) -> Self {
304        Self::Static {
305            multiplier,
306            start_at: change_at,
307        }
308    }
309
310    pub fn start_at(&self) -> Option<InstantMusical> {
311        match self {
312            Self::Static { start_at, .. } => *start_at,
313            Self::Automate { start_at, .. } => *start_at,
314        }
315    }
316}
317
318impl Default for TransportSpeed {
319    fn default() -> Self {
320        Self::Static {
321            multiplier: 1.0,
322            start_at: None,
323        }
324    }
325}
326
327/// The state of the musical transport in a Firewheel context.
328#[derive(Debug, Clone, PartialEq)]
329#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
330pub struct TransportState {
331    /// The current musical transport.
332    pub transport: Option<MusicalTransport>,
333
334    /// Whether or not the musical transport is playing (true) or is paused (false).
335    pub playing: Notify<bool>,
336
337    /// The playhead of the musical transport.
338    pub playhead: Notify<InstantMusical>,
339
340    /// A multiplier for the speed of the transport.
341    ///
342    /// A value of `1.0` means no change in speed, a value less than `1.0` means
343    /// a decrease in speed, and a value greater than `1.0` means an increase in
344    /// speed.
345    pub speed: TransportSpeed,
346
347    /// If this is `Some`, then the transport will automatically stop when the playhead
348    /// reaches the given musical time.
349    ///
350    /// This has no effect if [`TransportState::loop_range`] is `Some`.
351    pub stop_at: Option<InstantMusical>,
352
353    /// If this is `Some`, then the transport will continously loop the given region.
354    pub loop_range: Option<Range<InstantMusical>>,
355}
356
357impl TransportState {
358    /// Set the transport to a single static tempo ([`StaticTransport`]).
359    ///
360    /// If `beats_per_minute` is `None`, then this will set the transport to `None`.
361    pub fn set_static_transport(&mut self, beats_per_minute: Option<f64>) {
362        self.transport =
363            beats_per_minute.map(|bpm| MusicalTransport::Static(StaticTransport::new(bpm)));
364    }
365
366    /// Get the beats per minute of the current static transport.
367    ///
368    /// Returns `None` if `transport` is `None` or if `transport` is not
369    /// [`MusicalTransport::Static`].
370    pub fn beats_per_minute(&self) -> Option<f64> {
371        self.transport.as_ref().and_then(|t| t.beats_per_minute())
372    }
373
374    /// Set a multiplier for the speed of the transport to a single static value.
375    ///
376    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
377    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
378    /// in speed, and a value greater than `1.0` means an increase in speed.
379    /// * `change_at`: If this is `Some`, then the change will happen when the transport
380    /// reaches the given playhead. If this is `None`, then the change will happen as soon
381    /// as the processor receives the event.
382    pub fn set_speed_multiplier(
383        &mut self,
384        speed_multiplier: f64,
385        change_at: Option<InstantMusical>,
386    ) {
387        self.speed = TransportSpeed::static_multiplier(speed_multiplier, change_at);
388    }
389}
390
391impl Default for TransportState {
392    fn default() -> Self {
393        Self {
394            transport: None,
395            playing: Notify::new(false),
396            playhead: Notify::new(InstantMusical::ZERO),
397            speed: TransportSpeed::default(),
398            stop_at: None,
399            loop_range: None,
400        }
401    }
402}
403
404#[inline]
405pub fn seconds_per_beat(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
406    60.0 / (beats_per_minute * speed_multiplier)
407}
408
409#[inline]
410pub fn beats_per_second(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
411    beats_per_minute * speed_multiplier * (1.0 / 60.0)
412}