firewheel-core 0.10.1

Shared types for Firewheel crates
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
mod dynamic_transport;
mod static_transport;

use bevy_platform::prelude::Vec;
use bevy_platform::sync::Arc;

use core::{fmt::Debug, num::NonZeroU32, ops::Range};

pub use dynamic_transport::{DynamicTransport, TransportKeyframe};
pub use static_transport::StaticTransport;

use crate::{
    clock::{DurationSeconds, EventInstant, InstantMusical, InstantSamples, InstantSeconds},
    diff::Notify,
};

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
pub enum MusicalTransport {
    /// A musical transport with a single static tempo in beats per minute.
    Static(StaticTransport),
    /// A musical transport with multiple keyframes of tempo. The tempo
    /// immediately jumps from one keyframe to another (the tempo is *NOT*
    /// linearly interpolated between keyframes).
    Dynamic(Arc<DynamicTransport>),
}

impl MusicalTransport {
    /// Returns the beats per minute if this is of type [`MusicalTransport::Static`],
    /// `None` otherwise.
    pub fn beats_per_minute(&self) -> Option<f64> {
        if let MusicalTransport::Static(s) = self {
            Some(s.beats_per_minute)
        } else {
            None
        }
    }

    /// Convert the time in musical beats to the corresponding time in seconds.
    ///
    /// * `musical` - The time in musical beats to convert.
    /// * `transport_start` - The instant of the start of the transport (musical
    /// time of `0`).
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    pub fn musical_to_seconds(
        &self,
        musical: InstantMusical,
        transport_start: InstantSeconds,
        speed_multiplier: f64,
    ) -> InstantSeconds {
        match self {
            MusicalTransport::Static(t) => {
                t.musical_to_seconds(musical, transport_start, speed_multiplier)
            }
            MusicalTransport::Dynamic(t) => {
                t.musical_to_seconds(musical, transport_start, speed_multiplier)
            }
        }
    }

    /// Convert the time in musical beats to the corresponding time in samples.
    ///
    /// * `musical` - The time in musical beats to convert.
    /// * `transport_start` - The instant of the start of the transport (musical
    /// time of `0`).
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `sample_rate` - The sample rate of the stream.
    pub fn musical_to_samples(
        &self,
        musical: InstantMusical,
        transport_start: InstantSamples,
        speed_multiplier: f64,
        sample_rate: NonZeroU32,
    ) -> InstantSamples {
        match self {
            MusicalTransport::Static(t) => {
                t.musical_to_samples(musical, transport_start, speed_multiplier, sample_rate)
            }
            MusicalTransport::Dynamic(t) => {
                t.musical_to_samples(musical, transport_start, speed_multiplier, sample_rate)
            }
        }
    }

    /// Convert the time in seconds to the corresponding time in musical beats.
    ///
    /// * `seconds` - The time in seconds to convert.
    /// * `transport_start` - The instant of the start of the transport (musical
    /// time of `0`).
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `sample_rate` - The sample rate of the stream.
    pub fn seconds_to_musical(
        &self,
        seconds: InstantSeconds,
        transport_start: InstantSeconds,
        speed_multiplier: f64,
    ) -> InstantMusical {
        match self {
            MusicalTransport::Static(t) => {
                t.seconds_to_musical(seconds, transport_start, speed_multiplier)
            }
            MusicalTransport::Dynamic(t) => {
                t.seconds_to_musical(seconds, transport_start, speed_multiplier)
            }
        }
    }

    /// Convert the time in samples to the corresponding time in musical beats.
    ///
    /// * `sample_time` - The time in samples to convert.
    /// * `transport_start` - The instant of the start of the transport (musical
    /// time of `0`).
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `sample_rate` - The sample rate of the stream.
    /// * `sample_rate` - The reciprocal of the sample rate.
    pub fn samples_to_musical(
        &self,
        sample_time: InstantSamples,
        transport_start: InstantSamples,
        speed_multiplier: f64,
        sample_rate: NonZeroU32,
        sample_rate_recip: f64,
    ) -> InstantMusical {
        match self {
            MusicalTransport::Static(t) => t.samples_to_musical(
                sample_time,
                transport_start,
                speed_multiplier,
                sample_rate,
                sample_rate_recip,
            ),
            MusicalTransport::Dynamic(t) => t.samples_to_musical(
                sample_time,
                transport_start,
                speed_multiplier,
                sample_rate,
                sample_rate_recip,
            ),
        }
    }

