use std::collections::{BTreeMap, BTreeSet};
use std::hash::{DefaultHasher, Hash as _, Hasher};
use sva_formula::{ContentHasher, Hash, HashDomain, NodeId, Var};
use crate::cast::Cast;
use crate::lower::number_of;
use crate::time::Q;
use crate::typing::{Typing, Value, When};
use super::filter;
use super::range::{OP, Range};
use super::sampled;
pub(in crate::render::end) fn gain(tys: &Typing, reader: NodeId, read: NodeId) -> Option<f64> {
let path = path(tys, read);
if !path.contains(&reader) {
return Some(0.0);
}
let on = |n: &NodeId| path.contains(n);
let below = |id: NodeId| below(tys, &path, id);
let mut gains = BTreeMap::from([(read, Some(1.0))]);
let mut looped = BTreeMap::new();
for id in tys.unfolded_over(reader, below, |id| id == read) {
let found = match loops(tys, id, &mut looped) {
true => None,
false => moved(tys, id, &|n| match on(&n) {
true => gains.get(&n).copied().flatten(),
false => Some(0.0),
}),
};
gains.insert(id, found);
}
gains.get(&reader).copied().flatten()
}
pub(in crate::render::end) fn key(tys: &Typing, reader: NodeId, read: NodeId) -> Option<Hash> {
let path = path(tys, read);
let gridded = |shape: Hash, id: NodeId| {
let mut grid = DefaultHasher::new();
tys.grid(id).hash(&mut grid);
let mut hasher = ContentHasher::new(HashDomain::GainBoundKey);
hasher.hash(shape);
hasher.word(grid.finish());
hasher.finish()
};
if !path.contains(&reader) {
return Some(gridded(Hash(0, 0), reader));
}
let mut keys = BTreeMap::from([(read, gridded(Hash(0, 0), read))]);
for id in tys.unfolded_over(reader, |id| below(tys, &path, id), |id| id == read) {
let mut named = |n: NodeId| match keys.get(&n) {
Some(held) => Ok(*held),
None => Ok(gridded(crate::refs::identity(tys, n)?, n)),
};
let shape = crate::refs::shape(tys, id, &mut named).ok()?;
keys.insert(id, gridded(shape, id));
}
keys.get(&reader).copied()
}
fn path(tys: &Typing, read: NodeId) -> BTreeSet<NodeId> {
let mut path = BTreeSet::from([read]);
let mut open = vec![read];
while let Some(at) = open.pop() {
for up in tys.readers_of(at) {
if path.insert(up) {
open.push(up);
}
}
}
path
}
fn below(tys: &Typing, path: &BTreeSet<NodeId>, id: NodeId) -> Vec<NodeId> {
let mut out = tys.operands(id);
if let Value::Read { at, .. } = tys.value(id) {
out.extend(at.moving());
}
out.retain(|n| path.contains(n));
out
}
fn loops(tys: &Typing, id: NodeId, held: &mut BTreeMap<NodeId, bool>) -> bool {
for n in tys.unfolded_over(id, |n| program(tys, n), |n| held.contains_key(&n)) {
let found = match tys.value(n) {
Value::SelfAt { .. } => true,
_ => program(tys, n).iter().any(|o| held.get(o) == Some(&true)),
};
held.insert(n, found);
}
held[&id]
}
fn program(tys: &Typing, id: NodeId) -> Vec<NodeId> {
match tys.value(id) {
Value::Cast(Cast::Sample, _) | Value::Read { .. } => Vec::new(),
Value::ClosedForm(_) | Value::Noise(_) | Value::SelfAt { .. } => Vec::new(),
_ => tys.operands(id),
}
}
fn moved(tys: &Typing, id: NodeId, gain: &dyn Fn(NodeId) -> Option<f64>) -> Option<f64> {
match tys.value(id) {
Value::Cast(Cast::Sample, source) => gain(*source),
Value::Read { source, at, .. } => {
let same = tys.grid(*source) == tys.grid(id);
match at {
When::At(map) if map.scale == Q::ONE && same => gain(*source),
When::Index(_) if same => gain(*source),
_ => None,
}
}
Value::ClosedForm(form) if form.var == Var::T => Range::of(&form.body).ok()?.moved(gain),
Value::Op { name, args } => Range::of(&sampled(tys, name, args)?).ok()?.moved(gain),
Value::Filter {
shape,
x,
cutoff,
q,
gain: boost,
} => {
let grid = tys.grid(id);
let params = [*cutoff, *q, *boost];
if tys.grid(*x) != grid || params.iter().any(|p| gain(*p) != Some(0.0)) {
return None;
}
let [Some(f), Some(q), Some(db)] = params.map(|p| number_of(tys, p)) else {
return None;
};
let (coeffs, _) = sva_samples::filters::coefficients(*shape, f, q, db, grid.sr());
Some(gain(*x)? * filter::moved(&coeffs)? * (1.0 + OP))
}
_ => None,
}
}