motion_core/authoring/timeline/
soundtrack.rs1use super::invalid;
4use crate::audio::{AudioBus, Master};
5use crate::{AudioClip, Duration, FrameRate};
6use std::sync::Arc;
7
8const MAX_BUSES: usize = 16;
9
10#[derive(Clone, Debug, Default)]
15pub struct Soundtrack {
16 pub(super) clips: Arc<Vec<AudioClip>>,
17 pub(super) buses: Arc<Vec<AudioBus>>,
18 pub(super) master: Master,
19}
20
21impl Soundtrack {
22 pub fn clips(&self) -> &[AudioClip] {
25 &self.clips
26 }
27
28 pub fn buses(&self) -> &[AudioBus] {
30 &self.buses
31 }
32
33 pub fn master(&self) -> Master {
35 self.master
36 }
37
38 pub(super) fn validate(&self) -> crate::Result<()> {
39 self.master.validate()?;
40 if self.buses.len() > MAX_BUSES {
41 return Err(invalid("At most sixteen audio buses"));
42 }
43 for (index, bus) in self.buses.iter().enumerate() {
44 Master {
45 gain_db: bus.gain_db,
46 ..Default::default()
47 }
48 .validate()?;
49 if bus.name.is_empty()
50 || bus.name.len() > 128
51 || bus.effects.len() > 16
52 || self.buses[..index]
53 .iter()
54 .any(|other| other.name == bus.name)
55 {
56 return Err(invalid("Invalid or duplicate audio bus"));
57 }
58 }
59 Ok(())
60 }
61
62 pub(super) fn clip_end(&self, clip: &AudioClip, rate: FrameRate) -> crate::Result<Duration> {
65 let mut end = clip.end()?.since_origin().magnitude();
66 if let Some(bus) = self
67 .buses
68 .iter()
69 .find(|bus| Some(bus.name.as_str()) == clip.parameters().bus)
70 {
71 for effect in &bus.effects {
72 let tail = effect.tail();
73 if tail.as_seconds() > 30.0 {
74 return Err(invalid("Bus effect tail requires 0..=30 seconds"));
75 }
76 end = Duration::from_ticks(
77 end.ticks()
78 .checked_add(tail.ticks())
79 .ok_or_else(|| invalid("Bus tail exceeds frame clock"))?,
80 );
81 }
82 }
83 if end.as_seconds() > 86400.0 {
84 return Err(invalid("Audio end exceeds 24 hours"));
85 }
86 ceil_to_frame(end, rate)
87 }
88
89 pub(super) fn end(&self, rate: FrameRate) -> crate::Result<Duration> {
92 let mut end = Duration::ZERO;
93 for clip in self.clips.iter() {
94 if clip.unresolved_file() && clip.parameters().duration.is_none() {
95 continue;
96 }
97 end = end.max(self.clip_end(clip, rate)?);
98 }
99 Ok(end)
100 }
101}
102
103pub(super) fn ceil_to_frame(end: Duration, rate: FrameRate) -> crate::Result<Duration> {
105 let frame = end.to_frame(rate);
106 let boundary = Duration::checked_from_frame(frame, rate)
107 .ok_or_else(|| invalid("Audio end exceeds frame clock"))?;
108 if boundary < end {
109 Duration::checked_from_frame(frame + 1, rate)
110 .ok_or_else(|| invalid("Audio end exceeds frame clock"))
111 } else {
112 Ok(boundary)
113 }
114}