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}