rust_synth/tui/
pattern.rs1use ratatui::layout::Rect;
9use ratatui::style::{Color, Modifier, Style};
10use ratatui::text::{Line, Span};
11use ratatui::widgets::{Block, Borders, Paragraph};
12use ratatui::Frame;
13
14use super::app::AppState;
15use crate::audio::engine::EngineHandle;
16use crate::audio::preset::PresetKind;
17use crate::math::rhythm;
18
19const STEPS: u32 = rhythm::STEPS;
20
21pub fn render(f: &mut Frame, area: Rect, engine: &EngineHandle, app: &AppState) {
22 let tracks = engine.tracks.lock();
23 let Some(track) = tracks.get(app.selected_track) else {
24 return;
25 };
26 let snap = track.params.snapshot();
27 let kind = track.kind;
28 let name = track.name.clone();
29 drop(tracks);
30
31 let bpm = engine.global.bpm.value() as f64;
32 let t = engine.phase_clock.value() as f64;
33 let (cur_step_idx, _) = rhythm::step_position(t, bpm, 4.0);
34 let cur_step = (cur_step_idx % STEPS as u64) as u32;
35
36 let is_drum = matches!(kind, PresetKind::Heartbeat);
37 let bits = if is_drum { snap.pattern_bits } else { 0 };
38
39 let title = if is_drum {
40 format!(
41 " pattern · {} · {} hits, rot {} ",
42 name,
43 snap.pattern_hits.round() as u32,
44 snap.pattern_rotation.round() as u32,
45 )
46 } else {
47 format!(" pattern · {} · (non-drum, ignored) ", name)
48 };
49
50 let mut cells: Vec<Span> = Vec::with_capacity(STEPS as usize * 2);
51 for step in 0..STEPS {
52 let active = (bits >> step) & 1 == 1;
53 let is_current = step == cur_step && is_drum;
54 let glyph: &'static str = match (active, is_current) {
55 (true, true) => "██",
56 (true, false) => "▓▓",
57 (false, true) => "▕▏",
58 (false, false) => "··",
59 };
60 let color = match (active, is_current) {
61 (true, true) => Color::Yellow,
62 (true, false) if is_drum => Color::Red,
63 (true, false) => Color::DarkGray,
64 (false, true) => Color::Rgb(120, 120, 140),
65 (false, false) => Color::Rgb(40, 40, 44),
66 };
67 let style = if is_current {
68 Style::default().fg(color).add_modifier(Modifier::BOLD)
69 } else {
70 Style::default().fg(color)
71 };
72 cells.push(Span::styled(glyph, style));
73 if (step + 1) % 4 == 0 && step + 1 < STEPS {
75 cells.push(Span::raw(" "));
76 } else {
77 cells.push(Span::raw(" "));
78 }
79 }
80
81 let body = vec![Line::from(cells)];
85
86 let block = Block::default()
87 .borders(Borders::ALL)
88 .title(title)
89 .title_style(Style::default().add_modifier(Modifier::BOLD));
90 let para = Paragraph::new(body).block(block);
91 f.render_widget(para, area);
92}