Skip to main content

sequoia_draw/
waveform.rs

1use sequoia_core::{Color, Point, Rect};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
4pub struct MinMax {
5    pub min: f32,
6    pub max: f32,
7}
8
9impl MinMax {
10    pub const SILENCE: Self = Self { min: 0.0, max: 0.0 };
11
12    pub fn new(min: f32, max: f32) -> Self {
13        if min <= max {
14            Self { min, max }
15        } else {
16            Self { min: max, max: min }
17        }
18    }
19
20    pub fn span(self) -> f32 {
21        let d = self.max - self.min;
22        if d.is_finite() && d > 0.0 { d } else { 0.0 }
23    }
24
25    pub fn merge(self, other: Self) -> Self {
26        let a_min = if self.min.is_finite() { self.min } else { 0.0 };
27        let a_max = if self.max.is_finite() { self.max } else { 0.0 };
28        let b_min = if other.min.is_finite() {
29            other.min
30        } else {
31            0.0
32        };
33        let b_max = if other.max.is_finite() {
34            other.max
35        } else {
36            0.0
37        };
38        Self {
39            min: a_min.min(b_min),
40            max: a_max.max(b_max),
41        }
42    }
43}
44
45#[derive(Clone, Debug, Default)]
46pub struct Peaks {
47    levels: Vec<Vec<MinMax>>,
48    samples_per_peak: usize,
49    total_samples: usize,
50    version: u64,
51}
52
53impl Peaks {
54    pub fn from_samples(samples: &[f32], samples_per_peak: usize) -> Self {
55        let spp = samples_per_peak.max(1);
56        let mut base = Vec::new();
57        if !samples.is_empty() {
58            base.reserve(samples.len().div_ceil(spp));
59            for chunk in samples.chunks(spp) {
60                let mut mn = f32::INFINITY;
61                let mut mx = f32::NEG_INFINITY;
62                for &s in chunk {
63                    let v = if s.is_finite() { s } else { 0.0 };
64                    mn = mn.min(v);
65                    mx = mx.max(v);
66                }
67                base.push(MinMax::new(mn, mx));
68            }
69        }
70        Self::from_base(base, spp, samples.len())
71    }
72
73    pub fn from_min_max(base: Vec<MinMax>, samples_per_peak: usize) -> Self {
74        let n = base.len();
75        Self::from_base(base, samples_per_peak.max(1), n)
76    }
77
78    fn from_base(base: Vec<MinMax>, spp: usize, total: usize) -> Self {
79        let mut levels = Vec::new();
80        if !base.is_empty() {
81            levels.push(base);
82            while levels.last().is_some_and(|l| l.len() > 1) {
83                let prev = levels.last().expect("poziom istnieje");
84                let mut next = Vec::with_capacity(prev.len().div_ceil(2));
85                for pair in prev.chunks(2) {
86                    match pair {
87                        [a, b] => next.push(a.merge(*b)),
88                        [a] => next.push(*a),
89                        [] => {}
90                        _ => {}
91                    }
92                }
93                levels.push(next);
94                if levels.len() > 32 {
95                    break;
96                }
97            }
98        }
99        Self {
100            levels,
101            samples_per_peak: spp,
102            total_samples: total,
103            version: 1,
104        }
105    }
106
107    pub fn is_empty(&self) -> bool {
108        self.levels.first().is_none_or(Vec::is_empty)
109    }
110
111    pub fn num_levels(&self) -> usize {
112        self.levels.len()
113    }
114
115    pub fn base_len(&self) -> usize {
116        self.levels.first().map_or(0, Vec::len)
117    }
118
119    pub fn samples_per_peak(&self) -> usize {
120        self.samples_per_peak
121    }
122
123    pub fn total_samples(&self) -> usize {
124        self.total_samples
125    }
126
127    pub fn version(&self) -> u64 {
128        self.version
129    }
130
131    pub fn level(&self, level: usize) -> &[MinMax] {
132        self.levels.get(level).map_or(&[], Vec::as_slice)
133    }
134
135    pub fn level_for_width(&self, width: usize) -> usize {
136        if self.levels.is_empty() || width == 0 {
137            return 0;
138        }
139        for (i, l) in self.levels.iter().enumerate() {
140            if l.len() <= width.max(1) {
141                return i;
142            }
143        }
144        self.levels.len() - 1
145    }
146
147    pub fn query(&self, level: usize, start: usize, count: usize, width: usize) -> Vec<MinMax> {
148        let src = self.level(level);
149        if src.is_empty() || width == 0 || count == 0 {
150            return Vec::new();
151        }
152        let start = start.min(src.len());
153        let end = (start + count).min(src.len());
154        if start >= end {
155            return Vec::new();
156        }
157        let slice = &src[start..end];
158        if slice.len() == width {
159            return slice.to_vec();
160        }
161        let mut out = Vec::with_capacity(width);
162        for i in 0..width {
163            let a = (i * slice.len()) / width;
164            let mut b = ((i + 1) * slice.len()) / width;
165            if b <= a {
166                b = (a + 1).min(slice.len());
167            }
168            let mut m = slice[a];
169            for &v in &slice[a + 1..b] {
170                m = m.merge(v);
171            }
172            out.push(m);
173        }
174        out
175    }
176
177    pub fn query_for_width(
178        &self,
179        start_peak: usize,
180        count: usize,
181        width: usize,
182    ) -> (usize, Vec<MinMax>) {
183        if self.is_empty() || width == 0 {
184            return (0, Vec::new());
185        }
186        let base = self.base_len();
187        let start = start_peak.min(base);
188        let count = if count == 0 {
189            base.saturating_sub(start)
190        } else {
191            count.min(base - start)
192        };
193        if count == 0 {
194            return (0, Vec::new());
195        }
196        let level = self.level_for_width(width);
197        let div = 1usize << level.min(30);
198        let lstart = start / div;
199        let lcount = count.div_ceil(div).max(1);
200        (level, self.query(level, lstart, lcount, width))
201    }
202
203    pub fn extend_samples(&mut self, samples: &[f32]) {
204        if samples.is_empty() {
205            return;
206        }
207        if self.levels.is_empty() {
208            *self = Self::from_samples(samples, self.samples_per_peak.max(1));
209            return;
210        }
211        let spp = self.samples_per_peak.max(1);
212        let mut base = std::mem::take(&mut self.levels);
213        let level0 = &mut base[0];
214        for chunk in samples.chunks(spp) {
215            if chunk.len() < spp {
216                if let Some(last) = level0.last_mut() {
217                    for &s in chunk {
218                        let v = if s.is_finite() { s } else { 0.0 };
219                        *last = last.merge(MinMax::new(v, v));
220                    }
221                } else {
222                    let mut mn = f32::INFINITY;
223                    let mut mx = f32::NEG_INFINITY;
224                    for &s in chunk {
225                        let v = if s.is_finite() { s } else { 0.0 };
226                        mn = mn.min(v);
227                        mx = mx.max(v);
228                    }
229                    level0.push(MinMax::new(mn, mx));
230                }
231            } else {
232                let mut mn = f32::INFINITY;
233                let mut mx = f32::NEG_INFINITY;
234                for &s in chunk {
235                    let v = if s.is_finite() { s } else { 0.0 };
236                    mn = mn.min(v);
237                    mx = mx.max(v);
238                }
239                level0.push(MinMax::new(mn, mx));
240            }
241        }
242        base.truncate(1);
243        while base.last().is_some_and(|l| l.len() > 1) {
244            let prev = base.last().expect("poziom istnieje").clone();
245            let mut next = Vec::with_capacity(prev.len().div_ceil(2));
246            for pair in prev.chunks(2) {
247                match pair {
248                    [a, b] => next.push(a.merge(*b)),
249                    [a] => next.push(*a),
250                    [] => {}
251                    _ => {}
252                }
253            }
254            base.push(next);
255            if base.len() > 32 {
256                break;
257            }
258        }
259        self.levels = base;
260        self.total_samples += samples.len();
261        self.version = self.version.wrapping_add(1).max(1);
262    }
263}
264
265#[derive(Clone, Copy, Debug, PartialEq)]
266pub struct WaveformOpts {
267    pub gap_px: f32,
268    pub min_height_px: f32,
269    pub mirrored: bool,
270}
271
272impl Default for WaveformOpts {
273    fn default() -> Self {
274        Self {
275            gap_px: 0.0,
276            min_height_px: 1.0,
277            mirrored: false,
278        }
279    }
280}
281
282pub fn layout_bars(area: Rect, peaks: &[MinMax], opts: WaveformOpts) -> Vec<Rect> {
283    if area.is_empty() || peaks.is_empty() {
284        return Vec::new();
285    }
286    let n = peaks.len() as f32;
287    let col_w = area.size.width / n;
288    if !(col_w > 0.0) {
289        return Vec::new();
290    }
291    let gap = opts.gap_px.clamp(0.0, col_w * 0.9);
292    let bar_w = (col_w - gap).max(0.5);
293    let mid_y = area.top() + area.size.height * 0.5;
294    let half_h = area.size.height * 0.5;
295    let mut out = Vec::with_capacity(peaks.len());
296    for (i, p) in peaks.iter().enumerate() {
297        let x = area.left() + i as f32 * col_w + gap * 0.5;
298        let (top, bottom) = if opts.mirrored {
299            let lo = p.min.clamp(-1.0, 1.0);
300            let hi = p.max.clamp(-1.0, 1.0);
301            let amp = lo.abs().max(hi.abs()).clamp(0.0, 1.0);
302            let h = (amp * area.size.height).max(opts.min_height_px.max(0.0));
303            (mid_y - h * 0.5, mid_y + h * 0.5)
304        } else {
305            let lo = p.min.clamp(-1.0, 1.0);
306            let hi = p.max.clamp(-1.0, 1.0);
307            let y0 = mid_y - hi * half_h;
308            let y1 = mid_y - lo * half_h;
309            let (mut t, mut b) = (y0.min(y1), y0.max(y1));
310            if b - t < opts.min_height_px {
311                let c = (t + b) * 0.5;
312                t = c - opts.min_height_px * 0.5;
313                b = c + opts.min_height_px * 0.5;
314            }
315            (t, b)
316        };
317        let top = top.max(area.top());
318        let bottom = bottom.min(area.bottom());
319        if bottom > top {
320            out.push(Rect::from_xywh(x, top, bar_w, bottom - top));
321        }
322    }
323    out
324}
325
326pub fn waveform_line_points(area: Rect, peaks: &[MinMax]) -> Vec<Point> {
327    if area.is_empty() || peaks.is_empty() {
328        return Vec::new();
329    }
330    let n = peaks.len() as f32;
331    let col_w = area.size.width / n;
332    if !(col_w > 0.0) || !(area.size.height > 0.0) {
333        return Vec::new();
334    }
335    let map_y = |v: f32| {
336        let c = v.clamp(-1.0, 1.0);
337        area.top() + (1.0 - c) * 0.5 * area.size.height
338    };
339    let mut out = Vec::with_capacity(peaks.len() * 2);
340    for (i, p) in peaks.iter().enumerate() {
341        let x = area.left() + (i as f32 + 0.5) * col_w;
342        out.push(Point::new(x, map_y(p.min)));
343        out.push(Point::new(x, map_y(p.max)));
344    }
345    out
346}
347
348pub fn bake_strip(peaks: &[MinMax], w: u32, h: u32, fg: Color, bg: Color) -> Vec<u8> {
349    if w == 0 || h == 0 {
350        return Vec::new();
351    }
352
353    const MAX_DIM: u32 = 4096;
354    const MAX_BYTES: usize = 64 * 1024 * 1024;
355    if w > MAX_DIM || h > MAX_DIM {
356        return Vec::new();
357    }
358    let bytes = (w as usize)
359        .checked_mul(h as usize)
360        .and_then(|v| v.checked_mul(4))
361        .filter(|&v| v <= MAX_BYTES);
362    let Some(bytes) = bytes else {
363        return Vec::new();
364    };
365    let (fr, fgc, fb, fa) = fg.to_rgba8();
366    let (br, bgc, bb, ba) = bg.to_rgba8();
367    let mut px = vec![0u8; bytes];
368    for p in px.chunks_exact_mut(4) {
369        p[0] = br;
370        p[1] = bgc;
371        p[2] = bb;
372        p[3] = ba;
373    }
374    if peaks.is_empty() {
375        return px;
376    }
377    let n = peaks.len();
378    let hh = h as f32;
379    for x in 0..(w as usize) {
380        let peak = peaks[(x * n) / (w as usize)];
381        let hi = peak.max.clamp(-1.0, 1.0);
382        let lo = peak.min.clamp(-1.0, 1.0);
383        let mut y0 = (((1.0 - hi) * 0.5 * hh).floor() as isize).clamp(0, h as isize - 1);
384        let mut y1 = (((1.0 - lo) * 0.5 * hh).ceil() as isize).clamp(0, h as isize - 1);
385        if y1 < y0 {
386            std::mem::swap(&mut y0, &mut y1);
387        }
388
389        for y in y0..=y1 {
390            let o = (y as usize * (w as usize) + x) * 4;
391            px[o] = fr;
392            px[o + 1] = fgc;
393            px[o + 2] = fb;
394            px[o + 3] = fa;
395        }
396    }
397    px
398}
399
400#[derive(Clone, Debug, Default)]
401pub struct WaveformCache {
402    key: Option<(u64, usize, usize, usize, u64, u32)>,
403    bars: Vec<Rect>,
404}
405
406impl WaveformCache {
407    pub fn new() -> Self {
408        Self::default()
409    }
410
411    pub fn bars(&self) -> &[Rect] {
412        &self.bars
413    }
414
415    pub fn is_empty(&self) -> bool {
416        self.bars.is_empty()
417    }
418
419    pub fn invalidate(&mut self) {
420        self.key = None;
421        self.bars.clear();
422    }
423
424    pub fn update(
425        &mut self,
426        area: Rect,
427        peaks: &Peaks,
428        start_peak: usize,
429        count: usize,
430        width_px: usize,
431        opts: WaveformOpts,
432    ) -> bool {
433        let key = (
434            area_key(area),
435            start_peak,
436            count,
437            width_px,
438            peaks.version(),
439            opts_key(opts),
440        );
441        if self.key == Some(key) {
442            return false;
443        }
444        let (_, cols) = peaks.query_for_width(start_peak, count, width_px);
445        let bars = layout_bars(area, &cols, opts);
446        self.bars = bars;
447        self.key = Some(key);
448        true
449    }
450
451    pub fn update_cols(&mut self, area: Rect, cols: &[MinMax], opts: WaveformOpts) -> bool {
452        let mut hash: u64 = area_key(area) ^ opts_key(opts) as u64;
453        for c in cols {
454            hash = hash.wrapping_mul(0x1000_0000_01b3)
455                ^ ((c.min.to_bits() as u64).wrapping_add((c.max.to_bits() as u64) << 1));
456        }
457        let key = (hash, 0, 0, cols.len(), 0, 0);
458        if self.key == Some(key) {
459            return false;
460        }
461        self.bars = layout_bars(area, cols, opts);
462        self.key = Some(key);
463        true
464    }
465}
466
467fn area_key(r: Rect) -> u64 {
468    let q = |v: f32| (v * 2.0).round() as i32 as u64;
469    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
470    for v in [r.left(), r.top(), r.size.width, r.size.height] {
471        h ^= q(v).wrapping_add(0x9e37_79b9_7f4a_7c15);
472        h = h.wrapping_mul(0x1000_0000_01b3);
473    }
474    h
475}
476
477fn opts_key(o: WaveformOpts) -> u32 {
478    o.gap_px.to_bits()
479        ^ o.min_height_px.to_bits().wrapping_mul(31)
480        ^ (o.mirrored as u32).wrapping_mul(0x9e37)
481}
482
483pub fn white_noise(len: usize, seed: u32, gain: f32) -> Vec<f32> {
484    let mut st = seed.max(1);
485    let mut out = Vec::with_capacity(len);
486    for _ in 0..len {
487        st ^= st << 13;
488        st ^= st >> 17;
489        st ^= st << 5;
490        let u = (st as f64) / (u32::MAX as f64);
491        out.push(((u * 2.0 - 1.0) as f32) * gain);
492    }
493    out
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn minmax_merge_clamps_nan() {
502        let a = MinMax::new(f32::NAN, 1.0);
503        let b = MinMax::new(-1.0, f32::NAN);
504        let m = a.merge(b);
505        assert!(m.min.is_finite() && m.max.is_finite());
506        assert!(m.min <= m.max);
507        assert_eq!(MinMax::new(1.0, -1.0), MinMax::new(-1.0, 1.0));
508    }
509
510    #[test]
511    fn pyramid_halves_and_merges() {
512        let s: Vec<f32> = (0..1024)
513            .map(|i| if i % 2 == 0 { 0.5 } else { -0.5 })
514            .collect();
515        let p = Peaks::from_samples(&s, 4);
516        assert_eq!(p.base_len(), 256);
517        assert!(p.num_levels() >= 8);
518        let top = p.level(p.num_levels() - 1);
519        assert_eq!(top.len(), 1);
520        assert!((top[0].min - -0.5).abs() < 1e-6);
521        assert!((top[0].max - 0.5).abs() < 1e-6);
522    }
523
524    #[test]
525    fn query_resamples_to_width() {
526        let s: Vec<f32> = (0..1000).map(|i| (i as f32 / 1000.0) * 2.0 - 1.0).collect();
527        let p = Peaks::from_samples(&s, 1);
528        let cols = p.query(0, 0, p.base_len(), 100);
529        assert_eq!(cols.len(), 100);
530        assert!(cols[0].max < cols[99].min);
531    }
532
533    #[test]
534    fn query_for_width_picks_level() {
535        let p = Peaks::from_samples(&white_noise(4096, 7, 0.9), 4);
536        let (lvl, cols) = p.query_for_width(0, 0, 128);
537        assert_eq!(cols.len(), 128);
538        assert!(lvl < p.num_levels());
539    }
540
541    #[test]
542    fn layout_bars_fills_area() {
543        let area = Rect::from_xywh(0.0, 0.0, 100.0, 40.0);
544        let peaks = vec![MinMax::new(-1.0, 1.0); 10];
545        let bars = layout_bars(area, &peaks, WaveformOpts::default());
546        assert_eq!(bars.len(), 10);
547        for b in &bars {
548            assert!(b.top() >= area.top() - 0.01 && b.bottom() <= area.bottom() + 0.01);
549        }
550        let quiet = layout_bars(area, &[MinMax::SILENCE], WaveformOpts::default());
551        assert_eq!(quiet.len(), 1);
552        assert!(quiet[0].size.height >= 1.0);
553    }
554
555    #[test]
556    fn line_points_zigzag_two_per_column() {
557        let area = Rect::from_xywh(0.0, 10.0, 100.0, 40.0);
558        let peaks = vec![
559            MinMax::new(-1.0, 1.0),
560            MinMax::new(-0.5, 0.0),
561            MinMax::SILENCE,
562        ];
563        let pts = waveform_line_points(area, &peaks);
564        assert_eq!(pts.len(), 6, "two points per column: min, then max");
565        assert!(
566            (pts[0].y - 50.0).abs() < 1e-4,
567            "min=-1 is bottom: {:?}",
568            pts[0]
569        );
570        assert!(
571            (pts[1].y - 10.0).abs() < 1e-4,
572            "max=+1 is top: {:?}",
573            pts[1]
574        );
575        assert!((pts[2].y - 40.0).abs() < 1e-4, "{:?}", pts[2]);
576        assert!((pts[3].y - 30.0).abs() < 1e-4, "{:?}", pts[3]);
577        assert!((pts[4].y - 30.0).abs() < 1e-4, "{:?}", pts[4]);
578        assert!((pts[5].y - 30.0).abs() < 1e-4, "{:?}", pts[5]);
579        for w in pts.windows(2) {
580            assert!(w[1].x >= w[0].x);
581        }
582        assert_eq!(pts[0].x, pts[1].x, "para dzieli x kolumny");
583        assert!(pts[2].x > pts[1].x);
584        assert!(waveform_line_points(Rect::ZERO, &peaks).is_empty());
585        assert!(waveform_line_points(area, &[]).is_empty());
586    }
587
588    #[test]
589    fn cache_skips_recompute() {
590        let p = Peaks::from_samples(&white_noise(2048, 1, 0.8), 8);
591        let mut c = WaveformCache::new();
592        let area = Rect::from_xywh(0.0, 0.0, 200.0, 60.0);
593        assert!(c.update(area, &p, 0, 0, 200, WaveformOpts::default()));
594        assert!(!c.update(area, &p, 0, 0, 200, WaveformOpts::default()));
595        assert!(c.update(area, &p, 10, 0, 200, WaveformOpts::default()));
596    }
597
598    #[test]
599    fn bake_strip_size_and_colors() {
600        let peaks = vec![MinMax::new(-0.5, 0.5); 8];
601        let px = bake_strip(&peaks, 8, 4, Color::WHITE, Color::BLACK);
602        assert_eq!(px.len(), 8 * 4 * 4);
603        assert_eq!(&px[0..4], &[0, 0, 0, 255]);
604        let row1 = 8 * 4;
605        assert_eq!(&px[row1..row1 + 4], &[255, 255, 255, 255]);
606        let pos = vec![MinMax::new(0.4, 0.9); 4];
607        let px = bake_strip(&pos, 4, 8, Color::WHITE, Color::BLACK);
608        let bottom = 7 * 4 * 4;
609        assert_eq!(&px[bottom..bottom + 4], &[0, 0, 0, 255]);
610        assert_eq!(&px[0..4], &[255, 255, 255, 255]);
611    }
612
613    #[test]
614    fn white_noise_deterministic() {
615        assert_eq!(white_noise(16, 42, 1.0), white_noise(16, 42, 1.0));
616        assert_ne!(white_noise(16, 42, 1.0), white_noise(16, 43, 1.0));
617    }
618
619    #[test]
620    fn extend_appends() {
621        let mut p = Peaks::from_samples(&white_noise(256, 1, 0.9), 16);
622        let v0 = p.version();
623        let n0 = p.base_len();
624        p.extend_samples(&white_noise(256, 2, 0.9));
625        assert!(p.base_len() > n0);
626        assert_ne!(p.version(), v0);
627    }
628}