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}