1use crate::{Duration, 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 Duration::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: Duration::from_ticks(crate::TICKS_PER_SECOND as u64 / 200),
80 release: Duration::from_ticks(crate::TICKS_PER_SECOND as u64 / 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.as_seconds() > 0.02
95 || limit.release <= Duration::ZERO
96 || limit.release.as_seconds() > 2.0
97 }) {
98 return Err(invalid("limiter", "Invalid ceiling, lookahead or release"));
99 }
100 if self.normalize.is_some_and(|normal| {
101 !normal.target_lufs.is_finite()
102 || !(-70.0..=-5.0).contains(&normal.target_lufs)
103 || !normal.true_peak_db.is_finite()
104 || !(-24.0..=0.0).contains(&normal.true_peak_db)
105 }) {
106 return Err(invalid(
107 "normalize",
108 "Invalid LUFS target or true peak ceiling",
109 ));
110 }
111 Ok(())
112 }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq)]
118pub struct Duck {
119 pub threshold_db: f64,
121 pub depth_db: f64,
123 pub ratio: f64,
125 pub attack: Duration,
127 pub release: Duration,
129 pub lookahead: Duration,
131}
132
133impl Default for Duck {
134 fn default() -> Self {
135 Self {
136 threshold_db: -45.0,
137 depth_db: 12.0,
138 ratio: 100.0,
139 attack: Duration::from_ticks(crate::TICKS_PER_SECOND as u64 / 1000 * 80),
140 release: Duration::from_ticks(crate::TICKS_PER_SECOND as u64 / 1000 * 400),
141 lookahead: Duration::from_ticks(crate::TICKS_PER_SECOND as u64 / 100),
142 }
143 }
144}
145
146impl Duck {
147 pub fn new(threshold_db: f64, depth_db: f64) -> Self {
149 Self {
150 threshold_db,
151 depth_db,
152 ..Self::default()
153 }
154 }
155
156 pub fn ratio(mut self, ratio: f64) -> Self {
158 self.ratio = ratio;
159 self
160 }
161
162 pub fn timing(mut self, attack: Duration, release: Duration) -> Self {
164 self.attack = attack;
165 self.release = release;
166 self
167 }
168
169 pub fn lookahead(mut self, lookahead: Duration) -> Self {
171 self.lookahead = lookahead;
172 self
173 }
174
175 pub fn validate(self) -> Result<(), ValidationError> {
177 let seconds =
178 |time: Duration, max: f64| time.as_seconds().is_finite() && time.as_seconds() <= max;
179 if !self.threshold_db.is_finite()
180 || !(-96.0..=0.0).contains(&self.threshold_db)
181 || !self.depth_db.is_finite()
182 || !(0.0..=96.0).contains(&self.depth_db)
183 || !self.ratio.is_finite()
184 || !(1.0..=100.0).contains(&self.ratio)
185 || !seconds(self.attack, 5.0)
186 || !seconds(self.release, 10.0)
187 || !seconds(self.lookahead, 0.5)
188 {
189 return Err(invalid(
190 "duck",
191 "Require threshold -96..=0 dB, depth 0..=96 dB, ratio 1..=100, attack <=5 s, release <=10 s and lookahead <=0.5 s",
192 ));
193 }
194 Ok(())
195 }
196}
197
198#[derive(Clone, Debug)]
200pub enum DuckTrigger {
201 Clip(Arc<AudioClip>),
203 Bus(String),
205}
206
207#[derive(Clone, Debug)]
209pub struct Ducking {
210 pub trigger: DuckTrigger,
212 pub duck: Duck,
214}
215
216#[derive(Clone, Debug)]
218pub struct AudioBus {
219 pub name: String,
221 pub gain_db: f64,
223 pub effects: Vec<Arc<dyn AudioEffect>>,
225 pub ducking: Vec<Ducking>,
227}
228
229impl AudioBus {
230 pub fn new(name: impl Into<String>) -> Self {
232 Self {
233 name: name.into(),
234 gain_db: 0.0,
235 effects: Vec::new(),
236 ducking: Vec::new(),
237 }
238 }
239
240 pub fn ducked_by(mut self, trigger: DuckTrigger, duck: Duck) -> Self {
242 self.ducking.push(Ducking { trigger, duck });
243 self
244 }
245}
246
247pub trait AudioSource: Send + Sync {
251 fn composition(&self) -> Option<&crate::Timeline> {
253 None
254 }
255
256 fn file_path(&self) -> Option<&std::path::Path> {
258 None
259 }
260
261 fn try_read(&self, start_frame: u64, output: &mut [f32]) -> crate::Result<usize> {
263 Ok(self.read(start_frame, output))
264 }
265
266 fn sample_rate(&self) -> u32;
268
269 fn channels(&self) -> u16;
271
272 fn read(&self, start_frame: u64, output: &mut [f32]) -> usize;
275
276 fn duration(&self) -> Option<Duration>;
278}
279
280impl<Source: AudioSource + ?Sized> AudioSource for Arc<Source> {
281 fn composition(&self) -> Option<&crate::Timeline> {
282 self.as_ref().composition()
283 }
284
285 fn file_path(&self) -> Option<&std::path::Path> {
286 self.as_ref().file_path()
287 }
288
289 fn try_read(&self, start: u64, output: &mut [f32]) -> crate::Result<usize> {
290 self.as_ref().try_read(start, output)
291 }
292
293 fn sample_rate(&self) -> u32 {
294 self.as_ref().sample_rate()
295 }
296
297 fn channels(&self) -> u16 {
298 self.as_ref().channels()
299 }
300
301 fn read(&self, start: u64, output: &mut [f32]) -> usize {
302 self.as_ref().read(start, output)
303 }
304
305 fn duration(&self) -> Option<Duration> {
306 self.as_ref().duration()
307 }
308}
309
310pub fn frames(time: Duration, rate: u32) -> u64 {
313 ((u128::from(time.ticks()) * u128::from(rate) + crate::TICKS_PER_SECOND as u128 / 2)
314 / crate::TICKS_PER_SECOND as u128) as u64
315}
316
317pub fn ceil_frames(time: Duration, rate: u32) -> u64 {
320 (u128::from(time.ticks()) * u128::from(rate)).div_ceil(crate::TICKS_PER_SECOND as u128) as u64
321}