use super::proc::{Proc, reverb_proc, try_proc};
use super::source::{Src, try_src};
use crate::dsl::{AutoTarget, Node, SoundDoc};
use crate::dsp::layer_stream_key;
use crate::render::{LaneCursor, MASTER_STREAM, pan_gains, track_stream_seed};
struct TrackStream {
src: Option<Src>,
off: usize,
constant: (f32, f32),
gain: f32,
pan: f32,
gain_lane: Option<LaneCursor>,
pan_lane: Option<LaneCursor>,
dest: Option<usize>,
sends: Vec<(usize, f32)>,
}
struct DuckLink {
follower: usize,
source: usize,
source_gain: f32,
gain_lane: Option<LaneCursor>,
env: f32,
at: f32,
rt: f32,
amount: f32,
}
struct StereoChain {
fx: Vec<(Proc, Proc)>,
pos: usize,
engine: u32,
}
impl StereoChain {
fn build(nodes: &[Node], sr: u32, n: usize, engine: u32, path: u64) -> Option<Self> {
let fx = nodes
.iter()
.map(|node| {
if let Node::Reverb { room, mix } = node {
Some((
reverb_proc(*room, *mix, sr, 0),
reverb_proc(*room, *mix, sr, 23),
))
} else {
Some((
try_proc(node, sr, n, engine, path)?,
try_proc(node, sr, n, engine, path)?,
))
}
})
.collect::<Option<_>>()?;
Some(StereoChain { fx, pos: 0, engine })
}
fn step(&mut self, l: f32, r: f32) -> (f32, f32) {
let pos = self.pos;
self.pos += 1;
let engine = self.engine;
let (mut l, mut r) = (l, r);
for (pl, pr) in self.fx.iter_mut() {
l = pl.step(l, pos, 1.0, engine);
r = pr.step(r, pos, 1.0, engine);
}
(l, r)
}
fn set_cutoff(&mut self, scale: f32) {
for (pl, pr) in self.fx.iter_mut() {
pl.set_cutoff(scale);
pr.set_cutoff(scale);
}
}
}
struct BusStream {
chain: StereoChain,
gain: f32,
}
pub(crate) struct StreamTracks {
tracks: Vec<TrackStream>,
links: Vec<DuckLink>,
buses: Vec<BusStream>,
master: StereoChain,
xs: Vec<f32>,
ducks: Vec<f32>,
bus_in: Vec<(f32, f32)>,
sr: u32,
engine: u32,
pos: usize,
}
impl StreamTracks {
pub(crate) fn build(doc: &SoundDoc) -> Option<Self> {
let Node::Tracks {
tracks,
master,
buses,
} = &doc.root
else {
return None;
};
if doc.effective_version() < 2 {
return None;
}
let sr = doc.sample_rate;
let n = ((doc.duration.clamp(0.0, 600.0) * sr as f32).ceil() as usize).max(1);
let engine = doc.effective_engine();
let bus_index = |id: &str| buses.iter().position(|b| b.id == id);
let mut out_tracks = Vec::with_capacity(tracks.len());
let mut links = Vec::new();
for (ti, t) in tracks.iter().enumerate() {
let layer_id = t.id.clone().unwrap_or_else(|| format!("layer_{ti}"));
let base = track_stream_seed(doc.seed, layer_stream_key(&layer_id));
let off = ((t.at.max(0.0) * sr as f32).round() as usize).min(n);
let src = if t.mute {
None
} else {
Some(try_src(&t.node, sr, n, engine, base)?)
};
if let Some(sc) = &t.sidechain
&& let Some((si, source)) = tracks
.iter()
.enumerate()
.find(|(_, s)| s.id.as_deref() == Some(sc.source.as_str()))
{
let srf = sr as f32;
links.push(DuckLink {
follower: ti,
source: si,
source_gain: source.gain,
gain_lane: LaneCursor::build(&source.automation, AutoTarget::Gain, source.gain),
env: 0.0,
at: crate::dsp::exp(-1.0 / (sc.attack.max(1e-4) * srf), engine),
rt: crate::dsp::exp(-1.0 / (sc.release.max(1e-4) * srf), engine),
amount: sc.amount,
});
}
out_tracks.push(TrackStream {
src,
off,
constant: pan_gains(t.pan.clamp(-1.0, 1.0), t.gain, engine),
gain: t.gain,
pan: t.pan,
gain_lane: LaneCursor::build(&t.automation, AutoTarget::Gain, t.gain),
pan_lane: LaneCursor::build(&t.automation, AutoTarget::Pan, t.pan),
dest: t.bus.as_deref().and_then(bus_index),
sends: t
.sends
.iter()
.filter_map(|s| bus_index(&s.bus).map(|bi| (bi, s.amount.clamp(0.0, 1.0))))
.collect(),
});
}
let buses: Vec<BusStream> = buses
.iter()
.map(|b| {
let bpath = track_stream_seed(doc.seed, layer_stream_key(&format!("bus:{}", b.id)));
Some(BusStream {
chain: StereoChain::build(&b.effects, sr, n, engine, bpath)?,
gain: if b.gain.is_finite() { b.gain } else { 1.0 },
})
})
.collect::<Option<_>>()?;
let master = StereoChain::build(
master,
sr,
n,
engine,
track_stream_seed(doc.seed, MASTER_STREAM),
)?;
let n_tracks = out_tracks.len();
let n_buses = buses.len();
Some(StreamTracks {
tracks: out_tracks,
links,
buses,
master,
xs: vec![0.0; n_tracks],
ducks: vec![1.0; n_tracks],
bus_in: vec![(0.0, 0.0); n_buses],
sr,
engine,
pos: 0,
})
}
pub(crate) fn step(&mut self, pitch: f32) -> (f32, f32) {
let p = self.pos;
self.pos += 1;
let sr = self.sr;
let engine = self.engine;
for (i, t) in self.tracks.iter_mut().enumerate() {
self.xs[i] = match &mut t.src {
Some(src) if p >= t.off => src.step(p - t.off, pitch, engine),
_ => 0.0,
};
}
for d in self.ducks.iter_mut() {
*d = 1.0;
}
for link in self.links.iter_mut() {
let sig = if self.tracks[link.source].src.is_some() && p >= self.tracks[link.source].off
{
let g = link
.gain_lane
.as_mut()
.map_or(link.source_gain, |c| c.at(p, sr, engine));
self.xs[link.source] * g
} else {
0.0
};
let rect = sig.abs().min(1.0);
let coeff = if rect > link.env { link.at } else { link.rt };
link.env = rect + coeff * (link.env - rect);
self.ducks[link.follower] = 1.0 - link.amount * link.env;
}
let (mut ml, mut mr) = (0.0f32, 0.0f32);
for b in self.bus_in.iter_mut() {
*b = (0.0, 0.0);
}
for (i, t) in self.tracks.iter_mut().enumerate() {
if t.src.is_none() || p < t.off {
continue;
}
let x = self.xs[i];
let (gl, gr) = match (&mut t.gain_lane, &mut t.pan_lane) {
(None, None) => t.constant,
(g, pn) => {
let gain = g.as_mut().map_or(t.gain, |c| c.at(p, sr, engine));
let pan = pn
.as_mut()
.map_or(t.pan, |c| c.at(p, sr, engine))
.clamp(-1.0, 1.0);
pan_gains(pan, gain, engine)
}
};
let d = self.ducks[i];
let (la, ra) = (x * gl * d, x * gr * d);
match t.dest {
None => {
ml += la;
mr += ra;
}
Some(bi) => {
self.bus_in[bi].0 += la;
self.bus_in[bi].1 += ra;
}
}
for (bi, amount) in &t.sends {
self.bus_in[*bi].0 += la * amount;
self.bus_in[*bi].1 += ra * amount;
}
}
for (bi, b) in self.buses.iter_mut().enumerate() {
let (bl, br) = b.chain.step(self.bus_in[bi].0, self.bus_in[bi].1);
ml += bl * b.gain;
mr += br * b.gain;
}
self.master.step(ml, mr)
}
pub(crate) fn set_cutoff(&mut self, scale: f32) {
for t in self.tracks.iter_mut() {
if let Some(src) = &mut t.src {
src.set_cutoff(scale);
}
}
for b in self.buses.iter_mut() {
b.chain.set_cutoff(scale);
}
self.master.set_cutoff(scale);
}
}