Skip to main content

rust_synth/tui/
waveform.rs

1//! Scrolling stereo oscilloscope using ratatui's Canvas.
2
3use ratatui::layout::Rect;
4use ratatui::style::{Color, Modifier, Style};
5use ratatui::symbols::Marker;
6use ratatui::widgets::canvas::{Canvas, Context, Line};
7use ratatui::widgets::{Block, Borders};
8use ratatui::Frame;
9
10use crate::audio::engine::{EngineHandle, SCOPE_CAPACITY};
11
12pub fn render(f: &mut Frame, area: Rect, engine: &EngineHandle) {
13    let samples: Vec<(f32, f32)> = engine.scope.lock().iter().copied().collect();
14    let len = samples.len().max(1);
15
16    let canvas = Canvas::default()
17        .block(
18            Block::default()
19                .borders(Borders::ALL)
20                .title(" scope · L/R ")
21                .title_style(Style::default().add_modifier(Modifier::BOLD)),
22        )
23        .marker(Marker::Braille)
24        .x_bounds([0.0, SCOPE_CAPACITY as f64])
25        .y_bounds([-1.2, 1.2])
26        .paint(move |ctx| {
27            draw_channel(ctx, &samples, |s| s.0, Color::Cyan, len);
28            draw_channel(ctx, &samples, |s| s.1, Color::Magenta, len);
29            // Zero line — subtle grid.
30            ctx.draw(&Line {
31                x1: 0.0,
32                y1: 0.0,
33                x2: SCOPE_CAPACITY as f64,
34                y2: 0.0,
35                color: Color::DarkGray,
36            });
37        });
38
39    f.render_widget(canvas, area);
40}
41
42fn draw_channel(
43    ctx: &mut Context,
44    samples: &[(f32, f32)],
45    pick: impl Fn(&(f32, f32)) -> f32,
46    color: Color,
47    len: usize,
48) {
49    let n = samples.len();
50    if n < 2 {
51        return;
52    }
53    let step = SCOPE_CAPACITY as f64 / len as f64;
54    for i in 1..n {
55        let x1 = (i - 1) as f64 * step;
56        let x2 = i as f64 * step;
57        let y1 = pick(&samples[i - 1]) as f64;
58        let y2 = pick(&samples[i]) as f64;
59        ctx.draw(&Line {
60            x1,
61            y1: y1.clamp(-1.2, 1.2),
62            x2,
63            y2: y2.clamp(-1.2, 1.2),
64            color,
65        });
66    }
67}