1use fundsp::hacker::*;
9
10use std::sync::atomic::Ordering;
11
12use super::track::TrackParams;
13use crate::math::pulse::{pulse_decay, pulse_sine};
14use crate::math::rhythm;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum PresetKind {
18 PadZimmer,
19 DroneSub,
20 Shimmer,
21 Heartbeat,
22 BassPulse,
23 Bell,
24 SuperSaw,
25 PluckSaw,
26}
27
28pub const ALL_KINDS: [PresetKind; 8] = [
30 PresetKind::PadZimmer,
31 PresetKind::BassPulse,
32 PresetKind::Heartbeat,
33 PresetKind::DroneSub,
34 PresetKind::Shimmer,
35 PresetKind::Bell,
36 PresetKind::SuperSaw,
37 PresetKind::PluckSaw,
38];
39
40impl PresetKind {
41 pub fn label(self) -> &'static str {
42 match self {
43 PresetKind::PadZimmer => "Pad",
44 PresetKind::DroneSub => "Drone",
45 PresetKind::Shimmer => "Shimmer",
46 PresetKind::Heartbeat => "Heartbeat",
47 PresetKind::BassPulse => "Bass",
48 PresetKind::Bell => "Bell",
49 PresetKind::SuperSaw => "SuperSaw",
50 PresetKind::PluckSaw => "Pluck",
51 }
52 }
53
54 pub fn next(self) -> Self {
55 let i = ALL_KINDS.iter().position(|&k| k == self).unwrap_or(0);
56 ALL_KINDS[(i + 1) % ALL_KINDS.len()]
57 }
58
59 pub fn prev(self) -> Self {
60 let i = ALL_KINDS.iter().position(|&k| k == self).unwrap_or(0);
61 ALL_KINDS[(i + ALL_KINDS.len() - 1) % ALL_KINDS.len()]
62 }
63}
64
65#[derive(Clone)]
66pub struct GlobalParams {
67 pub bpm: Shared,
68 pub master_gain: Shared,
69 pub brightness: Shared,
78}
79
80impl Default for GlobalParams {
81 fn default() -> Self {
82 Self {
83 bpm: shared(72.0),
84 master_gain: shared(0.7),
85 brightness: shared(0.6),
86 }
87 }
88}
89
90pub const MASTER_SHELF_HZ: f64 = 3500.0;
91pub const MIN_SHELF_GAIN: f64 = 0.2;
92
93#[inline]
95pub fn brightness_to_shelf_gain(b: f64) -> f64 {
96 MIN_SHELF_GAIN + (1.0 - MIN_SHELF_GAIN) * b.clamp(0.0, 1.0)
97}
98
99#[inline]
101pub fn shelf_gain_db(g: f64) -> f64 {
102 20.0 * g.max(1e-6).log10()
103}
104
105#[inline]
110pub fn brightness_to_lp_cutoff(b: f64) -> f64 {
111 3000.0 * 6.0_f64.powf(b.clamp(0.0, 1.0))
112}
113
114pub fn master_bus(brightness: Shared) -> Net {
122 let b_shelf_l = brightness.clone();
123 let b_shelf_r = brightness.clone();
124 let b_lp_l = brightness.clone();
125 let b_lp_r = brightness;
126
127 let sh_f_l = lfo(|_t: f64| MASTER_SHELF_HZ);
129 let sh_f_r = lfo(|_t: f64| MASTER_SHELF_HZ);
130 let sh_q_l = lfo(|_t: f64| 0.7_f64);
131 let sh_q_r = lfo(|_t: f64| 0.7_f64);
132 let sh_g_l = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_l.value() as f64));
133 let sh_g_r = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_r.value() as f64));
134 let shelf_l = (pass() | sh_f_l | sh_q_l | sh_g_l) >> highshelf();
135 let shelf_r = (pass() | sh_f_r | sh_q_r | sh_g_r) >> highshelf();
136
137 let lp_c_l = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_l.value() as f64));
139 let lp_c_r = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_r.value() as f64));
140 let lp_q_l = lfo(|_t: f64| 0.5_f64);
141 let lp_q_r = lfo(|_t: f64| 0.5_f64);
142
143 let left = shelf_l >> (pass() | lp_c_l | lp_q_l) >> lowpass();
144 let right = shelf_r >> (pass() | lp_c_r | lp_q_r) >> lowpass();
145 let stereo = left | right;
146
147 let chain = stereo >> limiter_stereo(0.001, 0.3);
148 Net::wrap(Box::new(chain))
149}
150
151pub struct Preset;
152
153impl Preset {
154 pub fn build(kind: PresetKind, p: &TrackParams, g: &GlobalParams) -> Net {
155 match kind {
156 PresetKind::PadZimmer => pad_zimmer(p, g),
157 PresetKind::DroneSub => drone_sub(p, g),
158 PresetKind::Shimmer => shimmer(p, g),
159 PresetKind::Heartbeat => heartbeat(p, g),
160 PresetKind::BassPulse => bass_pulse(p, g),
161 PresetKind::Bell => bell_preset(p, g),
162 PresetKind::SuperSaw => super_saw(p, g),
163 PresetKind::PluckSaw => pluck_saw(p, g),
164 }
165 }
166}
167
168pub const LFO_OFF: u32 = 0;
171pub const LFO_CUTOFF: u32 = 1;
172pub const LFO_GAIN: u32 = 2;
173pub const LFO_FREQ: u32 = 3;
174pub const LFO_REVERB: u32 = 4;
175pub const LFO_TARGETS: u32 = 5;
176
177pub fn lfo_target_name(idx: u32) -> &'static str {
178 match idx {
179 LFO_OFF => "OFF",
180 LFO_CUTOFF => "CUT",
181 LFO_GAIN => "GAIN",
182 LFO_FREQ => "FREQ",
183 LFO_REVERB => "REV",
184 _ => "?",
185 }
186}
187
188#[derive(Clone)]
191pub struct LfoBundle {
192 pub rate: Shared,
193 pub depth: Shared,
194 pub target: Shared,
195}
196
197impl LfoBundle {
198 pub fn from_params(p: &TrackParams) -> Self {
199 Self {
200 rate: p.lfo_rate.clone(),
201 depth: p.lfo_depth.clone(),
202 target: p.lfo_target.clone(),
203 }
204 }
205
206 #[inline]
210 pub fn apply(
211 &self,
212 base: f64,
213 this_target: u32,
214 t: f64,
215 scaler: impl Fn(f64, f64) -> f64,
216 ) -> f64 {
217 let tgt = self.target.value().round() as u32;
218 if tgt != this_target {
219 return base;
220 }
221 let depth = self.depth.value() as f64;
222 if depth < 1.0e-4 {
223 return base;
224 }
225 let rate = self.rate.value() as f64;
226 let lv = (std::f64::consts::TAU * rate * t).sin();
227 scaler(base, lv * depth)
228 }
229}
230
231#[allow(dead_code)]
234fn stereo_from_shared(s: Shared) -> Net {
235 Net::wrap(Box::new(lfo(move |_t: f64| s.value() as f64) >> split::<U2>()))
236}
237
238fn stereo_reverb_mix(base: Shared, lb: LfoBundle) -> Net {
241 let mono = lfo(move |t: f64| {
242 let v = base.value() as f64;
243 lb.apply(v, LFO_REVERB, t, |b, m| (b + m * 0.4).clamp(0.0, 1.0))
244 });
245 Net::wrap(Box::new(mono >> split::<U2>()))
246}
247
248fn supermass_send(amount: Shared) -> Net {
249 let a1 = amount.clone();
250 let a2 = amount;
251 let amount_l = lfo(move |_t: f64| a1.value() as f64);
252 let amount_r = lfo(move |_t: f64| a2.value() as f64);
253 let amount_stereo = Net::wrap(Box::new(amount_l | amount_r));
254
255 let effect = reverb_stereo(35.0, 15.0, 0.88)
258 >> (chorus(3, 0.0, 0.022, 0.28) | chorus(4, 0.0, 0.026, 0.28))
259 >> reverb_stereo(50.0, 28.0, 0.90);
260
261 let wet_scaled = Net::wrap(Box::new(effect)) * amount_stereo;
262 let dry = Net::wrap(Box::new(multipass::<U2>()));
263 dry & wet_scaled
264}
265
266fn stereo_gate_voiced(
267 gain: Shared,
268 mute: Shared,
269 pulse_depth: Shared,
270 bpm: Shared,
271 life_mod: Shared,
272 lb: LfoBundle,
273) -> Net {
274 let raw = lfo(move |t: f64| {
275 let g_raw = (gain.value() * (1.0 - mute.value())) as f64;
276 let g = lb.apply(g_raw, LFO_GAIN, t, |b, m| (b * (1.0 + m * 0.6)).max(0.0));
278 let depth = pulse_depth.value().clamp(0.0, 1.0) as f64;
279 let pulse = pulse_sine(t, bpm.value() as f64);
280 let life = life_mod.value().clamp(0.0, 1.0) as f64;
281 let life_scaled = 0.4 + 0.9 * life;
282 g * (1.0 - depth + depth * pulse) * life_scaled
283 });
284 Net::wrap(Box::new(raw >> follow(0.4) >> split::<U2>()))
285}
286
287fn pad_zimmer(p: &TrackParams, g: &GlobalParams) -> Net {
289 let cut = p.cutoff.clone();
290 let res_s = p.resonance.clone();
291 let det = p.detune.clone();
292
293 let lb = LfoBundle::from_params(p);
294 let f0 = p.freq.clone();
295 let f1 = p.freq.clone();
296 let f2 = p.freq.clone();
297 let f3 = p.freq.clone();
298 let d1 = det.clone();
299 let d2 = det.clone();
300 let (lb0, lb1, lb2, lb3, lb_c) = (
301 lb.clone(),
302 lb.clone(),
303 lb.clone(),
304 lb.clone(),
305 lb.clone(),
306 );
307
308 let osc = ((lfo(move |t: f64| {
309 let b = f0.value() as f64;
310 lb0.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
311 }) >> (sine() * 0.30))
312 + (lfo(move |t: f64| {
313 let b = f1.value() as f64 * 1.501 * (1.0 + d1.value() as f64 * 0.000578);
314 lb1.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
315 }) >> (sine() * 0.20))
316 + (lfo(move |t: f64| {
317 let b = f2.value() as f64 * 2.013 * (1.0 + d2.value() as f64 * 0.000578);
318 lb2.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
319 }) >> (sine() * 0.14))
320 + (lfo(move |t: f64| {
321 let b = f3.value() as f64 * 3.007;
322 lb3.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
323 }) >> (sine() * 0.08)))
324 * 0.9;
325
326 let cutoff_mod = lfo(move |t: f64| {
327 let wobble = 1.0 + 0.10 * (0.5 - 0.5 * (t * 0.08).sin());
328 let base = cut.value() as f64 * wobble;
329 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
330 }) >> follow(0.08);
331 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
335
336 let filtered = (osc | cutoff_mod | res_mod) >> moog()
340 >> highshelf_hz(3000.0, 0.7, 0.67);
341
342 let stereo = filtered
343 >> split::<U2>()
344 >> (chorus(0, 0.0, 0.015, 0.35) | chorus(1, 0.0, 0.020, 0.35))
345 >> reverb_stereo(18.0, 4.0, 0.9);
346
347 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
348 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
349 voiced
350 * stereo_gate_voiced(
351 p.gain.clone(),
352 p.mute.clone(),
353 p.pulse_depth.clone(),
354 g.bpm.clone(),
355 p.life_mod.clone(),
356 lb,
357 )
358}
359
360fn drone_sub(p: &TrackParams, g: &GlobalParams) -> Net {
362 let lb = LfoBundle::from_params(p);
363 let cut = p.cutoff.clone();
364 let res_s = p.resonance.clone();
365
366 let f0 = p.freq.clone();
367 let f1 = p.freq.clone();
368 let (lb0, lb1, lb_c) = (lb.clone(), lb.clone(), lb.clone());
369
370 let sub = (lfo(move |t: f64| {
371 let b = f0.value() as f64 * 0.5;
372 lb0.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
373 }) >> (sine() * 0.45))
374 + (lfo(move |t: f64| {
375 let b = f1.value() as f64;
376 lb1.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
377 }) >> (sine() * 0.12));
378
379 let noise_cut = lfo(move |t: f64| {
380 let b = cut.value().clamp(40.0, 300.0) as f64;
381 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
382 }) >> follow(0.08);
383 let noise_q = lfo(move |_t: f64| res_s.value() as f64) >> follow(0.08);
384 let noise = (brown() | noise_cut | noise_q) >> moog();
385 let noise_body = noise * 0.28;
386
387 let bpm_am = g.bpm.clone();
388 let am = lfo(move |t: f64| 0.88 + 0.12 * pulse_sine(t, bpm_am.value() as f64));
389 let body = (sub + noise_body) * am;
390
391 let stereo = body >> split::<U2>() >> reverb_stereo(20.0, 5.0, 0.85);
392
393 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
394 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
395 voiced
396 * stereo_gate_voiced(
397 p.gain.clone(),
398 p.mute.clone(),
399 p.pulse_depth.clone(),
400 g.bpm.clone(),
401 p.life_mod.clone(),
402 lb,
403 )
404}
405
406fn shimmer(p: &TrackParams, g: &GlobalParams) -> Net {
408 let lb = LfoBundle::from_params(p);
409 let f0 = p.freq.clone();
410 let f1 = p.freq.clone();
411 let f2 = p.freq.clone();
412 let (lb0, lb1, lb2) = (lb.clone(), lb.clone(), lb.clone());
413
414 let osc = (lfo(move |t: f64| {
415 let b = f0.value() as f64 * 2.0;
416 lb0.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
417 }) >> (sine() * 0.18))
418 + (lfo(move |t: f64| {
419 let b = f1.value() as f64 * 3.0;
420 lb1.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
421 }) >> (sine() * 0.12))
422 + (lfo(move |t: f64| {
423 let b = f2.value() as f64 * 4.007;
424 lb2.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
425 }) >> (sine() * 0.08));
426
427 let bright = osc >> highpass_hz(400.0, 0.5);
428 let stereo = bright >> split::<U2>() >> reverb_stereo(22.0, 6.0, 0.85);
429
430 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
431 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
432 voiced
433 * stereo_gate_voiced(
434 p.gain.clone(),
435 p.mute.clone(),
436 p.pulse_depth.clone(),
437 g.bpm.clone(),
438 p.life_mod.clone(),
439 lb,
440 )
441}
442
443fn heartbeat(p: &TrackParams, g: &GlobalParams) -> Net {
448 let bpm = g.bpm.clone();
449
450 let bpm_body_f = bpm.clone();
452 let freq_body = p.freq.clone();
453 let pat_body_f = p.pattern_bits.clone();
454 let body_osc = lfo(move |t: f64| {
455 let bpm_v = bpm_body_f.value() as f64;
456 let bits = pat_body_f.load(Ordering::Relaxed);
457 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
458 let base = freq_body.value() as f64;
459 if active {
460 let drop = (-phi * 40.0).exp();
461 base * (0.7 + 1.5 * drop)
462 } else {
463 base
466 }
467 }) >> sine();
468
469 let bpm_body_e = bpm.clone();
470 let pat_body_e = p.pattern_bits.clone();
471 let body_env = lfo(move |t: f64| {
472 let bpm_v = bpm_body_e.value() as f64;
473 let bits = pat_body_e.load(Ordering::Relaxed);
474 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
475 if active {
476 (-phi * 4.0).exp()
477 } else {
478 0.0
479 }
480 });
481 let body = body_osc * body_env * 0.85;
482
483 let freq_sub = p.freq.clone();
485 let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
486 let bpm_sub_e = bpm.clone();
487 let pat_sub = p.pattern_bits.clone();
488 let sub_env = lfo(move |t: f64| {
489 let bpm_v = bpm_sub_e.value() as f64;
490 let bits = pat_sub.load(Ordering::Relaxed);
491 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
492 if active {
493 (-phi * 1.5).exp()
494 } else {
495 0.0
496 }
497 });
498 let sub = sub_osc * sub_env * 0.45;
499
500 let bpm_click = bpm.clone();
502 let pat_click = p.pattern_bits.clone();
503 let click_env = lfo(move |t: f64| {
504 let bpm_v = bpm_click.value() as f64;
505 let bits = pat_click.load(Ordering::Relaxed);
506 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
507 if active {
508 (-phi * 40.0).exp()
509 } else {
510 0.0
511 }
512 });
513 let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env * 0.12;
514
515 let kick = body + sub + click;
516
517 let stereo = kick >> split::<U2>() >> reverb_stereo(10.0, 1.5, 0.88);
518
519 let lb = LfoBundle::from_params(p);
520 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
521 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
522 voiced
523 * stereo_gate_voiced(
524 p.gain.clone(),
525 p.mute.clone(),
526 p.pulse_depth.clone(),
527 g.bpm.clone(),
528 p.life_mod.clone(),
529 lb,
530 )
531}
532
533fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
537 let lb = LfoBundle::from_params(p);
538 let f1 = p.freq.clone();
539 let f2 = p.freq.clone();
540 let f3 = p.freq.clone();
541 let cut = p.cutoff.clone();
542 let res_s = p.resonance.clone();
543 let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
544
545 let fundamental = lfo(move |t: f64| {
546 let b = f1.value() as f64;
547 lb1.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
548 }) >> (sine() * 0.55);
549 let second = lfo(move |t: f64| {
550 let b = f2.value() as f64 * 2.0;
551 lb2.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
552 }) >> (sine() * 0.22);
553 let sub = lfo(move |t: f64| {
554 let b = f3.value() as f64 * 0.5;
555 lb3.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
556 }) >> (sine() * 0.35);
557 let osc = fundamental + second + sub;
558
559 let cut_mod = lfo(move |t: f64| {
560 let b = cut.value().min(900.0) as f64;
561 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
562 }) >> follow(0.08);
563 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
564 let filtered = (osc | cut_mod | res_mod) >> moog();
565
566 let bpm_groove = g.bpm.clone();
567 let groove = lfo(move |t: f64| {
568 let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
569 0.45 + 0.55 * pump
570 });
571 let grooved = filtered * groove;
572
573 let stereo = grooved >> split::<U2>() >> reverb_stereo(14.0, 2.5, 0.88);
574
575 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
576 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
577 voiced
578 * stereo_gate_voiced(
579 p.gain.clone(),
580 p.mute.clone(),
581 p.pulse_depth.clone(),
582 g.bpm.clone(),
583 p.life_mod.clone(),
584 lb,
585 )
586}
587
588fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
593 let lb = LfoBundle::from_params(p);
594 let fc = p.freq.clone();
595 let fm = p.freq.clone();
596 let fm_depth = p.resonance.clone();
597 let (lb_c, lb_m) = (lb.clone(), lb.clone());
598
599 let modulator_freq = lfo(move |t: f64| {
600 let b = fm.value() as f64 * 2.76;
601 lb_m.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
602 });
603 let modulator = modulator_freq >> sine();
604 let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
605 let modulator_scaled = modulator * mod_scale;
606
607 let carrier_base = lfo(move |t: f64| {
608 let b = fc.value() as f64;
609 lb_c.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
610 });
611 let bell_sig = (carrier_base + modulator_scaled) >> sine();
612
613 let bpm_am = g.bpm.clone();
614 let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
615 let body = bell_sig * am * 0.30;
616
617 let stereo = body >> split::<U2>() >> reverb_stereo(25.0, 8.0, 0.85);
618
619 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
620 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
621 voiced
622 * stereo_gate_voiced(
623 p.gain.clone(),
624 p.mute.clone(),
625 p.pulse_depth.clone(),
626 g.bpm.clone(),
627 p.life_mod.clone(),
628 lb,
629 )
630}
631
632fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
637 let lb = LfoBundle::from_params(p);
638 let cut = p.cutoff.clone();
639 let res_s = p.resonance.clone();
640
641 const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
642 let voice_amp: f32 = 0.55 / OFFS.len() as f32;
644
645 let mut stack: Option<Net> = None;
647 for &off in OFFS.iter() {
648 let f_c = p.freq.clone();
649 let d_c = p.detune.clone();
650 let lb_c = lb.clone();
651 let voice = lfo(move |t: f64| {
652 let width = (d_c.value().abs() as f64).max(1.0);
653 let cents = off * width;
654 let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
655 lb_c.apply(base, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
656 }) >> (saw() * voice_amp);
657 let wrapped = Net::wrap(Box::new(voice));
658 stack = Some(match stack {
659 Some(acc) => acc + wrapped,
660 None => wrapped,
661 });
662 }
663 let saw_stack = stack.expect("N > 0");
664
665 let f_sub = p.freq.clone();
667 let lb_sub = lb.clone();
668 let sub = lfo(move |t: f64| {
669 let b = f_sub.value() as f64 * 0.5;
670 lb_sub.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
671 }) >> (sine() * 0.22);
672 let sub_net = Net::wrap(Box::new(sub));
673
674 let mixed = saw_stack + sub_net;
675
676 let lb_cut = lb.clone();
677 let cut_mod = lfo(move |t: f64| {
678 let b = cut.value() as f64;
679 lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
680 }) >> follow(0.05);
681 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
682
683 let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
684 >> Net::wrap(Box::new(moog()));
685
686 let stereo = filtered
687 >> Net::wrap(Box::new(split::<U2>()))
688 >> Net::wrap(Box::new(
689 chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
690 ))
691 >> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 0.88)));
692
693 let with_super = stereo >> supermass_send(p.supermass.clone());
694 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
695 voiced
696 * stereo_gate_voiced(
697 p.gain.clone(),
698 p.mute.clone(),
699 p.pulse_depth.clone(),
700 g.bpm.clone(),
701 p.life_mod.clone(),
702 lb,
703 )
704}
705
706fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
710 let lb = LfoBundle::from_params(p);
711
712 let f_a = p.freq.clone();
713 let lb_a = lb.clone();
714 let osc_a = lfo(move |t: f64| {
715 let b = f_a.value() as f64;
716 lb_a.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
717 }) >> (saw() * 0.35);
718
719 let f_b = p.freq.clone();
720 let det = p.detune.clone();
721 let lb_b = lb.clone();
722 let osc_b = lfo(move |t: f64| {
723 let cents = det.value() as f64 * 0.5;
724 let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
725 lb_b.apply(b, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
726 }) >> (saw() * 0.35);
727 let osc = osc_a + osc_b;
728
729 let bpm_f = g.bpm.clone();
732 let pat_f = p.pattern_bits.clone();
733 let cut_shared = p.cutoff.clone();
734 let lb_c = lb.clone();
735 let cut_env = lfo(move |t: f64| {
736 let bpm = bpm_f.value() as f64;
737 let bits = pat_f.load(Ordering::Relaxed);
738 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
739 let user_cut = cut_shared.value() as f64;
740 let base = if active {
741 180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
742 } else {
743 180.0
744 };
745 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
746 }) >> follow(0.01);
747
748 let res_s = p.resonance.clone();
749 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
750
751 let filtered =
752 (osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
753
754 let bpm_env = g.bpm.clone();
756 let pat_env = p.pattern_bits.clone();
757 let amp_env = lfo(move |t: f64| {
758 let bpm = bpm_env.value() as f64;
759 let bits = pat_env.load(Ordering::Relaxed);
760 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
761 if active {
762 (-phi * 4.5).exp()
763 } else {
764 0.0
765 }
766 });
767 let plucked = filtered * Net::wrap(Box::new(amp_env));
768
769 let stereo = plucked
770 >> Net::wrap(Box::new(split::<U2>()))
771 >> Net::wrap(Box::new(
772 chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
773 ))
774 >> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
775
776 let with_super = stereo >> supermass_send(p.supermass.clone());
777 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
778 voiced
779 * stereo_gate_voiced(
780 p.gain.clone(),
781 p.mute.clone(),
782 p.pulse_depth.clone(),
783 g.bpm.clone(),
784 p.life_mod.clone(),
785 lb,
786 )
787}