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
468 >> split::<U2>()
469 >> (chorus(10, 0.0, 0.025, 0.18) | chorus(11, 0.0, 0.031, 0.18))
470 >> reverb_stereo(20.0, 5.0, 0.85);
471
472 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
473 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
474 voiced
475 * stereo_gate_voiced(
476 p.gain.clone(),
477 p.mute.clone(),
478 p.pulse_depth.clone(),
479 g.bpm.clone(),
480 p.life_mod.clone(),
481 lb,
482 )
483}
484
485fn shimmer(p: &TrackParams, g: &GlobalParams) -> Net {
487 let lb = LfoBundle::from_params(p);
488 let f0 = p.freq.clone();
489 let f1 = p.freq.clone();
490 let f2 = p.freq.clone();
491 let (lb0, lb1, lb2) = (lb.clone(), lb.clone(), lb.clone());
492
493 let char_s1 = p.character.clone();
498 let char_s2 = p.character.clone();
499 let char_s3 = p.character.clone();
500 let fm = FreqMod::new(p, g);
501 let fm0 = fm.clone();
502 let fm1 = fm.clone();
503 let fm2 = fm.clone();
504 let _ = (lb0, lb1, lb2);
505 let osc = (lfo(move |t: f64| {
506 let c = char_s1.value() as f64;
507 let r = lerp3(2.0, 2.0, 2.1, c);
508 fm0.apply(f0.value() as f64 * r, t)
509 }) >> follow(0.08) >> (sine() * 0.18))
510 + (lfo(move |t: f64| {
511 let c = char_s2.value() as f64;
512 let r = lerp3(3.0, 3.0, 3.3, c);
513 fm1.apply(f1.value() as f64 * r, t)
514 }) >> follow(0.08) >> (sine() * 0.12))
515 + (lfo(move |t: f64| {
516 let c = char_s3.value() as f64;
517 let r = lerp3(4.0, 4.007, 4.8, c);
518 fm2.apply(f2.value() as f64 * r, t)
519 }) >> follow(0.08) >> (sine() * 0.08));
520
521 let bright = osc >> highpass_hz(400.0, 0.5);
522 let stereo = bright
525 >> split::<U2>()
526 >> (chorus(20, 0.0, 0.008, 0.6) | chorus(21, 0.0, 0.011, 0.6))
527 >> reverb_stereo(22.0, 6.0, 0.85);
528
529 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
530 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
531 voiced
532 * stereo_gate_voiced(
533 p.gain.clone(),
534 p.mute.clone(),
535 p.pulse_depth.clone(),
536 g.bpm.clone(),
537 p.life_mod.clone(),
538 lb,
539 )
540}
541
542fn heartbeat(p: &TrackParams, g: &GlobalParams) -> Net {
547 let bpm = g.bpm.clone();
548
549 let bpm_body_f = bpm.clone();
551 let freq_body = p.freq.clone();
552 let pat_body_f = p.pattern_bits.clone();
553 let body_osc = lfo(move |t: f64| {
554 let bpm_v = bpm_body_f.value() as f64;
555 let bits = pat_body_f.load(Ordering::Relaxed);
556 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
557 let base = freq_body.value() as f64;
558 if active {
559 let drop = (-phi * 40.0).exp();
560 base * (0.7 + 1.5 * drop)
561 } else {
562 base
565 }
566 }) >> sine();
567
568 let bpm_body_e = bpm.clone();
569 let pat_body_e = p.pattern_bits.clone();
570 let body_env = lfo(move |t: f64| {
571 let bpm_v = bpm_body_e.value() as f64;
572 let bits = pat_body_e.load(Ordering::Relaxed);
573 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
574 if active {
575 (-phi * 4.0).exp()
576 } else {
577 0.0
578 }
579 });
580 let body = body_osc * body_env * 0.85;
581
582 let freq_sub = p.freq.clone();
586 let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
587 let bpm_sub_e = bpm.clone();
588 let pat_sub = p.pattern_bits.clone();
589 let sub_env = lfo(move |t: f64| {
590 let bpm_v = bpm_sub_e.value() as f64;
591 let bits = pat_sub.load(Ordering::Relaxed);
592 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
593 if active {
594 (-phi * 1.5).exp()
595 } else {
596 0.0
597 }
598 });
599 let sub = sub_osc * sub_env;
600
601 let bpm_click = bpm.clone();
604 let pat_click = p.pattern_bits.clone();
605 let char_click = p.character.clone();
606 let click_env = lfo(move |t: f64| {
607 let bpm_v = bpm_click.value() as f64;
608 let bits = pat_click.load(Ordering::Relaxed);
609 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
610 if active {
611 let amp = 0.02 + char_click.value().clamp(0.0, 1.0) as f64 * 0.20;
616 (-phi * 40.0).exp() * amp
617 } else {
618 0.0
619 }
620 });
621 let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env;
622
623 let char_sub = p.character.clone();
627 let sub_scale = lfo(move |_t: f64| {
628 0.35 + (1.0 - char_sub.value().clamp(0.0, 1.0) as f64) * 0.20
630 });
631 let sub_scaled = sub * sub_scale;
632
633 let kick = body + sub_scaled + click;
634
635 let stereo = kick
639 >> split::<U2>()
640 >> (pass() | delay(0.008))
641 >> reverb_stereo(10.0, 1.5, 0.88);
642
643 let lb = LfoBundle::from_params(p);
644 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
645 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
646 voiced
647 * stereo_gate_voiced(
648 p.gain.clone(),
649 p.mute.clone(),
650 p.pulse_depth.clone(),
651 g.bpm.clone(),
652 p.life_mod.clone(),
653 lb,
654 )
655}
656
657fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
661 let lb = LfoBundle::from_params(p);
662 let f1 = p.freq.clone();
663 let f2 = p.freq.clone();
664 let f3 = p.freq.clone();
665 let cut = p.cutoff.clone();
666 let res_s = p.resonance.clone();
667 let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
668
669 let fm = FreqMod::new(p, g);
670 let (fm1_, fm2_, fm3_) = (fm.clone(), fm.clone(), fm.clone());
671 let _ = (lb1, lb2, lb3);
672 let fundamental = lfo(move |t: f64| fm1_.apply(f1.value() as f64, t))
673 >> follow(0.08) >> (sine() * 0.55);
674 let second = lfo(move |t: f64| fm2_.apply(f2.value() as f64 * 2.0, t))
675 >> follow(0.08) >> (sine() * 0.22);
676 let sub = lfo(move |t: f64| fm3_.apply(f3.value() as f64 * 0.5, t))
677 >> follow(0.08) >> (sine() * 0.35);
678 let osc = fundamental + second + sub;
679
680 let cut_mod = lfo(move |t: f64| {
681 let b = cut.value().min(900.0) as f64;
682 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
683 }) >> follow(0.08);
684 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
685 let filtered = (osc | cut_mod | res_mod) >> moog();
686
687 let bpm_groove = g.bpm.clone();
688 let groove = lfo(move |t: f64| {
689 let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
690 0.45 + 0.55 * pump
691 });
692 let grooved = filtered * groove;
693
694 let stereo = grooved
697 >> split::<U2>()
698 >> (pass() | delay(0.014))
699 >> reverb_stereo(14.0, 2.5, 0.88);
700
701 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
702 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
703 voiced
704 * stereo_gate_voiced(
705 p.gain.clone(),
706 p.mute.clone(),
707 p.pulse_depth.clone(),
708 g.bpm.clone(),
709 p.life_mod.clone(),
710 lb,
711 )
712}
713
714fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
719 let lb = LfoBundle::from_params(p);
720 let fc = p.freq.clone();
721 let fm = p.freq.clone();
722 let fm_depth = p.resonance.clone();
723 let (lb_c, lb_m) = (lb.clone(), lb.clone());
724
725 let char_m = p.character.clone();
730 let fmm = FreqMod::new(p, g);
731 let fmm_m = fmm.clone();
732 let fmm_c = fmm.clone();
733 let _ = (lb_m, lb_c);
734 let modulator_freq = lfo(move |t: f64| {
735 let c = char_m.value() as f64;
736 let ratio = lerp3(1.41, 2.76, 4.18, c);
737 let b = fm.value() as f64 * ratio;
738 fmm_m.apply(b, t)
739 }) >> follow(0.08);
740 let modulator = modulator_freq >> sine();
741 let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
742 let modulator_scaled = modulator * mod_scale;
743
744 let carrier_base = lfo(move |t: f64| fmm_c.apply(fc.value() as f64, t))
745 >> follow(0.08);
746 let bell_sig = (carrier_base + modulator_scaled) >> sine();
747
748 let bpm_am = g.bpm.clone();
749 let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
750 let body = bell_sig * am * 0.30;
751
752 let stereo = body
754 >> split::<U2>()
755 >> (chorus(30, 0.0, 0.018, 0.25) | chorus(31, 0.0, 0.022, 0.25))
756 >> reverb_stereo(25.0, 8.0, 0.85);
757
758 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
759 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
760 voiced
761 * stereo_gate_voiced(
762 p.gain.clone(),
763 p.mute.clone(),
764 p.pulse_depth.clone(),
765 g.bpm.clone(),
766 p.life_mod.clone(),
767 lb,
768 )
769}
770
771fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
776 let lb = LfoBundle::from_params(p);
777 let cut = p.cutoff.clone();
778 let res_s = p.resonance.clone();
779
780 const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
781 let voice_amp: f32 = 0.55 / OFFS.len() as f32;
783
784 let fm = FreqMod::new(p, g);
786 let mut stack: Option<Net> = None;
787 for &off in OFFS.iter() {
788 let f_c = p.freq.clone();
789 let d_c = p.detune.clone();
790 let fm_c = fm.clone();
791 let voice = lfo(move |t: f64| {
792 let width = (d_c.value().abs() as f64).max(1.0);
793 let cents = off * width;
794 let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
795 fm_c.apply(base, t)
796 }) >> follow(0.08) >> (saw() * voice_amp);
797 let wrapped = Net::wrap(Box::new(voice));
798 stack = Some(match stack {
799 Some(acc) => acc + wrapped,
800 None => wrapped,
801 });
802 }
803 let saw_stack = stack.expect("N > 0");
804
805 let f_sub = p.freq.clone();
807 let fm_sub = fm.clone();
808 let _ = lb.clone();
809 let sub = lfo(move |t: f64| fm_sub.apply(f_sub.value() as f64 * 0.5, t))
810 >> follow(0.08) >> (sine() * 0.22);
811 let sub_net = Net::wrap(Box::new(sub));
812
813 let mixed = saw_stack + sub_net;
814
815 let lb_cut = lb.clone();
816 let cut_mod = lfo(move |t: f64| {
817 let b = cut.value() as f64;
818 lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
819 }) >> follow(0.05);
820 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
821
822 let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
823 >> Net::wrap(Box::new(moog()));
824
825 let stereo = filtered
826 >> Net::wrap(Box::new(split::<U2>()))
827 >> Net::wrap(Box::new(
828 chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
829 ))
830 >> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 0.88)));
831
832 let with_super = stereo >> supermass_send(p.supermass.clone());
833 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
834 voiced
835 * stereo_gate_voiced(
836 p.gain.clone(),
837 p.mute.clone(),
838 p.pulse_depth.clone(),
839 g.bpm.clone(),
840 p.life_mod.clone(),
841 lb,
842 )
843}
844
845fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
849 let lb = LfoBundle::from_params(p);
850
851 let fm = FreqMod::new(p, g);
852 let fm_a = fm.clone();
853 let fm_b = fm.clone();
854 let f_a = p.freq.clone();
855 let osc_a = lfo(move |t: f64| fm_a.apply(f_a.value() as f64, t))
856 >> follow(0.08) >> (saw() * 0.35);
857
858 let f_b = p.freq.clone();
859 let det = p.detune.clone();
860 let osc_b = lfo(move |t: f64| {
861 let cents = det.value() as f64 * 0.5;
862 let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
863 fm_b.apply(b, t)
864 }) >> follow(0.08) >> (saw() * 0.35);
865 let osc = osc_a + osc_b;
866
867 let bpm_f = g.bpm.clone();
870 let pat_f = p.pattern_bits.clone();
871 let cut_shared = p.cutoff.clone();
872 let lb_c = lb.clone();
873 let cut_env = lfo(move |t: f64| {
874 let bpm = bpm_f.value() as f64;
875 let bits = pat_f.load(Ordering::Relaxed);
876 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
877 let user_cut = cut_shared.value() as f64;
878 let base = if active {
879 180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
880 } else {
881 180.0
882 };
883 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
884 }) >> follow(0.01);
885
886 let res_s = p.resonance.clone();
887 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
888
889 let filtered =
890 (osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
891
892 let bpm_env = g.bpm.clone();
894 let pat_env = p.pattern_bits.clone();
895 let amp_env = lfo(move |t: f64| {
896 let bpm = bpm_env.value() as f64;
897 let bits = pat_env.load(Ordering::Relaxed);
898 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
899 if active {
900 (-phi * 4.5).exp()
901 } else {
902 0.0
903 }
904 });
905 let plucked = filtered * Net::wrap(Box::new(amp_env));
906
907 let stereo = plucked
908 >> Net::wrap(Box::new(split::<U2>()))
909 >> Net::wrap(Box::new(
910 chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
911 ))
912 >> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
913
914 let with_super = stereo >> supermass_send(p.supermass.clone());
915 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
916 voiced
917 * stereo_gate_voiced(
918 p.gain.clone(),
919 p.mute.clone(),
920 p.pulse_depth.clone(),
921 g.bpm.clone(),
922 p.life_mod.clone(),
923 lb,
924 )
925}