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