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();
574 let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
575 let bpm_sub_e = bpm.clone();
576 let pat_sub = p.pattern_bits.clone();
577 let sub_env = lfo(move |t: f64| {
578 let bpm_v = bpm_sub_e.value() as f64;
579 let bits = pat_sub.load(Ordering::Relaxed);
580 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
581 if active {
582 (-phi * 1.5).exp()
583 } else {
584 0.0
585 }
586 });
587 let sub = sub_osc * sub_env * 0.45;
588
589 let bpm_click = bpm.clone();
591 let pat_click = p.pattern_bits.clone();
592 let click_env = lfo(move |t: f64| {
593 let bpm_v = bpm_click.value() as f64;
594 let bits = pat_click.load(Ordering::Relaxed);
595 let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
596 if active {
597 (-phi * 40.0).exp()
598 } else {
599 0.0
600 }
601 });
602 let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env * 0.12;
603
604 let kick = body + sub + click;
605
606 let stereo = kick >> split::<U2>() >> reverb_stereo(10.0, 1.5, 0.88);
607
608 let lb = LfoBundle::from_params(p);
609 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
610 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
611 voiced
612 * stereo_gate_voiced(
613 p.gain.clone(),
614 p.mute.clone(),
615 p.pulse_depth.clone(),
616 g.bpm.clone(),
617 p.life_mod.clone(),
618 lb,
619 )
620}
621
622fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
626 let lb = LfoBundle::from_params(p);
627 let f1 = p.freq.clone();
628 let f2 = p.freq.clone();
629 let f3 = p.freq.clone();
630 let cut = p.cutoff.clone();
631 let res_s = p.resonance.clone();
632 let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
633
634 let fm = FreqMod::new(p, g);
635 let (fm1_, fm2_, fm3_) = (fm.clone(), fm.clone(), fm.clone());
636 let _ = (lb1, lb2, lb3);
637 let fundamental = lfo(move |t: f64| fm1_.apply(f1.value() as f64, t))
638 >> follow(0.08) >> (sine() * 0.55);
639 let second = lfo(move |t: f64| fm2_.apply(f2.value() as f64 * 2.0, t))
640 >> follow(0.08) >> (sine() * 0.22);
641 let sub = lfo(move |t: f64| fm3_.apply(f3.value() as f64 * 0.5, t))
642 >> follow(0.08) >> (sine() * 0.35);
643 let osc = fundamental + second + sub;
644
645 let cut_mod = lfo(move |t: f64| {
646 let b = cut.value().min(900.0) as f64;
647 lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
648 }) >> follow(0.08);
649 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
650 let filtered = (osc | cut_mod | res_mod) >> moog();
651
652 let bpm_groove = g.bpm.clone();
653 let groove = lfo(move |t: f64| {
654 let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
655 0.45 + 0.55 * pump
656 });
657 let grooved = filtered * groove;
658
659 let stereo = grooved >> split::<U2>() >> reverb_stereo(14.0, 2.5, 0.88);
660
661 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
662 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
663 voiced
664 * stereo_gate_voiced(
665 p.gain.clone(),
666 p.mute.clone(),
667 p.pulse_depth.clone(),
668 g.bpm.clone(),
669 p.life_mod.clone(),
670 lb,
671 )
672}
673
674fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
679 let lb = LfoBundle::from_params(p);
680 let fc = p.freq.clone();
681 let fm = p.freq.clone();
682 let fm_depth = p.resonance.clone();
683 let (lb_c, lb_m) = (lb.clone(), lb.clone());
684
685 let char_m = p.character.clone();
690 let fmm = FreqMod::new(p, g);
691 let fmm_m = fmm.clone();
692 let fmm_c = fmm.clone();
693 let _ = (lb_m, lb_c);
694 let modulator_freq = lfo(move |t: f64| {
695 let c = char_m.value() as f64;
696 let ratio = lerp3(1.41, 2.76, 4.18, c);
697 let b = fm.value() as f64 * ratio;
698 fmm_m.apply(b, t)
699 }) >> follow(0.08);
700 let modulator = modulator_freq >> sine();
701 let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
702 let modulator_scaled = modulator * mod_scale;
703
704 let carrier_base = lfo(move |t: f64| fmm_c.apply(fc.value() as f64, t))
705 >> follow(0.08);
706 let bell_sig = (carrier_base + modulator_scaled) >> sine();
707
708 let bpm_am = g.bpm.clone();
709 let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
710 let body = bell_sig * am * 0.30;
711
712 let stereo = body >> split::<U2>() >> reverb_stereo(25.0, 8.0, 0.85);
713
714 let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
715 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
716 voiced
717 * stereo_gate_voiced(
718 p.gain.clone(),
719 p.mute.clone(),
720 p.pulse_depth.clone(),
721 g.bpm.clone(),
722 p.life_mod.clone(),
723 lb,
724 )
725}
726
727fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
732 let lb = LfoBundle::from_params(p);
733 let cut = p.cutoff.clone();
734 let res_s = p.resonance.clone();
735
736 const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
737 let voice_amp: f32 = 0.55 / OFFS.len() as f32;
739
740 let fm = FreqMod::new(p, g);
742 let mut stack: Option<Net> = None;
743 for &off in OFFS.iter() {
744 let f_c = p.freq.clone();
745 let d_c = p.detune.clone();
746 let fm_c = fm.clone();
747 let voice = lfo(move |t: f64| {
748 let width = (d_c.value().abs() as f64).max(1.0);
749 let cents = off * width;
750 let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
751 fm_c.apply(base, t)
752 }) >> follow(0.08) >> (saw() * voice_amp);
753 let wrapped = Net::wrap(Box::new(voice));
754 stack = Some(match stack {
755 Some(acc) => acc + wrapped,
756 None => wrapped,
757 });
758 }
759 let saw_stack = stack.expect("N > 0");
760
761 let f_sub = p.freq.clone();
763 let fm_sub = fm.clone();
764 let _ = lb.clone();
765 let sub = lfo(move |t: f64| fm_sub.apply(f_sub.value() as f64 * 0.5, t))
766 >> follow(0.08) >> (sine() * 0.22);
767 let sub_net = Net::wrap(Box::new(sub));
768
769 let mixed = saw_stack + sub_net;
770
771 let lb_cut = lb.clone();
772 let cut_mod = lfo(move |t: f64| {
773 let b = cut.value() as f64;
774 lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
775 }) >> follow(0.05);
776 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
777
778 let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
779 >> Net::wrap(Box::new(moog()));
780
781 let stereo = filtered
782 >> Net::wrap(Box::new(split::<U2>()))
783 >> Net::wrap(Box::new(
784 chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
785 ))
786 >> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 0.88)));
787
788 let with_super = stereo >> supermass_send(p.supermass.clone());
789 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
790 voiced
791 * stereo_gate_voiced(
792 p.gain.clone(),
793 p.mute.clone(),
794 p.pulse_depth.clone(),
795 g.bpm.clone(),
796 p.life_mod.clone(),
797 lb,
798 )
799}
800
801fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
805 let lb = LfoBundle::from_params(p);
806
807 let fm = FreqMod::new(p, g);
808 let fm_a = fm.clone();
809 let fm_b = fm.clone();
810 let f_a = p.freq.clone();
811 let osc_a = lfo(move |t: f64| fm_a.apply(f_a.value() as f64, t))
812 >> follow(0.08) >> (saw() * 0.35);
813
814 let f_b = p.freq.clone();
815 let det = p.detune.clone();
816 let osc_b = lfo(move |t: f64| {
817 let cents = det.value() as f64 * 0.5;
818 let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
819 fm_b.apply(b, t)
820 }) >> follow(0.08) >> (saw() * 0.35);
821 let osc = osc_a + osc_b;
822
823 let bpm_f = g.bpm.clone();
826 let pat_f = p.pattern_bits.clone();
827 let cut_shared = p.cutoff.clone();
828 let lb_c = lb.clone();
829 let cut_env = lfo(move |t: f64| {
830 let bpm = bpm_f.value() as f64;
831 let bits = pat_f.load(Ordering::Relaxed);
832 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
833 let user_cut = cut_shared.value() as f64;
834 let base = if active {
835 180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
836 } else {
837 180.0
838 };
839 lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
840 }) >> follow(0.01);
841
842 let res_s = p.resonance.clone();
843 let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
844
845 let filtered =
846 (osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
847
848 let bpm_env = g.bpm.clone();
850 let pat_env = p.pattern_bits.clone();
851 let amp_env = lfo(move |t: f64| {
852 let bpm = bpm_env.value() as f64;
853 let bits = pat_env.load(Ordering::Relaxed);
854 let (active, phi) = rhythm::step_is_active(bits, t, bpm);
855 if active {
856 (-phi * 4.5).exp()
857 } else {
858 0.0
859 }
860 });
861 let plucked = filtered * Net::wrap(Box::new(amp_env));
862
863 let stereo = plucked
864 >> Net::wrap(Box::new(split::<U2>()))
865 >> Net::wrap(Box::new(
866 chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
867 ))
868 >> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
869
870 let with_super = stereo >> supermass_send(p.supermass.clone());
871 let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
872 voiced
873 * stereo_gate_voiced(
874 p.gain.clone(),
875 p.mute.clone(),
876 p.pulse_depth.clone(),
877 g.bpm.clone(),
878 p.life_mod.clone(),
879 lb,
880 )
881}