use sva_formula::NodeId;
use sva_samples::machine::ops::Layout;
use sva_samples::{
Binary, BufId, Buffer, Ctx, Label, NodeRenderer, SampleError, Site, SiteId, Unary, stft,
};
use crate::cast::Cast;
use crate::error::{Diagnostic, EngineError, Located};
use crate::loops::Delay;
use crate::offset::Offset;
use crate::render::Render;
use crate::typing::Value;
pub fn run(
held: &mut Render,
id: NodeId,
cache: Option<&dyn crate::cache::Cache>,
) -> Result<(), EngineError> {
let key = crate::cache::buffer_key(
crate::refs::identity(&held.tys, id)?,
held.config.rate,
held.config.horizon.start_secs,
held.tys.ty(id).width as usize,
sva_samples::AliasScore::NotAsked,
);
if let Some((hit, label)) = super::warm(held, id, key, cache) {
held.buffers.insert(id, hit);
held.labels.insert(id, label);
return Ok(());
}
let began = crate::cache::Cost::begun();
let (buffer, label) = stepped(held, id)?;
super::store(key, &buffer, &label, began.elapsed(), cache);
held.buffers.insert(id, buffer);
held.labels.insert(id, label);
Ok(())
}
fn stepped(held: &Render, id: NodeId) -> Result<(Buffer, Label), EngineError> {
if let Value::Cast(Cast::Istft, source) = *held.tys.value(id) {
let frames = held
.frames
.get(&source)
.ok_or_else(|| missing(held, source))?;
return Ok(stft::inverse(frames, &held.config.profile));
}
let program = program(held, id)?;
let buffer = program.writes(held, id, &program.renderer)?;
Ok((
buffer,
Label::measured(held.config.profile.name, held.config.rate),
))
}
pub(super) struct Program {
pub renderer: NodeRenderer,
pub reads: Vec<NodeId>,
layout: Layout,
}
pub(super) fn program(held: &Render, id: NodeId) -> Result<Program, EngineError> {
let mut build = Build {
held,
reads: Vec::new(),
sites: Vec::new(),
};
let renderer = build.of(id)?;
let (reads, sites) = (build.reads, build.sites);
let layout = Layout {
width: held.tys.ty(id).width as usize,
read_widths: reads
.iter()
.map(|r| held.buffers.get(r).map_or(1, |b| b.width))
.collect(),
sites,
};
Ok(Program {
renderer,
reads,
layout,
})
}
impl Program {
pub(super) fn writes(
&self,
held: &Render,
id: NodeId,
renderer: &NodeRenderer,
) -> Result<Buffer, EngineError> {
let len = held
.config
.horizon
.len(held.config.rate)
.map_err(|e| collapse_refused(held, id, &e.to_string(), e.code()))?;
let buffers: Vec<&Buffer> = self
.reads
.iter()
.map(|r| held.buffers.get(r).expect("a read is materialized first"))
.collect();
let ctx = Ctx {
rate: held.config.rate,
origin_secs: held.config.horizon.start_secs,
len,
reads: &buffers,
self_planes: &[],
written: 0,
};
renderer
.run(&self.layout, &ctx)
.map_err(|e| refused(held, id, &e))
}
}
struct Build<'a> {
held: &'a Render,
reads: Vec<NodeId>,
sites: Vec<Site>,
}
impl Build<'_> {
fn of(&mut self, id: NodeId) -> Result<NodeRenderer, EngineError> {
match self.held.tys.value(id).clone() {
Value::ClosedForm(form) => match crate::lower::constant_value(&form.body, form.var) {
Some(v) => Ok(NodeRenderer::Const(v)),
None => Err(uncollapsed(self.held, id)),
},
Value::Cast(Cast::Sample, source) => Ok(self.buffer(source, 0)),
Value::Cast(..) => Err(uncollapsed(self.held, id)),
Value::Read { source, at, site } => {
let reader = self.held.tys.ty(id).held;
let steps = self.index_offset(source, at, site)?;
match crate::refs::resolve(&self.held.tys, source, steps, reader)? {
crate::refs::Read::BufferHit { source, shift } => {
Ok(self.buffer(source, shift))
}
crate::refs::Read::IndexOffset { source, steps } => {
Ok(self.buffer(source, steps))
}
_ => Err(uncollapsed(self.held, id)),
}
}
Value::Grid(count) => Ok(NodeRenderer::Const(
count / f64::from(self.held.config.rate),
)),
Value::SelfAt(delay) => match self.delay(delay) {
Some(steps) => Ok(NodeRenderer::SelfAt { steps }),
None => Err(varying(self.held, id)),
},
Value::Solver(params) => {
let site = self.site(Site::Physics(params));
Ok(NodeRenderer::Physics { site })
}
Value::Filter {
shape,
x,
cutoff,
q,
gain,
} => {
let site = self.site(Site::Filter(shape));
Ok(NodeRenderer::Filter {
site,
x: Box::new(self.of(x)?),
cutoff: Box::new(self.of(cutoff)?),
q: Box::new(self.of(q)?),
gain: Box::new(self.of(gain)?),
})
}
Value::Op { name, args } => self.operation(id, &name, &args),
}
}
fn index_offset(
&self,
source: NodeId,
at: Offset,
site: sva_formula::Origin,
) -> Result<i64, EngineError> {
let rate = self.held.config.rate;
at.steps_at(rate)
.map_err(|count| off_grid(self.held, source, site, count, rate))
}
fn buffer(&mut self, source: NodeId, shift: i64) -> NodeRenderer {
let id = BufId(self.reads.len() as u32);
self.reads.push(source);
NodeRenderer::Buffer { id, shift }
}
fn site(&mut self, site: Site) -> SiteId {
self.sites.push(site);
SiteId((self.sites.len() - 1) as u32)
}
fn delay(&self, delay: Delay) -> Option<u32> {
match delay {
Delay::Steps(steps) => Some(steps),
Delay::Secs(secs) => {
Some(((secs * f64::from(self.held.config.rate)).round() as u32).max(1))
}
Delay::Varying => None,
}
}
fn operation(
&mut self,
id: NodeId,
name: &str,
args: &[NodeId],
) -> Result<NodeRenderer, EngineError> {
let mut lowered = Vec::with_capacity(args.len());
for arg in args {
lowered.push(self.of(*arg)?);
}
let pair = |mut set: Vec<NodeRenderer>| {
let right = set.pop().expect("two operands");
let left = set.pop().expect("two operands");
(Box::new(left), Box::new(right))
};
let unary =
|op: Unary, mut set: Vec<NodeRenderer>| NodeRenderer::Map(op, Box::new(set.remove(0)));
Ok(match name {
"+" => NodeRenderer::Add(lowered),
"*" => NodeRenderer::Mul(lowered),
"-" => {
let (l, r) = pair(lowered);
NodeRenderer::Sub(l, r)
}
"/" => {
let (l, r) = pair(lowered);
NodeRenderer::Div(l, r)
}
"pow" => {
let (l, r) = pair(lowered);
NodeRenderer::Pow(l, r)
}
"%" => {
let (l, r) = pair(lowered);
NodeRenderer::Zip(Binary::Mod, l, r)
}
"max" => {
let (l, r) = pair(lowered);
NodeRenderer::Zip(Binary::Max, l, r)
}
"min" => {
let (l, r) = pair(lowered);
NodeRenderer::Zip(Binary::Min, l, r)
}
"crop" => {
let (a, b) = (constant(&lowered, 1), constant(&lowered, 2));
NodeRenderer::Crop {
x: Box::new(lowered.remove(0)),
a,
b,
}
}
"join" => NodeRenderer::Join(lowered),
"ch" => {
let k = constant(&lowered, 1).max(0.0) as usize;
NodeRenderer::Channel {
x: Box::new(lowered.remove(0)),
k,
}
}
_ => match sva_formula::Unary::from_name(name) {
Some(op) => unary(op.into(), lowered),
None => return Err(unplanned(self.held, id, name)),
},
})
}
}
pub fn refused(held: &Render, id: NodeId, e: &SampleError) -> EngineError {
collapse_refused(held, id, &e.to_string(), e.code())
}
fn collapse_refused(held: &Render, id: NodeId, message: &str, code: &str) -> EngineError {
EngineError::refused(Diagnostic {
code: code.to_string(),
message: message.to_string(),
location: Located::at(held.tys.name(id), None),
help: "write the node so the grid can hold it".to_string(),
})
}
fn uncollapsed(held: &Render, id: NodeId) -> EngineError {
collapse_refused(
held,
id,
"a closed form reaches the grid with no collapse written for it",
"type.samples_in_closed_form",
)
}
fn off_grid(
held: &Render,
source: NodeId,
site: sva_formula::Origin,
count: f64,
rate: u32,
) -> EngineError {
EngineError::refused(Diagnostic {
code: "ref.fractional_shift_on_samples".to_string(),
message: format!(
"`@{}` is samples, and this offset is not a whole one.",
held.tys.name(source)
),
location: held.tys.locate(site),
help: format!(
"{} samples at {rate} Hz; pick a rate or a delay that lands on the grid",
count.abs()
),
})
}
fn varying(held: &Render, id: NodeId) -> EngineError {
collapse_refused(
held,
id,
"a delay written as a closed form in t has no whole-sample offset this machine can read",
"engine.varying_delay",
)
}
fn missing(held: &Render, id: NodeId) -> EngineError {
collapse_refused(
held,
id,
"the frames this reads were never built",
"cast.istft_needs_frames",
)
}
fn unplanned(held: &Render, id: NodeId, name: &str) -> EngineError {
collapse_refused(
held,
id,
&format!("`{name}` has no sampled form"),
"engine.unshadered_operation",
)
}
fn constant(lowered: &[NodeRenderer], at: usize) -> f64 {
match lowered.get(at) {
Some(NodeRenderer::Const(v)) => *v,
other => unreachable!("a window edge reached the shader as {other:?}"),
}
}