#include "FluidSim/OutdoorAudio.hpp" #include "FluidSim/ReedValve.hpp" #include #include #include #include using namespace fluidsim::port; namespace { void check(bool c, const char *m) { if (!c) throw std::runtime_error(m); } OutdoorAudioConfig config() { OutdoorAudioConfig c; c.exhaust_position = {0, 1, 0}; c.listener = {0, 1, 3}; c.ear_spacing = 0; c.wall_x = -2; return c; } } // namespace int main() { // Geometric reflections must not arrive before the direct path or before // the ground image's path length, even with the diffuse network enabled. OutdoorAudio dry(44100, config()), wall(44100, config()); dry.set_scene(OutdoorScene::Dry); dry.set_monitor_filter(false); wall.set_monitor_filter(false); for (int i = 0; i < 4410; i++) { dry.process(0, 0); wall.process(0, 0); } int first = -1, first_reflection = -1; double tail_energy = 0, late_energy = 0; float peak = 0; for (int i = 0; i < 88200; i++) { const auto d = dry.process(0, .001f), w = wall.process(0, .001f); if (first < 0 && std::abs(d.left) > 1e-9) first = i; if (first_reflection < 0 && std::abs(w.left - d.left) > 1e-9) first_reflection = i; peak = std::max(peak, std::abs(w.left)); if (i > 2000 && i < 10000) tail_energy += w.left * w.left + w.right * w.right; if (i > 60000) late_energy += w.left * w.left + w.right * w.right; check(std::isfinite(w.left) && std::isfinite(w.right), "unstable outdoor tail"); } check(first == 385, "direct fractional propagation delay is wrong"); check(first_reflection >= 463 && first_reflection <= 465, "ground image reflection time is wrong"); check(tail_energy > 0 && late_energy < tail_energy * 1e-6, "outdoor tail does not decay"); check(peak < 1, "impulse reflection exceeds device range"); OutdoorAudio filtered(44100, config()), unfiltered(44100, config()), doubled(44100, config()); for (auto *p : {&filtered, &unfiltered, &doubled}) p->set_scene(OutdoorScene::Dry); unfiltered.set_monitor_filter(false); float prevf = 0, prevu = 0, maxf = 0, maxu = 0; for (int i = 0; i < 1000; i++) { const float q = i == 0 ? .001f : 0; auto f = filtered.process(0, q), u = unfiltered.process(0, q), twice = doubled.process(0, 2 * q); maxf = std::max(maxf, std::abs(f.left - prevf)); maxu = std::max(maxu, std::abs(u.left - prevu)); prevf = f.left; prevu = u.left; check(std::abs(twice.left - 2 * f.left) < 1e-7, "monitoring path is nonlinear"); } check(maxf < .6f * maxu, "monitor bandwidth fails to reduce adjacent-sample impulse steps"); auto near_config = config(), far_config = config(); near_config.listener.z = 1; far_config.listener.z = 2; OutdoorAudio near(44100, near_config), far(44100, far_config); near.set_scene(OutdoorScene::Dry); far.set_scene(OutdoorScene::Dry); near.set_monitor_filter(false); far.set_monitor_filter(false); double sum_near = 0, sum_far = 0; for (int i = 0; i < 1000; i++) { sum_near += near.process(0, .001f).left; sum_far += far.process(0, .001f).left; } check(std::abs(sum_near - 2 * sum_far) < 1e-7, "direct spreading does not follow inverse distance"); auto stereo_config = config(); stereo_config.ear_spacing = .18f; stereo_config.exhaust_position.x = 1; OutdoorAudio stereo(44100, stereo_config); stereo.set_scene(OutdoorScene::Dry); stereo.set_monitor_filter(false); int left = -1, right = -1; for (int i = 0; i < 1000; i++) { auto a = stereo.process(0, .001f); if (left < 0 && std::abs(a.left) > 1e-9) left = i; if (right < 0 && std::abs(a.right) > 1e-9) right = i; } check(right < left, "source on right does not reach right ear first"); ReedValve reed; for (int i = 0; i < 10000; i++) reed.step(1.f / 44100, 100); const double inertia = reed.width * std::pow(reed.thickness, 3) / 12; const double expected = 100 * reed.width * std::pow(reed.length, 4) / (8 * reed.youngs_modulus * inertia); check(std::abs(reed.lift() - expected) < expected * .001, "reed static compliance differs from cantilever equation"); for (int i = 0; i < 1000; i++) reed.step(1.f / 44100, -1000); check(reed.lift() == 0, "reed does not seat under reverse pressure"); std::cout << "Outdoor audio tests passed: direct=" << first << " samples, ground=" << first_reflection << " samples, filtered step ratio=" << maxf / maxu << ", stereo lead=" << left - right << " samples\n"; }