    /// Return the musical time that occurs `delta_seconds` seconds after the
    /// given `from` timestamp.
    ///
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    pub fn delta_seconds_from(
        &self,
        from: InstantMusical,
        delta_seconds: DurationSeconds,
        speed_multiplier: f64,
    ) -> InstantMusical {
        match self {
            MusicalTransport::Static(t) => {
                t.delta_seconds_from(from, delta_seconds, speed_multiplier)
            }
            MusicalTransport::Dynamic(t) => {
                t.delta_seconds_from(from, delta_seconds, speed_multiplier)
            }
        }
    }

    /// Return the tempo in beats per minute at the given musical time.
    ///
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    pub fn bpm_at_musical(&self, musical: InstantMusical, speed_multiplier: f64) -> f64 {
        match self {
            MusicalTransport::Static(t) => t.bpm_at_musical(musical, speed_multiplier),
            MusicalTransport::Dynamic(t) => t.bpm_at_musical(musical, speed_multiplier),
        }
    }

    /// Return information about this transport for this processing block.
    ///
    /// * `frames` - The number of frames in this processing block.
    /// * `playhead` - The current playhead of the transport at frame `0` in this
    /// processing block.
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `sample_rate` - The sample rate of the stream.
    pub fn proc_transport_info(
        &self,
        frames: usize,
        playhead: InstantMusical,
        speed_multiplier: f64,
        sample_rate: NonZeroU32,
    ) -> ProcTransportInfo {
        match self {
            MusicalTransport::Static(t) => t.proc_transport_info(frames, speed_multiplier),
            MusicalTransport::Dynamic(t) => {
                t.proc_transport_info(frames, playhead, speed_multiplier, sample_rate)
            }
        }
    }

