Skip to main content

firewheel_core/clock/
transport.rs

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