oxideav_scene/audio.rs
1//! Timeline-triggered audio cues.
2//!
3//! Audio is not positioned — there's one mix bus per [`Scene`].
4//! Each [`AudioCue`] has a trigger timestamp at which it starts
5//! playing. The renderer accumulates contributions from every live
6//! cue into the scene's output audio buffer.
7
8use std::sync::Arc;
9
10use crate::animation::Animation;
11use crate::duration::TimeStamp;
12
13/// One scheduled audio playback.
14#[derive(Clone, Debug)]
15pub struct AudioCue {
16 /// Scene-time at which this cue starts playing.
17 pub trigger: TimeStamp,
18 pub source: AudioSource,
19 /// Animated 0.0..=1.0 volume envelope. A cue with no keyframes
20 /// plays at unit gain.
21 pub volume: Animation,
22 /// Other cues / bus tags to duck while this cue is playing.
23 pub duck: Vec<DuckBus>,
24 /// Optional explicit stop time. `None` = play to the source's
25 /// natural end.
26 pub end: Option<TimeStamp>,
27}
28
29/// Audio content source.
30#[non_exhaustive]
31#[derive(Clone, Debug)]
32pub enum AudioSource {
33 Path(String),
34 EncodedBytes(Arc<[u8]>),
35 /// Pre-decoded PCM — interleaved S16.
36 PcmS16 {
37 sample_rate: u32,
38 channels: u8,
39 samples: Arc<[i16]>,
40 },
41 /// Pre-decoded PCM — interleaved F32.
42 PcmF32 {
43 sample_rate: u32,
44 channels: u8,
45 samples: Arc<[f32]>,
46 },
47 /// Generator — sine / noise / silence. Useful for placeholder
48 /// beds and quick tests.
49 Generator(Generator),
50}
51
52/// Simple procedural audio generators.
53#[non_exhaustive]
54#[derive(Clone, Copy, Debug)]
55pub enum Generator {
56 Silence,
57 SineWave { frequency_hz: f32, amplitude: f32 },
58 WhiteNoise { amplitude: f32 },
59}
60
61/// Ducking reference — attenuate all cues sharing this `bus` tag
62/// while the owning cue plays. `reduction_db` is the target level;
63/// `attack_ms` / `release_ms` control the envelope around the
64/// trigger.
65#[derive(Clone, Copy, Debug)]
66pub struct DuckBus {
67 pub bus: u32,
68 pub reduction_db: f32,
69 pub attack_ms: u32,
70 pub release_ms: u32,
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::animation::{AnimatedProperty, Easing, Repeat};
77
78 #[test]
79 fn default_volume_animation_holds_unity() {
80 let cue = AudioCue {
81 trigger: 0,
82 source: AudioSource::Generator(Generator::Silence),
83 volume: Animation::new(
84 AnimatedProperty::Volume,
85 Vec::new(),
86 Easing::Linear,
87 Repeat::Once,
88 ),
89 duck: Vec::new(),
90 end: None,
91 };
92 assert!(cue.volume.sample(0).is_none());
93 }
94}