1use crate::{Duration, Time, ValidationError};
3use std::sync::Arc;
4
5mod clip;
6mod metadata;
7
8pub use clip::{AudioClip, AudioClipParameters};
9pub use metadata::{AudioChapter, AudioMetadata};
10
11fn invalid(field: &str, message: &str) -> ValidationError {
12 ValidationError::invalid("audio", field, message)
13}
14
15pub trait AudioEffect: Send + Sync + std::fmt::Debug {
18 fn validate(&self, rate: u32) -> Result<(), ValidationError>;
20
21 fn tail(&self) -> Duration {
23 Time::ZERO
24 }
25
26 fn process(&self, input: &mut [f32], channels: u16, rate: u32) -> crate::Result;
28}
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
32pub enum PanLaw {
33 Linear,
35 #[default]
37 ConstantPower,
38 Minus4_5dB,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct Limiter {
45 pub ceiling_db: f64,
47 pub lookahead: Duration,
49 pub release: Duration,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq)]
55pub struct Loudness {
56 pub target_lufs: f64,
58 pub true_peak_db: f64,
60}
61
62#[derive(Clone, Copy, Debug, Default, PartialEq)]
64pub struct Master {
65 pub gain_db: f64,
67 pub limiter: Option<Limiter>,
69 pub normalize: Option<Loudness>,
71}
72
73impl Master {
74 pub fn safe() -> Self {
76 Self {
77 limiter: Some(Limiter {
78 ceiling_db: -1.0,
79 lookahead: Time::from_ticks(crate::TICKS_PER_SECOND / 200),
80 release: Time::from_ticks(crate::TICKS_PER_SECOND / 10),
81 }),
82 ..Self::default()
83 }
84 }
85
86 pub fn validate(self) -> Result<(), ValidationError> {
88 if !self.gain_db.is_finite() || !(-96.0..=24.0).contains(&self.gain_db) {
89 return Err(invalid("master_gain", "Require finite -96..=24 dB"));
90 }
91 if self.limiter.is_some_and(|limit| {
92 !limit.ceiling_db.is_finite()
93 || !(-24.0..=0.0).contains(&limit.ceiling_db)
94 || limit.lookahead < Time::ZERO
95 || limit.lookahead.as_seconds() > 0.02
96 || limit.release <= Time::ZERO
97 || limit.release.as_seconds() > 2.0
98 }) {
99 return Err(invalid("limiter", "Invalid ceiling, lookahead or release"));
100 }
101 if self.normalize.is_some_and(|normal| {
102 !normal.target_lufs.is_finite()
103 || !(-70.0..=-5.0).contains(&normal.target_lufs)
104 || !normal.true_peak_db.is_finite()
105 || !(-24.0..=0.0).contains(&normal.true_peak_db)
106 }) {
107 return Err(invalid(
108 "normalize",
109 "Invalid LUFS target or true peak ceiling",
110 ));
111 }
112 Ok(())
113 }
114}
115
116#[derive(Clone, Copy, Debug, PartialEq)]
119pub struct Duck {
120 pub threshold_db: f64,
122 pub depth_db: f64,
124 pub ratio: f64,
126 pub attack: Duration,
128 pub release: Duration,
130 pub lookahead: Duration,
132}
133
134impl Default for Duck {
135 fn default() -> Self {
136 Self {
137 threshold_db: -45.0,
138 depth_db: 12.0,
139 ratio: 100.0,
140 attack: Time::from_ticks(crate::TICKS_PER_SECOND / 1000 * 80),
141 release: Time::from_ticks(crate::TICKS_PER_SECOND / 1000 * 400),
142 lookahead: Time::from_ticks(crate::TICKS_PER_SECOND / 100),
143 }
144 }
145}
146
147impl Duck {
148 pub fn new(threshold_db: f64, depth_db: f64) -> Self {
150 Self {
151 threshold_db,
152 depth_db,
153 ..Self::default()
154 }
155 }
156
157 pub fn ratio(mut self, ratio: f64) -> Self {
159 self.ratio = ratio;
160 self
161 }
162
163 pub fn timing(mut self, attack: Duration, release: Duration) -> Self {
165 self.attack = attack;
166 self.release = release;
167 self
168 }
169
170 pub fn lookahead(mut self, lookahead: Duration) -> Self {
172 self.lookahead = lookahead;
173 self
174 }
175
176 pub fn validate(self) -> Result<(), ValidationError> {
178 let seconds = |time: Duration, max: f64| {
179 time >= Time::ZERO && time.as_seconds().is_finite() && time.as_seconds() <= max
180 };
181 if !self.threshold_db.is_finite()
182 || !(-96.0..=0.0).contains(&self.threshold_db)
183 || !self.depth_db.is_finite()
184 || !(0.0..=96.0).contains(&self.depth_db)
185 || !self.ratio.is_finite()
186 || !(1.0..=100.0).contains(&self.ratio)
187 || !seconds(self.attack, 5.0)
188 || !seconds(self.release, 10.0)
189 || !seconds(self.lookahead, 0.5)
190 {
191 return Err(invalid(
192 "duck",
193 "Require threshold -96..=0 dB, depth 0..=96 dB, ratio 1..=100, attack <=5 s, release <=10 s and lookahead <=0.5 s",
194 ));
195 }
196 Ok(())
197 }
198}
199
200#[derive(Clone, Debug)]
202pub enum DuckTrigger {
203 Clip(Arc<AudioClip>),
205 Bus(String),
207}
208
209#[derive(Clone, Debug)]
211pub struct Ducking {
212 pub trigger: DuckTrigger,
214 pub duck: Duck,
216}
217
218#[derive(Clone, Debug)]
220pub struct AudioBus {
221 pub name: String,
223 pub gain_db: f64,
225 pub effects: Vec<Arc<dyn AudioEffect>>,
227 pub ducking: Vec<Ducking>,
229}
230
231impl AudioBus {
232 pub fn new(name: impl Into<String>) -> Self {
234 Self {
235 name: name.into(),
236 gain_db: 0.0,
237 effects: Vec::new(),
238 ducking: Vec::new(),
239 }
240 }
241
242 pub fn ducked_by(mut self, trigger: DuckTrigger, duck: Duck) -> Self {
244 self.ducking.push(Ducking { trigger, duck });
245 self
246 }
247}
248
249pub trait AudioSource: Send + Sync {
253 fn composition(&self) -> Option<&crate::Timeline> {
255 None
256 }
257
258 fn file_path(&self) -> Option<&std::path::Path> {
260 None
261 }
262
263 fn try_read(&self, start_frame: u64, output: &mut [f32]) -> crate::Result<usize> {
265 Ok(self.read(start_frame, output))
266 }
267
268 fn sample_rate(&self) -> u32;
270
271 fn channels(&self) -> u16;
273
274 fn read(&self, start_frame: u64, output: &mut [f32]) -> usize;
277
278 fn duration(&self) -> Option<Duration>;
280}
281
282impl<Source: AudioSource + ?Sized> AudioSource for Arc<Source> {
283 fn composition(&self) -> Option<&crate::Timeline> {
284 self.as_ref().composition()
285 }
286
287 fn file_path(&self) -> Option<&std::path::Path> {
288 self.as_ref().file_path()
289 }
290
291 fn try_read(&self, start: u64, output: &mut [f32]) -> crate::Result<usize> {
292 self.as_ref().try_read(start, output)
293 }
294
295 fn sample_rate(&self) -> u32 {
296 self.as_ref().sample_rate()
297 }
298
299 fn channels(&self) -> u16 {
300 self.as_ref().channels()
301 }
302
303 fn read(&self, start: u64, output: &mut [f32]) -> usize {
304 self.as_ref().read(start, output)
305 }
306
307 fn duration(&self) -> Option<Duration> {
308 self.as_ref().duration()
309 }
310}
311
312pub fn frames(time: Time, rate: u32) -> Result<u64, ValidationError> {
315 if time < Time::ZERO {
316 return Err(invalid("time", "Audio time must be nonnegative"));
317 }
318 Ok(
319 ((u128::from(time.ticks() as u64) * u128::from(rate) + crate::TICKS_PER_SECOND as u128 / 2)
320 / crate::TICKS_PER_SECOND as u128) as u64,
321 )
322}
323
324pub fn ceil_frames(time: Time, rate: u32) -> Result<u64, ValidationError> {
327 if time < Time::ZERO {
328 return Err(invalid("duration", "Audio duration must be nonnegative"));
329 }
330 Ok((u128::from(time.ticks() as u64) * u128::from(rate))
331 .div_ceil(crate::TICKS_PER_SECOND as u128) as u64)
332}