aede_core/
playback.rs

1//! In-memory playback order and transport state for a catalog selection.
2//!
3//! The queue holds track IDs, including repeated entries. It does not decode
4//! audio, open files, or persist a session. Its seeded shuffle permutes the
5//! entries once, so both the next and previous track remain knowable.
6
7use crate::model::Id;
8
9pub mod decoder;
10pub mod exact_output;
11pub mod exact_session;
12pub mod format;
13pub mod gain_plan;
14pub mod loudness;
15pub mod normalization;
16pub mod output;
17pub mod session;
18pub mod shuffle;
19pub mod stream;
20
21/// What happens when the current track reaches its end.
22#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
23pub enum Repeat {
24    /// Stop after the final entry.
25    #[default]
26    Off,
27    /// Restart the current entry after it finishes naturally.
28    One,
29    /// Continue from the beginning after the final entry.
30    All,
31}
32
33/// Whether the transport is currently producing sound.
34#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
35pub enum Transport {
36    /// No audio is playing; the selected entry starts at its beginning.
37    #[default]
38    Stopped,
39    /// Audio is playing.
40    Playing,
41    /// The selected entry retains its position but produces no audio.
42    Paused,
43}
44
45/// A selection with a cursor and a deterministic playing order.
46#[derive(Clone, Debug)]
47pub struct Queue {
48    tracks: Vec<Id>,
49    order: Vec<usize>,
50    previous_cycle: Option<(Vec<usize>, Option<u64>)>,
51    next_cycle: Option<(Vec<usize>, Option<u64>)>,
52    cursor: Option<usize>,
53    seed: Option<u64>,
54    repeat: Repeat,
55    transport: Transport,
56    position_ms: u64,
57}
58
59impl Queue {
60    /// Build from the same ordered track IDs used by playlist rendering.
61    ///
62    /// `seed = None` keeps the selection order. A seed shuffles it with a
63    /// fixed algorithm, independently of platform or process state.
64    pub fn new(tracks: Vec<Id>, seed: Option<u64>) -> Self {
65        let mut order: Vec<usize> = (0..tracks.len()).collect();
66        if let Some(seed) = seed {
67            shuffle(&mut order, seed);
68        }
69        Self {
70            tracks,
71            order,
72            previous_cycle: None,
73            next_cycle: None,
74            cursor: None,
75            seed,
76            repeat: Repeat::Off,
77            transport: Transport::Stopped,
78            position_ms: 0,
79        }
80    }
81
82    /// The original selection, including duplicate entries.
83    pub fn selection(&self) -> &[Id] {
84        &self.tracks
85    }
86
87    /// IDs in their current playing order.
88    pub fn ordered_tracks(&self) -> impl Iterator<Item = Id> + '_ {
89        self.order.iter().map(|&index| self.tracks[index])
90    }
91
92    /// The shuffle seed, if the queue is shuffled.
93    pub fn seed(&self) -> Option<u64> {
94        self.seed
95    }
96
97    /// The currently selected track.
98    pub fn current(&self) -> Option<Id> {
99        self.cursor.map(|cursor| self.tracks[self.order[cursor]])
100    }
101
102    /// The cursor in the current playing order.
103    pub fn cursor(&self) -> Option<usize> {
104        self.cursor
105    }
106
107    /// Original selection occurrence under the cursor, distinguishing repeats.
108    pub fn current_entry(&self) -> Option<usize> {
109        self.cursor.map(|cursor| self.order[cursor])
110    }
111
112    /// Select an existing occurrence after the driver abandons preparation of
113    /// a later source. Missing occurrences leave the queue unchanged.
114    pub fn select_entry(&mut self, entry: usize) -> bool {
115        let Some(cursor) = self.order.iter().position(|&index| index == entry) else {
116            return false;
117        };
118        self.cursor = Some(cursor);
119        self.position_ms = 0;
120        true
121    }
122
123    /// The current permutation of original selection occurrences.
124    pub fn order(&self) -> &[usize] {
125        &self.order
126    }
127
128    /// Replace future order, retaining the played prefix and current occurrence.
129    ///
130    /// `order` must contain every original occurrence exactly once. Invalid
131    /// input leaves transport, history, seed and position unchanged. Changing
132    /// mode clears saved neighbouring cycles, but not Previous within this one.
133    pub fn reorder(&mut self, order: Vec<usize>, seed: Option<u64>) -> Result<(), &'static str> {
134        self.validate_order(&order)?;
135        let prefix_len = self.cursor.map_or(0, |cursor| cursor + 1);
136        let mut retained = vec![false; self.tracks.len()];
137        for &entry in &self.order[..prefix_len] {
138            retained[entry] = true;
139        }
140        self.order.truncate(prefix_len);
141        self.order
142            .extend(order.into_iter().filter(|&entry| !retained[entry]));
143        self.seed = seed;
144        self.previous_cycle = None;
145        self.next_cycle = None;
146        Ok(())
147    }
148
149    /// Supply an externally planned next repeat-all cycle.
150    ///
151    /// Return false when Previous has already retained the forward cycle.
152    /// Such an order must not be silently replaced by a new shuffle draw.
153    pub fn set_next_order(
154        &mut self,
155        order: Vec<usize>,
156        seed: Option<u64>,
157    ) -> Result<bool, &'static str> {
158        self.validate_order(&order)?;
159        if self.next_cycle.is_some() {
160            return Ok(false);
161        }
162        self.next_cycle = Some((order, seed));
163        Ok(true)
164    }
165
166    /// Reproducible seed for the next shuffled repeat-all cycle.
167    pub fn next_seed(&self) -> Option<u64> {
168        self.seed
169            .map(|seed| seed.wrapping_add(0x9e37_79b9_7f4a_7c15))
170    }
171
172    fn validate_order(&self, order: &[usize]) -> Result<(), &'static str> {
173        if order.len() != self.tracks.len() {
174            return Err("playback order must contain every selection occurrence");
175        }
176        let mut seen = vec![false; self.tracks.len()];
177        for &entry in order {
178            let Some(present) = seen.get_mut(entry) else {
179                return Err("playback order has an out-of-range occurrence");
180            };
181            if *present {
182                return Err("playback order repeats a selection occurrence");
183            }
184            *present = true;
185        }
186        Ok(())
187    }
188
189    /// Current transport state.
190    pub fn transport(&self) -> Transport {
191        self.transport
192    }
193
194    /// Position in the current track, supplied by the playback driver.
195    pub fn position_ms(&self) -> u64 {
196        self.position_ms
197    }
198
199    /// Update the observed position after a successful playback or seek.
200    /// An empty queue cannot acquire a position.
201    pub fn set_position_ms(&mut self, position_ms: u64) -> bool {
202        if self.cursor.is_none() {
203            return false;
204        }
205        self.position_ms = position_ms;
206        true
207    }
208
209    /// Change how natural ends are handled.
210    pub fn set_repeat(&mut self, repeat: Repeat) {
211        self.repeat = repeat;
212    }
213
214    /// The active repeat mode.
215    pub fn repeat(&self) -> Repeat {
216        self.repeat
217    }
218
219    /// Start or resume the selected track; select the first entry if needed.
220    pub fn play(&mut self) -> Option<Id> {
221        if self.cursor.is_none() && !self.order.is_empty() {
222            self.cursor = Some(0);
223        }
224        let current = self.current()?;
225        self.transport = Transport::Playing;
226        Some(current)
227    }
228
229    /// Pause only while playing.
230    pub fn pause(&mut self) {
231        if self.transport == Transport::Playing {
232            self.transport = Transport::Paused;
233        }
234    }
235
236    /// Stop sound and return the selected track to its start.
237    pub fn stop(&mut self) {
238        self.transport = Transport::Stopped;
239        self.position_ms = 0;
240    }
241
242    /// Skip to the next entry, even with repeat-one enabled.
243    /// Keeps the current playing or paused state until the queue is exhausted.
244    pub fn skip_next(&mut self) -> Option<Id> {
245        self.advance(false)
246    }
247
248    /// Advance after natural end; repeat-one restarts the current entry.
249    pub fn finished(&mut self) -> Option<Id> {
250        if self.transport != Transport::Playing || self.cursor.is_none() {
251            return None;
252        }
253        self.advance(true)
254    }
255
256    fn advance(&mut self, natural_end: bool) -> Option<Id> {
257        if self.order.is_empty() {
258            self.stop();
259            return None;
260        }
261        if natural_end && self.repeat == Repeat::One && self.cursor.is_some() {
262            self.position_ms = 0;
263            return self.current();
264        }
265        let next = self.cursor.map_or(0, |cursor| cursor + 1);
266        if next == self.order.len() {
267            if self.repeat == Repeat::All {
268                let (order, seed) = self.next_cycle.take().unwrap_or_else(|| {
269                    let seed = self.next_seed();
270                    let mut order: Vec<usize> = (0..self.tracks.len()).collect();
271                    if let Some(seed) = seed {
272                        shuffle(&mut order, seed);
273                    }
274                    (order, seed)
275                });
276                let old_order = std::mem::replace(&mut self.order, order);
277                self.previous_cycle = Some((old_order, self.seed));
278                self.seed = seed;
279                self.cursor = Some(0);
280            } else {
281                self.cursor = None;
282                self.stop();
283                return None;
284            }
285        } else {
286            self.cursor = Some(next);
287        }
288        self.position_ms = 0;
289        self.current()
290    }
291
292    /// Restart the current track after three seconds; otherwise step back.
293    /// At the beginning of the current playing order, restart its first entry.
294    pub fn previous(&mut self) -> Option<Id> {
295        let cursor = self.cursor?;
296        if self.position_ms <= 3_000 {
297            if cursor > 0 {
298                self.cursor = Some(cursor - 1);
299            } else if let Some((order, seed)) = self.previous_cycle.take() {
300                let forward_order = std::mem::replace(&mut self.order, order);
301                self.next_cycle = Some((forward_order, self.seed));
302                self.seed = seed;
303                self.cursor = Some(self.order.len() - 1);
304            }
305        }
306        self.position_ms = 0;
307        self.current()
308    }
309}
310
311/// SplitMix64 has a fully specified bit pattern, unlike a platform RNG.
312fn next_random(state: &mut u64) -> u64 {
313    *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
314    let mut bits = *state;
315    bits = (bits ^ (bits >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
316    bits = (bits ^ (bits >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
317    bits ^ (bits >> 31)
318}
319
320fn shuffle(order: &mut [usize], seed: u64) {
321    let mut state = seed;
322    for upper in (2..=order.len()).rev() {
323        let bound = upper as u64;
324        let threshold = bound.wrapping_neg() % bound;
325        let index = loop {
326            let value = next_random(&mut state);
327            if value >= threshold {
328                break (value % bound) as usize;
329            }
330        };
331        order.swap(upper - 1, index);
332    }
333}
334
335#[cfg(test)]
336#[path = "playback_tests.rs"]
337mod tests;
338
339/// Complete source envelopes for graphical seek controls.
340pub mod waveform;