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::SuperSaw => {
148            out.push(Line::from(Span::styled("Serum-style 7-voice saw stack:", hi)));
149            out.push(Line::from(Span::styled(
150                "  voice_i(t) = saw(2π · f · 2^(offsᵢ · |detune| / 1200) · t)",
151                hi,
152            )));
153            out.push(Line::from(Span::styled(
154                "  offsᵢ = [-1, -⅔, -⅓, 0, +⅓, +⅔, +1]",
155                dim,
156            )));
157            out.push(Line::from(Span::styled(
158                "sub(t) = 0.22 · sin(π · f · t)",
159                hi,
160            )));
161            out.push(Line::from(Span::styled(
162                "y = Moog(Σ voiceᵢ/7 + sub, cut, q) ⇒ chorus ⇒ hall(16m,3s)",
163                hi,
164            )));
165            out.push(Line::from(vec![
166                Span::styled("  spread = ", dim),
167                Span::styled(format!("{:.0} ct", s.detune.abs()), key),
168                Span::styled("  cut = ", dim),
169                Span::styled(format!("{:>5.0} Hz", s.cutoff), key),
170            ]));
171        }
172        PresetKind::PluckSaw => {
173            out.push(Line::from(Span::styled("Step-gated saw pluck:", hi)));
174            out.push(Line::from(Span::styled(
175                "  osc = 0.35·saw(f·t) + 0.35·saw(f · 2^(det/2400) · t)",
176                hi,
177            )));
178            out.push(Line::from(Span::styled(
179                "  cut_env = 180 + (cutoff − 180) · e^(−5·φₛ)    on active step",
180                hi,
181            )));
182            out.push(Line::from(Span::styled(
183                "  amp_env = e^(−4.5·φₛ)                         on active step",
184                hi,
185            )));
186            out.push(Line::from(Span::styled(
187                "y = Moog(osc, cut_env, q) · amp_env ⇒ chorus ⇒ hall(18m,3.5s)",
188                hi,
189            )));
190            out.push(Line::from(vec![
191                Span::styled("  cut = ", dim),
192                Span::styled(format!("{:>5.0}", s.cutoff), key),
193                Span::styled("  det = ", dim),
194                Span::styled(format!("{:>+3.0} ct", s.detune), key),
195            ]));
196        }
197        PresetKind::Bell => {
198            out.push(Line::from(Span::styled("2-operator FM bell:", hi)));
199            out.push(Line::from(Span::styled(
200                "  mod(t) = sin(2π · f·2.76 · t) · (q · 450)",
201                hi,
202            )));
203            out.push(Line::from(Span::styled(
204                "  bell(t) = sin(2π · (f + mod(t)) · t)",
205                hi,
206            )));
207            out.push(Line::from(""));
208            out.push(Line::from(Span::styled(
209                "y = bell · (0.85 + 0.15·sin_pulse(bpm/4)) · 0.30 ⇒ hall(25m, 8s)",
210                hi,
211            )));
212            out.push(Line::from(vec![
213                Span::styled("  FM depth (q) = ", dim),
214                Span::styled(format!("{:.2}", s.resonance.min(0.65)), key),
215                Span::styled("  gain = ", dim),
216                Span::styled(format!("{:.2}", s.gain), key),
217            ]));
218        }
219        PresetKind::BassPulse => {
220            out.push(Line::from(Span::styled(
221                "osc = 0.55·sin(2π·f·t) + 0.22·sin(4π·f·t) + 0.35·sin(π·f·t)",
222                hi,
223            )));
224            out.push(Line::from(Span::styled(
225                "y = Moog(osc, min(cut, 900), q) · groove(t)",
226                hi,
227            )));
228            out.push(Line::from(Span::styled(
229                "groove(t) = 0.45 + 0.55 · e^(−3.5·φ)    φ = (t mod 60/bpm)·bpm/60",
230                dim,
231            )));
232            out.push(Line::from(""));
233            out.push(Line::from(vec![
234                Span::styled("  cut = ", dim),
235                Span::styled(format!("{:>5.0} Hz", s.cutoff.min(900.0)), key),
236                Span::styled("  q = ", dim),
237                Span::styled(format!("{:.2}", s.resonance.min(0.65)), key),
238                Span::styled("  rev = ", dim),
239                Span::styled(format!("{:.2}", s.reverb_mix), key),
240                Span::styled("  gain = ", dim),
241                Span::styled(format!("{:.2}", s.gain), key),
242            ]));
243        }
244    }
245
246    // Universal supermassive tail line.
247    if s.supermass > 0.01 {
248        out.push(Line::from(""));
249        out.push(Line::from(vec![
250            Span::styled("Σ  supermass ", Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD)),
251            Span::styled(
252                format!("{:.2}", s.supermass),
253                Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
254            ),
255            Span::styled("   + rev(35m,15s)⇒chorus⇒rev(50m,28s)", dim),
256        ]));
257    }
258
259    out.push(Line::from(""));
260    if s.muted {
261        out.push(Line::from(Span::styled("· MUTED · press 'a' to activate next slot", dim)));
262    }
263
264    out
265}