mod proc;
mod source;
pub(crate) mod value;
#[cfg(test)]
mod tests;
use crate::dsl::{Node, Playback, SoundDoc, Stereo};
use crate::dsp::node_path;
use proc::{Proc, try_proc};
use source::{Src, try_src};
pub struct StreamGraph {
root: Src,
pos: usize,
pitch: f32,
pitch_target: f32,
glide: f32,
bend: f32,
}
impl StreamGraph {
pub fn try_from_doc(doc: &SoundDoc) -> Option<Self> {
if doc.normalize.is_some()
|| matches!(doc.playback, Playback::Loop { .. })
|| !matches!(doc.stereo, Stereo::Mono)
|| matches!(doc.root, Node::Tracks { .. })
{
return None;
}
let n = ((doc.duration.clamp(0.0, 600.0) * doc.sample_rate as f32).ceil() as usize).max(1);
Some(StreamGraph {
root: try_src(
&doc.root,
doc.sample_rate,
n,
doc.effective_engine(),
doc.seed,
)?,
pos: 0,
pitch: 1.0,
pitch_target: 1.0,
glide: 1.0,
bend: 1.0,
})
}
pub fn fill(&mut self, out: &mut [f32]) {
for s in out.iter_mut() {
self.pitch += (self.pitch_target - self.pitch) * self.glide;
*s = self.root.step(self.pos, self.pitch * self.bend);
self.pos += 1;
}
}
pub fn set_pitch(&mut self, scale: f32) {
self.pitch = scale.max(0.0);
self.pitch_target = self.pitch;
self.glide = 1.0;
}
pub fn glide_pitch(&mut self, scale: f32, coeff: f32) {
self.pitch_target = scale.max(0.0);
self.glide = if coeff.is_nan() {
1.0
} else {
coeff.clamp(f32::MIN_POSITIVE, 1.0)
};
}
pub fn pitch(&self) -> f32 {
self.pitch
}
pub fn set_bend(&mut self, mul: f32) {
self.bend = mul.max(0.0);
}
pub fn set_cutoff(&mut self, scale: f32) {
self.root.set_cutoff(scale.max(0.01));
}
}
pub struct EffectChain {
procs: Vec<Proc>,
pos: usize,
}
impl EffectChain {
pub fn try_new(nodes: &[Node], sr: u32, engine: u32) -> Option<Self> {
let n = sr as usize;
let procs = nodes
.iter()
.enumerate()
.map(|(i, node)| try_proc(node, sr, n, engine, node_path(0, i)))
.collect::<Option<_>>()?;
Some(EffectChain { procs, pos: 0 })
}
pub fn process(&mut self, block: &mut [f32]) {
for x in block.iter_mut() {
let mut v = *x;
for p in self.procs.iter_mut() {
v = p.step(v, self.pos, 1.0);
}
*x = v;
self.pos += 1;
}
}
}