Files
FluidSimCpp/src/audio_processing.cpp
2026-10-22 23:52:20 +02:00

89 lines
4.6 KiB
C++

#include "FluidSim/AudioProcessing.hpp"
#include "FluidSim/Engine.hpp"
#include <algorithm>
#include <cmath>
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<float>(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<float>(std::max(sample_rate, 1))) {
const std::size_t max_delay = static_cast<std::size_t>(std::max(1.0f,
0.5f / std::max(speed_of_sound, 1.0f) * static_cast<float>(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<std::size_t>(left_distance / speed_of_sound / dt_));
right_offset_ = std::min(right_delay_.size() - 1, static_cast<std::size_t>(right_distance / speed_of_sound / dt_));
const auto delay_sample = [this, filtered](std::vector<float>& 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<float>(sample_rate_);
const float alpha = 1.0f - std::exp(-angular);
state += alpha * (input - state);
return state;
}
} // namespace fluidsim::port