const TAU_SECONDS: f32 = 0.1;
const OCCLUDED_VOLUME_DB: f32 = -9.0;
const OPEN_CUTOFF_HZ: f64 = 20_000.0;
const OCCLUDED_CUTOFF_HZ: f64 = 1_000.0;
pub(crate) struct OcclusionSmoother {
current: f32,
}
impl OcclusionSmoother {
pub(crate) fn new() -> Self {
Self { current: 0.0 }
}
pub(crate) fn step(&mut self, blocked: bool, dt: f32) -> f32 {
let target = if blocked { 1.0 } else { 0.0 };
let alpha = 1.0 - (-dt / TAU_SECONDS).exp();
self.current += (target - self.current) * alpha;
self.current
}
#[cfg(test)]
pub(crate) fn current(&self) -> f32 {
self.current
}
}
pub(crate) fn volume_db(occlusion: f32) -> f32 {
OCCLUDED_VOLUME_DB * occlusion.clamp(0.0, 1.0)
}
pub(crate) fn cutoff_hz(occlusion: f32) -> f64 {
let t = occlusion.clamp(0.0, 1.0) as f64;
(OPEN_CUTOFF_HZ.ln() + (OCCLUDED_CUTOFF_HZ.ln() - OPEN_CUTOFF_HZ.ln()) * t).exp()
}
#[cfg(test)]
mod tests {
use super::*;
const DT: f32 = 1.0 / 60.0;
#[test]
fn smoother_converges_without_overshoot() {
let mut s = OcclusionSmoother::new();
let mut prev = 0.0;
for _ in 0..60 {
let v = s.step(true, DT);
assert!(v >= prev && v <= 1.0, "monotonic rise, no overshoot");
prev = v;
}
assert!(prev > 0.99, "settled near 1.0 after 1s: {prev}");
for _ in 0..60 {
prev = s.step(false, DT);
}
assert!(prev < 0.01, "settled near 0.0 after clearing: {prev}");
}
#[test]
fn smoothing_takes_several_ticks() {
let mut s = OcclusionSmoother::new();
let v = s.step(true, DT);
assert!(v > 0.0 && v < 0.3, "one tick moves partway: {v}");
}
#[test]
fn volume_maps_clear_to_unity_and_blocked_to_dip() {
assert_eq!(volume_db(0.0), 0.0);
assert_eq!(volume_db(1.0), OCCLUDED_VOLUME_DB);
assert!(volume_db(0.5) < 0.0 && volume_db(0.5) > OCCLUDED_VOLUME_DB);
assert_eq!(volume_db(-1.0), 0.0);
assert_eq!(volume_db(2.0), OCCLUDED_VOLUME_DB);
}
#[test]
fn cutoff_sweeps_between_endpoints() {
assert!((cutoff_hz(0.0) - OPEN_CUTOFF_HZ).abs() < 1.0);
assert!((cutoff_hz(1.0) - OCCLUDED_CUTOFF_HZ).abs() < 1.0);
let mid = cutoff_hz(0.5);
assert!(mid < OPEN_CUTOFF_HZ && mid > OCCLUDED_CUTOFF_HZ);
assert!((mid - 4472.0).abs() < 10.0, "log-space midpoint: {mid}");
}
}