Skip to main content

rust_synth/tui/
formula.rs

1//! Live formula display — shows the math of the selected preset with
2//! current parameter values substituted. This is the "I want to see the
3//! formulas" pane.
4
5use ratatui::layout::Rect;
6use ratatui::style::{Color, Modifier, Style};
7use ratatui::text::{Line, Span};
8use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
9use ratatui::Frame;
10
11use crate::audio::engine::EngineHandle;
12use crate::audio::preset::PresetKind;
13
14use super::app::AppState;
15
16pub fn render(f: &mut Frame, area: Rect, engine: &EngineHandle, app: &AppState) {
17    let tracks = engine.tracks.lock();
18    let Some(track) = tracks.get(app.selected_track) else {
19        return;
20    };
21    let s = track.params.snapshot();
22    let bpm = engine.global.bpm.value();
23
24    let title = format!(" formula · {} · {} ", track.name, track.kind.label());
25    let lines: Vec<Line> = lines_for(track.kind, &s, bpm);
26
27    let block = Block::default()
28        .borders(Borders::ALL)
29        .title(title)
30        .title_style(Style::default().add_modifier(Modifier::BOLD));
31    let para = Paragraph::new(lines).block(block).wrap(Wrap { trim: false });
32    f.render_widget(para, area);
33}
34
35fn lines_for(kind: PresetKind, s: &crate::audio::track::TrackSnapshot, bpm: f32) -> Vec<Line<'static>> {
36    let dim = Style::default().fg(Color::DarkGray);
37    let hi = Style::default().fg(Color::Yellow);
38    let key = Style::default().fg(Color::Green);
39
40    let mut out: Vec<Line> = Vec::new();
41    out.push(Line::from(vec![
42        Span::styled(format!("freq = {:6.2} Hz   ", s.freq), key),
43        Span::styled(format!("bpm = {:5.1}", bpm), key),
44    ]));
45    out.push(Line::from(""));
46
47    match kind {
48        PresetKind::PadZimmer => {
49            out.push(Line::from(Span::styled("osc(t) = Σ Aₖ · sin(2π·f·rₖ·t)", hi)));
50            out.push(Line::from(Span::styled(
51                "  rₖ = [1.000, 1.501, 2.013, 3.007]   Aₖ = [.30, .20, .14, .08]",
52                dim,
53            )));
54            out.push(Line::from(""));
55            out.push(Line::from(Span::styled("σ(t) = 1 / (1 + exp(−k·(t − c)))", hi)));
56            out.push(Line::from(vec![
57                Span::styled("  k = ", dim),
58                Span::styled(format!("{:.2}", s.sweep_k), key),
59                Span::styled("    c = ", dim),
60                Span::styled(format!("{:.1} s", s.sweep_center), key),
61            ]));
62            out.push(Line::from(""));
63            out.push(Line::from(Span::styled(
64                "cut(t) = lerp(140, cutoff · (1 + 0.15·phrase), σ(t))",
65                hi,
66            )));
67            out.push(Line::from(vec![
68                Span::styled("  cutoff = ", dim),
69                Span::styled(format!("{:>5.0} Hz", s.cutoff), key),
70                Span::styled("    q = ", dim),
71                Span::styled(format!("{:.2}", s.resonance), key),
72            ]));
73            out.push(Line::from(""));
74            out.push(Line::from(Span::styled(
75                "y = Moog(osc, cut, q) ⇒ chorus ⇒ hall(25m, 8s)",
76                hi,
77            )));
78            out.push(Line::from(vec![
79                Span::styled("  reverb = ", dim),
80                Span::styled(format!("{:.2}", s.reverb_mix), key),
81                Span::styled("   gain = ", dim),
82                Span::styled(format!("{:.2}", s.gain), key),
83            ]));
84        }
85        PresetKind::DroneSub => {
86            out.push(Line::from(Span::styled(
87                "sub(t) = 0.45·sin(2π·f/2·t) + 0.12·sin(2π·f·t)",
88                hi,
89            )));
90            out.push(Line::from(Span::styled(
91                "noise(t) = Moog(brown(t), clip(cut, 40..240), q)",
92                hi,
93            )));
94            out.push(Line::from(Span::styled(
95                "am(t) = 0.75 + 0.25·½(1 − cos(2π·bpm/240·t))",
96                hi,
97            )));
98            out.push(Line::from(""));
99            out.push(Line::from(Span::styled("y = (sub + 0.28·noise) · am · hall(30m,12s)", hi)));
100            out.push(Line::from(vec![
101                Span::styled("  cut = ", dim),
102                Span::styled(format!("{:>5.0}", s.cutoff.min(240.0)), key),
103                Span::styled("  reverb = ", dim),
104                Span::styled(format!("{:.2}", s.reverb_mix), key),
105                Span::styled("  gain = ", dim),
106                Span::styled(format!("{:.2}", s.gain), key),
107            ]));
108        }
109        PresetKind::Shimmer => {
110            out.push(Line::from(Span::styled(
111                "shimmer(t) = .18·sin(4π·f·t) + .12·sin(6π·f·t) + .08·sin(8π·f·t)",
112                hi,
113            )));
114            out.push(Line::from(Span::styled(
115                "y = HP(shimmer, 400Hz) ⇒ hall(28m, 10s)",
116                hi,
117            )));
118            out.push(Line::from(vec![
119                Span::styled("  reverb = ", dim),
120                Span::styled(format!("{:.2}", s.reverb_mix), key),
121                Span::styled("   gain = ", dim),
122                Span::styled(format!("{:.2}", s.gain), key),
123            ]));
124        }
125        PresetKind::Heartbeat => {
126            out.push(Line::from(Span::styled("3-layer kick:", hi)));
127            out.push(Line::from(Span::styled("  body = sin(2π · f·(0.7 + 1.5·e^(−30·φ)) · t)", hi)));
128            out.push(Line::from(Span::styled("  sub  = sin(2π · f/2 · t)", hi)));
129            out.push(Line::from(Span::styled(
130                "  click = HP(brown, 1.8 kHz)",
131                hi,
132            )));
133            out.push(Line::from(Span::styled(
134                "env_body = e^(−6·φ)  env_sub = e^(−3.2·φ)  env_click = e^(−55·φ)",
135                dim,
136            )));
137            out.push(Line::from(""));
138            out.push(Line::from(Span::styled(
139                "y = body·env_body + sub·env_sub + click·env_click ⇒ hall(10m, 1.5s)",
140                hi,
141            )));
142            out.push(Line::from(vec![
143                Span::styled("  gain = ", dim),
144                Span::styled(format!("{:.2}", s.gain), key),
145            ]));
146        }
147        PresetKind::BassPulse => {
148            out.push(Line::from(Span::styled(
149                "osc = 0.55·sin(2π·f·t) + 0.22·sin(4π·f·t) + 0.35·sin(π·f·t)",
150                hi,
151            )));
152            out.push(Line::from(Span::styled(
153                "y = Moog(osc, min(cut, 900), q) · groove(t)",
154                hi,
155            )));
156            out.push(Line::from(Span::styled(
157                "groove(t) = 0.45 + 0.55 · e^(−3.5·φ)    φ = (t mod 60/bpm)·bpm/60",
158                dim,
159            )));
160            out.push(Line::from(""));
161            out.push(Line::from(vec![
162                Span::styled("  cut = ", dim),
163                Span::styled(format!("{:>5.0} Hz", s.cutoff.min(900.0)), key),
164                Span::styled("  q = ", dim),
165                Span::styled(format!("{:.2}", s.resonance.min(0.65)), key),
166                Span::styled("  rev = ", dim),
167                Span::styled(format!("{:.2}", s.reverb_mix), key),
168                Span::styled("  gain = ", dim),
169                Span::styled(format!("{:.2}", s.gain), key),
170            ]));
171        }
172    }
173
174    // Universal supermassive tail line.
175    if s.supermass > 0.01 {
176        out.push(Line::from(""));
177        out.push(Line::from(vec![
178            Span::styled("Σ  supermass ", Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD)),
179            Span::styled(
180                format!("{:.2}", s.supermass),
181                Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
182            ),
183            Span::styled("   + rev(35m,15s)⇒chorus⇒rev(50m,28s)", dim),
184        ]));
185    }
186
187    out.push(Line::from(""));
188    if s.muted {
189        out.push(Line::from(Span::styled("· MUTED · press 'a' to activate next slot", dim)));
190    }
191
192    out
193}