171 lines
6.7 KiB
C++
171 lines
6.7 KiB
C++
#include "FluidSim/Scenario.hpp"
|
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
using namespace fluidsim::port;
|
|
namespace {
|
|
class Wav {
|
|
std::ofstream out_;
|
|
void integer(std::uint32_t n, int bytes) {
|
|
for (int i = 0; i < bytes; i++)
|
|
out_.put(char(n >> (8 * i)));
|
|
}
|
|
|
|
public:
|
|
Wav(const std::string &path, int frames) : out_(path, std::ios::binary) {
|
|
if (!out_)
|
|
throw std::runtime_error("cannot open WAV: " + path);
|
|
out_.write("RIFF", 4);
|
|
integer(36 + frames * 4, 4);
|
|
out_.write("WAVEfmt ", 8);
|
|
integer(16, 4);
|
|
integer(1, 2);
|
|
integer(2, 2);
|
|
integer(44100, 4);
|
|
integer(44100 * 4, 4);
|
|
integer(4, 2);
|
|
integer(16, 2);
|
|
out_.write("data", 4);
|
|
integer(frames * 4, 4);
|
|
}
|
|
void sample(AudioFrame f) {
|
|
for (float x : {f.left, f.right}) {
|
|
const auto v = static_cast<std::int16_t>(std::lround(std::clamp(x, -1.f, 1.f) * 32767));
|
|
integer(static_cast<std::uint16_t>(v), 2);
|
|
}
|
|
}
|
|
};
|
|
struct Metrics {
|
|
double sum{}, peak{}, delta{};
|
|
std::uint64_t samples{}, clips{};
|
|
AudioFrame previous{};
|
|
void add(AudioFrame f) {
|
|
for (float x : {f.left, f.right}) {
|
|
if (!std::isfinite(x))
|
|
throw std::runtime_error("nonfinite capture");
|
|
sum += x * x;
|
|
peak = std::max(peak, double(std::abs(x)));
|
|
clips += std::abs(x) > 1;
|
|
++samples;
|
|
}
|
|
delta = std::max(
|
|
{delta, double(std::abs(f.left - previous.left)), double(std::abs(f.right - previous.right))});
|
|
previous = f;
|
|
}
|
|
void print(const char *name) const {
|
|
std::cout << name << " peak=" << peak << " rms=" << std::sqrt(sum / samples)
|
|
<< " max_sample_delta=" << delta << " clipping_samples=" << clips << '\n';
|
|
}
|
|
};
|
|
template <class Scenario>
|
|
void capture(const std::string &prefix, int frames, float pedal, OutdoorScene scene, TwoStrokeConfig config) {
|
|
std::unique_ptr<Scenario> s;
|
|
if constexpr (std::is_same_v<Scenario, TwoStrokeScenario>) s = std::make_unique<Scenario>(44100, config);
|
|
else s = std::make_unique<Scenario>();
|
|
s->throttle = pedal;
|
|
s->set_audio_scene(scene);
|
|
Wav rendered(prefix + "-rendered.wav", frames), dry(prefix + "-dry.wav", frames),
|
|
raw(prefix + "-unfiltered.wav", frames);
|
|
Metrics m, d, r;
|
|
int cuts = 0;
|
|
for (int i = 0; i < frames; i++) {
|
|
auto f = s->process(), df = s->dry_audio(), rf = s->unfiltered_audio();
|
|
m.add(f);
|
|
d.add(df);
|
|
r.add(rf);
|
|
rendered.sample(f);
|
|
dry.sample(df);
|
|
raw.sample(rf);
|
|
if (i % 4410 == 0)
|
|
cuts += s->snapshot().fuel_cut;
|
|
}
|
|
m.print("rendered");
|
|
d.print("dry filtered");
|
|
r.print("unfiltered spatial");
|
|
std::cout << "fuel-cut checkpoints=" << cuts << " final_rpm=" << s->snapshot().rpm
|
|
<< " scene=" << scene_name(scene) << '\n';
|
|
}
|
|
} // namespace
|
|
int main(int argc, char **argv) {
|
|
try {
|
|
std::string scenario = "twin", prefix = "build/audio/twin", scene = "wall";
|
|
float seconds = 6, pedal = 1;
|
|
TwoStrokeConfig config;
|
|
bool turbo_enabled = false;
|
|
std::string turbo_name;
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string key = argv[i];
|
|
if (i + 1 >= argc)
|
|
throw std::runtime_error("missing option value");
|
|
std::string value = argv[++i];
|
|
if (key == "--scenario")
|
|
scenario = value;
|
|
else if (key == "--out")
|
|
prefix = value;
|
|
else if (key == "--seconds")
|
|
seconds = std::stof(value);
|
|
else if (key == "--throttle")
|
|
pedal = std::stof(value);
|
|
else if (key == "--turbo") {
|
|
if (value != "none" && value != "vz21" && value != "gt17") throw std::runtime_error("choose none, vz21 or gt17 turbo");
|
|
turbo_name = value;
|
|
turbo_enabled = value != "none";
|
|
}
|
|
else if (key == "--exhaust") {
|
|
if (value == "racing") config.exhaust = TwoStrokeExhaust::Racing;
|
|
else if (value == "straight") config.exhaust = TwoStrokeExhaust::Straight;
|
|
else throw std::runtime_error("choose racing or straight exhaust");
|
|
}
|
|
else if (key == "--scene")
|
|
scene = value;
|
|
else
|
|
throw std::runtime_error("unknown capture option");
|
|
}
|
|
if (!std::isfinite(seconds) || seconds <= 0 || seconds > 600 || !std::isfinite(pedal) || pedal < 0 ||
|
|
pedal > 1)
|
|
throw std::runtime_error("invalid capture duration or throttle");
|
|
OutdoorScene selected;
|
|
if (scene == "dry")
|
|
selected = OutdoorScene::Dry;
|
|
else if (scene == "field")
|
|
selected = OutdoorScene::Field;
|
|
else if (scene == "wall")
|
|
selected = OutdoorScene::Wall;
|
|
else if (scene == "woodland")
|
|
selected = OutdoorScene::Woodland;
|
|
else
|
|
throw std::runtime_error("invalid scene");
|
|
if (!std::filesystem::path(prefix).parent_path().empty())
|
|
std::filesystem::create_directories(std::filesystem::path(prefix).parent_path());
|
|
const int frames = int(std::lround(seconds * 44100));
|
|
if (turbo_name.empty() && scenario == "b205") turbo_enabled = true;
|
|
if (!turbo_name.empty() && turbo_name!="none" &&
|
|
!((turbo_name=="vz21" && scenario=="twin") || (turbo_name=="gt17" && scenario=="b205")))
|
|
throw std::runtime_error("use gt17 with b205 or vz21 with twin");
|
|
if (scenario == "twin" && turbo_enabled)
|
|
capture<TurboTwinScenario>(prefix, frames, pedal, selected, config);
|
|
else if (scenario == "twin")
|
|
capture<FourStrokeTwinScenario>(prefix, frames, pedal, selected, config);
|
|
else if (scenario == "b205" && turbo_enabled)
|
|
capture<SaabTurboScenario>(prefix, frames, pedal, selected, config);
|
|
else if (scenario == "b205")
|
|
capture<SaabB205Scenario>(prefix, frames, pedal, selected, config);
|
|
else if (scenario == "two-stroke")
|
|
capture<TwoStrokeScenario>(prefix, frames, pedal, selected, config);
|
|
else if (scenario == "v8")
|
|
capture<AmericanV8Scenario>(prefix, frames, pedal, selected, config);
|
|
else
|
|
throw std::runtime_error("choose twin, two-stroke, b205 or v8");
|
|
} catch (const std::exception &e) {
|
|
std::cerr << e.what() << '\n';
|
|
return 1;
|
|
}
|
|
}
|