#pragma once #include "FluidSim/Silencer.hpp" #include "FluidSim/Carburetor.hpp" #include #include "FluidSim/Junction.hpp" #include "FluidSim/AudioProcessing.hpp" #include "FluidSim/Engine.hpp" #include "FluidSim/Ecu.hpp" #include "FluidSim/Fluid.hpp" #include "FluidSim/OutdoorAudio.hpp" #include "FluidSim/Solver.hpp" #include "FluidSim/Vehicle.hpp" #include "FluidSim/MotorcycleTest.hpp" #include #include #include #include #include namespace fluidsim::port { enum class ScenarioKind { Twin, TwoStroke, SaabB205, AmericanV8 }; enum class FourStrokePreset { Twin, SaabB205, AmericanV8 }; enum class EngineLayout { Inline, V, Boxer }; struct EngineConfig { int cylinders{}; // zero selects the existing preset EngineLayout layout{EngineLayout::Inline}; float bank_angle{60}, bore{.086f}, stroke{.086f}, rod{.145f}, compression{10}; float idle_rpm{1000}, rev_limit{7000}, idle_opening{.045f}; bool carbureted{}; float carb_pilot_scale{1.f}; float carb_jet_scale{1.f}; // multiplier on displacement-scaled effective jet diameters std::array phase_degrees{}; // used when custom_phases is true bool custom_phases{}; }; struct V8ExhaustConfig { float primary_length{.75f}, primary_diameter{.038f}; bool h_pipe{true}; float junction_volume{50e-6f}; // m3, 50 cc per fitting }; struct ScenarioSnapshot { std::string example_name; EngineLayout layout{EngineLayout::Inline}; float bank_angle{}; bool v8_headers{}; PipeSolver pipe_solver{PipeSolver::Rusanov}; int cylinder_count{2}; float rev_limit_rpm{10000}; std::array cylinder_angles{}; std::array cylinder_pressures{}, piston_fractions{}, intake_areas{}, exhaust_areas{}; std::uint64_t solver_substeps{}, boundary_iterations{}, pipe_positivity_repairs{}; float rpm{}, speed_kmh{}, kinetic_energy{}, average_power{}, average_torque{}; float cylinder_pressure1{}, cylinder_pressure2{}; float cylinder_temperature1{}, cylinder_temperature2{}; float cylinder_piston_fraction1{}, cylinder_piston_fraction2{}; float valve1_intake_area{}, valve1_exhaust_area{}, valve2_intake_area{}, valve2_exhaust_area{}; float intake_pressure{}, exhaust_pressure{}, second_bank_pressure{}; std::array crossover_pressures{}; float throttle{}, pedal{}, target_afr{}, clutch{}, intake_flow{}, exhaust_flow{}; bool idle_control{}, fuel_cut{}, two_stroke{}, reed_open{}; bool racing_exhaust{}; bool silencers{}, h_pipe{}; ExhaustConfig exhaust_config{}; float crankcase_pressure{}; OutdoorScene audio_scene{OutdoorScene::Wall}; int gear{}; bool rev_limit1{}, rev_limit2{}; std::array, 32> pipe_states; std::array, 32> pipe_areas, pipe_lengths; void capture_pipes(const std::vector& pipes) { for (std::size_t k = 0; k < pipes.size(); ++k) { pipe_positivity_repairs += pipes[k].positivity_repairs; pipe_areas[k] = pipes[k].cell_areas; pipe_lengths[k] = pipes[k].cell_lengths; pipe_states[k].reserve(pipes[k].cells.size()); for (std::size_t i = 0; i < pipes[k].cells.size(); ++i) pipe_states[k].push_back(pipes[k].state(static_cast(i))); } } }; // Shared four-stroke topology: independent runners/headers and shared plenums. // Presets include a 600 cc twin, B205-sized inline-four and American 5.0 V8. class FourStrokeScenario : public MotorcycleTest { public: explicit FourStrokeScenario(int sample_rate = 44100, FourStrokePreset preset = FourStrokePreset::Twin, ExhaustConfig exhaust = {}, V8ExhaustConfig v8_exhaust = {}, EngineConfig engine = {}); const FourStrokePreset preset; const ExhaustConfig exhaust_config; const V8ExhaustConfig v8_exhaust_config; const EngineConfig engine_config; int cylinder_count() const { return engine_config.cylinders ? engine_config.cylinders : preset == FourStrokePreset::AmericanV8 ? 8 : preset == FourStrokePreset::SaabB205 ? 4 : 2; } std::array cylinders() { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4, &cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; } AudioFrame process(); void set_controls(float throttle_value, float load_value, float clutch_value); void set_audio_scene(OutdoorScene scene); void set_pipe_solver(PipeSolver solver); void set_monitor_filter(bool enabled); void set_starter(bool engaged); AudioFrame dry_audio() const; AudioFrame unfiltered_audio() const; void shift_up(); void shift_down(); void set_neutral(); void set_first_gear(); ScenarioSnapshot snapshot() const; float throttle{}; Ecu ecu{}; Crankshaft crankshaft{}; FourStrokeCylinder cylinder1{}, cylinder2{}, cylinder3{}, cylinder4{}; FourStrokeCylinder cylinder5{}, cylinder6{}, cylinder7{}, cylinder8{}; FourStrokeCylinder cylinder9{}, cylinder10{}, cylinder11{}, cylinder12{}; Volume0D intake_plenum{1.0e-3f, 101325.0f, 300.0f}; Junction exhaust_collector{0.15e-3f, 101325.0f, 700.0f}; std::vector pipes; float intake_flow{}, exhaust_flow{}; std::uint64_t sample_count{}; private: float throttle_area_{}, runner_area_{}, header_area_{}, tail_area_{}; std::array intake_valve_areas_{}, exhaust_valve_areas_{}; float dt_{}; BoundarySystem boundaries_; Solver solver_; OutdoorAudio audio_; mutable std::mutex mutex_; std::vector> exhaust_junctions_; std::unique_ptr carb_; Port* throttle_port_{}; static float area_for_diameter(float diameter); static std::vector create_pipes(FourStrokePreset preset, const ExhaustConfig& exhaust, const V8ExhaustConfig& v8_exhaust, const EngineConfig& engine); }; using FourStrokeTwinScenario = FourStrokeScenario; class AmericanV8Scenario : public FourStrokeScenario { public: explicit AmericanV8Scenario(int sample_rate = 44100) : FourStrokeScenario(sample_rate, FourStrokePreset::AmericanV8) {} }; class SaabB205Scenario : public FourStrokeScenario { public: explicit SaabB205Scenario(int sample_rate = 44100) : FourStrokeScenario(sample_rate, FourStrokePreset::SaabB205) {} }; } // namespace fluidsim::port