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 reverb;
15mod state;
16
17pub use state::{PlaybackBufferSettings, ReverbMetrics, ReverbSettings};
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    reverb_reset: Arc<AtomicU64>,
32    prot: Arc<Mutex<Prot>>,
33    buffer_settings: Arc<Mutex<PlaybackBufferSettings>>,
34    reverb_settings: Arc<Mutex<ReverbSettings>>,
35    reverb_metrics: Arc<Mutex<ReverbMetrics>>,
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        reverb_settings: Arc<Mutex<ReverbSettings>>,
46        reverb_metrics: Arc<Mutex<ReverbMetrics>>,
47        reverb_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 finished_tracks: Arc<Mutex<Vec<u16>>> = Arc::new(Mutex::new(Vec::new()));
53        let abort = if abort_option.is_some() {
54            abort_option.unwrap()
55        } else {
56            Arc::new(AtomicBool::new(false))
57        };
58
59        let prot_unlocked = prot.lock().unwrap();
60        let start_buffer_ms = buffer_settings.lock().unwrap().start_buffer_ms;
61        let channels = prot_unlocked.info.channels as usize;
62        let start_samples = ((prot_unlocked.info.sample_rate as f32 * start_buffer_ms) / 1000.0)
63            as usize
64            * channels;
65        let buffer_size = (prot_unlocked.info.sample_rate as usize * 10).max(start_samples * 2);
66        let effects_buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
67            0.0;
68            buffer_size
69        ])));
70        drop(prot_unlocked);
71
72        Self {
73            finished_tracks,
74            start_time,
75            buffer_map,
76            buffer_notify,
77            effects_buffer,
78            track_weights,
79            reverb_reset,
80            abort,
81            prot,
82            buffer_settings,
83            reverb_settings,
84            reverb_metrics,
85        }
86    }
87
88    /// Start the mixing loop and invoke `f` for each mixed chunk.
89    pub fn reception_loop(&mut self, f: &dyn Fn((SamplesBuffer, f64))) {
90        let prot = self.prot.lock().unwrap();
91        let keys = prot.get_keys();
92        drop(prot);
93        self.ready_buffer_map(&keys);
94        let receiver = self.get_receiver();
95
96        for (mixer, length_in_seconds) in receiver {
97            f((mixer, length_in_seconds));
98        }
99    }
100
101    /// Start mixing and return a receiver for `(buffer, duration)` chunks.
102    pub fn start_receiver(&mut self) -> Receiver<(SamplesBuffer, f64)> {
103        let prot = self.prot.lock().unwrap();
104        let keys = prot.get_keys();
105        drop(prot);
106        self.ready_buffer_map(&keys);
107        self.get_receiver()
108    }
109
110    fn get_receiver(&self) -> Receiver<(SamplesBuffer, f64)> {
111        let prot = self.prot.lock().unwrap();
112        let audio_info = prot.info.clone();
113        drop(prot);
114
115        spawn_mix_thread(MixThreadArgs {
116            audio_info,
117            buffer_map: self.buffer_map.clone(),
118            buffer_notify: self.buffer_notify.clone(),
119            effects_buffer: self.effects_buffer.clone(),
120            track_weights: self.track_weights.clone(),
121            reverb_reset: self.reverb_reset.clone(),
122            finished_tracks: self.finished_tracks.clone(),
123            prot: self.prot.clone(),
124            abort: self.abort.clone(),
125            start_time: self.start_time,
126            buffer_settings: self.buffer_settings.clone(),
127            reverb_settings: self.reverb_settings.clone(),
128            reverb_metrics: self.reverb_metrics.clone(),
129        })
130    }
131
132    /// Get the total duration (seconds) of the active selection.
133    pub fn get_duration(&self) -> f64 {
134        let prot = self.prot.lock().unwrap();
135        *prot.get_duration()
136    }
137
138    fn ready_buffer_map(&mut self, keys: &Vec<u32>) {
139        self.buffer_map = init_buffer_map();
140        self.track_weights.lock().unwrap().clear();
141
142        let prot = self.prot.lock().unwrap();
143        let sample_rate = prot.info.sample_rate;
144        let channels = prot.info.channels as usize;
145        let start_buffer_ms = self.buffer_settings.lock().unwrap().start_buffer_ms;
146        drop(prot);
147        let start_samples = ((sample_rate as f32 * start_buffer_ms) / 1000.0) as usize * channels;
148        let buffer_size = (sample_rate as usize * 10).max(start_samples * 2);
149
150        for key in keys {
151            let ring_buffer =
152                Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from(vec![
153                    0.0;
154                    buffer_size
155                ])));
156            self.buffer_map
157                .lock()
158                .unwrap()
159                .insert(*key as u16, ring_buffer);
160            self.track_weights.lock().unwrap().insert(*key as u16, 1.0);
161        }
162    }
163
164    /// Return true if all tracks have reported end-of-stream.
165    pub fn finished_buffering(&self) -> bool {
166        let finished_tracks = self.finished_tracks.lock().unwrap();
167        let prot = self.prot.lock().unwrap();
168        let keys = prot.get_keys();
169        drop(prot);
170
171        for key in keys {
172            if !finished_tracks.contains(&(key as u16)) {
173                return false;
174            }
175        }
176
177        true
178    }
179}