1use ratatui::layout::Rect;
4use ratatui::style::{Color, Modifier, Style};
5use ratatui::text::{Line, Span};
6use ratatui::widgets::{Block, Borders, Paragraph};
7use ratatui::Frame;
8
9use crate::audio::engine::EngineHandle;
10use crate::math::pulse::{beat_phase, phrase_phase};
11
12const STEPS: usize = 16;
13const PHRASE_BEATS: f32 = 16.0; pub fn render(f: &mut Frame, area: Rect, engine: &EngineHandle) {
16 let t = engine.phase_clock.value() as f64;
17 let bpm = engine.global.bpm.value() as f64;
18 let beat = beat_phase(t, bpm);
19 let current = (beat * STEPS as f64) as usize % STEPS;
20
21 let mut beat_spans: Vec<Span> = Vec::with_capacity(STEPS * 2 + 1);
23 for i in 0..STEPS {
24 let (glyph, color) = if i == current {
25 ("██", Color::Yellow)
26 } else if i % 4 == 0 {
27 ("▓▓", Color::Blue)
28 } else {
29 ("░░", Color::DarkGray)
30 };
31 beat_spans.push(Span::styled(glyph, Style::default().fg(color)));
32 beat_spans.push(Span::raw(" "));
33 }
34
35 let phr = phrase_phase(t, bpm, PHRASE_BEATS as f64);
37 let phr_idx = (phr * STEPS as f64) as usize % STEPS;
38 let mut phrase_spans: Vec<Span> = Vec::with_capacity(STEPS * 2 + 1);
39 for i in 0..STEPS {
40 let (glyph, color) = if i <= phr_idx {
41 ("▰ ", Color::Magenta)
42 } else {
43 ("▱ ", Color::DarkGray)
44 };
45 phrase_spans.push(Span::styled(glyph, Style::default().fg(color)));
46 }
47
48 let text = vec![
49 Line::from(vec![Span::styled(
50 format!(" beat {bpm:>5.1} bpm step {}/{} ", current + 1, STEPS),
51 Style::default().fg(Color::Gray),
52 )]),
53 Line::from(beat_spans),
54 Line::from(""),
55 Line::from(vec![Span::styled(
56 format!(" phrase {:.0}% ({:.0}/{:.0} beats)", phr * 100.0, phr * PHRASE_BEATS as f64, PHRASE_BEATS),
57 Style::default().fg(Color::Gray),
58 )]),
59 Line::from(phrase_spans),
60 ];
61
62 let para = Paragraph::new(text).block(
63 Block::default()
64 .borders(Borders::ALL)
65 .title(" tempo ")
66 .title_style(Style::default().add_modifier(Modifier::BOLD)),
67 );
68 f.render_widget(para, area);
69}