Skip to main content

alpha2_model/
audio_load.rs

1//! How much audio effect state a project holds at once.
2//!
3//! [`crate::MAX_AUDIO_EFFECTS`] and [`crate::MAX_AUDIO_DELAY_MS`] bound the state a mixer
4//! holds at one time, not across the whole timeline. The TypeScript validator measures the
5//! same way.
6
7use crate::audio::TICKS_PER_MS;
8use crate::{AUDIO_EFFECT_STATE_MARGIN_MS, AudioEffect, Project};
9
10/// The most audio effect state a project holds at once.
11///
12/// The master's and every track's effects hold state throughout. A clip's effects hold state
13/// from [`crate::AUDIO_EFFECT_STATE_MARGIN_MS`] plus its track's and its own lookahead before
14/// its start (clamped at zero) to its end, both ends inclusive, so a clip ending where
15/// another starts overlaps it. A clip with pitch-preserving stretched audio
16/// ([`crate::Clip::stretches_audio`]) counts one effect more.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct AudioEffectLoad {
19    /// The most effects holding state at once.
20    pub effects: usize,
21    /// The earliest timeline position, in ticks, at which `effects` hold state.
22    pub effects_at_ticks: i64,
23    /// The most delay held at once, in ticks (120 per millisecond), summed from each effect's
24    /// [`AudioEffect::delay_ticks`].
25    pub delay_ticks: u64,
26    /// The earliest timeline position, in ticks, at which `delay_ticks` is held.
27    pub delay_at_ticks: i64,
28}
29
30impl AudioEffectLoad {
31    /// [`Self::delay_ticks`] in milliseconds, rounded up.
32    #[must_use]
33    pub fn delay_ms(&self) -> u64 {
34        self.delay_ticks.div_ceil(u64::from(TICKS_PER_MS))
35    }
36}
37
38/// One end of the interval in which a clip's effects hold state.
39struct Edge {
40    at: i64,
41    /// Whether the interval ends here; at one position, starts sort first.
42    ends: bool,
43    effects: usize,
44    delay: u64,
45}
46
47impl Project {
48    /// Measures the most audio effect state this project holds at once, which validation bounds
49    /// by [`crate::MAX_AUDIO_EFFECTS`] and [`crate::MAX_AUDIO_DELAY_MS`]. O(n log n) in the clips
50    /// with effects.
51    #[must_use]
52    pub fn audio_effect_load(&self) -> AudioEffectLoad {
53        let chain = |effects: &[AudioEffect]| {
54            (
55                effects.len(),
56                effects.iter().map(AudioEffect::delay_ticks).sum::<u64>(),
57            )
58        };
59        let lookahead = |effects: &[AudioEffect]| {
60            effects
61                .iter()
62                .map(AudioEffect::lookahead_ticks)
63                .sum::<u64>()
64        };
65        let margin = u64::from(AUDIO_EFFECT_STATE_MARGIN_MS) * u64::from(TICKS_PER_MS);
66
67        let (mut effects, mut delay) = chain(&self.audio.effects);
68        let mut edges = Vec::new();
69        for track in &self.timeline.tracks {
70            let (count, held) = chain(&track.audio.effects);
71            effects += count;
72            delay += held;
73            let track_lookahead = lookahead(&track.audio.effects);
74            for clip in &track.clips {
75                // A stretched read holds state like one effect, without delay.
76                let stretch = usize::from(clip.stretches_audio());
77                if clip.audio.effects.is_empty() && stretch == 0 {
78                    continue;
79                }
80                let (count, held) = chain(&clip.audio.effects);
81                let count = count + stretch;
82                let lead = track_lookahead + lookahead(&clip.audio.effects) + margin;
83                let lead = i64::try_from(lead).unwrap_or(i64::MAX);
84                let range = clip.timeline_range;
85                edges.push(Edge {
86                    at: range.start().ticks().saturating_sub(lead).max(0),
87                    ends: false,
88                    effects: count,
89                    delay: held,
90                });
91                edges.push(Edge {
92                    at: range.end().ticks(),
93                    ends: true,
94                    effects: count,
95                    delay: held,
96                });
97            }
98        }
99        edges.sort_unstable_by_key(|edge| (edge.at, edge.ends));
100
101        let mut load = AudioEffectLoad {
102            effects,
103            effects_at_ticks: 0,
104            delay_ticks: delay,
105            delay_at_ticks: 0,
106        };
107        for edge in edges {
108            if edge.ends {
109                effects -= edge.effects;
110                delay -= edge.delay;
111                continue;
112            }
113            effects += edge.effects;
114            delay += edge.delay;
115            if effects > load.effects {
116                load.effects = effects;
117                load.effects_at_ticks = edge.at;
118            }
119            if delay > load.delay_ticks {
120                load.delay_ticks = delay;
121                load.delay_at_ticks = edge.at;
122            }
123        }
124        load
125    }
126}