///|
pub struct Response {
priv scene : Scene
priv samples : Array[Array[Double]]
priv contributing : Array[Int32]
priv late : Array[Int32]
priv zero : Array[Int32]
}
///|
/// Finite windowed sinc, normalized before placement. Integer delay is exact.
fn delay_taps(fraction : Double, half : Int32) -> Array[Double] {
if fraction == 0.0 {
return Array::makei(2 * half + 1, fn(i) {
if i == half {
1.0
} else {
0.0
}
})
}
let taps = Array::makei(2 * half + 1, fn(i) {
let t = (i - half).to_double() - fraction
if t.abs() > half.to_double() {
0.0
} else if t.abs() < 1.0e-14 {
1.0
} else {
@math.sin(PI * t) /
(PI * t) *
0.5 *
(1.0 + @math.cos(PI * t / half.to_double()))
}
})
let sum = taps.fold(init=0.0, fn(a, b) { a + b })
taps.map(fn(v) { v / sum })
}
///|
/// Every retained path gets its complete kernel in a globally delayed output.
pub fn render(s : Scene) -> Response {
let channels = Array::makei(s.receivers.length(), fn(_) {
Array::make(s.output_frames(), 0.0)
})
let contributing = Array::make(s.receivers.length(), 0)
let late = Array::make(s.receivers.length(), 0)
let zero = Array::make(s.receivers.length(), 0)
for p in trace(s) {
let r = p.receiver
if p.delay_samples >= s.frames.to_double() {
late[r] = late[r] + 1
continue
}
if p.gain == 0.0 {
zero[r] = zero[r] + 1
continue
}
let base = @math.floor(p.delay_samples).to_int()
let taps = delay_taps(p.delay_samples - base.to_double(), s.half_width)
for i = 0; i < taps.length(); i = i + 1 {
channels[r][base + i] = channels[r][base + i] + p.gain * taps[i]
}
contributing[r] = contributing[r] + 1
}
{ scene: s, samples: channels, contributing, late, zero, }
}
///|
pub fn Response::channel(
self : Response,
index : Int32,
) -> Result[Array[Double], String] {
if index < 0 || index >= self.samples.length() {
Err("receiver index out of range")
} else {
Ok(self.samples[index].copy())
}
}
///|
pub fn Response::channel_count(self : Response) -> Int32 {
self.samples.length()
}
///|
pub fn Response::settings(self : Response) -> Scene {
self.scene
}
///|
pub fn Response::contributing_paths(self : Response) -> Array[Int32] {
self.contributing.copy()
}
///|
pub fn Response::omitted_late_paths(self : Response) -> Array[Int32] {
self.late.copy()
}
///|
pub fn Response::zero_gain_paths(self : Response) -> Array[Int32] {
self.zero.copy()
}