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