use fundsp::hacker::*;
use std::sync::atomic::Ordering;
use super::track::TrackParams;
use crate::math::pulse::{arp_offset_semitones, pulse_decay, pulse_sine};
use crate::math::rhythm;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PresetKind {
PadZimmer,
DroneSub,
Shimmer,
Heartbeat,
BassPulse,
Bell,
SuperSaw,
PluckSaw,
}
pub const ALL_KINDS: [PresetKind; 8] = [
PresetKind::PadZimmer,
PresetKind::BassPulse,
PresetKind::Heartbeat,
PresetKind::DroneSub,
PresetKind::Shimmer,
PresetKind::Bell,
PresetKind::SuperSaw,
PresetKind::PluckSaw,
];
impl PresetKind {
pub fn label(self) -> &'static str {
match self {
PresetKind::PadZimmer => "Pad",
PresetKind::DroneSub => "Drone",
PresetKind::Shimmer => "Shimmer",
PresetKind::Heartbeat => "Heartbeat",
PresetKind::BassPulse => "Bass",
PresetKind::Bell => "Bell",
PresetKind::SuperSaw => "SuperSaw",
PresetKind::PluckSaw => "Pluck",
}
}
pub fn next(self) -> Self {
let i = ALL_KINDS.iter().position(|&k| k == self).unwrap_or(0);
ALL_KINDS[(i + 1) % ALL_KINDS.len()]
}
pub fn prev(self) -> Self {
let i = ALL_KINDS.iter().position(|&k| k == self).unwrap_or(0);
ALL_KINDS[(i + ALL_KINDS.len() - 1) % ALL_KINDS.len()]
}
}
#[derive(Clone)]
pub struct GlobalParams {
pub bpm: Shared,
pub master_gain: Shared,
pub brightness: Shared,
pub scale_mode: Shared,
}
impl Default for GlobalParams {
fn default() -> Self {
Self {
bpm: shared(72.0),
master_gain: shared(0.7),
brightness: shared(0.6),
scale_mode: shared(0.0),
}
}
}
pub const MASTER_SHELF_HZ: f64 = 3500.0;
pub const MIN_SHELF_GAIN: f64 = 0.2;
#[inline]
pub fn brightness_to_shelf_gain(b: f64) -> f64 {
MIN_SHELF_GAIN + (1.0 - MIN_SHELF_GAIN) * b.clamp(0.0, 1.0)
}
#[inline]
pub fn shelf_gain_db(g: f64) -> f64 {
20.0 * g.max(1e-6).log10()
}
#[inline]
pub fn brightness_to_lp_cutoff(b: f64) -> f64 {
3000.0 * 6.0_f64.powf(b.clamp(0.0, 1.0))
}
pub fn master_bus(brightness: Shared) -> Net {
let b_shelf_l = brightness.clone();
let b_shelf_r = brightness.clone();
let b_lp_l = brightness.clone();
let b_lp_r = brightness;
let sh_f_l = lfo(|_t: f64| MASTER_SHELF_HZ);
let sh_f_r = lfo(|_t: f64| MASTER_SHELF_HZ);
let sh_q_l = lfo(|_t: f64| 0.7_f64);
let sh_q_r = lfo(|_t: f64| 0.7_f64);
let sh_g_l = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_l.value() as f64));
let sh_g_r = lfo(move |_t: f64| brightness_to_shelf_gain(b_shelf_r.value() as f64));
let shelf_l = (pass() | sh_f_l | sh_q_l | sh_g_l) >> highshelf();
let shelf_r = (pass() | sh_f_r | sh_q_r | sh_g_r) >> highshelf();
let lp_c_l = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_l.value() as f64));
let lp_c_r = lfo(move |_t: f64| brightness_to_lp_cutoff(b_lp_r.value() as f64));
let lp_q_l = lfo(|_t: f64| 0.5_f64);
let lp_q_r = lfo(|_t: f64| 0.5_f64);
let left = shelf_l >> (pass() | lp_c_l | lp_q_l) >> lowpass();
let right = shelf_r >> (pass() | lp_c_r | lp_q_r) >> lowpass();
let stereo = left | right;
let chain = stereo >> limiter_stereo(0.001, 0.3);
Net::wrap(Box::new(chain))
}
pub struct Preset;
impl Preset {
pub fn build(kind: PresetKind, p: &TrackParams, g: &GlobalParams) -> Net {
match kind {
PresetKind::PadZimmer => pad_zimmer(p, g),
PresetKind::DroneSub => drone_sub(p, g),
PresetKind::Shimmer => shimmer(p, g),
PresetKind::Heartbeat => heartbeat(p, g),
PresetKind::BassPulse => bass_pulse(p, g),
PresetKind::Bell => bell_preset(p, g),
PresetKind::SuperSaw => super_saw(p, g),
PresetKind::PluckSaw => pluck_saw(p, g),
}
}
}
pub const LFO_OFF: u32 = 0;
pub const LFO_CUTOFF: u32 = 1;
pub const LFO_GAIN: u32 = 2;
pub const LFO_FREQ: u32 = 3;
pub const LFO_REVERB: u32 = 4;
pub const LFO_TARGETS: u32 = 5;
pub fn lfo_target_name(idx: u32) -> &'static str {
match idx {
LFO_OFF => "OFF",
LFO_CUTOFF => "CUT",
LFO_GAIN => "GAIN",
LFO_FREQ => "FREQ",
LFO_REVERB => "REV",
_ => "?",
}
}
#[derive(Clone)]
pub struct LfoBundle {
pub rate: Shared,
pub depth: Shared,
pub target: Shared,
}
impl LfoBundle {
pub fn from_params(p: &TrackParams) -> Self {
Self {
rate: p.lfo_rate.clone(),
depth: p.lfo_depth.clone(),
target: p.lfo_target.clone(),
}
}
#[inline]
pub fn apply(
&self,
base: f64,
this_target: u32,
t: f64,
scaler: impl Fn(f64, f64) -> f64,
) -> f64 {
let tgt = self.target.value().round() as u32;
if tgt != this_target {
return base;
}
let depth = self.depth.value() as f64;
if depth < 1.0e-4 {
return base;
}
let rate = self.rate.value() as f64;
let lv = (std::f64::consts::TAU * rate * t).sin();
scaler(base, lv * depth)
}
}
#[allow(dead_code)]
fn stereo_from_shared(s: Shared) -> Net {
Net::wrap(Box::new(lfo(move |_t: f64| s.value() as f64) >> split::<U2>()))
}
#[inline]
pub fn lerp3(a: f64, b: f64, d: f64, c: f64) -> f64 {
let c = c.clamp(0.0, 1.0);
if c < 0.5 {
a + (b - a) * (c * 2.0)
} else {
b + (d - b) * ((c - 0.5) * 2.0)
}
}
#[derive(Clone)]
pub struct FreqMod {
pub arp: Shared,
pub bpm: Shared,
pub scale_mode: Shared,
pub lb: LfoBundle,
}
impl FreqMod {
pub fn new(p: &TrackParams, g: &GlobalParams) -> Self {
Self {
arp: p.arp.clone(),
bpm: g.bpm.clone(),
scale_mode: g.scale_mode.clone(),
lb: LfoBundle::from_params(p),
}
}
#[inline]
pub fn apply(&self, base: f64, t: f64) -> f64 {
let seed = (base.max(1.0).ln() * 1_000.0) as u64;
let scale = self.scale_mode.value().round() as u32;
let off = arp_offset_semitones(
t,
self.bpm.value() as f64,
self.arp.value() as f64,
seed,
scale,
);
let arped = base * 2.0_f64.powf(off / 12.0);
self.lb
.apply(arped, LFO_FREQ, t, |b, m| b * 2.0_f64.powf(m / 12.0))
}
}
fn stereo_reverb_mix(base: Shared, lb: LfoBundle) -> Net {
let mono = lfo(move |t: f64| {
let v = base.value() as f64;
lb.apply(v, LFO_REVERB, t, |b, m| (b + m * 0.4).clamp(0.0, 1.0))
});
Net::wrap(Box::new(mono >> split::<U2>()))
}
fn supermass_send(amount: Shared) -> Net {
let a1 = amount.clone();
let a2 = amount;
let amount_l = lfo(move |_t: f64| a1.value() as f64);
let amount_r = lfo(move |_t: f64| a2.value() as f64);
let amount_stereo = Net::wrap(Box::new(amount_l | amount_r));
let effect = reverb_stereo(35.0, 15.0, 0.88)
>> (chorus(3, 0.0, 0.022, 0.28) | chorus(4, 0.0, 0.026, 0.28))
>> reverb_stereo(50.0, 28.0, 0.90);
let wet_scaled = Net::wrap(Box::new(effect)) * amount_stereo;
let dry = Net::wrap(Box::new(multipass::<U2>()));
dry & wet_scaled
}
fn stereo_gate_voiced(
gain: Shared,
mute: Shared,
pulse_depth: Shared,
bpm: Shared,
life_mod: Shared,
lb: LfoBundle,
) -> Net {
let raw = lfo(move |t: f64| {
let g_raw = (gain.value() * (1.0 - mute.value())) as f64;
let g = lb.apply(g_raw, LFO_GAIN, t, |b, m| (b * (1.0 + m * 0.6)).max(0.0));
let depth = pulse_depth.value().clamp(0.0, 1.0) as f64;
let pulse = pulse_sine(t, bpm.value() as f64);
let life = life_mod.value().clamp(0.0, 1.0) as f64;
let life_scaled = 0.4 + 0.9 * life;
g * (1.0 - depth + depth * pulse) * life_scaled
});
Net::wrap(Box::new(raw >> follow(0.4) >> split::<U2>()))
}
fn pad_zimmer(p: &TrackParams, g: &GlobalParams) -> Net {
let cut = p.cutoff.clone();
let res_s = p.resonance.clone();
let det = p.detune.clone();
let lb = LfoBundle::from_params(p);
let f0 = p.freq.clone();
let f1 = p.freq.clone();
let f2 = p.freq.clone();
let f3 = p.freq.clone();
let d1 = det.clone();
let d2 = det.clone();
let (lb0, lb1, lb2, lb3, lb_c) = (
lb.clone(),
lb.clone(),
lb.clone(),
lb.clone(),
lb.clone(),
);
let char0 = p.character.clone();
let char1 = p.character.clone();
let char2 = p.character.clone();
let fm = FreqMod::new(p, g);
let fm0 = fm.clone();
let fm1 = fm.clone();
let fm2 = fm.clone();
let fm3 = fm.clone();
let _ = (lb0, lb1, lb2, lb3); let osc = ((lfo(move |t: f64| fm0.apply(f0.value() as f64, t)) >> follow(0.08)
>> (sine() * 0.30))
+ (lfo(move |t: f64| {
let c = char0.value() as f64;
let r = 1.0 + lerp3(1.0, 0.501, 0.618, c);
let b = f1.value() as f64 * r * (1.0 + d1.value() as f64 * 0.000578);
fm1.apply(b, t)
}) >> follow(0.08) >> (sine() * 0.20))
+ (lfo(move |t: f64| {
let c = char1.value() as f64;
let r = 2.0 + lerp3(0.0, 0.013, 0.414, c);
let b = f2.value() as f64 * r * (1.0 + d2.value() as f64 * 0.000578);
fm2.apply(b, t)
}) >> follow(0.08) >> (sine() * 0.14))
+ (lfo(move |t: f64| {
let c = char2.value() as f64;
let r = 3.0 + lerp3(0.0, 0.007, 0.739, c);
let b = f3.value() as f64 * r;
fm3.apply(b, t)
}) >> follow(0.08) >> (sine() * 0.08)))
* 0.9;
let cutoff_mod = lfo(move |t: f64| {
let wobble = 1.0 + 0.10 * (0.5 - 0.5 * (t * 0.08).sin());
let base = cut.value() as f64 * wobble;
lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
}) >> follow(0.08);
let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
let filtered = (osc | cutoff_mod | res_mod) >> moog()
>> highshelf_hz(3000.0, 0.7, 0.67);
let stereo = filtered
>> split::<U2>()
>> (chorus(0, 0.0, 0.015, 0.35) | chorus(1, 0.0, 0.020, 0.35))
>> reverb_stereo(18.0, 4.0, 0.9);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn drone_sub(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let cut = p.cutoff.clone();
let res_s = p.resonance.clone();
let f0 = p.freq.clone();
let f1 = p.freq.clone();
let (lb0, lb1, lb_c) = (lb.clone(), lb.clone(), lb.clone());
let fm = FreqMod::new(p, g);
let fm0 = fm.clone();
let fm1 = fm.clone();
let _ = (lb0, lb1);
let sub = (lfo(move |t: f64| fm0.apply(f0.value() as f64 * 0.5, t))
>> follow(0.08) >> (sine() * 0.45))
+ (lfo(move |t: f64| fm1.apply(f1.value() as f64, t))
>> follow(0.08) >> (sine() * 0.12));
let noise_cut = lfo(move |t: f64| {
let b = cut.value().clamp(40.0, 300.0) as f64;
lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
}) >> follow(0.08);
let noise_q = lfo(move |_t: f64| res_s.value() as f64) >> follow(0.08);
let noise = (brown() | noise_cut | noise_q) >> moog();
let noise_body = noise * 0.28;
let bpm_am = g.bpm.clone();
let am = lfo(move |t: f64| 0.88 + 0.12 * pulse_sine(t, bpm_am.value() as f64));
let body = (sub + noise_body) * am;
let stereo = body >> split::<U2>() >> reverb_stereo(20.0, 5.0, 0.85);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn shimmer(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let f0 = p.freq.clone();
let f1 = p.freq.clone();
let f2 = p.freq.clone();
let (lb0, lb1, lb2) = (lb.clone(), lb.clone(), lb.clone());
let char_s1 = p.character.clone();
let char_s2 = p.character.clone();
let char_s3 = p.character.clone();
let fm = FreqMod::new(p, g);
let fm0 = fm.clone();
let fm1 = fm.clone();
let fm2 = fm.clone();
let _ = (lb0, lb1, lb2);
let osc = (lfo(move |t: f64| {
let c = char_s1.value() as f64;
let r = lerp3(2.0, 2.0, 2.1, c);
fm0.apply(f0.value() as f64 * r, t)
}) >> follow(0.08) >> (sine() * 0.18))
+ (lfo(move |t: f64| {
let c = char_s2.value() as f64;
let r = lerp3(3.0, 3.0, 3.3, c);
fm1.apply(f1.value() as f64 * r, t)
}) >> follow(0.08) >> (sine() * 0.12))
+ (lfo(move |t: f64| {
let c = char_s3.value() as f64;
let r = lerp3(4.0, 4.007, 4.8, c);
fm2.apply(f2.value() as f64 * r, t)
}) >> follow(0.08) >> (sine() * 0.08));
let bright = osc >> highpass_hz(400.0, 0.5);
let stereo = bright >> split::<U2>() >> reverb_stereo(22.0, 6.0, 0.85);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn heartbeat(p: &TrackParams, g: &GlobalParams) -> Net {
let bpm = g.bpm.clone();
let bpm_body_f = bpm.clone();
let freq_body = p.freq.clone();
let pat_body_f = p.pattern_bits.clone();
let body_osc = lfo(move |t: f64| {
let bpm_v = bpm_body_f.value() as f64;
let bits = pat_body_f.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
let base = freq_body.value() as f64;
if active {
let drop = (-phi * 40.0).exp();
base * (0.7 + 1.5 * drop)
} else {
base
}
}) >> sine();
let bpm_body_e = bpm.clone();
let pat_body_e = p.pattern_bits.clone();
let body_env = lfo(move |t: f64| {
let bpm_v = bpm_body_e.value() as f64;
let bits = pat_body_e.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
if active {
(-phi * 4.0).exp()
} else {
0.0
}
});
let body = body_osc * body_env * 0.85;
let freq_sub = p.freq.clone();
let sub_osc = lfo(move |_t: f64| freq_sub.value() as f64 * 0.5) >> sine();
let bpm_sub_e = bpm.clone();
let pat_sub = p.pattern_bits.clone();
let sub_env = lfo(move |t: f64| {
let bpm_v = bpm_sub_e.value() as f64;
let bits = pat_sub.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
if active {
(-phi * 1.5).exp()
} else {
0.0
}
});
let sub = sub_osc * sub_env * 0.45;
let bpm_click = bpm.clone();
let pat_click = p.pattern_bits.clone();
let click_env = lfo(move |t: f64| {
let bpm_v = bpm_click.value() as f64;
let bits = pat_click.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm_v);
if active {
(-phi * 40.0).exp()
} else {
0.0
}
});
let click = (brown() >> highpass_hz(1800.0, 0.5)) * click_env * 0.12;
let kick = body + sub + click;
let stereo = kick >> split::<U2>() >> reverb_stereo(10.0, 1.5, 0.88);
let lb = LfoBundle::from_params(p);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn bass_pulse(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let f1 = p.freq.clone();
let f2 = p.freq.clone();
let f3 = p.freq.clone();
let cut = p.cutoff.clone();
let res_s = p.resonance.clone();
let (lb1, lb2, lb3, lb_c) = (lb.clone(), lb.clone(), lb.clone(), lb.clone());
let fm = FreqMod::new(p, g);
let (fm1_, fm2_, fm3_) = (fm.clone(), fm.clone(), fm.clone());
let _ = (lb1, lb2, lb3);
let fundamental = lfo(move |t: f64| fm1_.apply(f1.value() as f64, t))
>> follow(0.08) >> (sine() * 0.55);
let second = lfo(move |t: f64| fm2_.apply(f2.value() as f64 * 2.0, t))
>> follow(0.08) >> (sine() * 0.22);
let sub = lfo(move |t: f64| fm3_.apply(f3.value() as f64 * 0.5, t))
>> follow(0.08) >> (sine() * 0.35);
let osc = fundamental + second + sub;
let cut_mod = lfo(move |t: f64| {
let b = cut.value().min(900.0) as f64;
lb_c.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
}) >> follow(0.08);
let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
let filtered = (osc | cut_mod | res_mod) >> moog();
let bpm_groove = g.bpm.clone();
let groove = lfo(move |t: f64| {
let pump = pulse_decay(t, bpm_groove.value() as f64, 3.5);
0.45 + 0.55 * pump
});
let grooved = filtered * groove;
let stereo = grooved >> split::<U2>() >> reverb_stereo(14.0, 2.5, 0.88);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn bell_preset(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let fc = p.freq.clone();
let fm = p.freq.clone();
let fm_depth = p.resonance.clone();
let (lb_c, lb_m) = (lb.clone(), lb.clone());
let char_m = p.character.clone();
let fmm = FreqMod::new(p, g);
let fmm_m = fmm.clone();
let fmm_c = fmm.clone();
let _ = (lb_m, lb_c);
let modulator_freq = lfo(move |t: f64| {
let c = char_m.value() as f64;
let ratio = lerp3(1.41, 2.76, 4.18, c);
let b = fm.value() as f64 * ratio;
fmm_m.apply(b, t)
}) >> follow(0.08);
let modulator = modulator_freq >> sine();
let mod_scale = lfo(move |_t: f64| fm_depth.value().min(0.65) as f64 * 450.0);
let modulator_scaled = modulator * mod_scale;
let carrier_base = lfo(move |t: f64| fmm_c.apply(fc.value() as f64, t))
>> follow(0.08);
let bell_sig = (carrier_base + modulator_scaled) >> sine();
let bpm_am = g.bpm.clone();
let am = lfo(move |t: f64| 0.85 + 0.15 * pulse_sine(t, bpm_am.value() as f64 * 0.25));
let body = bell_sig * am * 0.30;
let stereo = body >> split::<U2>() >> reverb_stereo(25.0, 8.0, 0.85);
let with_super = Net::wrap(Box::new(stereo)) >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn super_saw(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let cut = p.cutoff.clone();
let res_s = p.resonance.clone();
const OFFS: [f64; 7] = [-1.0, -0.66, -0.33, 0.0, 0.33, 0.66, 1.0];
let voice_amp: f32 = 0.55 / OFFS.len() as f32;
let fm = FreqMod::new(p, g);
let mut stack: Option<Net> = None;
for &off in OFFS.iter() {
let f_c = p.freq.clone();
let d_c = p.detune.clone();
let fm_c = fm.clone();
let voice = lfo(move |t: f64| {
let width = (d_c.value().abs() as f64).max(1.0);
let cents = off * width;
let base = f_c.value() as f64 * 2.0_f64.powf(cents / 1200.0);
fm_c.apply(base, t)
}) >> follow(0.08) >> (saw() * voice_amp);
let wrapped = Net::wrap(Box::new(voice));
stack = Some(match stack {
Some(acc) => acc + wrapped,
None => wrapped,
});
}
let saw_stack = stack.expect("N > 0");
let f_sub = p.freq.clone();
let fm_sub = fm.clone();
let _ = lb.clone();
let sub = lfo(move |t: f64| fm_sub.apply(f_sub.value() as f64 * 0.5, t))
>> follow(0.08) >> (sine() * 0.22);
let sub_net = Net::wrap(Box::new(sub));
let mixed = saw_stack + sub_net;
let lb_cut = lb.clone();
let cut_mod = lfo(move |t: f64| {
let b = cut.value() as f64;
lb_cut.apply(b, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
}) >> follow(0.05);
let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.08);
let filtered = (mixed | Net::wrap(Box::new(cut_mod)) | Net::wrap(Box::new(res_mod)))
>> Net::wrap(Box::new(moog()));
let stereo = filtered
>> Net::wrap(Box::new(split::<U2>()))
>> Net::wrap(Box::new(
chorus(0, 0.0, 0.012, 0.4) | chorus(1, 0.0, 0.014, 0.4),
))
>> Net::wrap(Box::new(reverb_stereo(16.0, 3.0, 0.88)));
let with_super = stereo >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}
fn pluck_saw(p: &TrackParams, g: &GlobalParams) -> Net {
let lb = LfoBundle::from_params(p);
let fm = FreqMod::new(p, g);
let fm_a = fm.clone();
let fm_b = fm.clone();
let f_a = p.freq.clone();
let osc_a = lfo(move |t: f64| fm_a.apply(f_a.value() as f64, t))
>> follow(0.08) >> (saw() * 0.35);
let f_b = p.freq.clone();
let det = p.detune.clone();
let osc_b = lfo(move |t: f64| {
let cents = det.value() as f64 * 0.5;
let b = f_b.value() as f64 * 2.0_f64.powf(cents / 1200.0);
fm_b.apply(b, t)
}) >> follow(0.08) >> (saw() * 0.35);
let osc = osc_a + osc_b;
let bpm_f = g.bpm.clone();
let pat_f = p.pattern_bits.clone();
let cut_shared = p.cutoff.clone();
let lb_c = lb.clone();
let cut_env = lfo(move |t: f64| {
let bpm = bpm_f.value() as f64;
let bits = pat_f.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm);
let user_cut = cut_shared.value() as f64;
let base = if active {
180.0 + (user_cut - 180.0) * (-phi * 5.0).exp()
} else {
180.0
};
lb_c.apply(base, LFO_CUTOFF, t, |b, m| b * 2.0_f64.powf(m))
}) >> follow(0.01);
let res_s = p.resonance.clone();
let res_mod = lfo(move |_t: f64| res_s.value().min(0.65) as f64) >> follow(0.05);
let filtered =
(osc | Net::wrap(Box::new(cut_env)) | Net::wrap(Box::new(res_mod))) >> Net::wrap(Box::new(moog()));
let bpm_env = g.bpm.clone();
let pat_env = p.pattern_bits.clone();
let amp_env = lfo(move |t: f64| {
let bpm = bpm_env.value() as f64;
let bits = pat_env.load(Ordering::Relaxed);
let (active, phi) = rhythm::step_is_active(bits, t, bpm);
if active {
(-phi * 4.5).exp()
} else {
0.0
}
});
let plucked = filtered * Net::wrap(Box::new(amp_env));
let stereo = plucked
>> Net::wrap(Box::new(split::<U2>()))
>> Net::wrap(Box::new(
chorus(0, 0.0, 0.010, 0.5) | chorus(1, 0.0, 0.013, 0.5),
))
>> Net::wrap(Box::new(reverb_stereo(18.0, 3.5, 0.88)));
let with_super = stereo >> supermass_send(p.supermass.clone());
let voiced = with_super * stereo_reverb_mix(p.reverb_mix.clone(), lb.clone());
voiced
* stereo_gate_voiced(
p.gain.clone(),
p.mute.clone(),
p.pulse_depth.clone(),
g.bpm.clone(),
p.life_mod.clone(),
lb,
)
}