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