mod effects;
mod kit;
mod osc;
mod seq;
#[cfg(test)]
mod tests;
pub(crate) use effects::{FilterKind, biquad_coeffs, drive_antideriv, drive_curve};
pub(crate) use osc::{osc, poly_blep};
pub(crate) use seq::seq_to_signal;
use crate::dsl::{Adsr, Node, Playback, Shape, SoundDoc, Value};
use crate::dsp::{Rng, node_path, node_seed, peak_limit};
use effects::{biquad, chorus, compress, drive_adaa, flanger, modal_bank, phaser, reverb};
use osc::{
dust_signal, fm_signal, impact_signal, noise_signal, osc_signal, saw_signal, square_signal,
super_signal, tri_signal,
};
use std::f32::consts::TAU;
type Signal = Vec<f32>;
pub struct RenderProduct {
pub mono: Signal,
pub stereo: Option<(Signal, Signal)>,
pub layers: Vec<LayerStats>,
}
mod output;
mod tracks;
pub use output::{loop_seam_db, make_loop_buffer, stereoize};
use output::{normalize_output, normalize_output_v4};
pub use tracks::{LayerStats, TracksRender, render_tracks};
pub fn render_product(doc: &SoundDoc) -> RenderProduct {
if let Some(tr) = render_tracks(doc) {
let mono = tr
.left
.iter()
.zip(&tr.right)
.map(|(a, b)| 0.5 * (a + b))
.collect();
return RenderProduct {
mono,
stereo: Some((tr.left, tr.right)),
layers: tr.layers,
};
}
RenderProduct {
mono: render_plain(doc),
stereo: None,
layers: Vec::new(),
}
}
pub fn render(doc: &SoundDoc) -> Signal {
render_product(doc).mono
}
#[cfg(test)]
pub(crate) fn render_graph(doc: &SoundDoc) -> Signal {
let sr = doc.sample_rate;
let n = ((doc.duration.clamp(0.0, 600.0) * sr as f32).ceil() as usize).max(1);
let mut rng = Rng::new(doc.seed);
render_node(&doc.root, n, sr, &mut rng, doc.effective_engine(), doc.seed)
}
fn render_plain(doc: &SoundDoc) -> Signal {
let sr = doc.sample_rate;
let n = ((doc.duration.clamp(0.0, 600.0) * sr as f32).ceil() as usize).max(1);
let mut rng = Rng::new(doc.seed);
let engine = doc.effective_engine();
let mut out = render_node(&doc.root, n, sr, &mut rng, engine, doc.seed);
if let Playback::Loop {
start_secs,
end_secs,
crossfade_secs,
} = doc.playback
{
out = make_loop_buffer(&out, sr, start_secs, end_secs, crossfade_secs);
}
match &doc.normalize {
Some(nz) if engine >= 4 => normalize_output_v4(&mut [&mut out], nz, sr),
Some(nz) => normalize_output(&mut out, nz),
None => peak_limit(&mut [&mut out]),
}
out
}
fn eval_value(v: &Value, n: usize, sr: u32) -> Vec<f32> {
let mut val = crate::streaming::value::Val::build(v, sr, n);
(0..n).map(|t| val.eval(t)).collect()
}
pub(crate) fn rand_seed(seed: u64, from: f32, to: f32, rate: f32) -> u64 {
let mut h = seed ^ crate::dsp::GOLDEN_GAMMA;
for bits in [from.to_bits(), to.to_bits(), rate.to_bits()] {
h = (h ^ bits as u64).wrapping_mul(crate::dsp::FNV_PRIME);
}
h
}
fn render_node(node: &Node, n: usize, sr: u32, rng: &mut Rng, engine: u32, path: u64) -> Signal {
match node {
Node::Square { freq, duty } => square_signal(freq, duty, n, sr),
Node::Triangle { freq } => tri_signal(freq, n, sr),
Node::Sawtooth { freq } => saw_signal(freq, n, sr),
Node::Super {
wave,
freq,
voices,
detune_cents,
} => super_signal(*wave, freq, *voices, *detune_cents, n, sr),
Node::Sine { freq } => osc_signal(freq, n, sr, |p| osc(Shape::Sine, p)),
Node::Noise { color } => {
if engine >= 2 {
let mut local = Rng::new(node_seed(path));
noise_signal(*color, n, &mut local)
} else {
noise_signal(*color, n, rng)
}
}
Node::Fm { freq, ratio, index } => fm_signal(freq, *ratio, index, n, sr),
Node::Seq { .. } => {
if engine >= 2 {
let mut local = Rng::new(node_seed(path));
seq_to_signal(node, n, sr, &mut local, engine)
} else {
seq_to_signal(node, n, sr, rng, engine)
}
}
Node::Impact { hardness, velocity } => impact_signal(*hardness, *velocity, n, sr),
Node::Dust { density, decay } => {
if engine >= 2 {
let mut local = Rng::new(node_seed(path));
dust_signal(*density, *decay, n, sr, &mut local)
} else {
dust_signal(*density, *decay, n, sr, rng)
}
}
Node::Env { adsr: env } => adsr(env, n, sr),
Node::Tracks { tracks, .. } => {
let mut acc = vec![0.0f32; n];
for (i, t) in tracks.iter().enumerate() {
let sig = render_node(&t.node, n, sr, rng, engine, node_path(path, i));
for (o, v) in acc.iter_mut().zip(sig) {
*o += v * t.gain;
}
}
acc
}
Node::Mix { inputs } => {
let mut acc = vec![0.0f32; n];
for (i, input) in inputs.iter().enumerate() {
let s = render_node(input, n, sr, rng, engine, node_path(path, i));
for (o, v) in acc.iter_mut().zip(s) {
*o += v;
}
}
acc
}
Node::Mul { inputs } => {
let mut acc = vec![1.0f32; n];
for (i, input) in inputs.iter().enumerate() {
let s = render_node(input, n, sr, rng, engine, node_path(path, i));
for (o, v) in acc.iter_mut().zip(s) {
*o *= v;
}
}
acc
}
Node::Chain { stages } => {
let mut buf: Option<Signal> = None;
for (i, stage) in stages.iter().enumerate() {
let cp = node_path(path, i);
buf = Some(match (&buf, stage.is_processor()) {
(Some(input), true) => apply_processor(stage, input, sr, rng, engine, cp),
(_, _) => render_node(stage, n, sr, rng, engine, cp),
});
}
buf.unwrap_or_else(|| vec![0.0; n])
}
_ if node.is_processor() => vec![0.0; n],
_ => unreachable!("unhandled source node in render_node"),
}
}
fn apply_processor(
node: &Node,
input: &[f32],
sr: u32,
rng: &mut Rng,
engine: u32,
path: u64,
) -> Signal {
match node {
Node::Duck {
trigger,
amount,
attack,
release,
} => {
let trig = render_node(trigger, input.len(), sr, rng, engine, node_path(path, 0));
let srf = sr as f32;
let at = (-1.0 / (attack.max(1e-4) * srf)).exp();
let rt = (-1.0 / (release.max(1e-4) * srf)).exp();
let mut env = 0.0f32;
input
.iter()
.zip(trig)
.map(|(&x, t)| {
let rect = t.abs().min(1.0);
let coeff = if rect > env { at } else { rt };
env = rect + coeff * (env - rect);
x * (1.0 - amount * env)
})
.collect()
}
Node::Lowpass { cutoff, q } => biquad(input, cutoff, *q, sr, FilterKind::Low),
Node::Highpass { cutoff, q } => biquad(input, cutoff, *q, sr, FilterKind::High),
Node::Bandpass { cutoff, q } => biquad(input, cutoff, *q, sr, FilterKind::Band),
Node::Notch { cutoff, q } => biquad(input, cutoff, *q, sr, FilterKind::Notch),
Node::Peak { cutoff, q, gain_db } => {
biquad(input, cutoff, *q, sr, FilterKind::Peak(*gain_db))
}
Node::Lowshelf { cutoff, gain_db } => {
biquad(input, cutoff, 0.707, sr, FilterKind::LowShelf(*gain_db))
}
Node::Highshelf { cutoff, gain_db } => {
biquad(input, cutoff, 0.707, sr, FilterKind::HighShelf(*gain_db))
}
Node::Gain { amount } => {
let g = eval_value(amount, input.len(), sr);
input.iter().zip(g).map(|(x, k)| x * k).collect()
}
Node::Bitcrush { bits } => {
let levels = (1u32 << (*bits as u32).min(31)) as f32;
let half = levels / 2.0;
input
.iter()
.map(|x| (x.clamp(-1.0, 1.0) * half).round() / half)
.collect()
}
Node::Downsample { factor } => {
let f = (*factor).max(1) as usize;
let mut out = Vec::with_capacity(input.len());
let mut held = 0.0;
for (i, &x) in input.iter().enumerate() {
if i % f == 0 {
held = x;
}
out.push(held);
}
out
}
Node::Delay { secs, feedback } => {
let dn = crate::dsp::delay_line_len(*secs, sr);
let mut buf = vec![0.0f32; dn];
let mut w = 0usize;
let mut out = Vec::with_capacity(input.len());
for &x in input {
let delayed = buf[w];
let y = x + feedback * delayed;
buf[w] = y;
w = (w + 1) % dn;
out.push(y);
}
out
}
Node::Reverb { room, mix } => reverb(input, *room, *mix, sr, 0),
Node::Modal { modes, mix } => modal_bank(input, modes, *mix, sr),
Node::Drive { amount, shape, aa } => {
let a = eval_value(amount, input.len(), sr);
let use_adaa = engine >= 1 && aa.unwrap_or(true);
if use_adaa {
drive_adaa(input, &a, *shape)
} else {
input
.iter()
.zip(a)
.map(|(x, amt)| drive_curve(amt.max(0.0) * x, *shape))
.collect()
}
}
Node::RingMod { freq } => {
let f = eval_value(freq, input.len(), sr);
let srf = sr as f32;
let mut phase = 0.0f32;
let mut out = Vec::with_capacity(input.len());
for (i, &x) in input.iter().enumerate() {
out.push(x * (TAU * phase).sin());
phase += f[i].max(0.0) / srf;
phase -= phase.floor();
}
out
}
Node::Chorus { rate, depth, mix } => chorus(input, *rate, *depth, *mix, sr),
Node::Flanger {
rate,
depth,
feedback,
mix,
} => flanger(input, *rate, *depth, *feedback, *mix, sr),
Node::Phaser {
rate,
depth,
feedback,
mix,
} => phaser(input, *rate, *depth, *feedback, *mix, sr),
Node::Compress {
threshold,
ratio,
attack,
release,
makeup,
} => compress(input, *threshold, *ratio, *attack, *release, *makeup, sr),
_ => unreachable!("unhandled processor node in apply_processor"),
}
}
fn adsr(env: &Adsr, n: usize, sr: u32) -> Signal {
let Adsr { a, d, s, r, punch } = *env;
let srf = sr as f32;
let rel_start = (n as f32 / srf - r).max(0.0);
(0..n)
.map(|i| crate::dsp::adsr_env(i as f32 / srf, a, d, s, r, punch, rel_start))
.collect()
}