Skip to content
File

Blob: firmware/crates/radio-core/src/playback.rs

rust145 lines
1//! Absolute playback deadlines. Late packets are skipped, never sent in a burst.
2use crate::{music::FRAME_MS, protocol::Action};
3use std::num::NonZeroU32;
4 
5const FRAME_US: u64 = FRAME_MS as u64 * 1000;
6 
7#[derive(Debug)]
8pub struct Playback {
9 frames: Vec<NonZeroU32>,
10 track: usize,
11 revision: u32,
12 index: u32,
13 elapsed: u64,
14 next_us: u64,
15 paused: bool,
16 skipped: u64,
17 epoch: u32,
18 transition_pending: bool,
19}
20 
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Due {
23 pub epoch: u32,
24 pub track_index: u32,
25 pub revision: u32,
26 pub index: u32,
27 pub pts_ms: u32,
28 pub paused: bool,
29 pub spectrum: bool,
30}
31 
32impl Playback {
33 pub fn new(frames: NonZeroU32, now_us: u64) -> Self {
34 Self::playlist(vec![frames], now_us).expect("one nonempty song")
35 }
36 
37 pub fn playlist(frames: Vec<NonZeroU32>, now_us: u64) -> Option<Self> {
38 if frames.is_empty() || frames.len() > crate::music::MAX_TRACKS {
39 return None;
40 }
41 Some(Self {
42 frames,
43 track: 0,
44 revision: 0,
45 index: 0,
46 elapsed: 0,
47 next_us: now_us,
48 paused: false,
49 skipped: 0,
50 epoch: 0,
51 transition_pending: false,
52 })
53 }
54 
55 /// Returns at most one packet per call; `now_us` comes from a monotonic clock.
56 pub fn due(&mut self, now_us: u64) -> Option<Due> {
57 if now_us < self.next_us {
58 return None;
59 }
60 let late = (now_us - self.next_us) / FRAME_US;
61 self.elapsed = self.elapsed.wrapping_add(late);
62 self.skipped = self.skipped.saturating_add(late);
63 if !self.paused {
64 if self.transition_pending {
65 self.next_track();
66 }
67 self.advance(late);
68 }
69 self.next_us = self
70 .next_us
71 .saturating_add((late + 1).saturating_mul(FRAME_US));
72 let due = Due {
73 epoch: self.epoch,
74 track_index: self.track as u32,
75 revision: self.revision,
76 index: self.index,
77 pts_ms: self.elapsed.wrapping_mul(FRAME_MS as u64) as u32,
78 paused: self.paused,
79 spectrum: self.elapsed % 2 == 0,
80 };
81 self.elapsed = self.elapsed.wrapping_add(1);
82 if !self.paused {
83 // Keep the emitted song current while its final packet is playing.
84 // Commands may arrive before the next deadline commits the transition.
85 if self.index + 1 == self.frames[self.track].get() {
86 self.transition_pending = true;
87 } else {
88 self.index += 1;
89 }
90 }
91 Some(due)
92 }
93 
94 fn advance(&mut self, count: u64) {
95 let total: u64 = self.frames.iter().map(|n| u64::from(n.get())).sum();
96 let cycles = count / total;
97 let changes = (cycles as u32).wrapping_mul(self.frames.len() as u32);
98 self.epoch = self.epoch.wrapping_add(changes);
99 self.revision = self.revision.wrapping_add(changes);
100 let mut count = count % total;
101 while count >= u64::from(self.frames[self.track].get() - self.index) {
102 count -= u64::from(self.frames[self.track].get() - self.index);
103 self.next_track();
104 }
105 self.index += count as u32;
106 }
107 
108 fn next_track(&mut self) {
109 self.transition_pending = false;
110 self.track = (self.track + 1) % self.frames.len();
111 self.index = 0;
112 self.epoch = self.epoch.wrapping_add(1);
113 self.revision = self.revision.wrapping_add(1);
114 }
115 
116 pub fn apply(&mut self, action: Action) {
117 match action {
118 Action::Play | Action::Pause => {
119 let paused = action == Action::Pause;
120 if paused != self.paused {
121 self.epoch = self.epoch.wrapping_add(1);
122 }
123 self.paused = paused;
124 }
125 Action::Next => self.next_track(),
126 Action::Restart => {
127 self.transition_pending = false;
128 self.epoch = self.epoch.wrapping_add(1);
129 self.revision = self.revision.wrapping_add(1);
130 self.index = 0;
131 self.paused = false;
132 }
133 }
134 }
135 pub fn paused(&self) -> bool {
136 self.paused
137 }
138 pub fn epoch(&self) -> u32 {
139 self.epoch
140 }
141 pub fn skipped_frames(&self) -> u64 {
142 self.skipped
143 }
144}