use super::invalid;
use crate::audio::{AudioBus, Master};
use crate::{AudioClip, Duration, FrameRate};
use std::sync::Arc;
const MAX_BUSES: usize = 16;
#[derive(Clone, Debug, Default)]
pub struct Soundtrack {
pub(super) clips: Arc<Vec<AudioClip>>,
pub(super) buses: Arc<Vec<AudioBus>>,
pub(super) master: Master,
}
impl Soundtrack {
pub fn clips(&self) -> &[AudioClip] {
&self.clips
}
pub fn buses(&self) -> &[AudioBus] {
&self.buses
}
pub fn master(&self) -> Master {
self.master
}
pub(super) fn validate(&self) -> crate::Result<()> {
self.master.validate()?;
if self.buses.len() > MAX_BUSES {
return Err(invalid("At most sixteen audio buses"));
}
for (index, bus) in self.buses.iter().enumerate() {
Master {
gain_db: bus.gain_db,
..Default::default()
}
.validate()?;
if bus.name.is_empty()
|| bus.name.len() > 128
|| bus.effects.len() > 16
|| self.buses[..index]
.iter()
.any(|other| other.name == bus.name)
{
return Err(invalid("Invalid or duplicate audio bus"));
}
}
Ok(())
}
pub(super) fn clip_end(&self, clip: &AudioClip, rate: FrameRate) -> crate::Result<Duration> {
let mut end = clip.end()?.since_origin().magnitude();
if let Some(bus) = self
.buses
.iter()
.find(|bus| Some(bus.name.as_str()) == clip.parameters().bus)
{
for effect in &bus.effects {
let tail = effect.tail();
if tail.as_seconds() > 30.0 {
return Err(invalid("Bus effect tail requires 0..=30 seconds"));
}
end = Duration::from_ticks(
end.ticks()
.checked_add(tail.ticks())
.ok_or_else(|| invalid("Bus tail exceeds frame clock"))?,
);
}
}
if end.as_seconds() > 86400.0 {
return Err(invalid("Audio end exceeds 24 hours"));
}
ceil_to_frame(end, rate)
}
pub(super) fn end(&self, rate: FrameRate) -> crate::Result<Duration> {
let mut end = Duration::ZERO;
for clip in self.clips.iter() {
if clip.unresolved_file() && clip.parameters().duration.is_none() {
continue;
}
end = end.max(self.clip_end(clip, rate)?);
}
Ok(end)
}
}
pub(super) fn ceil_to_frame(end: Duration, rate: FrameRate) -> crate::Result<Duration> {
let frame = end.to_frame(rate);
let boundary = Duration::checked_from_frame(frame, rate)
.ok_or_else(|| invalid("Audio end exceeds frame clock"))?;
if boundary < end {
Duration::checked_from_frame(frame + 1, rate)
.ok_or_else(|| invalid("Audio end exceeds frame clock"))
} else {
Ok(boundary)
}
}