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}
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
238#[inline]
242pub fn lerp3(a: f64, b: f64, d: f64, c: f64) -> f64 {
243 let c = c.clamp(0.0, 1.0);
244 if c < 0.5 {
245 a + (b - a) * (c * 2.0)
246 } else {
247 b + (d - b) * ((c - 0.5) * 2.0)
248 }
249}
250
251#[derive(Clone)]
255pub struct FreqMod {
256 pub arp: Shared,
257 pub bpm: Shared,
258 pub lb: LfoBundle,
259}
260
261impl FreqMod {
262 pub fn new(p: &TrackParams, g: &GlobalParams) -> Self {
263 Self {
264 arp: p.arp.clone(),
265 bpm: g.bpm.clone(),
266 lb: LfoBundle::from_params(p),
267 }
268 }
269
270 #[inline]
274 pub fn apply(&self, base: f64, t: f64) -> f64 {
275 let seed = (base.max(1.0).ln() * 1_000.0) as u64;
276 let off = arp_offset_semitones(t, self.bpm.value() as f64, self.arp.value() as f64, seed);
277 let arped = base * 2.0_f64.powf(off / 12.0);
278 self.lb
279 .apply(arped, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
280 }
281}
282
283fn stereo_reverb_mix(base: Shared, lb: LfoBundle) -> Net {
286 let mono = lfo(move |t: f64| {
287 let v = base.value() as f64;
288 lb.apply(v, LFO_REVERB, t, |b, m| (b + m * 0.4).clamp(0.0, 1.0))
289 });
290 Net::wrap(Box::new(mono >> split::<U2>()))
291}
292
293fn supermass_send(amount: Shared) -> Net {
294 let a1 = amount.clone();
295 let a2 = amount;
296 let amount_l = lfo(move |_t: f64| a1.value() as f64);
297 let amount_r = lfo(move |_t: f64| a2.value() as f64);
298 let amount_stereo = Net::wrap(Box::new(amount_l | amount_r));
299
300 let effect = reverb_stereo(35.0, 15.0, 0.88)
303 >> (chorus(3, 0.0, 0.022, 0.28) | chorus(4, 0.0, 0.026, 0.28))
304 >> reverb_stereo(50.0, 28.0, 0.90);
305
306 let wet_scaled = Net::wrap(Box::new(effect)) * amount_stereo;
307 let dry = Net::wrap(Box::new(multipass::<U2>()));
308 dry & wet_scaled
309}
310
311fn stereo_gate_voiced(
312 gain: Shared,
313 mute: Shared,
314 pulse_depth: Shared,
315 bpm: Shared,
316 life_mod: Shared,
317 lb: LfoBundle,
318) -> Net {
319 let raw = lfo(move |t: f64| {
320 let g_raw = (gain.value() * (1.0 - mute.value())) as f64;
321 let g = lb.apply(g_raw, LFO_GAIN, t, |b, m| (b * (1.0 + m * 0.6)).max(0.0));
323 let depth = pulse_depth.value().clamp(0.0, 1.0) as f64;
324 let pulse = pulse_sine(t, bpm.value() as f64);
325 let life = life_mod.value().clamp(0.0, 1.0) as f64;
326 let life_scaled = 0.4 + 0.9 * life;
327 g * (1.0 - depth + depth * pulse) * life_scaled
328 });
329 Net::wrap(Box::new(raw >> follow(0.4) >> split::<U2>()))
330}
331
332fn pad_zimmer(p: &TrackParams, g: &GlobalParams) -> Net {
334 let cut = p.cutoff.clone();
335 let res_s = p.resonance.clone();
336 let det = p.detune.clone();
337
338 let lb = LfoBundle::from_params(p);
339 let f0 = p.freq.clone();
340 let f1 = p.freq.clone();
341 let f2 = p.freq.clone();
342 let f3 = p.freq.clone();
343 let d1 = det.clone();
344 let d2 = det.clone();
345 let (lb0, lb1, lb2, lb3, lb_c) = (
346 lb.clone(),
347 lb.clone(),
348 lb.clone(),
349 lb.clone(),
350 lb.clone(),
351 );
352
353 let char0 = p.character.clone();
358 let char1 = p.character.clone();
359 let char2 = p.character.clone();
360 let fm = FreqMod::new(p, g);
361 let fm0 = fm.clone();
362 let fm1 = fm.clone();
363 let fm2 = fm.clone();
364 let fm3 = fm.clone();
365 let _ = (lb0, lb1, lb2, lb3); let osc = ((lfo(move |t: f64| fm0.apply(f0.value() as f64, t)) >> follow(0.08)
367 >> (sine() * 0.30))
368 + (lfo(move |t: f64| {
369 let c = char0.value() as f64;
370 let r = 1.0 + lerp3(1.0, 0.501, 0.618, c);
371 let b = f1.value() as f64 * r * (1.0 + d1.value() as f64 * 0.000578);
372 fm1.apply(b, t)
373 }) >> follow(0.08) >> (sine() * 0.20))
374 + (lfo(move |t: f64| {
375 let c = char1.value() as f64;
376 let r = 2.0 + lerp3(0.0, 0.013, 0.414, c);
377 let b = f2.value() as f64 * r * (1.0 + d2.value() as f64 * 0.000578);
378 fm2.apply(b, t)
379 }) >> follow(0.08) >> (sine() * 0.14))
380 + (lfo(move |t: f64| {
381 let c = char2.value() as f64;
382 let r = 3.0 + lerp3(0.0, 0.007, 0.739, c);
383 let b = f3.value() as f64 * r;
384 fm3.apply(b, t)
385 }) >> follow(0.08) >> (sine() * 0.08)))
386 * 0.9;
387
388 let cutoff_mod = lfo(move |t: f64| {
389 let wobble = 1.0 + 0.10 * (0.5 - 0.5 * (t * 0.08).sin());
390 let base = cut.value() as f64 * wobble;
391 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
392 }) >> follow(0.08);
393 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
397
398 let filtered = (osc | cutoff_mod | res_mod) >> moog()
402 >> highshelf_hz(3000.0, 0.7, 0.67);
403
404 let stereo = filtered
405 >> split::<U2>()
406 >> (chorus(0, 0.0, 0.015, 0.35) | chorus(1, 0.0, 0.020, 0.35))
407 >> reverb_stereo(18.0, 4.0, 0.9);
408
409 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
410 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
411 voiced
412 * stereo_gate_voiced(
413 p.gain.clone(),
414 p.mute.clone(),
415 p.pulse_depth.clone(),
416 g.bpm.clone(),
417 p.life_mod.clone(),
418 lb,
419 )
420}
421
422fn drone_sub(p: &TrackParams, g: &GlobalParams) -> Net {
424 let lb = LfoBundle::from_params(p);
425 let cut = p.cutoff.clone();
426 let res_s = p.resonance.clone();
427
428 let f0 = p.freq.clone();
429 let f1 = p.freq.clone();
430 let (lb0, lb1, lb_c) = (lb.clone(), lb.clone(), lb.clone());
431
432 let fm = FreqMod::new(p, g);
433 let fm0 = fm.clone();
434 let fm1 = fm.clone();
435 let _ = (lb0, lb1);
436 let sub = (lfo(move |t: f64| fm0.apply(f0.value() as f64 * 0.5, t))
437 >> follow(0.08) >> (sine() * 0.45))
438 + (lfo(move |t: f64| fm1.apply(f1.value() as f64, t))
439 >> follow(0.08) >> (sine() * 0.12));
440
441 let noise_cut = lfo(move |t: f64| {
442 let b = cut.value().clamp(40.0, 300.0) as f64;
443 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
444 }) >> follow(0.08);
445 let noise_q = lfo(move |_t: f64| res_s.value() as f64) >> follow(0.08);
446 let noise = (brown() | noise_cut | noise_q) >> moog();
447 let noise_body = noise * 0.28;
448
449 let bpm_am = g.bpm.clone();
450 let am = lfo(move |t: f64| 0.88 + 0.12 * pulse_sine(t, bpm_am.value() as f64));
451 let body = (sub + noise_body) * am;
452
453 let stereo = body >> split::<U2>() >> reverb_stereo(20.0, 5.0, 0.85);
454
455 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
456 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
457 voiced
458 * stereo_gate_voiced(
459 p.gain.clone(),
460 p.mute.clone(),
461 p.pulse_depth.clone(),
462 g.bpm.clone(),
463 p.life_mod.clone(),
464 lb,
465 )
466}
467
468fn shimmer(p: &TrackParams, g: &GlobalParams) -> Net {
470 let lb = LfoBundle::from_params(p);
471 let f0 = p.freq.clone();
472 let f1 = p.freq.clone();
473 let f2 = p.freq.clone();
474 let (lb0, lb1, lb2) = (lb.clone(), lb.clone(), lb.clone());
475
476 let char_s1 = p.character.clone();
481 let char_s2 = p.character.clone();
482 let char_s3 = p.character.clone();
483 let fm = FreqMod::new(p, g);
484 let fm0 = fm.clone();
485 let fm1 = fm.clone();
486 let fm2 = fm.clone();
487 let _ = (lb0, lb1, lb2);
488 let osc = (lfo(move |t: f64| {
489 let c = char_s1.value() as f64;
490 let r = lerp3(2.0, 2.0, 2.1, c);
491 fm0.apply(f0.value() as f64 * r, t)
492 }) >> follow(0.08) >> (sine() * 0.18))
493 + (lfo(move |t: f64| {
494 let c = char_s2.value() as f64;
495 let r = lerp3(3.0, 3.0, 3.3, c);
496 fm1.apply(f1.value() as f64 * r, t)
497 }) >> follow(0.08) >> (sine() * 0.12))
498 + (lfo(move |t: f64| {
499 let c = char_s3.value() as f64;
500 let r = lerp3(4.0, 4.007, 4.8, c);
501 fm2.apply(f2.value() as f64 * r, t)
502 }) >> follow(0.08) >> (sine() * 0.08));
503
504 let bright = osc >> highpass_hz(400.0, 0.5);
505 let stereo = bright >> split::<U2>() >> reverb_stereo(22.0, 6.0, 0.85);
506
507 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
508 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
509 voiced
510 * stereo_gate_voiced(
511 p.gain.clone(),
512 p.mute.clone(),
513 p.pulse_depth.clone(),
514 g.bpm.clone(),
515 p.life_mod.clone(),
516 lb,
517 )
518}
519
520fn heartbeat(p: &TrackParams, g: &GlobalParams) -> Net {
525 let bpm = g.bpm.clone();
526
527 let bpm_body_f = bpm.clone();
529 let freq_body = p.freq.clone();
530 let pat_body_f = p.pattern_bits.clone();
531 let body_osc = lfo(move |t: f64| {
532 let bpm_v = bpm_body_f.value() as f64;
533 let bits = pat_body_f.load(Ordering::Relaxed);
534 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
535 let base = freq_body.value() as f64;
536 if active {
537 let drop = (-phi * 40.0).exp();
538 base * (0.7 + 1.5 * drop)
539 } else {
540 base
543 }
544 }) >> sine();
545
546 let bpm_body_e = bpm.clone();
547 let pat_body_e = p.pattern_bits.clone();
548 let body_env = lfo(move |t: f64| {
549 let bpm_v = bpm_body_e.value() as f64;
550 let bits = pat_body_e.load(Ordering::Relaxed);
551 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
552 if active {
553 (-phi * 4.0).exp()
554 } else {
555 0.0
556 }
557 });
558 let body = body_osc * body_env * 0.85;
559
560 let freq_sub = p.freq.clone();
562 let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
563 let bpm_sub_e = bpm.clone();
564 let pat_sub = p.pattern_bits.clone();
565 let sub_env = lfo(move |t: f64| {
566 let bpm_v = bpm_sub_e.value() as f64;
567 let bits = pat_sub.load(Ordering::Relaxed);
568 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
569 if active {
570 (-phi * 1.5).exp()
571 } else {
572 0.0
573 }
574 });
575 let sub = sub_osc * sub_env * 0.45;
576
577 let bpm_click = bpm.clone();
579 let pat_click = p.pattern_bits.clone();
580 let click_env = lfo(move |t: f64| {
581 let bpm_v = bpm_click.value() as f64;
582 let bits = pat_click.load(Ordering::Relaxed);
583 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
584 if active {
585 (-phi * 40.0).exp()
586 } else {
587 0.0
588 }
589 });
590 let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env * 0.12;
591
592 let kick = body + sub + click;
593
594 let stereo = kick >> split::<U2>() >> reverb_stereo(10.0, 1.5, 0.88);
595
596 let lb = LfoBundle::from_params(p);
597 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
598 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
599 voiced
600 * stereo_gate_voiced(
601 p.gain.clone(),
602 p.mute.clone(),
603 p.pulse_depth.clone(),
604 g.bpm.clone(),
605 p.life_mod.clone(),
606 lb,
607 )
608}
609
610fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
614 let lb = LfoBundle::from_params(p);
615 let f1 = p.freq.clone();
616 let f2 = p.freq.clone();
617 let f3 = p.freq.clone();
618 let cut = p.cutoff.clone();
619 let res_s = p.resonance.clone();
620 let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
621
622 let fm = FreqMod::new(p, g);
623 let (fm1_, fm2_, fm3_) = (fm.clone(), fm.clone(), fm.clone());
624 let _ = (lb1, lb2, lb3);
625 let fundamental = lfo(move |t: f64| fm1_.apply(f1.value() as f64, t))
626 >> follow(0.08) >> (sine() * 0.55);
627 let second = lfo(move |t: f64| fm2_.apply(f2.value() as f64 * 2.0, t))
628 >> follow(0.08) >> (sine() * 0.22);
629 let sub = lfo(move |t: f64| fm3_.apply(f3.value() as f64 * 0.5, t))
630 >> follow(0.08) >> (sine() * 0.35);
631 let osc = fundamental + second + sub;
632
633 let cut_mod = lfo(move |t: f64| {
634 let b = cut.value().min(900.0) as f64;
635 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
636 }) >> follow(0.08);
637 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
638 let filtered = (osc | cut_mod | res_mod) >> moog();
639
640 let bpm_groove = g.bpm.clone();
641 let groove = lfo(move |t: f64| {
642 let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
643 0.45 + 0.55 * pump
644 });
645 let grooved = filtered * groove;
646
647 let stereo = grooved >> split::<U2>() >> reverb_stereo(14.0, 2.5, 0.88);
648
649 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
650 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
651 voiced
652 * stereo_gate_voiced(
653 p.gain.clone(),
654 p.mute.clone(),
655 p.pulse_depth.clone(),
656 g.bpm.clone(),
657 p.life_mod.clone(),
658 lb,
659 )
660}
661
662fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
667 let lb = LfoBundle::from_params(p);
668 let fc = p.freq.clone();
669 let fm = p.freq.clone();
670 let fm_depth = p.resonance.clone();
671 let (lb_c, lb_m) = (lb.clone(), lb.clone());
672
673 let char_m = p.character.clone();
678 let fmm = FreqMod::new(p, g);
679 let fmm_m = fmm.clone();
680 let fmm_c = fmm.clone();
681 let _ = (lb_m, lb_c);
682 let modulator_freq = lfo(move |t: f64| {
683 let c = char_m.value() as f64;
684 let ratio = lerp3(1.41, 2.76, 4.18, c);
685 let b = fm.value() as f64 * ratio;
686 fmm_m.apply(b, t)
687 }) >> follow(0.08);
688 let modulator = modulator_freq >> sine();
689 let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
690 let modulator_scaled = modulator * mod_scale;
691
692 let carrier_base = lfo(move |t: f64| fmm_c.apply(fc.value() as f64, t))
693 >> follow(0.08);
694 let bell_sig = (carrier_base + modulator_scaled) >> sine();
695
696 let bpm_am = g.bpm.clone();
697 let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
698 let body = bell_sig * am * 0.30;
699
700 let stereo = body >> split::<U2>() >> reverb_stereo(25.0, 8.0, 0.85);
701
702 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
703 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
704 voiced
705 * stereo_gate_voiced(
706 p.gain.clone(),
707 p.mute.clone(),
708 p.pulse_depth.clone(),
709 g.bpm.clone(),
710 p.life_mod.clone(),
711 lb,
712 )
713}
714
715fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
720 let lb = LfoBundle::from_params(p);
721 let cut = p.cutoff.clone();
722 let res_s = p.resonance.clone();
723
724 const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
725 let voice_amp: f32 = 0.55 / OFFS.len() as f32;
727
728 let fm = FreqMod::new(p, g);
730 let mut stack: Option<Net> = None;
731 for &off in OFFS.iter() {
732 let f_c = p.freq.clone();
733 let d_c = p.detune.clone();
734 let fm_c = fm.clone();
735 let voice = lfo(move |t: f64| {
736 let width = (d_c.value().abs() as f64).max(1.0);
737 let cents = off * width;
738 let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
739 fm_c.apply(base, t)
740 }) >> follow(0.08) >> (saw() * voice_amp);
741 let wrapped = Net::wrap(Box::new(voice));
742 stack = Some(match stack {
743 Some(acc) => acc + wrapped,
744 None => wrapped,
745 });
746 }
747 let saw_stack = stack.expect("N > 0");
748
749 let f_sub = p.freq.clone();
751 let fm_sub = fm.clone();
752 let _ = lb.clone();
753 let sub = lfo(move |t: f64| fm_sub.apply(f_sub.value() as f64 * 0.5, t))
754 >> follow(0.08) >> (sine() * 0.22);
755 let sub_net = Net::wrap(Box::new(sub));
756
757 let mixed = saw_stack + sub_net;
758
759 let lb_cut = lb.clone();
760 let cut_mod = lfo(move |t: f64| {
761 let b = cut.value() as f64;
762 lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
763 }) >> follow(0.05);
764 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
765
766 let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
767 >> Net::wrap(Box::new(moog()));
768
769 let stereo = filtered
770 >> Net::wrap(Box::new(split::<U2>()))
771 >> Net::wrap(Box::new(
772 chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
773 ))
774 >> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 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}
788
789fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
793 let lb = LfoBundle::from_params(p);
794
795 let fm = FreqMod::new(p, g);
796 let fm_a = fm.clone();
797 let fm_b = fm.clone();
798 let f_a = p.freq.clone();
799 let osc_a = lfo(move |t: f64| fm_a.apply(f_a.value() as f64, t))
800 >> follow(0.08) >> (saw() * 0.35);
801
802 let f_b = p.freq.clone();
803 let det = p.detune.clone();
804 let osc_b = lfo(move |t: f64| {
805 let cents = det.value() as f64 * 0.5;
806 let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
807 fm_b.apply(b, t)
808 }) >> follow(0.08) >> (saw() * 0.35);
809 let osc = osc_a + osc_b;
810
811 let bpm_f = g.bpm.clone();
814 let pat_f = p.pattern_bits.clone();
815 let cut_shared = p.cutoff.clone();
816 let lb_c = lb.clone();
817 let cut_env = lfo(move |t: f64| {
818 let bpm = bpm_f.value() as f64;
819 let bits = pat_f.load(Ordering::Relaxed);
820 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
821 let user_cut = cut_shared.value() as f64;
822 let base = if active {
823 180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
824 } else {
825 180.0
826 };
827 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
828 }) >> follow(0.01);
829
830 let res_s = p.resonance.clone();
831 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
832
833 let filtered =
834 (osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
835
836 let bpm_env = g.bpm.clone();
838 let pat_env = p.pattern_bits.clone();
839 let amp_env = lfo(move |t: f64| {
840 let bpm = bpm_env.value() as f64;
841 let bits = pat_env.load(Ordering::Relaxed);
842 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
843 if active {
844 (-phi * 4.5).exp()
845 } else {
846 0.0
847 }
848 });
849 let plucked = filtered * Net::wrap(Box::new(amp_env));
850
851 let stereo = plucked
852 >> Net::wrap(Box::new(split::<U2>()))
853 >> Net::wrap(Box::new(
854 chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
855 ))
856 >> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
857
858 let with_super = stereo >> supermass_send(p.supermass.clone());
859 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
860 voiced
861 * stereo_gate_voiced(
862 p.gain.clone(),
863 p.mute.clone(),
864 p.pulse_depth.clone(),
865 g.bpm.clone(),
866 p.life_mod.clone(),
867 lb,
868 )
869}