use crate::impulse::{ImpulseResponse, IrConfig, MultibandIr, generate_ir, sabine_rt60};
use crate::room::AcousticRoom;
use hisab::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DhvaniIr {
pub ir: ImpulseResponse,
pub multiband: Option<MultibandIr>,
pub room_volume: f32,
pub rt60_bands: [f32; crate::material::NUM_BANDS],
}
#[must_use]
#[tracing::instrument(skip(room, config), fields(
sample_rate = config.sample_rate,
))]
pub fn generate_dhvani_ir(
source: Vec3,
listener: Vec3,
room: &AcousticRoom,
config: &IrConfig,
) -> DhvaniIr {
let multiband = generate_ir(source, listener, room, config);
let broadband = multiband.to_broadband();
let volume = room.geometry.volume_shoebox();
let rt60_bands = std::array::from_fn(|band| {
let total_abs: f32 = room
.geometry
.walls
.iter()
.map(|w| w.area() * w.material.absorption[band])
.sum();
if total_abs > 0.0 {
sabine_rt60(volume, total_abs)
} else {
f32::INFINITY
}
});
DhvaniIr {
ir: broadband,
multiband: Some(multiband),
room_volume: volume,
rt60_bands,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::material::AcousticMaterial;
#[test]
fn dhvani_ir_has_valid_data() {
let room = AcousticRoom::shoebox(10.0, 8.0, 3.0, AcousticMaterial::concrete());
let config = IrConfig {
num_diffuse_rays: 100,
max_time_seconds: 0.2,
..IrConfig::default()
};
let ir = generate_dhvani_ir(
Vec3::new(3.0, 1.5, 4.0),
Vec3::new(7.0, 1.5, 4.0),
&room,
&config,
);
assert!(!ir.ir.samples.is_empty());
assert!(ir.room_volume > 0.0);
assert!(ir.multiband.is_some());
for &rt in &ir.rt60_bands {
assert!(rt > 0.0, "RT60 should be positive");
}
}
#[test]
fn dhvani_ir_serializes() {
let room = AcousticRoom::shoebox(5.0, 4.0, 3.0, AcousticMaterial::concrete());
let config = IrConfig {
num_diffuse_rays: 10,
max_time_seconds: 0.05,
..IrConfig::default()
};
let ir = generate_dhvani_ir(
Vec3::new(2.5, 1.5, 2.0),
Vec3::new(2.5, 1.5, 2.0 + 0.5),
&room,
&config,
);
let json = serde_json::to_string(&ir);
assert!(json.is_ok(), "should serialize to JSON");
}
#[test]
fn dhvani_ir_zero_absorption_band_infinite_rt60() {
let mat = AcousticMaterial::new("zero_low", [0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6], 0.1)
.unwrap();
let room = AcousticRoom::shoebox(5.0, 4.0, 3.0, mat);
let config = IrConfig {
num_diffuse_rays: 10,
max_time_seconds: 0.05,
..IrConfig::default()
};
let ir = generate_dhvani_ir(
Vec3::new(2.5, 1.5, 2.0),
Vec3::new(2.5, 1.5, 2.0 + 0.5),
&room,
&config,
);
assert!(
ir.rt60_bands[0].is_infinite(),
"zero absorption band should give infinite RT60"
);
}
}