aede_core/playback/
waveform.rs1use super::stream::PcmTrack;
3use std::convert::Infallible;
4use std::path::Path;
5use std::time::{Duration, Instant};
6pub struct Waveform {
8 pub duration_ms: u64,
10 pub peaks: Vec<f32>,
12 pub bucket_frames: u64,
14 pub sample_rate: u32,
16}
17struct Envelope {
18 peaks: Vec<f32>,
19 width: u64,
20 used: u64,
21 peak: f32,
22 frames: u64,
23}
24impl Envelope {
25 fn new() -> Self {
26 Self {
27 peaks: vec![],
28 width: 256,
29 used: 0,
30 peak: 0.,
31 frames: 0,
32 }
33 }
34 fn push(&mut self, peak: f32) {
35 self.frames += 1;
36 self.used += 1;
37 self.peak = self.peak.max(peak.abs().min(1.));
38 if self.used == self.width {
39 self.peaks.push(self.peak);
40 self.used = 0;
41 self.peak = 0.;
42 if self.peaks.len() == 2048 {
43 self.peaks = self
44 .peaks
45 .as_chunks::<2>()
46 .0
47 .iter()
48 .map(|v| v[0].max(v[1]))
49 .collect();
50 self.width *= 2;
51 }
52 }
53 }
54 fn finish(mut self, rate: u32) -> Waveform {
55 if self.used > 0 {
56 self.peaks.push(self.peak);
57 }
58 Waveform {
59 duration_ms: self.frames.saturating_mul(1000) / u64::from(rate),
60 peaks: self.peaks,
61 bucket_frames: self.width,
62 sample_rate: rate,
63 }
64 }
65}
66pub fn decode(path: &Path, budget: Duration) -> Result<Waveform, String> {
69 let mut track = PcmTrack::open(path).map_err(|error| error.to_string())?;
70 let format = track.format();
71 let start = Instant::now();
72 let mut envelope = Envelope::new();
73 while let Some(block) = track
74 .read_block::<Infallible>(|_| Ok(()))
75 .map_err(|error| error.to_string())?
76 {
77 if start.elapsed() > budget || envelope.frames > u64::from(format.sample_rate()) * 86400 {
78 return Err("waveform decode budget exceeded".into());
79 }
80 for frame in block.samples.chunks_exact(usize::from(format.channels())) {
81 envelope.push(frame.iter().copied().map(f32::abs).fold(0., f32::max));
82 }
83 }
84 if envelope.frames == 0 {
85 return Err("empty audio source".into());
86 }
87 Ok(envelope.finish(format.sample_rate()))
88}
89#[cfg(test)]
90#[path = "waveform_tests.rs"]
91mod tests;