proteus_lib/playback/
output_meter.rs1#[cfg(feature = "output-meter")]
4mod enabled {
5 use std::collections::VecDeque;
6
7 use rodio::buffer::SamplesBuffer;
8 use rodio::Source;
9
10 #[derive(Debug)]
11 struct Frame {
12 peak: Vec<f32>,
13 avg: Vec<f32>,
14 len_samples: usize,
15 }
16
17 #[derive(Debug)]
18 pub struct OutputMeter {
19 sample_rate: u32,
20 channels: usize,
21 refresh_hz: f32,
22 frame_samples_per_channel: usize,
23 sample_remainder: f64,
24 current_frame_remaining: usize,
25 levels: Vec<f32>,
26 averages: Vec<f32>,
27 queue: VecDeque<Frame>,
28 }
29
30 impl OutputMeter {
31 pub fn new(channels: usize, sample_rate: u32, refresh_hz: f32) -> Self {
32 let channels = channels.max(1);
33 let sample_rate = sample_rate.max(1);
34 let refresh_hz = refresh_hz.max(1.0);
35 Self {
36 sample_rate,
37 channels,
38 refresh_hz,
39 frame_samples_per_channel: frame_samples_per_channel(sample_rate, refresh_hz),
40 sample_remainder: 0.0,
41 current_frame_remaining: 0,
42 levels: vec![0.0; channels],
43 averages: vec![0.0; channels],
44 queue: VecDeque::new(),
45 }
46 }
47
48 pub fn reset(&mut self) {
49 self.queue.clear();
50 self.sample_remainder = 0.0;
51 self.current_frame_remaining = 0;
52 self.levels.fill(0.0);
53 self.averages.fill(0.0);
54 }
55
56 pub fn set_refresh_hz(&mut self, refresh_hz: f32) {
57 let refresh_hz = refresh_hz.max(1.0);
58 if (refresh_hz - self.refresh_hz).abs() <= f32::EPSILON {
59 return;
60 }
61 self.refresh_hz = refresh_hz;
62 self.frame_samples_per_channel =
63 frame_samples_per_channel(self.sample_rate, self.refresh_hz);
64 self.reset();
65 }
66
67 pub fn push_samples(&mut self, buffer: &SamplesBuffer) {
68 let channels = buffer.channels().max(1) as usize;
69 let sample_rate = buffer.sample_rate().max(1);
70 if channels != self.channels {
71 self.channels = channels;
72 self.levels = vec![0.0; channels];
73 self.averages = vec![0.0; channels];
74 }
75 if sample_rate != self.sample_rate {
76 self.sample_rate = sample_rate;
77 self.frame_samples_per_channel =
78 frame_samples_per_channel(self.sample_rate, self.refresh_hz);
79 self.reset();
80 }
81
82 let frame_len_samples = self.frame_samples_per_channel * channels;
83 let mut peak = vec![0.0_f32; channels];
84 let mut sum = vec![0.0_f32; channels];
85 let mut count = vec![0_usize; channels];
86 let mut in_frame = 0_usize;
87
88 for (idx, sample) in buffer.clone().enumerate() {
89 let ch = idx % channels;
90 let value = sample.abs();
91 if value > peak[ch] {
92 peak[ch] = value;
93 }
94 sum[ch] += value;
95 count[ch] += 1;
96 in_frame += 1;
97
98 if in_frame >= frame_len_samples {
99 self.queue
100 .push_back(finalize_frame(&peak, &sum, &count, in_frame));
101 peak.fill(0.0);
102 sum.fill(0.0);
103 count.fill(0);
104 in_frame = 0;
105 }
106 }
107
108 if in_frame > 0 {
109 self.queue
110 .push_back(finalize_frame(&peak, &sum, &count, in_frame));
111 }
112 }
113
114 pub fn advance(&mut self, elapsed_seconds: f64) {
115 if elapsed_seconds <= 0.0 {
116 return;
117 }
118
119 let mut samples = elapsed_seconds * self.sample_rate as f64 * self.channels as f64;
120 samples += self.sample_remainder;
121 let mut samples_to_advance = samples.floor() as usize;
122 self.sample_remainder = samples - samples_to_advance as f64;
123
124 while samples_to_advance > 0 {
125 if self.current_frame_remaining == 0 {
126 let Some(frame) = self.queue.pop_front() else {
127 break;
128 };
129 self.levels = frame.peak;
130 self.averages = frame.avg;
131 self.current_frame_remaining = frame.len_samples;
132 }
133
134 let take = samples_to_advance.min(self.current_frame_remaining);
135 self.current_frame_remaining -= take;
136 samples_to_advance -= take;
137 }
138 }
139
140 pub fn levels(&self) -> Vec<f32> {
141 self.levels.clone()
142 }
143
144 pub fn averages(&self) -> Vec<f32> {
145 self.averages.clone()
146 }
147 }
148
149 fn frame_samples_per_channel(sample_rate: u32, refresh_hz: f32) -> usize {
150 ((sample_rate as f32 / refresh_hz).round() as usize).max(1)
151 }
152
153 fn finalize_frame(peak: &[f32], sum: &[f32], count: &[usize], len_samples: usize) -> Frame {
154 let mut avg = Vec::with_capacity(sum.len());
155 for (idx, value) in sum.iter().enumerate() {
156 let denom = count[idx].max(1) as f32;
157 avg.push(value / denom);
158 }
159 Frame {
160 peak: peak.to_vec(),
161 avg,
162 len_samples,
163 }
164 }
165}
166
167#[cfg(not(feature = "output-meter"))]
168mod disabled {
169 use rodio::buffer::SamplesBuffer;
170
171 #[derive(Debug)]
172 pub struct OutputMeter {
173 channels: usize,
174 }
175
176 impl OutputMeter {
177 pub fn new(channels: usize, _sample_rate: u32, _refresh_hz: f32) -> Self {
178 Self {
179 channels: channels.max(1),
180 }
181 }
182
183 pub fn reset(&mut self) {}
184
185 pub fn set_refresh_hz(&mut self, _refresh_hz: f32) {}
186
187 pub fn push_samples(&mut self, _buffer: &SamplesBuffer) {}
188
189 pub fn advance(&mut self, _elapsed_seconds: f64) {}
190
191 pub fn levels(&self) -> Vec<f32> {
192 vec![0.0; self.channels]
193 }
194
195 pub fn averages(&self) -> Vec<f32> {
196 vec![0.0; self.channels]
197 }
198 }
199}
200
201#[cfg(not(feature = "output-meter"))]
202pub use disabled::OutputMeter;
203#[cfg(feature = "output-meter")]
204pub use enabled::OutputMeter;
205
206#[cfg(test)]
207mod tests {
208 use super::OutputMeter;
209
210 #[cfg(feature = "output-meter")]
211 #[test]
212 fn output_meter_tracks_peak_and_avg() {
213 use rodio::buffer::SamplesBuffer;
214
215 let mut meter = OutputMeter::new(2, 10, 1.0);
216 let samples = vec![
217 0.1_f32, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 0.2, 0.1, 0.4, 0.3, 0.6, 0.5,
218 0.8, 0.7, 1.0, 0.9,
219 ];
220 let buffer = SamplesBuffer::new(2, 10, samples);
221 meter.push_samples(&buffer);
222 meter.advance(1.0);
223
224 let levels = meter.levels();
225 let avg = meter.averages();
226 assert_eq!(levels.len(), 2);
227 assert_eq!(avg.len(), 2);
228 assert!((levels[0] - 1.0).abs() < 1e-6);
229 assert!((levels[1] - 1.0).abs() < 1e-6);
230 assert!(avg[0] > 0.0);
231 assert!(avg[1] > 0.0);
232 }
233
234 #[cfg(not(feature = "output-meter"))]
235 #[test]
236 fn output_meter_disabled_returns_zeroes() {
237 let meter = OutputMeter::new(2, 48_000, 10.0);
238 assert_eq!(meter.levels(), vec![0.0, 0.0]);
239 assert_eq!(meter.averages(), vec![0.0, 0.0]);
240 }
241}