    /// Return the instant the beginning of this transport (musical time of `0`)
    /// occurs on.
    ///
    /// * `now` - The current time in samples.
    /// * `playhead` - The current playhead of the transport.
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `sample_rate` - The sample rate of the stream.
    pub fn transport_start(
        &self,
        now: InstantSamples,
        playhead: InstantMusical,
        speed_multiplier: f64,
        sample_rate: NonZeroU32,
    ) -> InstantSamples {
        match self {
            MusicalTransport::Static(t) => {
                t.transport_start(now, playhead, speed_multiplier, sample_rate)
            }
            MusicalTransport::Dynamic(t) => {
                t.transport_start(now, playhead, speed_multiplier, sample_rate)
            }
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProcTransportInfo {
    /// The number of frames in this processing block that this information
    /// lasts for before either the information changes, or the end of the
    /// processing block is reached (whichever comes first).
    pub frames: usize,

    /// The beats per minute at the first frame of this process block.
    pub beats_per_minute: f64,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpeedMultiplierKeyframe {
    /// The multiplier for the playback speed. A value of `1.0` means no change
    /// in speed, a value less than `1.0` means a decrease in speed, and a value
    /// greater than `1.0` means an increase in speed.
    ///
    /// This can cause a panic if `multiplier <= 0.0`.
    pub multiplier: f64,

    /// The instant that this keyframe happens.
    pub instant: EventInstant,
}

/// A multiplier for the speed of the transport.
///
/// A value of `1.0` means no change in speed, a value less than `1.0` means
/// a decrease in speed, and a value greater than `1.0` means an increase in
/// speed.
#[derive(Debug, Clone, PartialEq)]
pub enum TransportSpeed {
    /// Set the mulitplier to a single static value.
    Static {
        /// The speed multiplier.
        ///
        /// This can cause a panic if `multiplier <= 0.0`.
        multiplier: f64,
        /// If this is `Some`, then the change will happen when the transport
        /// reaches the given playhead.
        ///
        /// If this is `None`, then the change will happen as soon as the
        /// processor receives the event.
        start_at: Option<InstantMusical>,
    },
    /// Automate the speed multiplier values.
    Automate {
        /// The keyframes of animation.
        ///
        /// Note, the keyframes must be sorted by the event instant or else it
        /// will not work correctly.
        keyframes: Arc<Vec<SpeedMultiplierKeyframe>>,
        /// If this is `Some`, then the change will happen when the transport
        /// reaches the given playhead.
        ///
        /// If this is `None`, then the change will happen as soon as the
        /// processor receives the event.
        start_at: Option<InstantMusical>,
    },
}

impl TransportSpeed {
    /// Create a [`TransportSpeed`] with a single static value.
    ///
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `change_at`: If this is `Some`, then the change will happen when the transport
    /// reaches the given playhead. If this is `None`, then the change will happen as soon
    /// as the processor receives the event.
    pub const fn static_multiplier(multiplier: f64, change_at: Option<InstantMusical>) -> Self {
        Self::Static {
            multiplier,
            start_at: change_at,
        }
    }

    pub fn start_at(&self) -> Option<InstantMusical> {
        match self {
            Self::Static { start_at, .. } => *start_at,
            Self::Automate { start_at, .. } => *start_at,
        }
    }
}

impl Default for TransportSpeed {
    fn default() -> Self {
        Self::Static {
            multiplier: 1.0,
            start_at: None,
        }
    }
}

/// The state of the musical transport in a Firewheel context.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
pub struct TransportState {
    /// The current musical transport.
    pub transport: Option<MusicalTransport>,

    /// Whether or not the musical transport is playing (true) or is paused (false).
    pub playing: Notify<bool>,

    /// The playhead of the musical transport.
    pub playhead: Notify<InstantMusical>,

    /// A multiplier for the speed of the transport.
    ///
    /// A value of `1.0` means no change in speed, a value less than `1.0` means
    /// a decrease in speed, and a value greater than `1.0` means an increase in
    /// speed.
    pub speed: TransportSpeed,

    /// If this is `Some`, then the transport will automatically stop when the playhead
    /// reaches the given musical time.
    ///
    /// This has no effect if [`TransportState::loop_range`] is `Some`.
    pub stop_at: Option<InstantMusical>,

    /// If this is `Some`, then the transport will continously loop the given region.
    pub loop_range: Option<Range<InstantMusical>>,
}

impl TransportState {
    /// Set the transport to a single static tempo ([`StaticTransport`]).
    ///
    /// If `beats_per_minute` is `None`, then this will set the transport to `None`.
    pub fn set_static_transport(&mut self, beats_per_minute: Option<f64>) {
        self.transport =
            beats_per_minute.map(|bpm| MusicalTransport::Static(StaticTransport::new(bpm)));
    }

    /// Get the beats per minute of the current static transport.
    ///
    /// Returns `None` if `transport` is `None` or if `transport` is not
    /// [`MusicalTransport::Static`].
    pub fn beats_per_minute(&self) -> Option<f64> {
        self.transport.as_ref().and_then(|t| t.beats_per_minute())
    }

    /// Set a multiplier for the speed of the transport to a single static value.
    ///
    /// * `speed_multiplier` - A multiplier for the playback speed. A value of
    /// `1.0` means no change in speed, a value less than `1.0` means a decrease
    /// in speed, and a value greater than `1.0` means an increase in speed.
    /// * `change_at`: If this is `Some`, then the change will happen when the transport
    /// reaches the given playhead. If this is `None`, then the change will happen as soon
    /// as the processor receives the event.
    pub fn set_speed_multiplier(
        &mut self,
        speed_multiplier: f64,
        change_at: Option<InstantMusical>,
    ) {
        self.speed = TransportSpeed::static_multiplier(speed_multiplier, change_at);
    }
}

impl Default for TransportState {
    fn default() -> Self {
        Self {
            transport: None,
            playing: Notify::new(false),
            playhead: Notify::new(InstantMusical::ZERO),
            speed: TransportSpeed::default(),
            stop_at: None,
            loop_range: None,
        }
    }
}

#[inline]
pub fn seconds_per_beat(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
    60.0 / (beats_per_minute * speed_multiplier)
}

#[inline]
pub fn beats_per_second(beats_per_minute: f64, speed_multiplier: f64) -> f64 {
    beats_per_minute * speed_multiplier * (1.0 / 60.0)
}