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}