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 state;
15
16pub use state::{DspChainMetrics, PlaybackBufferSettings};
17
18use mix::{spawn_mix_thread, MixThreadArgs};
19
20/// Internal playback engine used by the high-level [`Player`].
21#[derive(Debug, Clone)]
22pub struct PlayerEngine {
23    pub finished_tracks: Arc<Mutex<Vec<u16>>>,
24    start_time: f64,
25    abort: Arc<AtomicBool>,
26    buffer_map: Arc<Mutex<HashMap<u16, TrackBuffer>>>,
27    buffer_notify: Arc<Condvar>,
28    effects_buffer: Arc<Mutex<Bounded<Vec<f32>>>>,
29    track_weights: Arc<Mutex<HashMap<u16, f32>>>,
30    track_channel_gains: Arc<Mutex<HashMap<u16, Vec<f32>>>>,
31    effects_reset: Arc<AtomicU64>,
32    prot: Arc<Mutex<Prot>>,
33    buffer_settings: Arc<Mutex<PlaybackBufferSettings>>,
34    effects: Arc<Mutex<Vec<crate::dsp::effects::AudioEffect>>>,
35    dsp_metrics: Arc<Mutex<DspChainMetrics>>,
36}
37
38impl PlayerEngine {
39    /// Create a new engine for the given container and settings.
40    pub fn new(
41        prot: Arc<Mutex<Prot>>,
42        abort_option: Option<Arc<AtomicBool>>,
43        start_time: f64,
44        buffer_settings: Arc<Mutex<PlaybackBufferSettings>>,
45        effects: Arc<Mutex<Vec<crate::dsp::effects::AudioEffect>>>,
46        dsp_metrics: Arc<Mutex<DspChainMetrics>>,
47        effects_reset: Arc<AtomicU64>,
48    ) -> Self {
49        let buffer_map = init_buffer_map();
50        let buffer_notify = Arc::new(Condvar::new());
51        let track_weights = Arc::new(Mutex::new(HashMap::new()));
52        let track_channel_gains = Arc::new(Mutex::new(HashMap::new()));
53        let finished_tracks: Arc<Mutex<Vec<u16>>> = Arc::new(Mutex::new(Vec::new()));
54        let abort = if abort_option.is_some() {
55            abort_option.unwrap()
56        } else {
57            Arc::new(AtomicBool::new(false))
58        };
59
60        let prot_unlocked = prot.lock().unwrap();
61        let start_buffer_ms = buffer_settings.lock().unwrap().start_buffer_ms;
62        let channels = prot_unlocked.info.channels as usize;
63        let start_samples = ((prot_unlocked.info.sample_rate as f32 * start_buffer_ms) / 1000.0)
64            as usize
65            * channels;
66        let buffer_size = (prot_unlocked.info.sample_rate as usize * 10).max(start_samples * 2);
67        let effects_buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
68            0.0;
69            buffer_size
70        ])));
71        drop(prot_unlocked);
72
73        Self {
74            finished_tracks,
75            start_time,
76            buffer_map,
77            buffer_notify,
78            effects_buffer,
79            track_weights,
80            track_channel_gains,
81            effects_reset,
82            abort,
83            prot,
84            buffer_settings,
85            effects,
86            dsp_metrics,
87        }
88    }
89
90    /// Start the mixing loop and invoke `f` for each mixed chunk.
91    pub fn reception_loop(&mut self, f: &dyn Fn((SamplesBuffer, f64))) {
92        let prot = self.prot.lock().unwrap();
93        let keys = prot.get_keys();
94        drop(prot);
95        self.ready_buffer_map(&keys);
96        let receiver = self.get_receiver();
97
98        for (mixer, length_in_seconds) in receiver {
99            f((mixer, length_in_seconds));
100        }
101    }
102
103    /// Start mixing and return a receiver for `(buffer, duration)` chunks.
104    pub fn start_receiver(&mut self) -> Receiver<(SamplesBuffer, f64)> {
105        let prot = self.prot.lock().unwrap();
106        let keys = prot.get_keys();
107        drop(prot);
108        self.ready_buffer_map(&keys);
109        self.get_receiver()
110    }
111
112    fn get_receiver(&self) -> Receiver<(SamplesBuffer, f64)> {
113        let prot = self.prot.lock().unwrap();
114        let audio_info = prot.info.clone();
115        drop(prot);
116
117        spawn_mix_thread(MixThreadArgs {
118            audio_info,
119            buffer_map: self.buffer_map.clone(),
120            buffer_notify: self.buffer_notify.clone(),
121            effects_buffer: self.effects_buffer.clone(),
122            track_weights: self.track_weights.clone(),
123            track_channel_gains: self.track_channel_gains.clone(),
124            effects_reset: self.effects_reset.clone(),
125            finished_tracks: self.finished_tracks.clone(),
126            prot: self.prot.clone(),
127            abort: self.abort.clone(),
128            start_time: self.start_time,
129            buffer_settings: self.buffer_settings.clone(),
130            effects: self.effects.clone(),
131            dsp_metrics: self.dsp_metrics.clone(),
132        })
133    }
134
135    /// Get the total duration (seconds) of the active selection.
136    pub fn get_duration(&self) -> f64 {
137        let prot = self.prot.lock().unwrap();
138        *prot.get_duration()
139    }
140
141    fn ready_buffer_map(&mut self, keys: &Vec<u32>) {
142        self.buffer_map = init_buffer_map();
143        self.track_weights.lock().unwrap().clear();
144        self.track_channel_gains.lock().unwrap().clear();
145
146        let prot = self.prot.lock().unwrap();
147        let sample_rate = prot.info.sample_rate;
148        let channels = prot.info.channels as usize;
149        let track_mix_settings = prot.get_track_mix_settings();
150        let start_buffer_ms = self.buffer_settings.lock().unwrap().start_buffer_ms;
151        drop(prot);
152        let start_samples = ((sample_rate as f32 * start_buffer_ms) / 1000.0) as usize * channels;
153        let buffer_size = (sample_rate as usize * 10).max(start_samples * 2);
154
155        for key in keys {
156            let ring_buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
157                    0.0;
158                    buffer_size
159                ])));
160            self.buffer_map
161                .lock()
162                .unwrap()
163                .insert(*key as u16, ring_buffer);
164            self.track_weights.lock().unwrap().insert(*key as u16, 1.0);
165            let (level, pan) = track_mix_settings
166                .get(&(*key as u16))
167                .copied()
168                .unwrap_or((1.0, 0.0));
169            let gains = compute_track_channel_gains(level, pan, channels);
170            self.track_channel_gains
171                .lock()
172                .unwrap()
173                .insert(*key as u16, gains);
174        }
175    }
176
177    /// Return true if all tracks have reported end-of-stream.
178    pub fn finished_buffering(&self) -> bool {
179        let finished_tracks = self.finished_tracks.lock().unwrap();
180        let prot = self.prot.lock().unwrap();
181        let keys = prot.get_keys();
182        drop(prot);
183
184        for key in keys {
185            if !finished_tracks.contains(&(key as u16)) {
186                return false;
187            }
188        }
189
190        true
191    }
192}
193
194fn compute_track_channel_gains(level: f32, pan: f32, channels: usize) -> Vec<f32> {
195    let level = level.max(0.0);
196    if channels <= 1 {
197        return vec![level];
198    }
199
200    let pan = pan.clamp(-1.0, 1.0);
201    let left = if pan > 0.0 { 1.0 - pan } else { 1.0 };
202    let right = if pan < 0.0 { 1.0 + pan } else { 1.0 };
203
204    let mut gains = vec![level; channels];
205    gains[0] = level * left;
206    gains[1] = level * right;
207    gains
208}