#pragma once #include "FluidSim/AudioProcessing.hpp" #include "FluidSim/OutdoorExhaustReverb.hpp" #include #include #include #include namespace fluidsim::port { enum class OutdoorScene { Dry, Field, Wall, Woodland, Tunnel }; const char *scene_name(OutdoorScene scene); struct OutdoorAudioConfig { Vec3 listener{1.6f, 1.0f, 2.0f}; Vec3 listener_right{1.0f, 0.0f, 0.0f}; Vec3 intake_position{-0.2f, 0.7f, 0.0f}; Vec3 exhaust_position{0.55f, 0.5f, 0.0f}; Vec3 second_exhaust_position{-0.55f, 0.5f, 0.0f}; bool dual_exhaust{}; float ear_spacing{0.18f}, sound_speed{343.0f}; float ground_y{0.0f}, wall_x{-1.5f}; float ground_reflection{0.35f}, wall_reflection{0.65f}; float pressure_gain{0.005f}; // 200 Pa per device full scale, not speaker SPL. float monitor_cutoff_hz{6000.0f}; // Second-order acquisition/rendering filter. }; // Moving geometric direct/first-order reflection paths plus an explicitly // approximate diffuse tail. No feedback into the fluid/combustion simulation. class OutdoorAudio { public: explicit OutdoorAudio(int sample_rate = 44100, OutdoorAudioConfig config = {}); // Acoustic source inputs are ambient-density times mouth volume flow, kg/s. // Fluid transport still uses actual donor-density mass flux independently. AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0); void set_scene(OutdoorScene scene) { requested_scene_.store(scene, std::memory_order_relaxed); } OutdoorScene scene() const { return requested_scene_.load(std::memory_order_relaxed); } // Call on the simulation thread or under the scenario's simulation lock. void set_listener(AudioListener listener); void set_source_positions(const std::array& positions); void set_monitor_filter(bool enabled) { monitor_filter_ = enabled; } // Set before playback/capture. AudioFrame dry_frame() const { return last_dry_; } AudioFrame unfiltered_frame() const { return last_raw_; } private: struct LowPass { float b0{}, b1{}, b2{}, a1{}, a2{}, z1{}, z2{}; float process(float x); }; struct Path { float delay_left{}, delay_right{}, gain_left{}, gain_right{}, alpha{1}; float target_delay_left{}, target_delay_right{}, target_gain_left{}, target_gain_right{}; float state_left{}, state_right{}; int group{}; // direct, ground, wall, trees, tunnel }; struct Emitter { std::vector filtered, raw; std::vector paths; std::size_t position{}; float previous_flow{}; float center_delay{}, target_center_delay{}, center_gain{}, target_center_gain{}; LowPass filter; }; int rate_{}; OutdoorAudioConfig config_; std::atomic requested_scene_{OutdoorScene::Wall}; OutdoorScene active_scene_{OutdoorScene::Wall}; bool monitor_filter_{true}; std::array emitters_; OutdoorExhaustReverb late_reverb_; std::array weights_{1, 0, 0, 0, 0}; float scene_alpha_{}, tail_mix_{}; float geometry_alpha_{}, reverb_input_{}; AudioFrame last_dry_{}, last_raw_{}; void configure_emitter(Emitter &emitter, Vec3 position); void update_geometry(); AudioFrame render(Emitter &emitter, float flow, AudioFrame &dry, AudioFrame &raw); }; } // namespace fluidsim::port