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    pub instant: EventInstant,
257}
258
259/// A multiplier for the speed of the transport.
260///
261/// A value of `1.0` means no change in speed, a value less than `1.0` means
262/// a decrease in speed, and a value greater than `1.0` means an increase in
263/// speed.
264#[derive(Debug, Clone, PartialEq)]
265pub enum TransportSpeed {
266    /// Set the mulitplier to a single static value.
267    Static {
268        /// The speed multiplier.
269        ///
270        /// This can cause a panic if `multiplier <= 0.0`.
271        multiplier: f64,
272        /// If this is `Some`, then the change will happen when the transport
273        /// reaches the given playhead.
274        ///
275        /// If this is `None`, then the change will happen as soon as the
276        /// processor receives the event.
277        start_at: Option<InstantMusical>,
278    },
279    /// Automate the speed multiplier values.
280    Automate {
281        /// The keyframes of animation.
282        ///
283        /// Note, the keyframes must be sorted by the event instant or else it
284        /// will not work correctly.
285        keyframes: Arc<Vec<SpeedMultiplierKeyframe>>,
286        /// If this is `Some`, then the change will happen when the transport
287        /// reaches the given playhead.
288        ///
289        /// If this is `None`, then the change will happen as soon as the
290        /// processor receives the event.
291        start_at: Option<InstantMusical>,
292    },
293}
294
295impl TransportSpeed {
296    /// Create a [`TransportSpeed`] with a single static value.
297    ///
298    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
299    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
300    /// in speed, and a value greater than `1.0` means an increase in speed.
301    /// * `change_at`: If this is `Some`, then the change will happen when the transport
302    /// reaches the given playhead. If this is `None`, then the change will happen as soon
303    /// as the processor receives the event.
304    pub const fn static_multiplier(multiplier: f64, change_at: Option<InstantMusical>) -> Self {
305        Self::Static {
306            multiplier,
307            start_at: change_at,
308        }
309    }
310
311    pub fn start_at(&self) -> Option<InstantMusical> {
312        match self {
313            Self::Static { start_at, .. } => *start_at,
314            Self::Automate { start_at, .. } => *start_at,
315        }
316    }
317}
318
319impl Default for TransportSpeed {
320    fn default() -> Self {
321        Self::Static {
322            multiplier: 1.0,
323            start_at: None,
324        }
325    }
326}
327
328/// The state of the musical transport in a Firewheel context.
329#[derive(Debug, Clone, PartialEq)]
330#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
331pub struct TransportState {
332    /// The current musical transport.
333    pub transport: Option<MusicalTransport>,
334
335    /// Whether or not the musical transport is playing (true) or is paused (false).
336    pub playing: Notify<bool>,
337
338    /// The playhead of the musical transport.
339    pub playhead: Notify<InstantMusical>,
340
341    /// A multiplier for the speed of the transport.
342    ///
343    /// A value of `1.0` means no change in speed, a value less than `1.0` means
344    /// a decrease in speed, and a value greater than `1.0` means an increase in
345    /// speed.
346    pub speed: TransportSpeed,
347
348    /// If this is `Some`, then the transport will automatically stop when the playhead
349    /// reaches the given musical time.
350    ///
351    /// This has no effect if [`TransportState::loop_range`] is `Some`.
352    pub stop_at: Option<InstantMusical>,
353
354    /// If this is `Some`, then the transport will continously loop the given region.
355    pub loop_range: Option<Range<InstantMusical>>,
356}
357
358impl TransportState {
359    /// Set the transport to a single static tempo ([`StaticTransport`]).
360    ///
361    /// If `beats_per_minute` is `None`, then this will set the transport to `None`.
362    pub fn set_static_transport(&mut self, beats_per_minute: Option<f64>) {
363        self.transport =
364            beats_per_minute.map(|bpm| MusicalTransport::Static(StaticTransport::new(bpm)));
365    }
366
367    /// Get the beats per minute of the current static transport.
368    ///
369    /// Returns `None` if `transport` is `None` or if `transport` is not
370    /// [`MusicalTransport::Static`].
371    pub fn beats_per_minute(&self) -> Option<f64> {
372        self.transport.as_ref().and_then(|t| t.beats_per_minute())
373    }
374
375    /// Set a multiplier for the speed of the transport to a single static value.
376    ///
377    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
378    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
379    /// in speed, and a value greater than `1.0` means an increase in speed.
380    /// * `change_at`: If this is `Some`, then the change will happen when the transport
381    /// reaches the given playhead. If this is `None`, then the change will happen as soon
382    /// as the processor receives the event.
383    pub fn set_speed_multiplier(
384        &mut self,
385        speed_multiplier: f64,
386        change_at: Option<InstantMusical>,
387    ) {
388        self.speed = TransportSpeed::static_multiplier(speed_multiplier, change_at);
389    }
390}
391
392impl Default for TransportState {
393    fn default() -> Self {
394        Self {
395            transport: None,
396            playing: Notify::new(false),
397            playhead: Notify::new(InstantMusical::ZERO),
398            speed: TransportSpeed::default(),
399            stop_at: None,
400            loop_range: None,
401        }
402    }
403}
404
405#[inline]
406pub fn seconds_per_beat(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
407    60.0 / (beats_per_minute * speed_multiplier)
408}
409
410#[inline]
411pub fn beats_per_second(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
412    beats_per_minute * speed_multiplier * (1.0 / 60.0)
413}