Skip to main content

proteus_lib/playback/engine/
mod.rs

1//! Playback mixing engine and buffer coordination.
2
3use dasp_ring_buffer::Bounded;
4use rodio::buffer::SamplesBuffer;
5use std::collections::HashMap;
6use std::sync::atomic::AtomicBool;
7use std::sync::atomic::AtomicU64;
8use std::sync::{mpsc::Receiver, Arc, Condvar, Mutex};
9
10use crate::audio::buffer::{init_buffer_map, TrackBuffer};
11use crate::container::prot::Prot;
12
13mod mix;
14mod premix;
15mod state;
16
17pub use state::{DspChainMetrics, PlaybackBufferSettings};
18
19use mix::{spawn_mix_thread, MixThreadArgs};
20
21/// Request to update the active effects chain inline during playback.
22#[derive(Debug, Clone)]
23pub struct InlineEffectsUpdate {
24    pub effects: Vec<crate::dsp::effects::AudioEffect>,
25    pub transition_ms: f32,
26}
27
28impl InlineEffectsUpdate {
29    /// Create an inline effect update request.
30    pub fn new(effects: Vec<crate::dsp::effects::AudioEffect>, transition_ms: f32) -> Self {
31        Self {
32            effects,
33            transition_ms: transition_ms.max(0.0),
34        }
35    }
36}
37
38/// Request to update per-slot track mix settings inline during playback.
39#[derive(Debug, Clone, Copy)]
40pub struct InlineTrackMixUpdate {
41    pub slot_index: usize,
42    pub level: f32,
43    pub pan: f32,
44}
45
46/// Internal playback engine used by the high-level [`Player`].
47#[derive(Debug, Clone)]
48pub struct PlayerEngine {
49    pub finished_tracks: Arc<Mutex<Vec<u16>>>,
50    start_time: f64,
51    abort: Arc<AtomicBool>,
52    buffer_map: Arc<Mutex<HashMap<u16, TrackBuffer>>>,
53    buffer_notify: Arc<Condvar>,
54    effects_buffer: Arc<Mutex<Bounded<Vec<f32>>>>,
55    track_weights: Arc<Mutex<HashMap<u16, f32>>>,
56    track_channel_gains: Arc<Mutex<HashMap<u16, Vec<f32>>>>,
57    effects_reset: Arc<AtomicU64>,
58    inline_effects_update: Arc<Mutex<Option<InlineEffectsUpdate>>>,
59    inline_track_mix_updates: Arc<Mutex<Vec<InlineTrackMixUpdate>>>,
60    prot: Arc<Mutex<Prot>>,
61    buffer_settings: Arc<Mutex<PlaybackBufferSettings>>,
62    effects: Arc<Mutex<Vec<crate::dsp::effects::AudioEffect>>>,
63    dsp_metrics: Arc<Mutex<DspChainMetrics>>,
64}
65
66impl PlayerEngine {
67    /// Create a new engine for the given container and settings.
68    pub fn new(
69        prot: Arc<Mutex<Prot>>,
70        abort_option: Option<Arc<AtomicBool>>,
71        start_time: f64,
72        buffer_settings: Arc<Mutex<PlaybackBufferSettings>>,
73        effects: Arc<Mutex<Vec<crate::dsp::effects::AudioEffect>>>,
74        dsp_metrics: Arc<Mutex<DspChainMetrics>>,
75        effects_reset: Arc<AtomicU64>,
76        inline_effects_update: Arc<Mutex<Option<InlineEffectsUpdate>>>,
77        inline_track_mix_updates: Arc<Mutex<Vec<InlineTrackMixUpdate>>>,
78    ) -> Self {
79        let buffer_map = init_buffer_map();
80        let buffer_notify = Arc::new(Condvar::new());
81        let track_weights = Arc::new(Mutex::new(HashMap::new()));
82        let track_channel_gains = Arc::new(Mutex::new(HashMap::new()));
83        let finished_tracks: Arc<Mutex<Vec<u16>>> = Arc::new(Mutex::new(Vec::new()));
84        let abort = if abort_option.is_some() {
85            abort_option.unwrap()
86        } else {
87            Arc::new(AtomicBool::new(false))
88        };
89
90        let prot_unlocked = prot.lock().unwrap();
91        let start_buffer_ms = buffer_settings.lock().unwrap().start_buffer_ms;
92        let channels = prot_unlocked.info.channels as usize;
93        let start_samples = ((prot_unlocked.info.sample_rate as f32 * start_buffer_ms) / 1000.0)
94            as usize
95            * channels;
96        let buffer_size = (prot_unlocked.info.sample_rate as usize * 10).max(start_samples * 2);
97        let effects_buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
98            0.0;
99            buffer_size
100        ])));
101        drop(prot_unlocked);
102
103        Self {
104            finished_tracks,
105            start_time,
106            buffer_map,
107            buffer_notify,
108            effects_buffer,
109            track_weights,
110            track_channel_gains,
111            effects_reset,
112            inline_effects_update,
113            inline_track_mix_updates,
114            abort,
115            prot,
116            buffer_settings,
117            effects,
118            dsp_metrics,
119        }
120    }
121
122    /// Start the mixing loop and invoke `f` for each mixed chunk.
123    pub fn reception_loop(&mut self, f: &dyn Fn((SamplesBuffer, f64))) {
124        let prot = self.prot.lock().unwrap();
125        let keys = prot.get_keys();
126        drop(prot);
127        self.ready_buffer_map(&keys);
128        let receiver = self.get_receiver();
129
130        for (mixer, length_in_seconds) in receiver {
131            f((mixer, length_in_seconds));
132        }
133    }
134
135    /// Start mixing and return a receiver for `(buffer, duration)` chunks.
136    pub fn start_receiver(&mut self) -> Receiver<(SamplesBuffer, f64)> {
137        let prot = self.prot.lock().unwrap();
138        let keys = prot.get_keys();
139        drop(prot);
140        self.ready_buffer_map(&keys);
141        self.get_receiver()
142    }
143
144    fn get_receiver(&self) -> Receiver<(SamplesBuffer, f64)> {
145        let prot = self.prot.lock().unwrap();
146        let audio_info = prot.info.clone();
147        drop(prot);
148
149        spawn_mix_thread(MixThreadArgs {
150            audio_info,
151            buffer_map: self.buffer_map.clone(),
152            buffer_notify: self.buffer_notify.clone(),
153            effects_buffer: self.effects_buffer.clone(),
154            track_weights: self.track_weights.clone(),
155            track_channel_gains: self.track_channel_gains.clone(),
156            effects_reset: self.effects_reset.clone(),
157            inline_effects_update: self.inline_effects_update.clone(),
158            inline_track_mix_updates: self.inline_track_mix_updates.clone(),
159            finished_tracks: self.finished_tracks.clone(),
160            prot: self.prot.clone(),
161            abort: self.abort.clone(),
162            start_time: self.start_time,
163            buffer_settings: self.buffer_settings.clone(),
164            effects: self.effects.clone(),
165            dsp_metrics: self.dsp_metrics.clone(),
166        })
167    }
168
169    /// Get the total duration (seconds) of the active selection.
170    pub fn get_duration(&self) -> f64 {
171        let prot = self.prot.lock().unwrap();
172        *prot.get_duration()
173    }
174
175    fn ready_buffer_map(&mut self, keys: &Vec<u32>) {
176        self.buffer_map = init_buffer_map();
177        self.track_weights.lock().unwrap().clear();
178        self.track_channel_gains.lock().unwrap().clear();
179
180        let prot = self.prot.lock().unwrap();
181        let sample_rate = prot.info.sample_rate;
182        let channels = prot.info.channels as usize;
183        let track_mix_settings = prot.get_track_mix_settings();
184        let start_buffer_ms = self.buffer_settings.lock().unwrap().start_buffer_ms;
185        drop(prot);
186        let start_samples = ((sample_rate as f32 * start_buffer_ms) / 1000.0) as usize * channels;
187        let buffer_size = (sample_rate as usize * 10).max(start_samples * 2);
188
189        for key in keys {
190            let ring_buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
191                    0.0;
192                    buffer_size
193                ])));
194            self.buffer_map
195                .lock()
196                .unwrap()
197                .insert(*key as u16, ring_buffer);
198            self.track_weights.lock().unwrap().insert(*key as u16, 1.0);
199            let (level, pan) = track_mix_settings
200                .get(&(*key as u16))
201                .copied()
202                .unwrap_or((1.0, 0.0));
203            let gains = compute_track_channel_gains(level, pan, channels);
204            self.track_channel_gains
205                .lock()
206                .unwrap()
207                .insert(*key as u16, gains);
208        }
209    }
210
211    /// Return true if all tracks have reported end-of-stream.
212    pub fn finished_buffering(&self) -> bool {
213        let finished_tracks = self.finished_tracks.lock().unwrap();
214        let prot = self.prot.lock().unwrap();
215        let keys = prot.get_keys();
216        drop(prot);
217
218        for key in keys {
219            if !finished_tracks.contains(&(key as u16)) {
220                return false;
221            }
222        }
223
224        true
225    }
226}
227
228pub(crate) fn compute_track_channel_gains(level: f32, pan: f32, channels: usize) -> Vec<f32> {
229    let level = level.max(0.0);
230    if channels <= 1 {
231        return vec![level];
232    }
233
234    let pan = pan.clamp(-1.0, 1.0);
235    let left = if pan > 0.0 { 1.0 - pan } else { 1.0 };
236    let right = if pan < 0.0 { 1.0 + pan } else { 1.0 };
237
238    let mut gains = vec![level; channels];
239    gains[0] = level * left;
240    gains[1] = level * right;
241    gains
242}
243
244#[cfg(test)]
245mod tests {
246    use super::compute_track_channel_gains;
247
248    #[test]
249    fn channel_gains_apply_level_and_pan() {
250        let gains = compute_track_channel_gains(0.5, 0.5, 2);
251        assert_eq!(gains.len(), 2);
252        assert!((gains[0] - 0.25).abs() < 1e-6);
253        assert!((gains[1] - 0.5).abs() < 1e-6);
254    }
255
256    #[test]
257    fn mono_gain_uses_level_only() {
258        let gains = compute_track_channel_gains(0.8, -1.0, 1);
259        assert_eq!(gains, vec![0.8]);
260    }
261}