Skip to main content

wisp_storybook/stories/
s_audio_histogram.rs

1//! Story: synthetic audio histogram rendered through wisp Graphics
2//! (M-MEDIA.10 / AUT-106).
3//!
4//! Proves the seam — `media` quantizes audio + lays out bar geometry,
5//! `wisp` draws the rectangles. wisp does not depend on `media`; this
6//! story sits on the storybook side of the boundary.
7
8use media::audio::AudioFormat;
9use media::clock::MediaDuration;
10use media::histogram::quantize;
11use media::mock_audio::SineWaveSource;
12use media::waveform::{BarMetric, WaveformDisplayMode, WaveformLayout, mono_bars};
13use wisp::application::Application;
14use wisp::math::Rect;
15use wisp::{Color, Fill, Graphics, Stage};
16
17use crate::story::Story;
18
19pub fn story() -> Story {
20    Story {
21        id: "audio-histogram",
22        category: "Media",
23        title: "Synthetic audio histogram",
24        milestone: "M-MEDIA.10",
25        writeup: include_str!("writeups/audio_histogram.md"),
26        build,
27        tick: None,
28    }
29}
30
31fn build(_app: &Application, stage: &mut Stage) {
32    // 440 Hz sine, amplitude 0.6, mono, 1 second at 48 kHz.
33    let fmt = AudioFormat::mono_f32(48_000);
34    let mut src = SineWaveSource::new(fmt, 440.0, 0.6);
35    let chunk = src.next_chunk(48_000);
36    let histogram = quantize(&chunk, MediaDuration::from_millis(50)); // 20 bars
37
38    let layout = WaveformLayout {
39        origin_x: -0.85,
40        baseline_y: 0.0,
41        bar_width: 0.075,
42        bar_gap: 0.012,
43        max_height: 1.1,
44        color: [1.0, 0.74, 0.30, 1.0], // amber
45        metric: BarMetric::Peak,
46        mode: WaveformDisplayMode::Mirrored,
47    };
48    let rects = mono_bars(&histogram, &layout);
49
50    // Backdrop — a wide rounded panel under the bars to make the
51    // amber + dark contrast carry without an alpha-on-black story.
52    let mut bg = Graphics::new();
53    bg.fill(Fill::Solid(Color::rgba_u8(28, 32, 40, 255)));
54    bg.draw_rounded_rect(Rect::new(-0.95, -0.7, 1.9, 1.4), 0.06);
55
56    // Centerline — a thin horizontal strip at baseline_y.
57    bg.fill(Fill::Solid(Color::rgba_u8(64, 72, 88, 255)));
58    bg.draw_rect(Rect::new(-0.92, -0.005, 1.84, 0.010));
59
60    let _ = stage.add_child(stage.root(), bg);
61
62    // The bars themselves — pure rectangles, one Graphics node.
63    let mut bars = Graphics::new();
64    let [r, g, b, a] = layout.color;
65    bars.fill(Fill::Solid(Color::rgba(r, g, b, a)));
66    for rect in &rects {
67        bars.draw_rect(Rect::new(rect.x, rect.y, rect.width, rect.height));
68    }
69    let _ = stage.add_child(stage.root(), bars);
70}