1use 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 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}