Skip to main content

math_audio_dsp/
waveform.rs

1/// Number of amplitude samples in the waveform
2pub const WAVEFORM_SAMPLES: usize = 128;
3
4/// Compute a waveform representation from mono audio samples.
5///
6/// Takes pre-decoded mono f32 samples and computes [`WAVEFORM_SAMPLES`] RMS
7/// amplitude bins, each normalised to 0–255.
8///
9/// Returns a `Vec<u8>` of exactly [`WAVEFORM_SAMPLES`] elements.
10pub fn compute_waveform(mono_samples: &[f32]) -> Vec<u8> {
11    if mono_samples.is_empty() {
12        log::warn!("[Waveform] No samples provided");
13        return vec![0u8; WAVEFORM_SAMPLES];
14    }
15
16    let samples_per_chunk = mono_samples.len() / WAVEFORM_SAMPLES;
17
18    // Handle case where input is shorter than WAVEFORM_SAMPLES
19    if samples_per_chunk == 0 {
20        log::warn!(
21            "[Waveform] Input too short ({} samples), padding waveform",
22            mono_samples.len()
23        );
24        let mut waveform = Vec::with_capacity(WAVEFORM_SAMPLES);
25        for i in 0..WAVEFORM_SAMPLES {
26            if i < mono_samples.len() {
27                let amplitude = mono_samples[i].abs();
28                waveform.push((amplitude.min(1.0) * 255.0) as u8);
29            } else {
30                waveform.push(0);
31            }
32        }
33        return waveform;
34    }
35
36    // Compute RMS for each chunk
37    let mut rms_values: Vec<f32> = Vec::with_capacity(WAVEFORM_SAMPLES);
38
39    for chunk_idx in 0..WAVEFORM_SAMPLES {
40        let start = chunk_idx * samples_per_chunk;
41        let end = if chunk_idx == WAVEFORM_SAMPLES - 1 {
42            mono_samples.len()
43        } else {
44            start + samples_per_chunk
45        };
46
47        let chunk = &mono_samples[start..end];
48        let sum_squares: f32 = chunk.iter().map(|s| s * s).sum();
49        let rms = (sum_squares / chunk.len() as f32).sqrt();
50        rms_values.push(rms);
51    }
52
53    // Normalise to 0-255
54    let max_rms = rms_values
55        .iter()
56        .cloned()
57        .fold(0.0f32, |a, b| a.max(b))
58        .max(0.001);
59
60    let waveform: Vec<u8> = rms_values
61        .iter()
62        .map(|&rms| {
63            let normalized = rms / max_rms;
64            (normalized * 255.0) as u8
65        })
66        .collect();
67
68    log::debug!(
69        "[Waveform] Computed waveform with {} samples, max_rms={:.4}",
70        waveform.len(),
71        max_rms
72    );
73
74    waveform
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_empty_input() {
83        let waveform = compute_waveform(&[]);
84        assert_eq!(waveform.len(), WAVEFORM_SAMPLES);
85        assert!(waveform.iter().all(|&v| v == 0));
86    }
87
88    #[test]
89    fn test_short_input() {
90        let samples = vec![0.5; 10];
91        let waveform = compute_waveform(&samples);
92        assert_eq!(waveform.len(), WAVEFORM_SAMPLES);
93    }
94
95    #[test]
96    fn test_waveform_length_constant() {
97        assert_eq!(WAVEFORM_SAMPLES, 128);
98    }
99
100    #[test]
101    fn test_normal_input() {
102        let samples: Vec<f32> = (0..48000)
103            .map(|i| (i as f32 / 48000.0 * 440.0 * std::f32::consts::TAU).sin())
104            .collect();
105        let waveform = compute_waveform(&samples);
106        assert_eq!(waveform.len(), WAVEFORM_SAMPLES);
107        assert!(waveform.iter().any(|&v| v > 0));
108    }
109}