1use fundsp::hacker::*;
9
10use std::sync::atomic::Ordering;
11
12use super::track::TrackParams;
13use crate::math::pulse::{arp_offset_semitones, 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 pub scale_mode: Shared,
80}
81
82impl Default for GlobalParams {
83 fn default() -> Self {
84 Self {
85 bpm: shared(72.0),
86 master_gain: shared(0.7),
87 brightness: shared(0.6),
88 scale_mode: shared(0.0),
89 }
90 }
91}
92
93pub const MASTER_SHELF_HZ: f64 = 3500.0;
94pub const MIN_SHELF_GAIN: f64 = 0.2;
95
96#[inline]
98pub fn brightness_to_shelf_gain(b: f64) -> f64 {
99 MIN_SHELF_GAIN + (1.0 - MIN_SHELF_GAIN) * b.clamp(0.0, 1.0)
100}
101
102#[inline]
104pub fn shelf_gain_db(g: f64) -> f64 {
105 20.0 * g.max(1e-6).log10()
106}
107
108#[inline]
113pub fn brightness_to_lp_cutoff(b: f64) -> f64 {
114 3000.0 * 6.0_f64.powf(b.clamp(0.0, 1.0))
115}
116
117pub fn master_bus(brightness: Shared) -> Net {
125 let b_shelf_l = brightness.clone();
126 let b_shelf_r = brightness.clone();
127 let b_lp_l = brightness.clone();
128 let b_lp_r = brightness;
129
130 let sh_f_l = lfo(|_t: f64| MASTER_SHELF_HZ);
132 let sh_f_r = lfo(|_t: f64| MASTER_SHELF_HZ);
133 let sh_q_l = lfo(|_t: f64| 0.7_f64);
134 let sh_q_r = lfo(|_t: f64| 0.7_f64);
135 let sh_g_l = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_l.value() as f64));
136 let sh_g_r = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_r.value() as f64));
137 let shelf_l = (pass() | sh_f_l | sh_q_l | sh_g_l) >> highshelf();
138 let shelf_r = (pass() | sh_f_r | sh_q_r | sh_g_r) >> highshelf();
139
140 let lp_c_l = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_l.value() as f64));
142 let lp_c_r = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_r.value() as f64));
143 let lp_q_l = lfo(|_t: f64| 0.5_f64);
144 let lp_q_r = lfo(|_t: f64| 0.5_f64);
145
146 let left = shelf_l >> (pass() | lp_c_l | lp_q_l) >> lowpass();
147 let right = shelf_r >> (pass() | lp_c_r | lp_q_r) >> lowpass();
148 let stereo = left | right;
149
150 let chain = stereo >> limiter_stereo(0.001, 0.3);
151 Net::wrap(Box::new(chain))
152}
153
154pub struct Preset;
155
156impl Preset {
157 pub fn build(kind: PresetKind, p: &TrackParams, g: &GlobalParams) -> Net {
158 match kind {
159 PresetKind::PadZimmer => pad_zimmer(p, g),
160 PresetKind::DroneSub => drone_sub(p, g),
161 PresetKind::Shimmer => shimmer(p, g),
162 PresetKind::Heartbeat => heartbeat(p, g),
163 PresetKind::BassPulse => bass_pulse(p, g),
164 PresetKind::Bell => bell_preset(p, g),
165 PresetKind::SuperSaw => super_saw(p, g),
166 PresetKind::PluckSaw => pluck_saw(p, g),
167 }
168 }
169}
170
171pub const LFO_OFF: u32 = 0;
174pub const LFO_CUTOFF: u32 = 1;
175pub const LFO_GAIN: u32 = 2;
176pub const LFO_FREQ: u32 = 3;
177pub const LFO_REVERB: u32 = 4;
178pub const LFO_TARGETS: u32 = 5;
179
180pub fn lfo_target_name(idx: u32) -> &'static str {
181 match idx {
182 LFO_OFF => "OFF",
183 LFO_CUTOFF => "CUT",
184 LFO_GAIN => "GAIN",
185 LFO_FREQ => "FREQ",
186 LFO_REVERB => "REV",
187 _ => "?",
188 }
189}
190
191#[derive(Clone)]
194pub struct LfoBundle {
195 pub rate: Shared,
196 pub depth: Shared,
197 pub target: Shared,
198}
199
200impl LfoBundle {
201 pub fn from_params(p: &TrackParams) -> Self {
202 Self {
203 rate: p.lfo_rate.clone(),
204 depth: p.lfo_depth.clone(),
205 target: p.lfo_target.clone(),
206 }
207 }
208
209 #[inline]
213 pub fn apply(
214 &self,
215 base: f64,
216 this_target: u32,
217 t: f64,
218 scaler: impl Fn(f64, f64) -> f64,
219 ) -> f64 {
220 let tgt = self.target.value().round() as u32;
221 if tgt != this_target {
222 return base;
223 }
224 let depth = self.depth.value() as f64;
225 if depth < 1.0e-4 {
226 return base;
227 }
228 let rate = self.rate.value() as f64;
229 let lv = (std::f64::consts::TAU * rate * t).sin();
230 scaler(base, lv * depth)
231 }
232}
233
234#[allow(dead_code)]
237fn stereo_from_shared(s: Shared) -> Net {
238 Net::wrap(Box::new(lfo(move |_t: f64| s.value() as f64) >> split::<U2>()))
239}
240
241#[inline]
245pub fn lerp3(a: f64, b: f64, d: f64, c: f64) -> f64 {
246 let c = c.clamp(0.0, 1.0);
247 if c < 0.5 {
248 a + (b - a) * (c * 2.0)
249 } else {
250 b + (d - b) * ((c - 0.5) * 2.0)
251 }
252}
253
254#[derive(Clone)]
258pub struct FreqMod {
259 pub arp: Shared,
260 pub bpm: Shared,
261 pub scale_mode: Shared,
262 pub lb: LfoBundle,
263}
264
265impl FreqMod {
266 pub fn new(p: &TrackParams, g: &GlobalParams) -> Self {
267 Self {
268 arp: p.arp.clone(),
269 bpm: g.bpm.clone(),
270 scale_mode: g.scale_mode.clone(),
271 lb: LfoBundle::from_params(p),
272 }
273 }
274
275 #[inline]
279 pub fn apply(&self, base: f64, t: f64) -> f64 {
280 let seed = (base.max(1.0).ln() * 1_000.0) as u64;
281 let scale = self.scale_mode.value().round() as u32;
282 let off = arp_offset_semitones(
283 t,
284 self.bpm.value() as f64,
285 self.arp.value() as f64,
286 seed,
287 scale,
288 );
289 let arped = base * 2.0_f64.powf(off / 12.0);
290 self.lb
291 .apply(arped, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
292 }
293}
294
295fn stereo_reverb_mix(base: Shared, lb: LfoBundle) -> Net {
298 let mono = lfo(move |t: f64| {
299 let v = base.value() as f64;
300 lb.apply(v, LFO_REVERB, t, |b, m| (b + m * 0.4).clamp(0.0, 1.0))
301 });
302 Net::wrap(Box::new(mono >> split::<U2>()))
303}
304
305fn supermass_send(amount: Shared) -> Net {
306 let a1 = amount.clone();
307 let a2 = amount;
308 let amount_l = lfo(move |_t: f64| a1.value() as f64);
309 let amount_r = lfo(move |_t: f64| a2.value() as f64);
310 let amount_stereo = Net::wrap(Box::new(amount_l | amount_r));
311
312 let effect = reverb_stereo(35.0, 15.0, 0.88)
315 >> (chorus(3, 0.0, 0.022, 0.28) | chorus(4, 0.0, 0.026, 0.28))
316 >> reverb_stereo(50.0, 28.0, 0.90);
317
318 let wet_scaled = Net::wrap(Box::new(effect)) * amount_stereo;
319 let dry = Net::wrap(Box::new(multipass::<U2>()));
320 dry & wet_scaled
321}
322
323fn stereo_gate_voiced(
324 gain: Shared,
325 mute: Shared,
326 pulse_depth: Shared,
327 bpm: Shared,
328 life_mod: Shared,
329 lb: LfoBundle,
330) -> Net {
331 let raw = lfo(move |t: f64| {
332 let g_raw = (gain.value() * (1.0 - mute.value())) as f64;
333 let g = lb.apply(g_raw, LFO_GAIN, t, |b, m| (b * (1.0 + m * 0.6)).max(0.0));
335 let depth = pulse_depth.value().clamp(0.0, 1.0) as f64;
336 let pulse = pulse_sine(t, bpm.value() as f64);
337 let life = life_mod.value().clamp(0.0, 1.0) as f64;
338 let life_scaled = 0.4 + 0.9 * life;
339 g * (1.0 - depth + depth * pulse) * life_scaled
340 });
341 Net::wrap(Box::new(raw >> follow(0.4) >> split::<U2>()))
342}
343
344fn pad_zimmer(p: &TrackParams, g: &GlobalParams) -> Net {
346 let cut = p.cutoff.clone();
347 let res_s = p.resonance.clone();
348 let det = p.detune.clone();
349
350 let lb = LfoBundle::from_params(p);
351 let f0 = p.freq.clone();
352 let f1 = p.freq.clone();
353 let f2 = p.freq.clone();
354 let f3 = p.freq.clone();
355 let d1 = det.clone();
356 let d2 = det.clone();
357 let (lb0, lb1, lb2, lb3, lb_c) = (
358 lb.clone(),
359 lb.clone(),
360 lb.clone(),
361 lb.clone(),
362 lb.clone(),
363 );
364
365 let char0 = p.character.clone();
370 let char1 = p.character.clone();
371 let char2 = p.character.clone();
372 let fm = FreqMod::new(p, g);
373 let fm0 = fm.clone();
374 let fm1 = fm.clone();
375 let fm2 = fm.clone();
376 let fm3 = fm.clone();
377 let _ = (lb0, lb1, lb2, lb3); let osc = ((lfo(move |t: f64| fm0.apply(f0.value() as f64, t)) >> follow(0.08)
379 >> (sine() * 0.30))
380 + (lfo(move |t: f64| {
381 let c = char0.value() as f64;
382 let r = 1.0 + lerp3(1.0, 0.501, 0.618, c);
383 let b = f1.value() as f64 * r * (1.0 + d1.value() as f64 * 0.000578);
384 fm1.apply(b, t)
385 }) >> follow(0.08) >> (sine() * 0.20))
386 + (lfo(move |t: f64| {
387 let c = char1.value() as f64;
388 let r = 2.0 + lerp3(0.0, 0.013, 0.414, c);
389 let b = f2.value() as f64 * r * (1.0 + d2.value() as f64 * 0.000578);
390 fm2.apply(b, t)
391 }) >> follow(0.08) >> (sine() * 0.14))
392 + (lfo(move |t: f64| {
393 let c = char2.value() as f64;
394 let r = 3.0 + lerp3(0.0, 0.007, 0.739, c);
395 let b = f3.value() as f64 * r;
396 fm3.apply(b, t)
397 }) >> follow(0.08) >> (sine() * 0.08)))
398 * 0.9;
399
400 let cutoff_mod = lfo(move |t: f64| {
401 let wobble = 1.0 + 0.10 * (0.5 - 0.5 * (t * 0.08).sin());
402 let base = cut.value() as f64 * wobble;
403 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
404 }) >> follow(0.08);
405 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
409
410 let filtered = (osc | cutoff_mod | res_mod) >> moog()
414 >> highshelf_hz(3000.0, 0.7, 0.67);
415
416 let stereo = filtered
417 >> split::<U2>()
418 >> (chorus(0, 0.0, 0.015, 0.35) | chorus(1, 0.0, 0.020, 0.35))
419 >> reverb_stereo(18.0, 4.0, 0.9);
420
421 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
422 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
423 voiced
424 * stereo_gate_voiced(
425 p.gain.clone(),
426 p.mute.clone(),
427 p.pulse_depth.clone(),
428 g.bpm.clone(),
429 p.life_mod.clone(),
430 lb,
431 )
432}
433
434fn drone_sub(p: &TrackParams, g: &GlobalParams) -> Net {
436 let lb = LfoBundle::from_params(p);
437 let cut = p.cutoff.clone();
438 let res_s = p.resonance.clone();
439
440 let f0 = p.freq.clone();
441 let f1 = p.freq.clone();
442 let (lb0, lb1, lb_c) = (lb.clone(), lb.clone(), lb.clone());
443
444 let fm = FreqMod::new(p, g);
445 let fm0 = fm.clone();
446 let fm1 = fm.clone();
447 let _ = (lb0, lb1);
448 let sub = (lfo(move |t: f64| fm0.apply(f0.value() as f64 * 0.5, t))
449 >> follow(0.08) >> (sine() * 0.45))
450 + (lfo(move |t: f64| fm1.apply(f1.value() as f64, t))
451 >> follow(0.08) >> (sine() * 0.12));
452
453 let noise_cut = lfo(move |t: f64| {
454 let b = cut.value().clamp(40.0, 300.0) as f64;
455 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
456 }) >> follow(0.08);
457 let noise_q = lfo(move |_t: f64| res_s.value() as f64) >> follow(0.08);
458 let noise = (brown() | noise_cut | noise_q) >> moog();
459 let noise_body = noise * 0.28;
460
461 let bpm_am = g.bpm.clone();
462 let am = lfo(move |t: f64| 0.88 + 0.12 * pulse_sine(t, bpm_am.value() as f64));
463 let body = (sub + noise_body) * am;
464
465 let stereo = body >> split::<U2>() >> reverb_stereo(20.0, 5.0, 0.85);
466
467 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
468 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
469 voiced
470 * stereo_gate_voiced(
471 p.gain.clone(),
472 p.mute.clone(),
473 p.pulse_depth.clone(),
474 g.bpm.clone(),
475 p.life_mod.clone(),
476 lb,
477 )
478}
479
480fn shimmer(p: &TrackParams, g: &GlobalParams) -> Net {
482 let lb = LfoBundle::from_params(p);
483 let f0 = p.freq.clone();
484 let f1 = p.freq.clone();
485 let f2 = p.freq.clone();
486 let (lb0, lb1, lb2) = (lb.clone(), lb.clone(), lb.clone());
487
488 let char_s1 = p.character.clone();
493 let char_s2 = p.character.clone();
494 let char_s3 = p.character.clone();
495 let fm = FreqMod::new(p, g);
496 let fm0 = fm.clone();
497 let fm1 = fm.clone();
498 let fm2 = fm.clone();
499 let _ = (lb0, lb1, lb2);
500 let osc = (lfo(move |t: f64| {
501 let c = char_s1.value() as f64;
502 let r = lerp3(2.0, 2.0, 2.1, c);
503 fm0.apply(f0.value() as f64 * r, t)
504 }) >> follow(0.08) >> (sine() * 0.18))
505 + (lfo(move |t: f64| {
506 let c = char_s2.value() as f64;
507 let r = lerp3(3.0, 3.0, 3.3, c);
508 fm1.apply(f1.value() as f64 * r, t)
509 }) >> follow(0.08) >> (sine() * 0.12))
510 + (lfo(move |t: f64| {
511 let c = char_s3.value() as f64;
512 let r = lerp3(4.0, 4.007, 4.8, c);
513 fm2.apply(f2.value() as f64 * r, t)
514 }) >> follow(0.08) >> (sine() * 0.08));
515
516 let bright = osc >> highpass_hz(400.0, 0.5);
517 let stereo = bright >> split::<U2>() >> reverb_stereo(22.0, 6.0, 0.85);
518
519 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
520 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
521 voiced
522 * stereo_gate_voiced(
523 p.gain.clone(),
524 p.mute.clone(),
525 p.pulse_depth.clone(),
526 g.bpm.clone(),
527 p.life_mod.clone(),
528 lb,
529 )
530}
531
532fn heartbeat(p: &TrackParams, g: &GlobalParams) -> Net {
537 let bpm = g.bpm.clone();
538
539 let bpm_body_f = bpm.clone();
541 let freq_body = p.freq.clone();
542 let pat_body_f = p.pattern_bits.clone();
543 let body_osc = lfo(move |t: f64| {
544 let bpm_v = bpm_body_f.value() as f64;
545 let bits = pat_body_f.load(Ordering::Relaxed);
546 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
547 let base = freq_body.value() as f64;
548 if active {
549 let drop = (-phi * 40.0).exp();
550 base * (0.7 + 1.5 * drop)
551 } else {
552 base
555 }
556 }) >> sine();
557
558 let bpm_body_e = bpm.clone();
559 let pat_body_e = p.pattern_bits.clone();
560 let body_env = lfo(move |t: f64| {
561 let bpm_v = bpm_body_e.value() as f64;
562 let bits = pat_body_e.load(Ordering::Relaxed);
563 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
564 if active {
565 (-phi * 4.0).exp()
566 } else {
567 0.0
568 }
569 });
570 let body = body_osc * body_env * 0.85;
571
572 let freq_sub = p.freq.clone();
576 let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
577 let bpm_sub_e = bpm.clone();
578 let pat_sub = p.pattern_bits.clone();
579 let sub_env = lfo(move |t: f64| {
580 let bpm_v = bpm_sub_e.value() as f64;
581 let bits = pat_sub.load(Ordering::Relaxed);
582 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
583 if active {
584 (-phi * 1.5).exp()
585 } else {
586 0.0
587 }
588 });
589 let sub = sub_osc * sub_env;
590
591 let bpm_click = bpm.clone();
594 let pat_click = p.pattern_bits.clone();
595 let char_click = p.character.clone();
596 let click_env = lfo(move |t: f64| {
597 let bpm_v = bpm_click.value() as f64;
598 let bits = pat_click.load(Ordering::Relaxed);
599 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
600 if active {
601 let amp = 0.02 + char_click.value().clamp(0.0, 1.0) as f64 * 0.20;
606 (-phi * 40.0).exp() * amp
607 } else {
608 0.0
609 }
610 });
611 let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env;
612
613 let char_sub = p.character.clone();
617 let sub_scale = lfo(move |_t: f64| {
618 0.35 + (1.0 - char_sub.value().clamp(0.0, 1.0) as f64) * 0.20
620 });
621 let sub_scaled = sub * sub_scale;
622
623 let kick = body + sub_scaled + click;
624
625 let stereo = kick >> split::<U2>() >> reverb_stereo(10.0, 1.5, 0.88);
626
627 let lb = LfoBundle::from_params(p);
628 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
629 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
630 voiced
631 * stereo_gate_voiced(
632 p.gain.clone(),
633 p.mute.clone(),
634 p.pulse_depth.clone(),
635 g.bpm.clone(),
636 p.life_mod.clone(),
637 lb,
638 )
639}
640
641fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
645 let lb = LfoBundle::from_params(p);
646 let f1 = p.freq.clone();
647 let f2 = p.freq.clone();
648 let f3 = p.freq.clone();
649 let cut = p.cutoff.clone();
650 let res_s = p.resonance.clone();
651 let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
652
653 let fm = FreqMod::new(p, g);
654 let (fm1_, fm2_, fm3_) = (fm.clone(), fm.clone(), fm.clone());
655 let _ = (lb1, lb2, lb3);
656 let fundamental = lfo(move |t: f64| fm1_.apply(f1.value() as f64, t))
657 >> follow(0.08) >> (sine() * 0.55);
658 let second = lfo(move |t: f64| fm2_.apply(f2.value() as f64 * 2.0, t))
659 >> follow(0.08) >> (sine() * 0.22);
660 let sub = lfo(move |t: f64| fm3_.apply(f3.value() as f64 * 0.5, t))
661 >> follow(0.08) >> (sine() * 0.35);
662 let osc = fundamental + second + sub;
663
664 let cut_mod = lfo(move |t: f64| {
665 let b = cut.value().min(900.0) as f64;
666 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
667 }) >> follow(0.08);
668 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
669 let filtered = (osc | cut_mod | res_mod) >> moog();
670
671 let bpm_groove = g.bpm.clone();
672 let groove = lfo(move |t: f64| {
673 let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
674 0.45 + 0.55 * pump
675 });
676 let grooved = filtered * groove;
677
678 let stereo = grooved >> split::<U2>() >> reverb_stereo(14.0, 2.5, 0.88);
679
680 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
681 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
682 voiced
683 * stereo_gate_voiced(
684 p.gain.clone(),
685 p.mute.clone(),
686 p.pulse_depth.clone(),
687 g.bpm.clone(),
688 p.life_mod.clone(),
689 lb,
690 )
691}
692
693fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
698 let lb = LfoBundle::from_params(p);
699 let fc = p.freq.clone();
700 let fm = p.freq.clone();
701 let fm_depth = p.resonance.clone();
702 let (lb_c, lb_m) = (lb.clone(), lb.clone());
703
704 let char_m = p.character.clone();
709 let fmm = FreqMod::new(p, g);
710 let fmm_m = fmm.clone();
711 let fmm_c = fmm.clone();
712 let _ = (lb_m, lb_c);
713 let modulator_freq = lfo(move |t: f64| {
714 let c = char_m.value() as f64;
715 let ratio = lerp3(1.41, 2.76, 4.18, c);
716 let b = fm.value() as f64 * ratio;
717 fmm_m.apply(b, t)
718 }) >> follow(0.08);
719 let modulator = modulator_freq >> sine();
720 let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
721 let modulator_scaled = modulator * mod_scale;
722
723 let carrier_base = lfo(move |t: f64| fmm_c.apply(fc.value() as f64, t))
724 >> follow(0.08);
725 let bell_sig = (carrier_base + modulator_scaled) >> sine();
726
727 let bpm_am = g.bpm.clone();
728 let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
729 let body = bell_sig * am * 0.30;
730
731 let stereo = body >> split::<U2>() >> reverb_stereo(25.0, 8.0, 0.85);
732
733 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
734 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
735 voiced
736 * stereo_gate_voiced(
737 p.gain.clone(),
738 p.mute.clone(),
739 p.pulse_depth.clone(),
740 g.bpm.clone(),
741 p.life_mod.clone(),
742 lb,
743 )
744}
745
746fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
751 let lb = LfoBundle::from_params(p);
752 let cut = p.cutoff.clone();
753 let res_s = p.resonance.clone();
754
755 const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
756 let voice_amp: f32 = 0.55 / OFFS.len() as f32;
758
759 let fm = FreqMod::new(p, g);
761 let mut stack: Option<Net> = None;
762 for &off in OFFS.iter() {
763 let f_c = p.freq.clone();
764 let d_c = p.detune.clone();
765 let fm_c = fm.clone();
766 let voice = lfo(move |t: f64| {
767 let width = (d_c.value().abs() as f64).max(1.0);
768 let cents = off * width;
769 let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
770 fm_c.apply(base, t)
771 }) >> follow(0.08) >> (saw() * voice_amp);
772 let wrapped = Net::wrap(Box::new(voice));
773 stack = Some(match stack {
774 Some(acc) => acc + wrapped,
775 None => wrapped,
776 });
777 }
778 let saw_stack = stack.expect("N > 0");
779
780 let f_sub = p.freq.clone();
782 let fm_sub = fm.clone();
783 let _ = lb.clone();
784 let sub = lfo(move |t: f64| fm_sub.apply(f_sub.value() as f64 * 0.5, t))
785 >> follow(0.08) >> (sine() * 0.22);
786 let sub_net = Net::wrap(Box::new(sub));
787
788 let mixed = saw_stack + sub_net;
789
790 let lb_cut = lb.clone();
791 let cut_mod = lfo(move |t: f64| {
792 let b = cut.value() as f64;
793 lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
794 }) >> follow(0.05);
795 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
796
797 let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
798 >> Net::wrap(Box::new(moog()));
799
800 let stereo = filtered
801 >> Net::wrap(Box::new(split::<U2>()))
802 >> Net::wrap(Box::new(
803 chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
804 ))
805 >> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 0.88)));
806
807 let with_super = stereo >> supermass_send(p.supermass.clone());
808 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
809 voiced
810 * stereo_gate_voiced(
811 p.gain.clone(),
812 p.mute.clone(),
813 p.pulse_depth.clone(),
814 g.bpm.clone(),
815 p.life_mod.clone(),
816 lb,
817 )
818}
819
820fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
824 let lb = LfoBundle::from_params(p);
825
826 let fm = FreqMod::new(p, g);
827 let fm_a = fm.clone();
828 let fm_b = fm.clone();
829 let f_a = p.freq.clone();
830 let osc_a = lfo(move |t: f64| fm_a.apply(f_a.value() as f64, t))
831 >> follow(0.08) >> (saw() * 0.35);
832
833 let f_b = p.freq.clone();
834 let det = p.detune.clone();
835 let osc_b = lfo(move |t: f64| {
836 let cents = det.value() as f64 * 0.5;
837 let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
838 fm_b.apply(b, t)
839 }) >> follow(0.08) >> (saw() * 0.35);
840 let osc = osc_a + osc_b;
841
842 let bpm_f = g.bpm.clone();
845 let pat_f = p.pattern_bits.clone();
846 let cut_shared = p.cutoff.clone();
847 let lb_c = lb.clone();
848 let cut_env = lfo(move |t: f64| {
849 let bpm = bpm_f.value() as f64;
850 let bits = pat_f.load(Ordering::Relaxed);
851 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
852 let user_cut = cut_shared.value() as f64;
853 let base = if active {
854 180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
855 } else {
856 180.0
857 };
858 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
859 }) >> follow(0.01);
860
861 let res_s = p.resonance.clone();
862 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
863
864 let filtered =
865 (osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
866
867 let bpm_env = g.bpm.clone();
869 let pat_env = p.pattern_bits.clone();
870 let amp_env = lfo(move |t: f64| {
871 let bpm = bpm_env.value() as f64;
872 let bits = pat_env.load(Ordering::Relaxed);
873 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
874 if active {
875 (-phi * 4.5).exp()
876 } else {
877 0.0
878 }
879 });
880 let plucked = filtered * Net::wrap(Box::new(amp_env));
881
882 let stereo = plucked
883 >> Net::wrap(Box::new(split::<U2>()))
884 >> Net::wrap(Box::new(
885 chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
886 ))
887 >> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
888
889 let with_super = stereo >> supermass_send(p.supermass.clone());
890 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
891 voiced
892 * stereo_gate_voiced(
893 p.gain.clone(),
894 p.mute.clone(),
895 p.pulse_depth.clone(),
896 g.bpm.clone(),
897 p.life_mod.clone(),
898 lb,
899 )
900}