Files
FluidSimCpp/src/main.cpp
max bf8621ad39 Load engines from TOML and improve scenario audio and carb tuning
Migrate 32 engine definitions into validated startup-loaded TOML files and separate configuration and snapshots from scenario assembly. Add camera-based stereo propagation from shared pipe geometry, preserve exhaust character, tune carburetor mixtures and idle, use ignition cuts for carb engines, and support returning to the scenario menu.

Validation: full Windows build and all 14 CTest checks passed.
2026-10-24 03:24:35 +02:00

115 lines
6.2 KiB
C++

#include "FluidSim/Scenario.hpp"
#include "FluidSim/ScenarioCatalog.hpp"
#include "FluidSim/HelmholtzScenario.hpp"
#include "FluidSim/PulseTestScenario.hpp"
#include "FluidSim/TwoStrokeScenario.hpp"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>
int main(int argc, char** argv) try {
using namespace fluidsim::port;
float duration_seconds = 1.0f;
float throttle = 0.35f;
std::string scenario_name = "twin";
auto two_stroke_config=scenario_example("yz-racing").two;
PipeSolver pipe_solver=PipeSolver::Rusanov;
for (int i = 1; i + 1 < argc; ++i) {
const std::string option = argv[i];
if (option == "--seconds") duration_seconds = std::max(0.0f, std::strtof(argv[++i], nullptr));
else if (option == "--throttle") throttle = std::clamp(std::strtof(argv[++i], nullptr), 0.0f, 1.0f);
else if (option == "--scenario") scenario_name = argv[++i];
else if (option == "--solver") {
try { pipe_solver=parse_pipe_solver(argv[++i]); }
catch(const std::exception& e) { std::cerr<<e.what()<<"\n"; return 2; }
}
else if (option == "--exhaust") {
const std::string value = argv[++i];
if (value == "racing") two_stroke_config.exhaust = TwoStrokeExhaust::Racing;
else if (value == "straight") two_stroke_config.exhaust = TwoStrokeExhaust::Straight;
else { std::cerr << "Choose racing or straight exhaust\n"; return 2; }
}
}
constexpr int sample_rate = 44100;
const auto total_samples = static_cast<std::uint64_t>(duration_seconds * sample_rate);
const auto report_interval = static_cast<std::uint64_t>(sample_rate / 10);
if (scenario_name == "twin" || scenario_name == "b205" || scenario_name == "v8") {
FourStrokeScenario scenario(sample_rate, scenario_name == "v8" ? FourStrokePreset::AmericanV8 : scenario_name == "b205" ? FourStrokePreset::SaabB205 : FourStrokePreset::Twin);
scenario.set_pipe_solver(pipe_solver);
scenario.throttle = throttle;
std::cout << (scenario_name == "v8" ? "FluidSimCpp | American 5.0 V8 | throttle " : scenario_name == "b205" ? "FluidSimCpp | Saab B205 / 9-5 wagon | throttle " : "FluidSimCpp | 600 cc parallel twin | throttle ") << std::fixed << std::setprecision(0)
<< throttle * 100.0f << "%\n";
for (std::uint64_t i = 0; i < total_samples; ++i) {
scenario.process();
if ((i + 1) % report_interval == 0 || i + 1 == total_samples) {
std::cout << std::fixed << std::setprecision(1)
<< "t=" << static_cast<float>(i + 1) / sample_rate << " s"
<< " rpm=" << scenario.crankshaft.omega * 60.0f / (2.0f * pi)
<< " speed=" << scenario.vehicle.speed * 3.6f << " km/h"
<< " gear=" << scenario.vehicle.current_gear
<< " cyl=" << scenario.cylinder1.pressure / 1000.0f << "/"
<< scenario.cylinder2.pressure / 1000.0f << " kPa"
<< " intake=" << scenario.intake_plenum.pressure() / 1000.0f << " kPa"
<< " exhaust=" << scenario.exhaust_collector.pressure() / 1000.0f << " kPa";
std::cout << '\n';
}
}
} else if (scenario_name == "two-stroke") {
TwoStrokeScenario scenario(sample_rate, two_stroke_config);
scenario.set_pipe_solver(pipe_solver);
scenario.set_throttle(throttle);
std::cout << "FluidSimCpp | 125 cc crankcase-scavenged two-stroke | throttle "
<< std::fixed << std::setprecision(0) << throttle * 100.0f << "%\n";
for (std::uint64_t i = 0; i < total_samples; ++i) {
scenario.process();
if ((i + 1) % report_interval == 0 || i + 1 == total_samples) {
const auto state = scenario.snapshot();
std::cout << std::fixed << std::setprecision(1)
<< "t=" << static_cast<float>(i + 1) / sample_rate << " s rpm=" << state.rpm
<< " cyl=" << state.cylinder_pressure / 100000.0f << " bar"
<< " crankcase=" << state.crankcase_pressure / 1000.0f << " kPa"
<< " intake=" << state.intake_pressure / 1000.0f << " kPa"
<< " reed=" << (state.reed_open ? "open" : "closed") << '\n';
}
}
} else if (scenario_name == "helmholtz") {
HelmholtzScenario scenario;
std::cout << "FluidSimCpp | Helmholtz resonator | initial cavity 1.1 bar\n";
for (std::uint64_t i = 0; i < total_samples; ++i) {
const float sample = scenario.process();
if ((i + 1) % report_interval == 0 || i + 1 == total_samples)
std::cout << std::fixed << std::setprecision(1)
<< "t=" << static_cast<float>(i + 1) / sample_rate << " s"
<< " cavity=" << scenario.cavity_pressure() / 1000.0f << " kPa"
<< " flow=" << scenario.open_end_flow() << " kg/s"
<< " audio=" << sample << '\n';
}
} else if (scenario_name == "pulse") {
PulseTestScenario scenario;
std::cout << "FluidSimCpp | pressure pulse in a 2 m pipe\n";
for (std::uint64_t i = 0; i < total_samples; ++i) {
const float sample = scenario.process();
if ((i + 1) % report_interval == 0 || i + 1 == total_samples)
std::cout << std::fixed << std::setprecision(1)
<< "t=" << static_cast<float>(i + 1) / sample_rate << " s"
<< " left=" << scenario.pipe().state(0).pressure / 1000.0f << " kPa"
<< " right=" << scenario.pipe().state(199).pressure / 1000.0f << " kPa"
<< " flow=" << scenario.pipe().state(199).velocity << " m/s"
<< " audio=" << sample << '\n';
}
} else {
std::cerr << "Unknown scenario '" << scenario_name
<< "'. Choose twin, two-stroke, b205, v8, helmholtz, or pulse.\n";
return 2;
}
return 0;
} catch(const std::exception& error) {
std::cerr<<error.what()<<'\n';
return 2;
}