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}