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}