#include "FluidSim/AudioProcessing.hpp" #include "FluidSim/Engine.hpp" #include #include namespace fluidsim::port { SoundProcessor::SoundProcessor(int sample_rate, float listener_distance) { const int safe_rate = std::max(sample_rate, 1); dt_ = 1.0f / static_cast(safe_rate); scale_factor_ = 1.0f / (4.0f * pi * std::max(listener_distance, 1e-3f)); derivative_alpha_ = std::exp(-dt_ / 0.020f); lowpass_alpha_ = std::exp(-dt_ / 0.005f); } float SoundProcessor::process(float mass_flow_out) { flow_lowpass_ = lowpass_alpha_ * flow_lowpass_ + (1.0f - lowpass_alpha_) * mass_flow_out; const float raw_derivative = (flow_lowpass_ - previous_flow_) / dt_; previous_flow_ = flow_lowpass_; smooth_derivative_ = derivative_alpha_ * smooth_derivative_ + (1.0f - derivative_alpha_) * raw_derivative; return std::tanh(smooth_derivative_ * scale_factor_ * gain); } RadiationSource::RadiationSource(int sample_rate) : sample_rate_(std::max(sample_rate, 1)), dt_(1.0f / static_cast(std::max(sample_rate, 1))) { const std::size_t max_delay = static_cast(std::max(1.0f, 0.5f / std::max(speed_of_sound, 1.0f) * static_cast(sample_rate_))); left_delay_.resize(max_delay); right_delay_.resize(max_delay); } AudioFrame RadiationSource::process(float mass_flow) { const float flow_alpha = 1.0f - std::exp(-dt_ / std::max(flow_tau, 1e-5f)); const float derivative_decay = std::exp(-dt_ / std::max(smooth_tau, 1e-5f)); flow_lp_ += flow_alpha * (mass_flow - flow_lp_); const float raw_derivative = (flow_lp_ - previous_flow_lp_) / dt_; previous_flow_lp_ = flow_lp_; smooth_derivative_ = derivative_decay * smooth_derivative_ + (1.0f - derivative_decay) * raw_derivative; const Vec3 to_listener = subtract(listener, pipe_end); const float distance = std::max(length(to_listener), 0.1f); const Vec3 direction{to_listener.x / distance, to_listener.y / distance, to_listener.z / distance}; const float cosine = dot(pipe_axis, direction); const float directivity = (1.0f - directivity_balance) + directivity_balance * cosine; last_mono_pressure_ = std::tanh(smooth_derivative_ * gain * directivity / (4.0f * 3.14159265358979323846f * distance)); const float air_cutoff = std::max(min_air_cutoff, 20000.0f * std::exp(-distance * air_absorption_exponent)); const float filtered = one_pole(last_mono_pressure_, air_cutoff, air_filter_); const Vec3 ear_offset{listener_right.x * ear_spacing * 0.5f, listener_right.y * ear_spacing * 0.5f, listener_right.z * ear_spacing * 0.5f}; const Vec3 left_ear = subtract(listener, ear_offset); const Vec3 right_ear{listener.x + ear_offset.x, listener.y + ear_offset.y, listener.z + ear_offset.z}; const float left_distance = std::max(length(subtract(left_ear, pipe_end)), 0.1f); const float right_distance = std::max(length(subtract(right_ear, pipe_end)), 0.1f); left_offset_ = std::min(left_delay_.size() - 1, static_cast(left_distance / speed_of_sound / dt_)); right_offset_ = std::min(right_delay_.size() - 1, static_cast(right_distance / speed_of_sound / dt_)); const auto delay_sample = [this, filtered](std::vector& buffer, std::size_t offset, float ear_distance) { const std::size_t read = (write_position_ + buffer.size() - offset) % buffer.size(); const float value = buffer[read]; buffer[write_position_] = filtered * (length(subtract(listener, pipe_end)) / ear_distance); return value; }; float left = delay_sample(left_delay_, left_offset_, left_distance); float right = delay_sample(right_delay_, right_offset_, right_distance); write_position_ = (write_position_ + 1) % left_delay_.size(); if (left_distance > right_distance) left = one_pole(left, head_shadow_cutoff, shadow_left_); else if (right_distance > left_distance) right = one_pole(right, head_shadow_cutoff, shadow_right_); return {left, right}; } float RadiationSource::dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } float RadiationSource::length(Vec3 v) { return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } Vec3 RadiationSource::subtract(Vec3 a, Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; } float RadiationSource::one_pole(float input, float cutoff, float& state) const { const float angular = 2.0f * 3.14159265358979323846f * cutoff / static_cast(sample_rate_); const float alpha = 1.0f - std::exp(-angular); state += alpha * (input - state); return state; } } // namespace fluidsim::port