all of the changes

This commit is contained in:
2026-10-23 18:29:51 +02:00
parent 4c435fdd76
commit 5607dcf2b5
73 changed files with 4365 additions and 2059 deletions

View File

@@ -22,6 +22,8 @@ const char *scene_name(OutdoorScene scene) {
return "Nearby wall";
case OutdoorScene::Woodland:
return "Woodland";
case OutdoorScene::Tunnel:
return "Tunnel";
}
return "Unknown";
}
@@ -35,7 +37,7 @@ float OutdoorAudio::LowPass::process(float x) {
z2 = 0;
return y;
}
OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), config_(config) {
OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), config_(config), late_reverb_(rate) {
if (rate < 8000 || !std::isfinite(config.sound_speed) || config.sound_speed <= 0 ||
!std::isfinite(config.monitor_cutoff_hz) || config.monitor_cutoff_hz <= 0 ||
!std::isfinite(config.pressure_gain) || config.pressure_gain < 0)
@@ -48,14 +50,11 @@ OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), c
configure_emitter(emitters_[0], config_.intake_position);
configure_emitter(emitters_[1], config_.exhaust_position);
if (config_.dual_exhaust) configure_emitter(emitters_[2], config_.second_exhaust_position);
constexpr std::array<int, 4> lengths{1499, 2111, 2633, 3251};
for (std::size_t i = 0; i < tail_.size(); ++i) {
auto &line = tail_[i];
line.buffer.resize(std::max(1, int(std::lround(lengths[i] * rate_ / 44100.0))));
line.gain = std::pow(10.0f, -3.0f * (float(line.buffer.size()) / rate_) / 0.35f);
}
late_reverb_.dry_mix = 0;
late_reverb_.early_mix = 0;
late_reverb_.tail_mix = 1;
late_reverb_.set_decay_times(.75f, .45f, .22f);
scene_alpha_ = 1 - std::exp(-1.0f / (rate_ * .02f));
tail_alpha_ = 1 - std::exp(-2 * pi * 2500 / rate_);
}
void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
emitter.filtered.resize(std::bit_ceil(std::size_t(rate_)));
@@ -78,8 +77,14 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
p.delay_right = std::size_t(sr);
p.fraction_left = sl - p.delay_left;
p.fraction_right = sr - p.delay_right;
p.gain_left = coefficient / (dl * source_distance);
p.gain_right = coefficient / (dr * source_distance);
// A scatterer adds path length before the listener leg. Use spreading
// over the complete reflected path rather than multiplying two
// inverse-distance losses, which made the woodland paths nearly
// inaudible.
const float path_left = source_distance > 1.0f ? dl + source_distance : dl;
const float path_right = source_distance > 1.0f ? dr + source_distance : dr;
p.gain_left = coefficient / path_left;
p.gain_right = coefficient / path_right;
p.alpha = cutoff > 0 ? 1 - std::exp(-2 * pi * cutoff / rate_) : 1;
emitter.paths.push_back(p);
};
@@ -92,6 +97,21 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
const float first = std::max(distance(source, tree), .1f);
path(tree, .35f, 3, 2400, first, first);
}
// Image sources for an approximate 6 m wide, 4 m high hard-surfaced
// tunnel. Single and combined ceiling/side-wall bounces make the profile
// distinct before the diffuse FDN tail builds up.
constexpr float tunnel_width = 6.0f;
constexpr float tunnel_height = 4.0f;
const Vec3 ceiling{source.x, 2 * tunnel_height - source.y, source.z};
const Vec3 left_wall{source.x, source.y, -tunnel_width - source.z};
const Vec3 right_wall{source.x, source.y, tunnel_width - source.z};
path(ceiling, .82f, 4, 7200);
path(left_wall, .82f, 4, 6200);
path(right_wall, .82f, 4, 6200);
path({ceiling.x, ceiling.y, left_wall.z}, .68f, 4, 5600);
path({ceiling.x, ceiling.y, right_wall.z}, .68f, 4, 5600);
path({source.x, source.y, -2 * tunnel_width + source.z}, .56f, 4, 4800);
path({source.x, source.y, 2 * tunnel_width + source.z}, .56f, 4, 4800);
const float cutoff = std::min(config_.monitor_cutoff_hz, rate_ * .45f);
const float w = 2 * pi * cutoff / rate_, cs = std::cos(w), alpha = std::sin(w) / std::sqrt(2.0f),
a0 = 1 + alpha;
@@ -101,9 +121,8 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFrame &raw) {
const float pressure = (flow - e.previous_flow) * rate_ * config_.pressure_gain / (4 * pi);
e.previous_flow = flow;
const float filtered = e.filter.process(pressure);
e.raw[e.position] = pressure;
e.filtered[e.position] = monitor_filter_ ? filtered : pressure;
e.filtered[e.position] = monitor_filter_ ? e.filter.process(pressure) : pressure;
const auto read = [&](const std::vector<float> &data, std::size_t delay, float fraction) {
const auto i = (e.position + data.size() - delay) & (data.size() - 1);
const auto j = (i + data.size() - 1) & (data.size() - 1);
@@ -130,44 +149,52 @@ AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFr
e.position = (e.position + 1) & (e.filtered.size() - 1);
return result;
}
AudioFrame OutdoorAudio::diffuse(float input) {
std::array<float, 4> values{};
float sum = 0;
for (std::size_t i = 0; i < 4; ++i) {
auto &line = tail_[i];
values[i] = line.buffer[line.position];
sum += values[i];
}
for (std::size_t i = 0; i < 4; ++i) {
auto &line = tail_[i];
const float scattered = values[i] - .5f * sum;
line.state += tail_alpha_ * (scattered - line.state);
if (std::abs(line.state) < 1e-25f)
line.state = 0;
line.buffer[line.position] = line.gain * line.state + .5f * input;
line.position = (line.position + 1) % line.buffer.size();
}
return {(values[0] + values[2]) * .35f, (values[1] + values[3]) * .35f};
}
AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhaust) {
if (!std::isfinite(intake) || !std::isfinite(exhaust) || !std::isfinite(second_exhaust))
throw std::runtime_error("nonfinite acoustic source flow");
const bool wet = scene_ != OutdoorScene::Dry;
const std::array<float, 4> target{1, wet ? 1.f : 0.f, scene_ == OutdoorScene::Wall ? 1.f : 0.f,
scene_ == OutdoorScene::Woodland ? 1.f : 0.f};
for (std::size_t i = 0; i < 4; ++i)
const OutdoorScene scene = requested_scene_.load(std::memory_order_relaxed);
if (scene != active_scene_) {
active_scene_ = scene;
switch (scene) {
case OutdoorScene::Dry:
late_reverb_.set_decay_times(.20f, .12f, .06f);
break;
case OutdoorScene::Field:
late_reverb_.set_decay_times(.28f, .17f, .08f);
break;
case OutdoorScene::Wall:
late_reverb_.set_decay_times(.75f, .45f, .22f);
break;
case OutdoorScene::Woodland:
late_reverb_.set_decay_times(.42f, .22f, .09f);
break;
case OutdoorScene::Tunnel:
late_reverb_.set_decay_times(1.8f, 1.35f, .75f);
break;
}
}
const bool wet = scene != OutdoorScene::Dry;
const std::array<float, 5> target{1, wet && scene != OutdoorScene::Tunnel ? 1.f : 0.f,
scene == OutdoorScene::Wall ? 1.f : 0.f,
scene == OutdoorScene::Woodland ? 1.f : 0.f,
scene == OutdoorScene::Tunnel ? 1.f : 0.f};
for (std::size_t i = 0; i < weights_.size(); ++i)
weights_[i] += (target[i] - weights_[i]) * scene_alpha_;
const float target_tail =
scene_ == OutdoorScene::Wall
? .14f
: (scene_ == OutdoorScene::Woodland ? .25f : (scene_ == OutdoorScene::Field ? .025f : 0));
// Keep the diffuse tail present between sharp events; the prior mix values
// made it perceptible mainly after large transients.
const float target_tail = scene == OutdoorScene::Wall ? .75f
: scene == OutdoorScene::Woodland ? 1.0f
: scene == OutdoorScene::Tunnel ? 1.15f
: scene == OutdoorScene::Field ? .30f : 0.f;
tail_mix_ += (target_tail - tail_mix_) * scene_alpha_;
last_dry_ = {};
last_raw_ = {};
const auto a = render(emitters_[0], intake, last_dry_, last_raw_);
const auto b = render(emitters_[1], exhaust, last_dry_, last_raw_);
const auto c = config_.dual_exhaust ? render(emitters_[2], second_exhaust, last_dry_, last_raw_) : AudioFrame{};
const auto tail = diffuse((last_dry_.left + last_dry_.right) * .5f);
const auto c = config_.dual_exhaust
? render(emitters_[2], second_exhaust, last_dry_, last_raw_)
: AudioFrame{};
const auto tail = late_reverb_.process_stereo((last_dry_.left + last_dry_.right) * .5f);
return {a.left + b.left + c.left + tail.left * tail_mix_, a.right + b.right + c.right + tail.right * tail_mix_};
}
} // namespace fluidsim::port