89 lines
4.6 KiB
C++
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
|