diff --git a/.gitignore b/.gitignore index 11ed569..ce0086e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ CMakeUserPresets.json /verification/bin/ /verification/obj/ + +# Generated compiler debug databases +*.pdb diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f76de5..4f306c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ option(FLUIDSIM_BUILD_DESKTOP "Build the SFML windowed simulator with keyboard c set(FLUIDSIM_FONT_FILE "" CACHE FILEPATH "Optional font file copied beside the desktop executable") add_library(fluidsim_core + src/engine_config.cpp src/carburetor.cpp src/ecu.cpp src/engine.cpp @@ -17,11 +18,15 @@ add_library(fluidsim_core src/outdoor_audio.cpp src/outdoor_exhaust_reverb.cpp src/scenario.cpp + src/scenario_snapshot.cpp + src/scenario_geometry.cpp src/solver.cpp src/vehicle.cpp src/simulation_ring_buffer.cpp ) target_include_directories(fluidsim_core PUBLIC include) +target_include_directories(fluidsim_core SYSTEM PRIVATE third_party/tomlplusplus) +target_compile_definitions(fluidsim_core PRIVATE FLUIDSIM_ENGINE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/engines") target_compile_features(fluidsim_core PUBLIC cxx_std_20) option(FLUIDSIM_ENABLE_SIMD "Allow compiler SIMD in fluid kernels without fast-math" ON) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") @@ -109,6 +114,9 @@ endif() include(CTest) if(BUILD_TESTING) + add_executable(fluidsim_engine_config_tests tests/engine_config_tests.cpp) + target_link_libraries(fluidsim_engine_config_tests PRIVATE fluidsim_core) + add_test(NAME fluidsim_engine_config_tests COMMAND fluidsim_engine_config_tests) add_executable(fluidsim_scenario_examples tests/scenario_examples.cpp) target_link_libraries(fluidsim_scenario_examples PRIVATE fluidsim_core) add_test(NAME fluidsim_scenario_examples COMMAND fluidsim_scenario_examples) @@ -167,4 +175,24 @@ if(BUILD_TESTING) add_test(NAME fluidsim_b205_regression COMMAND fluidsim_b205_regression) endif() +add_executable(fluidsim_carb_calibration tools/carb_calibration.cpp) +target_link_libraries(fluidsim_carb_calibration PRIVATE fluidsim_core) +if(BUILD_TESTING) + add_test(NAME fluidsim_carb_calibration COMMAND fluidsim_carb_calibration --check) +endif() + include(cmake/MinGWRuntime.cmake) + +# Keep definitions beside every built executable, including tests and CLI tools. +add_custom_target(fluidsim_engine_assets ALL + COMMAND ${CMAKE_COMMAND} + "-DSOURCE=${CMAKE_CURRENT_SOURCE_DIR}/engines" + "-DDESTINATION=${CMAKE_CURRENT_BINARY_DIR}/engines" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyEngineConfigs.cmake") +get_property(fluidsim_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) +foreach(target IN LISTS fluidsim_targets) + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "EXECUTABLE") + add_dependencies(${target} fluidsim_engine_assets) + endif() +endforeach() diff --git a/CMakePresets.json b/CMakePresets.json index e14605b..e21d544 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -39,7 +39,6 @@ "CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_C_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/gcc.exe", "CMAKE_CXX_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/g++.exe", - "CMAKE_MAKE_PROGRAM": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/ninja.exe", "FLUIDSIM_BUILD_DESKTOP": "ON", "BUILD_TESTING": "ON" } diff --git a/README.md b/README.md index c83b7e5..c919830 100644 --- a/README.md +++ b/README.md @@ -123,18 +123,27 @@ The primary source is a linear compact-monopole approximation, `p(r,t) = rho_ambient * (d volume_flow/dt)/(4*pi*r)`. Mouth volume flow comes from the boundary face velocity and area. Fluid conservation retains the actual mass flux; hot/cold density changes are excluded from the acoustic source. -Intake and exhaust have separate static -positions, fractional-sample propagation delays, inverse-distance spreading, -and left/right ear paths. Playback calibration is 200 Pa per device full scale; +Intake and exhaust sources sit at the open ends of the same world routes used +to draw the pipes, including both outlets of dual exhausts. The desktop camera +is the listener: orbit, pan and zoom change its position and ear orientation. +Left and right ears are 18 cm apart, with separate fractional-sample travel +delays at 343 m/s and inverse-distance spreading. Camera updates smooth over +20 ms while retaining propagation history and reflection filter state. +Playback calibration is 200 Pa per device full scale; this is not calibrated speaker SPL. A second-order 6 kHz monitoring filter reduces sharp sample-to-sample transients without saturation or synthetic bangs. A roughly 3.5 Hz device DC blocker and final PCM clipping protection remain. -Use 1x playback for acoustic evaluation. +Use 1x playback for acoustic evaluation. Toggle **G** off to hear the full +distance-related loudness change; automatic gain otherwise compensates for it. The default environment is a nearby wall. Press **R** to cycle dry, field, wall, woodland, and tunnel. Ground/wall image paths provide early reflections; the live eight-line feedback-delay network supplies a scene-dependent late -tail. Woodland scatterers and the hard-surface tunnel images are approximate. +tail. Early reflections follow the listener geometry, with plane reflections +disabled when the listener crosses to the other side. The diffuse tail receives +a delayed centre signal rather than summing the ears, avoiding ear-delay comb +filtering in the reverb input. Woodland scatterers and the hard-surface tunnel +images are approximate. These are listening profiles, not validated acoustics. Mechanical sounds are not implemented. Open pipe mouths now use a causal unflanged radiation boundary with frequency- @@ -149,7 +158,9 @@ for capture commands, current renderer details and limitations. Launching `fluidsim_desktop` without `--scenario` opens a small scenario menu. Choose the twin, YZ125, Saab B205, or American V8 card, or select with Up/Down and press Enter. -Escape closes the menu. Simulation and audio start after selection. Passing +Escape returns from a running scenario to the menu, stopping its simulation and +audio so another scenario can be selected. Escape closes the menu at the category +screen. Simulation and audio start after selection. Passing `--scenario twin`, `--scenario two-stroke`, `--scenario b205`, or `--scenario v8` bypasses the menu; `--scene` and `--exhaust` options also apply when choosing through the menu. @@ -353,11 +364,12 @@ catalyst, cold-start temperature model or injector electrical model. Idle control can still stall under excessive load and does not impose crankshaft speed or add artificial sustaining torque. Existing combustion/thermal models remain approximations; ECU changes do not establish their physical accuracy. -The two-stroke uses a fixed mechanical carb slide stop instead of RPM-controlled -idle throttle or idle ignition trim. Each carb scenario config sets -`TwoStrokeConfig::idle_slide_opening` (normalized 0..1); the default is 0.045, -or 4.5% slide travel. Pedal demand above that stop opens the slide further. -The resulting idle RPM depends on the simulation and load. Its +The two-stroke uses a fixed mechanical carb slide stop. With the pedal released, +the ECU trims ignition timing around the configured idle RPM instead of moving +the carb stop. Each carb scenario config sets +`TwoStrokeConfig::idle_slide_opening` (normalized 0..1); the catalog 125 cc +scenarios now use a calibrated 3% stop. Pedal demand above that stop opens the +slide further. The resulting idle RPM depends on the simulation and load. Its reed is a damped lumped cantilever model with finite opening time and seating contacts. Cylinder and crankcase moving-volume work use adiabatic energy changes. Jet dimensions are effective flow calibrations rather than measured Mikuni @@ -492,7 +504,8 @@ equal 400 mm x 45 mm secondaries, then a 50 mm exhaust. Pairings are 1+2 / 3+4 and 5+7 / 6+8 with this simulation's bank numbering and firing order. Two T junctions and a 780 mm x 38 mm tube form the H-pipe. Small configurable port-coupled junction volumes are used by the V8. The intake plenum also remains a storage volume. -`V8ExhaustConfig` configures primary length/diameter and enables the H crossover; +The TOML `primaries` pipe group configures primary length/diameter, while +`[junctions] h_pipe` enables the H crossover; pass it as the fourth `FourStrokeScenario` constructor argument. The renderer routes the primary pairs into Y merges, then the final Y collectors and H-pipe, using local pipe diameter and pressure colors without collector boxes. @@ -505,16 +518,25 @@ The previous header optimization measurements applied to the earlier finite-volu The startup browser now scrolls with the mouse wheel, Up/Down, Page Up/Down, Home and End. It adapts to the window size and shows a scroll indicator. Click an example or press Enter to start it. The examples are defined in -`ScenarioCatalog.hpp`, grouped into categories with selectable variants. These are configuration comparisons of +`engines/*.toml`, loaded at startup and grouped into categories with selectable variants. These are configuration comparisons of the existing engine models, not measurements of additional production engines. The V8 currently uses port-coupled finite-volume fittings, configured by -`V8ExhaustConfig::junction_volume` (default 50 cc per fitting). They use the +`JunctionConfig::junction_volume` (default 50 cc per fitting). They use the existing conservative component/port exchange and have no volume boxes in the renderer. The earlier zero-storage Rusanov junction remains available for comparison through `BoundarySystem::add_junction`, but is not used by the V8. The 4-2-1 piping and the H crossover geometry remain the same. +### TOML engine definitions + +Every engine is defined in its own file under `engines/`. Copy an existing +`.toml` file, change its `id`, name and calibration, then restart to load it. +No rebuild is required when editing definitions in the active directory. +Unknown keys, invalid dimensions, malformed timing and duplicate IDs produce +a file-specific startup error. See [the engine configuration guide](docs/ENGINE_CONFIG.md) +for units, directory discovery, sections and pipe topology ordering. + ### Engine families and inspired builds The browser opens at engine categories; click a category to see variants, then @@ -531,7 +553,7 @@ estimated calibrations and simplified common-plenum/exhaust networks: they are not factory replicas, measured torque curves, or turbocharged configurations. The 8.0 L big-block variant transports carburetor fuel through its intake into -the cylinders. Its fixed idle stop is configured with `EngineConfig::idle_opening`. +the cylinders. Its fixed idle stop is configured with `[ecu] idle_base_opening` in its TOML file. It reuses the lumped carb model with scaled effective jets; multiple carb barrels, floats and cylinder-specific fuel distribution are not resolved. Larger engine geometry requires its own mixture and idle calibration. @@ -544,5 +566,56 @@ chamber is a geometry experiment, not a guaranteed RPM or power improvement. Launch a specific variant directly with `--example big-carb`, `--example flat6`, `--example fifty` or another catalog ID. Carb jet scaling is configurable per -engine (`EngineConfig::carb_jet_scale`, `carb_pilot_scale`, or -`TwoStrokeConfig::jet_scale`). +engine: four-strokes use effective jet diameters in `[fuel]`; two-strokes +use jet multipliers in `[two_stroke]`. + +### Pulse-preserving custom exhausts + +The custom inline three, F20 four, inline five and inline six examples use +unequal-length primaries, 50 cc junction collectors and secondary pipes rather +than a single large mixing volume. Their pipe damping matches the undamped Saab +path. Inline fours/fives/sixes use physical firing orders so collector grouping +preserves pulse timing; combustion remains even-fire. + +The custom big-block V8 uses the American V8's cross-plane 1-5-4-2-6-3-7-8 firing +order and 4-2-1 headers with an H crossover, retaining its own displacement and +carburetor. V10/V12 examples retain separate banks through junction collectors, +secondary pipes and two radiating outlets, including both outlets in the spatial +audio renderer. The existing Saab, American V8, single and twin presets retain +their exhaust paths. + +Capture any catalog entry at controlled RPM for comparison: + +```powershell +./out/build/windows-gcc/fluidsim_audio_capture.exe --example v12 --rpm 3000 --warmup 0.6 --seconds 2 --throttle 0.55 --scene dry --out out/audio/v12 +``` + +See [custom exhaust verification](verification/CUSTOM_EXHAUST_CHARACTER.md) for +fixed-RPM comparison results. + +### Carburetor calibration + +All seven two-stroke catalog variants and both carbureted four-strokes have +separate pilot, main-jet and needle-taper calibrations. Progressive pilot metering +fills the 5% throttle transition after lowering the 125 idle stops. The smaller four-stroke +single (`single-450`) now uses a 40 mm carburetor, transported fuel and milder +355/565-degree intake and 145/370-degree exhaust timing. The 650 cc single keeps +EFI. The big-block idle opening is 7%; the straight-pipe 125 uses 3%. The racing +125 and its chamber variants use the calibrated 3% mechanical slide stop. + +Needle annulus area is interpolated continuously across slide positions; liquid +fuel still comes from the simulated jet pressure drop and float bowl. There is +no AFR feedback or cylinder-side fuel correction. The displayed carb AFR is the +uncapped ratio of filtered air flow to delivered fuel flow. + +`fluidsim_carb_calibration --check` tests every carbureted catalog scenario at +idle, 5%, 10%, 15%, 25%, 50%, 75% and full pedal on an imposed-speed dyno, then +releases the dyno for an idle run. It fails on AFR outside the calibration +bounds, stalling, nonfinite RPM/AFR, pipe positivity repairs, a changed racing +125 stop, or loss of transported carb fuel in the 450 single. Run it through +`ctest --preset windows -R carb_calibration --output-on-failure`. + +The tool also prints CSV diagnostics: run without arguments for the four main +operating points, or use `--sweep single-450` for a denser sweep of one scenario. +See [carb calibration results](verification/CARB_CALIBRATION.md) for operating +conditions, AFR values and the distinction between carb and recirculated charge. diff --git a/cmake/CopyEngineConfigs.cmake b/cmake/CopyEngineConfigs.cmake new file mode 100644 index 0000000..ce53931 --- /dev/null +++ b/cmake/CopyEngineConfigs.cmake @@ -0,0 +1,14 @@ +file(MAKE_DIRECTORY "${DESTINATION}") +file(GLOB definitions "${SOURCE}/*.toml") +foreach(definition IN LISTS definitions) + get_filename_component(name "${definition}" NAME) + configure_file("${definition}" "${DESTINATION}/${name}" COPYONLY) +endforeach() +# Remove only stale definitions in the build's asset directory. +file(GLOB old_definitions "${DESTINATION}/*.toml") +foreach(definition IN LISTS old_definitions) + get_filename_component(name "${definition}" NAME) + if(NOT EXISTS "${SOURCE}/${name}") + file(REMOVE "${definition}") + endif() +endforeach() diff --git a/docs/ENGINE_CONFIG.md b/docs/ENGINE_CONFIG.md new file mode 100644 index 0000000..e2e4eb2 --- /dev/null +++ b/docs/ENGINE_CONFIG.md @@ -0,0 +1,126 @@ +# Engine definitions + +Each `engines/*.toml` file defines one engine and its default vehicle/load +calibration. Files are loaded once when the program starts. Add a file, give it +a unique `id`, then restart to see it in the browser. Editing a definition does +not require recompiling the engine simulator. + +Start by copying a similar engine, such as `single-450.toml`, `five.toml`, +`v8-h.toml` or `yz-racing.toml`. Existing definitions contain their effective +calibration values, including the previous carb and idle tunes. + +```toml +schema_version = 1 +id = "my-single" +name = "My four-stroke single" +category = "Singles / four-stroke" +description = "My calibration" +order = 100 +cycle = "four_stroke" +``` + +`id`, `name`, `category`, `cycle` and `schema_version` are required. IDs use +lowercase letters, digits, `-` and `_`. `order` controls menu order; ties sort by +ID. Duplicate IDs, unknown keys, unsupported versions, incorrect types and +invalid physical values stop loading with a diagnostic identifying the file +and field. TOML syntax errors also report their location. + +## File discovery and distribution + +The first available directory is used: + +1. `FLUIDSIM_ENGINE_DIR`, when set. A missing override directory is an error. +2. `engines/` under the working directory. +3. `engines/` beside the executable on Windows and Linux. +4. The source checkout's `engines/` directory as a development fallback. + +Definitions are cached for the process lifetime. The selected directory is +loaded as one catalog; it is not merged with the bundled files. In a repository +working directory, source definitions take priority over build copies. + +CMake synchronizes the definitions into the build output when building an +executable. Distribute that `engines/` directory with the executable. The TOML +parser is vendored, so loading definitions needs no network connection. + +```powershell +$env:FLUIDSIM_ENGINE_DIR = "C:\MyEngines" +.\fluidsim_desktop.exe --example my-single +``` + +## Units and sections + +File keys state dimensional units. `_mm` is millimetres, `_litres` is litres, +`_cc` is cubic centimetres, `_deg` is crank degrees, `_s` is seconds, `_nm` is +newton metres and `_kg_m2` is rotational inertia. The loader converts lengths +and volumes to SI units. Openings and mixture/taper multipliers are normalized +fractions, not percentages: `0.03` means 3%. + +Four-stroke definitions use these sections: + +| Section | Contents | +| --- | --- | +| `[engine]` | Cylinder count, layout, bank angle, phases, intake volumes, throttle, startup, load, and exhaust topology | +| `[cylinder]` | Bore/stroke/rod, compression, valve geometry and timing, combustion and heat transfer | +| `[crankshaft]` | Inertia and mechanical friction | +| `[ecu]` | Idle, actuator response, ignition curve, limiter and overrun thresholds | +| `[vehicle]` | Vehicle mass, gears, clutch, wheel geometry and road resistance | +| `[fuel]` | `system = "injection"` or `"carburetor"`, jet diameters and taper/film calibration | +| `[junctions]` | Fitting volume and H crossover selection | +| `[silencers]` | Connecting pipes, resonator and muffler profiles, when used | +| `[[pipes]]` | Ordered pipe groups for the selected exhaust topology | + +`engine.phase_degrees` contains one phase per cylinder in cylinder index order, +within `[0,720)`. It records the actual crank offsets rather than a firing-order +label. `ecu.ignition_map` is an array of `[RPM, degrees BTDC]` pairs with strictly +increasing RPM. Negative advance means ignition after TDC. + +For a carbureted four-stroke, use `ecu.idle_control = "fixed"` and set +`ecu.idle_base_opening`. Its limiter and overrun control cut ignition while the +carburetor continues supplying fuel. Jet diameters in `[fuel]` are effective +diameters after the previous displacement and jet multipliers have been applied. + +Two-stroke definitions use `[two_stroke]`, `[ecu]` and `[vehicle]`. +`two_stroke.idle_slide_opening` sets the mechanical stop; +`idle_spark_gain` and `idle_minimum_advance_deg` set its idle spark trim. +Two-stroke jet multipliers retain their existing meaning. +`chamber_sections_mm` contains six `[length, inlet diameter, outlet diameter]` +sections: header, two diffusers, belly, baffle cone and stinger. + +Required cylinder geometry must be explicit. Optional calibration fields use +the typed config defaults; a misspelled field is always an error. Existing files +include explicit values so their behavior does not depend on future defaults. + +## Pipe groups and exhaust topologies + +These definitions configure the existing topology builders. They do not yet +describe arbitrary graphs of cylinders and junctions. A group's `id` must be +unique; `count` repeats its dimensions across adjacent pipes. + +```toml +[[pipes]] +id = "primaries" +count = 8 +diameter_mm = 38.0 +length_mm = 750.0 +cells = 15 +``` + +The order is significant because the topology builder connects the expanded +groups to fixed cylinder and junction ports: + +| `engine.topology` | Pipe order after expansion | +| --- | --- | +| `collector` | Intake, N runners, N primaries, one tailpipe | +| `staged` | Intake, N runners, N primaries, outlet pipe(s), secondary pipe(s) | +| `american_v8` | Intake, 8 runners, 8 primaries, 4 secondaries, 2 bank collectors, 2 tailpipes, H crossover | + +The staged inline triple has one secondary; other staged layouts have two. +Staged V engines have two independent outlets. The American topology requires +eight cylinders in a V layout; setting `junctions.h_pipe = false` closes both +ends of the unused crossover and leaves the banks independent. + +A collector outlet can instead use `profile = "silenced"`. Its dimensions and +mesh then come from `[silencers]`, `[silencers.resonator]` and +`[silencers.muffler]`; omit the group's uniform pipe dimensions. The loader +checks expanded pipe counts and mesh limits before construction. Pipe positions +continue to use the shared scene routes for rendering and sound sources. diff --git a/engines/big-carb.toml b/engines/big-carb.toml new file mode 100644 index 0000000..3ce177e --- /dev/null +++ b/engines/big-carb.toml @@ -0,0 +1,163 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "big-carb" +name = "8.0 litre / carbureted big-block" +category = "V8 / big-block" +description = "Cross-plane firing / American 4-2-1 + H / carb fuel transport" +order = 29 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 90.0 +collector_volume_litres = 2.0 +cylinders = 8 +initial_rpm = 1120.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 8.0 +starter_torque_nm = 201.1122 +throttle_diameter_mm = 85.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 114.3 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 9.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 36.576 +exhaust_valve_lift_mm = 10.287 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 43.434 +intake_valve_lift_mm = 11.43 +misfire_probability = 0.0 +rod_mm = 176.4 +stroke_mm = 98.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 32.17795 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.03217795 +inertia_kg_m2 = 0.9653385 + +[ecu] +idle_base_opening = 0.07 +idle_control = "fixed" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 800.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = false +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1769.787 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "carburetor" +pilot_jet_diameter_mm = 0.2337673 +main_jet_diameter_mm = 4.524528 +needle_jet_diameter_mm = 4.656493 +needle_low_scale = 0.6 +needle_mid_scale = 0.65 +needle_off_idle_scale = 0.964 +needle_quarter_scale = 0.64 +needle_tip_scale = 0.782 +needle_transition_scale = 0.89 +needle_upper_scale = 0.76 +bowl_capacity_kg = 0.1 +bowl_refill_kg_per_s = 0.1 + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 15 +diameter_mm = 38.0 +length_mm = 750.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/busso.toml b/engines/busso.toml new file mode 100644 index 0000000..612870b --- /dev/null +++ b/engines/busso.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "busso" +name = "Busso-inspired / 3.0 V6" +category = "V6" +description = "Estimated concept calibration / EFI" +order = 26 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 1.5 +cylinders = 6 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0] +pipe_damping = 1.0 +plenum_volume_litres = 6.0 +starter_torque_nm = 73.97478 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 93.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 29.76 +exhaust_valve_lift_mm = 8.370001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 35.34 +intake_valve_lift_mm = 9.3 +misfire_probability = 0.0 +rod_mm = 130.68 +stroke_mm = 72.6 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 11.83596 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01183596 +inertia_kg_m2 = 0.3550789 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 650.978 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 6 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 6 +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/ej20.toml b/engines/ej20.toml new file mode 100644 index 0000000..4f8ab72 --- /dev/null +++ b/engines/ej20.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "ej20" +name = "EJ20-inspired / 2.0 flat four NA" +category = "Boxer / flat four" +description = "Estimated concept calibration / EFI" +order = 27 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 180.0 +collector_volume_litres = 1.0 +cylinders = 4 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "boxer" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 540.0, 360.0, 180.0] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 49.85708 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 92.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 29.44 +exhaust_valve_lift_mm = 8.280001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 34.96 +intake_valve_lift_mm = 9.2 +misfire_probability = 0.0 +rod_mm = 135.0 +stroke_mm = 75.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 7.977133 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.007977134 +inertia_kg_m2 = 0.239314 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 438.7423 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 4 +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/f20.toml b/engines/f20.toml new file mode 100644 index 0000000..d8a93dc --- /dev/null +++ b/engines/f20.toml @@ -0,0 +1,159 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "f20" +name = "F20C-inspired / 2.0 high-rev four" +category = "Inline four / specials" +description = "Unequal primaries / small junctions / staged collectors / EFI" +order = 22 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 1.0 +cylinders = 4 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 180.0, 540.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 49.9353 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 87.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 27.84 +exhaust_valve_lift_mm = 7.83 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 33.06 +intake_valve_lift_mm = 8.7 +misfire_probability = 0.0 +rod_mm = 151.2 +stroke_mm = 84.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 7.989648 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.007989648 +inertia_kg_m2 = 0.2396894 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 9000.0 +rev_resume_rpm = 8750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 439.4306 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "tailpipe_9" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "secondary_10" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_11" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/fifty.toml b/engines/fifty.toml new file mode 100644 index 0000000..0db4f89 --- /dev/null +++ b/engines/fifty.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "fifty" +name = "50 cc / little screamer" +category = "Two-stroke / 50 cc" +description = "Concept single / transported carb fuel" +order = 15 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.004006997 +bore_mm = 40.0 +carburetor_diameter_mm = 19.0 +crankcase_clearance_cc = 60.10495 +exhaust = "racing" +exhaust_cell_length_mm = 15.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 26.54048 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.045 +idle_spark_gain = 0.05 +jet_scale = 0.5 +main_jet_scale = 1.6 +minimum_burn_s = 0.0005897885 +needle_jet_scale = 1.0 +needle_low_scale = 2.72 +needle_mid_scale = 0.8 +needle_off_idle_scale = 12.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 5.0 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.7125 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.0 +rod_mm = 81.09593 +stroke_mm = 39.8 +transfer_duration_deg = 130.0 +transfer_width_mm = 36.86178 +trapped_compression = 8.2 +chamber_sections_mm = [[117.9577, 26.54048, 26.54048], [132.7024, 26.54048, 44.23414], [140.0748, 44.23414, 77.40974], [95.84063, 77.40974, 77.40974], [154.8195, 77.40974, 16.21918], [176.9366, 16.21918, 16.21918]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/five.toml b/engines/five.toml new file mode 100644 index 0000000..aa86e23 --- /dev/null +++ b/engines/five.toml @@ -0,0 +1,165 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "five" +name = "Audi-inspired / 2.2 inline five" +category = "Inline five" +description = "Unequal primaries / small junctions / staged collectors / EFI" +order = 23 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 1.25 +cylinders = 5 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 576.0, 144.0, 432.0, 288.0] +pipe_damping = 0.0 +plenum_volume_litres = 5.0 +starter_torque_nm = 55.65237 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 81.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 25.92 +exhaust_valve_lift_mm = 7.29 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 30.78 +intake_valve_lift_mm = 8.1 +misfire_probability = 0.0 +rod_mm = 155.52 +stroke_mm = 86.4 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 8.90438 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.008904381 +inertia_kg_m2 = 0.2671314 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 489.7409 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 5 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_5" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "tailpipe_11" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "secondary_12" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_13" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/fivehundred.toml b/engines/fivehundred.toml new file mode 100644 index 0000000..111664d --- /dev/null +++ b/engines/fivehundred.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "fivehundred" +name = "500 cc / big single" +category = "Two-stroke / 500 cc" +description = "Concept single / transported carb fuel" +order = 16 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.04002316 +bore_mm = 86.0 +carburetor_diameter_mm = 41.0 +crankcase_clearance_cc = 600.3475 +exhaust = "racing" +exhaust_cell_length_mm = 15.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 57.15746 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.045 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.7504 +minimum_burn_s = 0.001270166 +needle_jet_scale = 1.06 +needle_low_scale = 3.04 +needle_mid_scale = 0.8 +needle_off_idle_scale = 12.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 5.0 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.75 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.0 +rod_mm = 174.6478 +stroke_mm = 86.0 +transfer_duration_deg = 130.0 +transfer_width_mm = 79.38537 +trapped_compression = 8.2 +chamber_sections_mm = [[254.0332, 57.15746, 57.15746], [285.7873, 57.15746, 95.26245], [301.6644, 95.26245, 166.7093], [206.402, 166.7093, 166.7093], [333.4185, 166.7093, 34.92956], [381.0498, 34.92956, 34.92956]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/flat6.toml b/engines/flat6.toml new file mode 100644 index 0000000..cccdf0c --- /dev/null +++ b/engines/flat6.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "flat6" +name = "911-inspired / 3.0 flat six" +category = "Boxer / flat six" +description = "Estimated concept calibration / EFI" +order = 28 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 180.0 +collector_volume_litres = 1.5 +cylinders = 6 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "boxer" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0] +pipe_damping = 1.0 +plenum_volume_litres = 6.0 +starter_torque_nm = 74.85159 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 95.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 30.4 +exhaust_valve_lift_mm = 8.55 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 36.1 +intake_valve_lift_mm = 9.5 +misfire_probability = 0.0 +rod_mm = 126.72 +stroke_mm = 70.4 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 11.97625 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01197626 +inertia_kg_m2 = 0.3592876 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 658.694 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 6 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 6 +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/ltwin.toml b/engines/ltwin.toml new file mode 100644 index 0000000..3ff4a97 --- /dev/null +++ b/engines/ltwin.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "ltwin" +name = "916-inspired / 90-degree V twin" +category = "Twins / V" +description = "Estimated concept calibration / EFI" +order = 20 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 90.0 +collector_volume_litres = 0.5 +cylinders = 2 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0] +pipe_damping = 1.0 +plenum_volume_litres = 2.0 +starter_torque_nm = 22.90127 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 94.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 30.08 +exhaust_valve_lift_mm = 8.46 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 35.72 +intake_valve_lift_mm = 9.4 +misfire_probability = 0.0 +rod_mm = 118.8 +stroke_mm = 66.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 3.664203 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.003664203 +inertia_kg_m2 = 0.1099261 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 201.5311 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 240.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 2 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 2 +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/rb26.toml b/engines/rb26.toml new file mode 100644 index 0000000..b95bf34 --- /dev/null +++ b/engines/rb26.toml @@ -0,0 +1,171 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "rb26" +name = "RB26-inspired / 2.6 inline six NA" +category = "Inline six" +description = "Unequal primaries / small junctions / staged collectors / EFI" +order = 24 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 1.5 +cylinders = 6 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 6.0 +starter_torque_nm = 64.21635 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 86.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 27.52 +exhaust_valve_lift_mm = 7.740001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 32.68 +intake_valve_lift_mm = 8.6 +misfire_probability = 0.0 +rod_mm = 132.66 +stroke_mm = 73.7 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 10.27462 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01027462 +inertia_kg_m2 = 0.3082384 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 565.1038 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 6 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_5" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_6" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe_13" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "secondary_14" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_15" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/saab-chambered.toml b/engines/saab-chambered.toml new file mode 100644 index 0000000..e48527d --- /dev/null +++ b/engines/saab-chambered.toml @@ -0,0 +1,152 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "saab-chambered" +name = "Saab B205 / chambered" +category = "Inline four / Saab" +description = "Expansion resonator and two-chamber rear muffler" +order = 4 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 0.6 +cylinders = 4 +initial_rpm = 1200.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 200.0 +minimum_substeps = 2 +phase_degrees = [0.0, 180.0, 540.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 3.0 +starter_torque_nm = 65.0 +throttle_diameter_mm = 52.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 90.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 8.8 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 2 +exhaust_valve_diameter_mm = 28.0 +exhaust_valve_lift_mm = 8.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 2 +intake_valve_diameter_mm = 32.0 +intake_valve_lift_mm = 8.5 +misfire_probability = 0.0 +rod_mm = 153.0 +stroke_mm = 78.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 12.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.012 +inertia_kg_m2 = 0.18 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -10.0 +idle_rpm = 850.0 +idle_spark_gain = 0.025 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1300.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 6500.0 +rev_resume_rpm = 6250.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 300.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.32 +final_drive_ratio = 4.05 +frontal_area = 2.2 +gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593] +gravity = 9.80665 +mass_kg = 1675.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 3.6 +wheel_radius_mm = 317.0 + +[fuel] +system = "injection" + +[silencers] +cell_length_mm = 35.0 +connecting_length_mm = 800.0 +diameter_mm = 60.0 +enabled = true +inlet_length_mm = 1400.0 +outlet_length_mm = 350.0 + +[silencers.resonator] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 400.0 +loss_coefficient = 0.1 +passage_diameter_mm = 60.0 +resistance = 2.0 +type = "expansion" +volume_litres = 6.0 + +[silencers.muffler] +chambers = 2 +core_diameter_mm = 60.0 +length_mm = 650.0 +loss_coefficient = 0.35 +passage_diameter_mm = 60.0 +resistance = 4.0 +type = "chambered" +volume_litres = 18.0 + +[[pipes]] +id = "intake" +cells = 8 +diameter_mm = 60.0 +length_mm = 150.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 10 +diameter_mm = 32.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 4 +cells = 10 +diameter_mm = 34.0 +length_mm = 280.0 + +[[pipes]] +id = "tailpipe" +profile = "silenced" diff --git a/engines/saab-expansion.toml b/engines/saab-expansion.toml new file mode 100644 index 0000000..a9405c5 --- /dev/null +++ b/engines/saab-expansion.toml @@ -0,0 +1,152 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "saab-expansion" +name = "Saab B205 / expansion silencers" +category = "Inline four / Saab" +description = "Two single expansion chambers, same body volumes" +order = 5 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 0.6 +cylinders = 4 +initial_rpm = 1200.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 200.0 +minimum_substeps = 2 +phase_degrees = [0.0, 180.0, 540.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 3.0 +starter_torque_nm = 65.0 +throttle_diameter_mm = 52.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 90.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 8.8 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 2 +exhaust_valve_diameter_mm = 28.0 +exhaust_valve_lift_mm = 8.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 2 +intake_valve_diameter_mm = 32.0 +intake_valve_lift_mm = 8.5 +misfire_probability = 0.0 +rod_mm = 153.0 +stroke_mm = 78.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 12.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.012 +inertia_kg_m2 = 0.18 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -10.0 +idle_rpm = 850.0 +idle_spark_gain = 0.025 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1300.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 6500.0 +rev_resume_rpm = 6250.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 300.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.32 +final_drive_ratio = 4.05 +frontal_area = 2.2 +gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593] +gravity = 9.80665 +mass_kg = 1675.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 3.6 +wheel_radius_mm = 317.0 + +[fuel] +system = "injection" + +[silencers] +cell_length_mm = 35.0 +connecting_length_mm = 800.0 +diameter_mm = 60.0 +enabled = true +inlet_length_mm = 1400.0 +outlet_length_mm = 350.0 + +[silencers.resonator] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 400.0 +loss_coefficient = 0.1 +passage_diameter_mm = 60.0 +resistance = 2.0 +type = "expansion" +volume_litres = 6.0 + +[silencers.muffler] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 650.0 +loss_coefficient = 0.35 +passage_diameter_mm = 60.0 +resistance = 4.0 +type = "expansion" +volume_litres = 18.0 + +[[pipes]] +id = "intake" +cells = 8 +diameter_mm = 60.0 +length_mm = 150.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 10 +diameter_mm = 32.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 4 +cells = 10 +diameter_mm = 34.0 +length_mm = 280.0 + +[[pipes]] +id = "tailpipe" +profile = "silenced" diff --git a/engines/saab-open.toml b/engines/saab-open.toml new file mode 100644 index 0000000..22fbc2e --- /dev/null +++ b/engines/saab-open.toml @@ -0,0 +1,152 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "saab-open" +name = "Saab B205 / open exhaust" +category = "Inline four / Saab" +description = "Silencers bypassed; naturally aspirated baseline" +order = 6 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 0.6 +cylinders = 4 +initial_rpm = 1200.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 200.0 +minimum_substeps = 2 +phase_degrees = [0.0, 180.0, 540.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 3.0 +starter_torque_nm = 65.0 +throttle_diameter_mm = 52.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 90.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 8.8 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 2 +exhaust_valve_diameter_mm = 28.0 +exhaust_valve_lift_mm = 8.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 2 +intake_valve_diameter_mm = 32.0 +intake_valve_lift_mm = 8.5 +misfire_probability = 0.0 +rod_mm = 153.0 +stroke_mm = 78.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 12.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.012 +inertia_kg_m2 = 0.18 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -10.0 +idle_rpm = 850.0 +idle_spark_gain = 0.025 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1300.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 6500.0 +rev_resume_rpm = 6250.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 300.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.32 +final_drive_ratio = 4.05 +frontal_area = 2.2 +gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593] +gravity = 9.80665 +mass_kg = 1675.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 3.6 +wheel_radius_mm = 317.0 + +[fuel] +system = "injection" + +[silencers] +cell_length_mm = 35.0 +connecting_length_mm = 800.0 +diameter_mm = 60.0 +enabled = false +inlet_length_mm = 1400.0 +outlet_length_mm = 350.0 + +[silencers.resonator] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 400.0 +loss_coefficient = 0.1 +passage_diameter_mm = 60.0 +resistance = 2.0 +type = "straight_through" +volume_litres = 6.0 + +[silencers.muffler] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 650.0 +loss_coefficient = 0.35 +passage_diameter_mm = 60.0 +resistance = 4.0 +type = "straight_through" +volume_litres = 18.0 + +[[pipes]] +id = "intake" +cells = 8 +diameter_mm = 60.0 +length_mm = 150.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 10 +diameter_mm = 32.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 4 +cells = 10 +diameter_mm = 34.0 +length_mm = 280.0 + +[[pipes]] +id = "tailpipe" +profile = "silenced" diff --git a/engines/saab-straight.toml b/engines/saab-straight.toml new file mode 100644 index 0000000..f908d9b --- /dev/null +++ b/engines/saab-straight.toml @@ -0,0 +1,152 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "saab-straight" +name = "Saab B205 / straight-through" +category = "Inline four / Saab" +description = "6 L resonator and 18 L straight-through rear muffler" +order = 3 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 0.6 +cylinders = 4 +initial_rpm = 1200.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 200.0 +minimum_substeps = 2 +phase_degrees = [0.0, 180.0, 540.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 3.0 +starter_torque_nm = 65.0 +throttle_diameter_mm = 52.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 90.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 8.8 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 2 +exhaust_valve_diameter_mm = 28.0 +exhaust_valve_lift_mm = 8.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 2 +intake_valve_diameter_mm = 32.0 +intake_valve_lift_mm = 8.5 +misfire_probability = 0.0 +rod_mm = 153.0 +stroke_mm = 78.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 12.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.012 +inertia_kg_m2 = 0.18 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -10.0 +idle_rpm = 850.0 +idle_spark_gain = 0.025 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1300.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 6500.0 +rev_resume_rpm = 6250.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 300.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.32 +final_drive_ratio = 4.05 +frontal_area = 2.2 +gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593] +gravity = 9.80665 +mass_kg = 1675.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 3.6 +wheel_radius_mm = 317.0 + +[fuel] +system = "injection" + +[silencers] +cell_length_mm = 35.0 +connecting_length_mm = 800.0 +diameter_mm = 60.0 +enabled = true +inlet_length_mm = 1400.0 +outlet_length_mm = 350.0 + +[silencers.resonator] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 400.0 +loss_coefficient = 0.1 +passage_diameter_mm = 60.0 +resistance = 2.0 +type = "straight_through" +volume_litres = 6.0 + +[silencers.muffler] +chambers = 1 +core_diameter_mm = 60.0 +length_mm = 650.0 +loss_coefficient = 0.35 +passage_diameter_mm = 60.0 +resistance = 4.0 +type = "straight_through" +volume_litres = 18.0 + +[[pipes]] +id = "intake" +cells = 8 +diameter_mm = 60.0 +length_mm = 150.0 + +[[pipes]] +id = "runners" +count = 4 +cells = 10 +diameter_mm = 32.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 4 +cells = 10 +diameter_mm = 34.0 +length_mm = 280.0 + +[[pipes]] +id = "tailpipe" +profile = "silenced" diff --git a/engines/single-450.toml b/engines/single-450.toml new file mode 100644 index 0000000..0bff131 --- /dev/null +++ b/engines/single-450.toml @@ -0,0 +1,136 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "single-450" +name = "450 cc / carbureted four-stroke thumper" +category = "Singles / four-stroke" +description = "40 mm carb / mild valve timing / fixed idle stop" +order = 17 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 0.25 +cylinders = 1 +initial_rpm = 1960.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0] +pipe_damping = 1.0 +plenum_volume_litres = 1.0 +starter_torque_nm = 11.21926 +throttle_diameter_mm = 40.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 96.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 145.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 30.72 +exhaust_valve_lift_mm = 8.640001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 565.0 +intake_open_deg = 355.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 36.48 +intake_valve_lift_mm = 9.6 +misfire_probability = 0.0 +rod_mm = 111.6 +stroke_mm = 62.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 1.795081 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.001795081 +inertia_kg_m2 = 0.05385243 + +[ecu] +idle_base_opening = 0.045 +idle_control = "fixed" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1400.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = false +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 98.72945 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 240.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "carburetor" +pilot_jet_diameter_mm = 0.09349564 +main_jet_diameter_mm = 2.091829 +needle_jet_diameter_mm = 2.15284 +needle_low_scale = 0.944 +needle_mid_scale = 0.564 +needle_off_idle_scale = 0.995 +needle_quarter_scale = 0.835 +needle_tip_scale = 0.97 +needle_transition_scale = 1.6 +needle_upper_scale = 0.65 +bowl_capacity_kg = 0.1 +bowl_refill_kg_per_s = 0.1 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/single-650.toml b/engines/single-650.toml new file mode 100644 index 0000000..8593c48 --- /dev/null +++ b/engines/single-650.toml @@ -0,0 +1,124 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "single-650" +name = "650 cc / big thumper" +category = "Singles / four-stroke" +description = "Estimated concept calibration / EFI" +order = 18 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 0.25 +cylinders = 1 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0] +pipe_damping = 1.0 +plenum_volume_litres = 1.0 +starter_torque_nm = 16.29701 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 100.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 32.0 +exhaust_valve_lift_mm = 9.000001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 38.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 149.4 +stroke_mm = 83.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 2.607522 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.002607522 +inertia_kg_m2 = 0.07822565 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 6500.0 +rev_resume_rpm = 6250.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 143.4137 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 240.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/six-long.toml b/engines/six-long.toml new file mode 100644 index 0000000..46598f8 --- /dev/null +++ b/engines/six-long.toml @@ -0,0 +1,171 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "six-long" +name = "3.0 litre / long-stroke inline six" +category = "Inline six" +description = "Unequal primaries / small junctions / staged collectors / EFI" +order = 25 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 1.5 +cylinders = 6 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0] +pipe_damping = 0.0 +plenum_volume_litres = 6.0 +starter_torque_nm = 74.93359 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 86.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 27.52 +exhaust_valve_lift_mm = 7.740001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 32.68 +intake_valve_lift_mm = 8.6 +misfire_probability = 0.0 +rod_mm = 154.8 +stroke_mm = 86.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 11.98938 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01198938 +inertia_kg_m2 = 0.3596812 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 659.4156 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 6 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_5" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_6" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe_13" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "secondary_14" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_15" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/triple.toml b/engines/triple.toml new file mode 100644 index 0000000..b159641 --- /dev/null +++ b/engines/triple.toml @@ -0,0 +1,147 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "triple" +name = "1.0 litre / inline triple" +category = "Inline three" +description = "Unequal primaries / small junctions / staged collectors / EFI" +order = 21 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 0.75 +cylinders = 3 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 480.0, 240.0] +pipe_damping = 0.0 +plenum_volume_litres = 3.0 +starter_torque_nm = 24.99852 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 79.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 25.28 +exhaust_valve_lift_mm = 7.110001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 30.02 +intake_valve_lift_mm = 7.900001 +misfire_probability = 0.0 +rod_mm = 122.4 +stroke_mm = 68.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 3.999763 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.003999764 +inertia_kg_m2 = 0.1199929 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 219.987 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 3 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe_7" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "secondary_8" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 diff --git a/engines/twin-270.toml b/engines/twin-270.toml new file mode 100644 index 0000000..fad1f9c --- /dev/null +++ b/engines/twin-270.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "twin-270" +name = "700 cc / 270-degree parallel twin" +category = "Twins / parallel" +description = "Estimated concept calibration / EFI" +order = 19 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 0.5 +cylinders = 2 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0] +pipe_damping = 1.0 +plenum_volume_litres = 2.0 +starter_torque_nm = 17.09026 +throttle_diameter_mm = 65.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 80.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 25.6 +exhaust_valve_lift_mm = 7.2 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 30.4 +intake_valve_lift_mm = 8.0 +misfire_probability = 0.0 +rod_mm = 122.4 +stroke_mm = 68.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 2.734442 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.002734442 +inertia_kg_m2 = 0.08203326 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 150.3943 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 240.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 2 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primaries" +count = 2 +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 diff --git a/engines/twin.toml b/engines/twin.toml new file mode 100644 index 0000000..f10e92c --- /dev/null +++ b/engines/twin.toml @@ -0,0 +1,126 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "twin" +name = "600 cc parallel twin" +category = "Twins / parallel" +description = "EFI motorcycle baseline" +order = 0 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 0.15 +cylinders = 2 +initial_rpm = 2000.0 +initialize_phased_charge = false +layout = "inline" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 180.0] +pipe_damping = 1.0 +plenum_volume_litres = 1.0 +starter_torque_nm = 18.0 +throttle_diameter_mm = 26.0 +topology = "collector" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 75.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 11.5 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 26.0 +exhaust_valve_lift_mm = 7.5 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 30.0 +intake_valve_lift_mm = 8.0 +misfire_probability = 0.0 +rod_mm = 112.0 +stroke_mm = 68.0 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 5.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.003 +inertia_kg_m2 = 0.04 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1800.0 +idle_spark_gain = 0.0 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 10000.0 +rev_resume_rpm = 9750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 80.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 160.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[[pipes]] +id = "intake" +cells = 10 +diameter_mm = 27.5 +length_mm = 60.0 + +[[pipes]] +id = "runners" +count = 2 +cells = 10 +diameter_mm = 27.5 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 2 +cells = 6 +diameter_mm = 28.0 +length_mm = 200.0 + +[[pipes]] +id = "tailpipe" +cells = 10 +diameter_mm = 28.0 +length_mm = 400.0 diff --git a/engines/v10.toml b/engines/v10.toml new file mode 100644 index 0000000..74bc51e --- /dev/null +++ b/engines/v10.toml @@ -0,0 +1,201 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v10" +name = "5.0 litre / even-fire V10" +category = "V10" +description = "Separate five-cylinder banks / junction collectors / dual outlets" +order = 30 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 72.0 +collector_volume_litres = 2.5 +cylinders = 10 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 576.0, 432.0, 288.0, 144.0, 648.0001, 504.0, 360.0, 216.0, 72.00003] +pipe_damping = 0.0 +plenum_volume_litres = 10.0 +starter_torque_nm = 124.9751 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 92.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 29.44 +exhaust_valve_lift_mm = 8.280001 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 34.96 +intake_valve_lift_mm = 9.2 +misfire_probability = 0.0 +rod_mm = 135.36 +stroke_mm = 75.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 19.99601 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01999602 +inertia_kg_m2 = 0.5998804 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1099.781 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 10 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_5" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_6" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_7" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_8" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_9" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_10" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "tailpipe_21" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "tailpipe_22" +cells = 28 +diameter_mm = 60.0 +length_mm = 1380.0 + +[[pipes]] +id = "secondary_23" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_24" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/v12.toml b/engines/v12.toml new file mode 100644 index 0000000..f06d3b5 --- /dev/null +++ b/engines/v12.toml @@ -0,0 +1,213 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v12" +name = "250-inspired / 3.0 V12" +category = "V12" +description = "Separate six-cylinder banks / junction collectors / dual outlets" +order = 31 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 60.0 +collector_volume_litres = 3.0 +cylinders = 12 +initial_rpm = 1400.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 30.0 +minimum_substeps = 1 +phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0, 660.0, 540.0, 420.0, 300.0, 180.0, 59.99998] +pipe_damping = 0.0 +plenum_volume_litres = 12.0 +starter_torque_nm = 73.83022 +throttle_diameter_mm = 65.0 +topology = "staged" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 73.0 +burn_duration_deg = 60.0 +burn_start_deg = 5.0 +compression = 10.0 +energy_variation = 0.05 +exhaust_close_deg = 390.0 +exhaust_open_deg = 130.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 23.36 +exhaust_valve_lift_mm = 6.57 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 590.0 +intake_open_deg = 340.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 27.74 +intake_valve_lift_mm = 7.3 +misfire_probability = 0.0 +rod_mm = 110.0 +stroke_mm = 58.8 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 11.81283 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.01181284 +inertia_kg_m2 = 0.354385 + +[ecu] +idle_base_opening = 0.05 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 1000.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [15000.0, 40.0]] +overrun_cut_rpm = 3500.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 2400.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 649.7059 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.7 +final_drive_ratio = 2.7 +frontal_area = 0.65 +gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98] +gravity = 9.80665 +mass_kg = 1400.0 +primary_reduction = 1.8 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 4 +diameter_mm = 75.0 +length_mm = 200.0 + +[[pipes]] +id = "runners" +count = 12 +cells = 5 +diameter_mm = 35.0 +length_mm = 250.0 + +[[pipes]] +id = "primary_1" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_2" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_3" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_4" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_5" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_6" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_7" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_8" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_9" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "primary_10" +cells = 11 +diameter_mm = 38.0 +length_mm = 520.0 + +[[pipes]] +id = "primary_11" +cells = 12 +diameter_mm = 38.0 +length_mm = 585.0 + +[[pipes]] +id = "primary_12" +cells = 13 +diameter_mm = 38.0 +length_mm = 650.0 + +[[pipes]] +id = "tailpipe_25" +cells = 30 +diameter_mm = 60.0 +length_mm = 1500.0 + +[[pipes]] +id = "tailpipe_26" +cells = 28 +diameter_mm = 60.0 +length_mm = 1380.0 + +[[pipes]] +id = "secondary_27" +cells = 9 +diameter_mm = 48.0 +length_mm = 420.0 + +[[pipes]] +id = "secondary_28" +cells = 11 +diameter_mm = 48.0 +length_mm = 540.0 diff --git a/engines/v8-dual.toml b/engines/v8-dual.toml new file mode 100644 index 0000000..95b264a --- /dev/null +++ b/engines/v8-dual.toml @@ -0,0 +1,151 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v8-dual" +name = "American 5.0 / independent banks" +category = "V8 / American 5.0" +description = "Same 4-2-1 headers with H crossover disconnected" +order = 8 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 1.5 +cylinders = 8 +initial_rpm = 1000.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 300.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 180.0 +throttle_diameter_mm = 65.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 101.6 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 7.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 37.0 +exhaust_valve_lift_mm = 10.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 45.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 129.0 +stroke_mm = 76.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 22.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.025 +inertia_kg_m2 = 1.1 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 750.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1100.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 3000.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = false +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 15 +diameter_mm = 38.0 +length_mm = 750.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/v8-h.toml b/engines/v8-h.toml new file mode 100644 index 0000000..8bfd0ac --- /dev/null +++ b/engines/v8-h.toml @@ -0,0 +1,151 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v8-h" +name = "American 5.0 / 4-2-1 + H" +category = "V8 / American 5.0" +description = "750 mm primaries, 50 cc junctions, H crossover" +order = 7 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 1.5 +cylinders = 8 +initial_rpm = 1000.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 300.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 180.0 +throttle_diameter_mm = 65.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 101.6 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 7.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 37.0 +exhaust_valve_lift_mm = 10.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 45.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 129.0 +stroke_mm = 76.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 22.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.025 +inertia_kg_m2 = 1.1 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 750.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1100.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 3000.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 15 +diameter_mm = 38.0 +length_mm = 750.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/v8-large.toml b/engines/v8-large.toml new file mode 100644 index 0000000..c780a01 --- /dev/null +++ b/engines/v8-large.toml @@ -0,0 +1,151 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v8-large" +name = "American 5.0 / larger primaries" +category = "V8 / American 5.0" +description = "42 mm x 750 mm primaries with H crossover" +order = 11 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 1.5 +cylinders = 8 +initial_rpm = 1000.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 300.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 180.0 +throttle_diameter_mm = 65.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 101.6 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 7.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 37.0 +exhaust_valve_lift_mm = 10.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 45.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 129.0 +stroke_mm = 76.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 22.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.025 +inertia_kg_m2 = 1.1 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 750.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1100.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 3000.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 15 +diameter_mm = 42.0 +length_mm = 750.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/v8-long.toml b/engines/v8-long.toml new file mode 100644 index 0000000..774d912 --- /dev/null +++ b/engines/v8-long.toml @@ -0,0 +1,151 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v8-long" +name = "American 5.0 / long headers" +category = "V8 / American 5.0" +description = "900 mm primaries with H crossover" +order = 10 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 1.5 +cylinders = 8 +initial_rpm = 1000.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 300.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 180.0 +throttle_diameter_mm = 65.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 101.6 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 7.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 37.0 +exhaust_valve_lift_mm = 10.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 45.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 129.0 +stroke_mm = 76.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 22.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.025 +inertia_kg_m2 = 1.1 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 750.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1100.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 3000.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 18 +diameter_mm = 38.0 +length_mm = 900.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/v8-short.toml b/engines/v8-short.toml new file mode 100644 index 0000000..d2562eb --- /dev/null +++ b/engines/v8-short.toml @@ -0,0 +1,151 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "v8-short" +name = "American 5.0 / short headers" +category = "V8 / American 5.0" +description = "600 mm primaries with H crossover" +order = 9 +cycle = "four_stroke" + +[engine] +bank_angle_deg = 56.0 +collector_volume_litres = 1.5 +cylinders = 8 +initial_rpm = 1000.0 +initialize_phased_charge = true +layout = "v" +maximum_load_torque_nm = 300.0 +minimum_substeps = 1 +phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998] +pipe_damping = 1.0 +plenum_volume_litres = 4.0 +starter_torque_nm = 180.0 +throttle_diameter_mm = 65.0 +topology = "american_v8" + +[cylinder] +afr = 14.7 +ambient_temperature_k = 300.0 +bore_mm = 101.6 +burn_duration_deg = 65.0 +burn_start_deg = 5.0 +compression = 7.0 +energy_variation = 0.05 +exhaust_close_deg = 370.0 +exhaust_open_deg = 140.0 +exhaust_valve_count = 1 +exhaust_valve_diameter_mm = 37.0 +exhaust_valve_lift_mm = 10.0 +fuel_lhv_j_per_kg = 4.4e+07 +heat_transfer = 100.0 +intake_close_deg = 570.0 +intake_open_deg = 350.0 +intake_valve_count = 1 +intake_valve_diameter_mm = 45.0 +intake_valve_lift_mm = 10.0 +misfire_probability = 0.0 +rod_mm = 129.0 +stroke_mm = 76.2 +wall_area = 0.02 +wiebe_a = 5.0 +wiebe_m = 2.0 + +[crankshaft] +friction_constant_nm = 22.0 +friction_quadratic_nm_s2 = 0.0 +friction_viscous_nm_s = 0.025 +inertia_kg_m2 = 1.1 + +[ecu] +idle_base_opening = 0.08 +idle_control = "feedback" +idle_ki = 4e-05 +idle_kp = 0.0001 +idle_max_opening = 0.25 +idle_minimum_advance_deg = -5.0 +idle_rpm = 750.0 +idle_spark_gain = 0.02 +ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]] +overrun_cut_rpm = 2000.0 +overrun_fuel_cut_enabled = true +overrun_ignition_cut_enabled = false +overrun_resume_rpm = 1100.0 +power_afr = 12.8 +rev_limit_enabled = true +rev_limit_fuel_cut_enabled = true +rev_limit_rpm = 7000.0 +rev_resume_rpm = 6750.0 +stoichiometric_afr = 14.7 +throttle_closing_rate = 0.0 +throttle_closing_time_constant_s = 0.0 +throttle_rate = 1000.0 +throttle_time_constant_s = 0.001 + +[vehicle] +air_density = 1.225 +clutch_capacity = 1100.0 +clutch_engagement_rate = 8.0 +drag_coefficient = 0.4 +final_drive_ratio = 3.623529 +frontal_area = 2.0 +gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68] +gravity = 9.80665 +mass_kg = 3000.0 +primary_reduction = 1.0 +rolling_resistance = 0.015 +rotational_inertia_kg_m2 = 1.2 +wheel_radius_mm = 320.0 + +[fuel] +system = "injection" + +[junctions] +h_pipe = true +junction_volume_cc = 50.0 + +[[pipes]] +id = "intake" +cells = 3 +diameter_mm = 70.0 +length_mm = 180.0 + +[[pipes]] +id = "runners" +count = 8 +cells = 2 +diameter_mm = 38.0 +length_mm = 120.0 + +[[pipes]] +id = "primaries" +count = 8 +cells = 12 +diameter_mm = 38.0 +length_mm = 600.0 + +[[pipes]] +id = "secondaries" +count = 4 +cells = 8 +diameter_mm = 45.0 +length_mm = 400.0 + +[[pipes]] +id = "bank_collectors" +count = 2 +cells = 11 +diameter_mm = 50.0 +length_mm = 550.0 + +[[pipes]] +id = "tailpipes" +count = 2 +cells = 25 +diameter_mm = 50.0 +length_mm = 1250.0 + +[[pipes]] +id = "crossover" +cells = 16 +diameter_mm = 38.0 +length_mm = 780.0 diff --git a/engines/yz-fat.toml b/engines/yz-fat.toml new file mode 100644 index 0000000..0d56eac --- /dev/null +++ b/engines/yz-fat.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "yz-fat" +name = "125 / wide belly chamber" +category = "Two-stroke / 125 cc" +description = "Estimated chamber geometry / same 125 cc engine" +order = 14 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.01 +bore_mm = 54.0 +carburetor_diameter_mm = 38.1 +crankcase_clearance_cc = 150.0 +exhaust = "racing" +exhaust_cell_length_mm = 10.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 36.0 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.03 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.6 +minimum_burn_s = 0.0008 +needle_jet_scale = 1.0 +needle_low_scale = 2.9 +needle_mid_scale = 0.8 +needle_off_idle_scale = 24.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 6.5 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.5475 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.55 +rod_mm = 110.0 +stroke_mm = 54.5 +transfer_duration_deg = 130.0 +transfer_width_mm = 50.0 +trapped_compression = 8.2 +chamber_sections_mm = [[160.0, 42.48, 42.48], [180.0, 42.48, 70.8], [190.0, 70.8, 123.9], [130.0, 123.9, 123.9], [210.0, 123.9, 25.96], [240.0, 25.96, 25.96]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/yz-racing.toml b/engines/yz-racing.toml new file mode 100644 index 0000000..e6f45de --- /dev/null +++ b/engines/yz-racing.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "yz-racing" +name = "YZ125 / expansion chamber" +category = "Two-stroke / 125 cc" +description = "Racing pipe / calibrated carb / 3% idle stop" +order = 1 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.01 +bore_mm = 54.0 +carburetor_diameter_mm = 38.1 +crankcase_clearance_cc = 150.0 +exhaust = "racing" +exhaust_cell_length_mm = 10.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 36.0 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.03 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.6 +minimum_burn_s = 0.0008 +needle_jet_scale = 1.0 +needle_low_scale = 2.9 +needle_mid_scale = 0.8 +needle_off_idle_scale = 24.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 6.5 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.5475 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.55 +rod_mm = 110.0 +stroke_mm = 54.5 +transfer_duration_deg = 130.0 +transfer_width_mm = 50.0 +trapped_compression = 8.2 +chamber_sections_mm = [[160.0, 36.0, 36.0], [180.0, 36.0, 60.0], [190.0, 60.0, 105.0], [130.0, 105.0, 105.0], [210.0, 105.0, 22.0], [240.0, 22.0, 22.0]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/yz-short.toml b/engines/yz-short.toml new file mode 100644 index 0000000..c4510e1 --- /dev/null +++ b/engines/yz-short.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "yz-short" +name = "125 / short high-RPM chamber" +category = "Two-stroke / 125 cc" +description = "Estimated chamber geometry / same 125 cc engine" +order = 13 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.01 +bore_mm = 54.0 +carburetor_diameter_mm = 38.1 +crankcase_clearance_cc = 150.0 +exhaust = "racing" +exhaust_cell_length_mm = 10.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 36.0 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.03 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.6 +minimum_burn_s = 0.0008 +needle_jet_scale = 1.0 +needle_low_scale = 2.9 +needle_mid_scale = 0.8 +needle_off_idle_scale = 24.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 6.5 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.5475 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.55 +rod_mm = 110.0 +stroke_mm = 54.5 +transfer_duration_deg = 130.0 +transfer_width_mm = 50.0 +trapped_compression = 8.2 +chamber_sections_mm = [[131.2, 36.0, 36.0], [147.6, 36.0, 60.0], [155.8, 60.0, 105.0], [106.6, 105.0, 105.0], [172.2, 105.0, 22.0], [196.8, 22.0, 22.0]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/yz-straight.toml b/engines/yz-straight.toml new file mode 100644 index 0000000..0046307 --- /dev/null +++ b/engines/yz-straight.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "yz-straight" +name = "YZ125 / straight exhaust" +category = "Two-stroke / 125 cc" +description = "Straight exhaust / calibrated pilot and needle / 3% idle stop" +order = 2 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.01 +bore_mm = 54.0 +carburetor_diameter_mm = 38.1 +crankcase_clearance_cc = 150.0 +exhaust = "straight" +exhaust_cell_length_mm = 10.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 36.0 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.03 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.6 +minimum_burn_s = 0.0008 +needle_jet_scale = 1.0 +needle_low_scale = 2.9 +needle_mid_scale = 0.8 +needle_off_idle_scale = 50.0 +needle_quarter_scale = 1.42 +needle_tip_scale = 6.0 +needle_transition_scale = 14.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.534 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.1 +rod_mm = 110.0 +stroke_mm = 54.5 +transfer_duration_deg = 130.0 +transfer_width_mm = 50.0 +trapped_compression = 8.2 +chamber_sections_mm = [[160.0, 36.0, 36.0], [180.0, 36.0, 60.0], [190.0, 60.0, 105.0], [130.0, 105.0, 105.0], [210.0, 105.0, 22.0], [240.0, 22.0, 22.0]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/engines/yz-torque.toml b/engines/yz-torque.toml new file mode 100644 index 0000000..f0e20ea --- /dev/null +++ b/engines/yz-torque.toml @@ -0,0 +1,68 @@ +# Engine definition. Dimensional keys state their units. +schema_version = 1 +id = "yz-torque" +name = "125 / long torque chamber" +category = "Two-stroke / 125 cc" +description = "Estimated chamber geometry / same 125 cc engine" +order = 12 +cycle = "two_stroke" + +[two_stroke] +initial_rpm = 3000.0 +starter_torque_nm = 8.0 +maximum_load_torque_nm = 10.0 +crankshaft_inertia_kg_m2 = 0.01 +bore_mm = 54.0 +carburetor_diameter_mm = 38.1 +crankcase_clearance_cc = 150.0 +exhaust = "racing" +exhaust_cell_length_mm = 10.0 +exhaust_duration_deg = 190.0 +exhaust_width_mm = 36.0 +friction_bar = 0.4 +friction_bar_per_mps = 0.09 +friction_bar_per_mps2 = 0.0009 +idle_minimum_advance_deg = -10.0 +idle_slide_opening = 0.03 +idle_spark_gain = 0.05 +jet_scale = 1.0 +main_jet_scale = 1.6 +minimum_burn_s = 0.0008 +needle_jet_scale = 1.0 +needle_low_scale = 2.9 +needle_mid_scale = 0.8 +needle_off_idle_scale = 24.0 +needle_quarter_scale = 1.4 +needle_tip_scale = 6.5 +needle_transition_scale = 9.0 +needle_upper_scale = 0.76 +pilot_jet_scale = 0.5475 +pilot_tip_scale = 1.0 +pilot_transition_scale = 1.55 +rod_mm = 110.0 +stroke_mm = 54.5 +transfer_duration_deg = 130.0 +transfer_width_mm = 50.0 +trapped_compression = 8.2 +chamber_sections_mm = [[188.8, 36.0, 36.0], [212.4, 36.0, 60.0], [224.2, 60.0, 105.0], [153.4, 105.0, 105.0], [247.8, 105.0, 22.0], [283.2, 22.0, 22.0]] + +[ecu] +idle_control = "fixed" +idle_rpm = 3000.0 +idle_max_opening = 0.3 +throttle_time_constant_s = 0.0002 +throttle_rate = 5000.0 +rev_limit_enabled = false +rev_limit_fuel_cut_enabled = false +overrun_cut_rpm = 5000.0 +overrun_resume_rpm = 3500.0 +overrun_fuel_cut_enabled = false +overrun_ignition_cut_enabled = true +ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]] + +[vehicle] +primary_reduction = 3.368421 +final_drive_ratio = 3.769231 +mass_kg = 170 +wheel_radius_mm = 331.3 +gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556] diff --git a/include/FluidSim/AudioProcessing.hpp b/include/FluidSim/AudioProcessing.hpp index d40ca86..26e66a7 100644 --- a/include/FluidSim/AudioProcessing.hpp +++ b/include/FluidSim/AudioProcessing.hpp @@ -4,6 +4,7 @@ namespace fluidsim::port { struct AudioFrame { float left{}, right{}; }; struct Vec3 { float x{}, y{}, z{}; }; +struct AudioListener { Vec3 position{}, right{1,0,0}; }; // Simple flow-derivative source used by the Helmholtz and pulse scenarios. class SoundProcessor { diff --git a/include/FluidSim/Carburetor.hpp b/include/FluidSim/Carburetor.hpp index 2865f5d..b69f05b 100644 --- a/include/FluidSim/Carburetor.hpp +++ b/include/FluidSim/Carburetor.hpp @@ -5,21 +5,30 @@ namespace fluidsim::port { -// Lumped slide-carburetor model for the crankcase-scavenged two-stroke. The +// Lumped carburetor shared by two-stroke and four-stroke scenarios. The // jets use liquid-orifice flow from a float bowl into the measured venturi // depression. Delivered fuel is injected into the downstream fluid volume and // is then transported as a conserved species; this class never doses a cylinder. -class Carburetor { -public: +struct CarburetorConfig { float fuel_density{740.0f}; float pilot_jet_diameter{0.00040f}, main_jet_diameter{0.00150f}; float needle_jet_diameter{0.00190f}; + // Needle annulus area corrections at configured slide positions; jets meter + // liquid fuel from measured pressure, without AFR feedback. + float needle_low_scale{1.f}, needle_mid_scale{1.f}; + float needle_off_idle_scale{1.f}, needle_transition_scale{1.f}, needle_tip_scale{1.f}; + float needle_quarter_scale{1.f}, needle_upper_scale{1.f}; + float pilot_transition_scale{1.f}, pilot_tip_scale{1.f}; float pilot_discharge_coefficient{0.62f}, main_discharge_coefficient{0.72f}; float bowl_head_pressure{}; float film_fraction{0.28f}, film_time_constant{0.11f}; float bowl_capacity{0.008f}, bowl_refill_rate{0.0025f}; // kg, kg/s float overrun_rpm{4200.0f}, overrun_exit_rpm{3300.0f}; +}; + +class Carburetor : public CarburetorConfig { +public: explicit Carburetor(float throat_diameter = 0.0381f); // air_mass_flow is positive from atmosphere toward the engine. diff --git a/include/FluidSim/Desktop/DesktopUi.hpp b/include/FluidSim/Desktop/DesktopUi.hpp index c9d8de2..22502fb 100644 --- a/include/FluidSim/Desktop/DesktopUi.hpp +++ b/include/FluidSim/Desktop/DesktopUi.hpp @@ -1,5 +1,5 @@ #pragma once -#include "FluidSim/Scenario.hpp" +#include "FluidSim/ScenarioSnapshot.hpp" #include "FluidSim/DynoCurve.hpp" #include "FluidSim/Desktop/PedalControl.hpp" #include "FluidSim/Desktop/EngineRenderer3D.hpp" @@ -22,6 +22,7 @@ public: void nudge_pedal(float amount) {pedal_.set(pedal_.value()+amount);} void update_pedal(float dt,bool raise,bool lower) {if(!pedal_drag_)pedal_.advance(dt,raise,lower);} bool handle_event(const sf::Event&,sf::Vector2u); + AudioListener listener(const ScenarioSnapshot&,sf::Vector2u); void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&); private: DynoCurve dyno_; diff --git a/include/FluidSim/Desktop/EngineRenderer3D.hpp b/include/FluidSim/Desktop/EngineRenderer3D.hpp index 112375f..903e7a4 100644 --- a/include/FluidSim/Desktop/EngineRenderer3D.hpp +++ b/include/FluidSim/Desktop/EngineRenderer3D.hpp @@ -1,5 +1,5 @@ #pragma once -#include "FluidSim/Scenario.hpp" +#include "FluidSim/ScenarioSnapshot.hpp" #include "FluidSim/Desktop/OrbitCamera.hpp" #include #include diff --git a/include/FluidSim/Desktop/OrbitCamera.hpp b/include/FluidSim/Desktop/OrbitCamera.hpp index 37c2334..5657719 100644 --- a/include/FluidSim/Desktop/OrbitCamera.hpp +++ b/include/FluidSim/Desktop/OrbitCamera.hpp @@ -7,6 +7,13 @@ namespace fluidsim::port::desktop { struct OrbitCamera { Vec3 target{.12f,.25f,0}; float yaw{-28},pitch{24},distance{2.2f}; + AudioListener listener(float distance_scale=1, float target_offset_x=0) const { + const float y=yaw*.017453293f,p=pitch*.017453293f,d=distance*distance_scale; + // Inverse of the renderer's pitch/yaw world-to-view rotation. + return {{target.x+target_offset_x-std::sin(y)*std::cos(p)*d, + target.y+std::sin(p)*d,target.z+std::cos(y)*std::cos(p)*d}, + {std::cos(y),0,std::sin(y)}}; + } void reset(float aspect) {target={.12f,.25f,0};yaw=-28;pitch=24;distance=std::max(.65f,1.6f/aspect)/.414214f;} void orbit(sf::Vector2i delta) {yaw+=delta.x*.35f;pitch=std::clamp(pitch+delta.y*.3f,-75.f,80.f);} void pan(sf::Vector2i delta,float height) { diff --git a/include/FluidSim/DesktopScenario.hpp b/include/FluidSim/DesktopScenario.hpp index 1303947..116131f 100644 --- a/include/FluidSim/DesktopScenario.hpp +++ b/include/FluidSim/DesktopScenario.hpp @@ -1,24 +1,28 @@ #pragma once #include "FluidSim/Scenario.hpp" +#include "FluidSim/ScenarioCatalog.hpp" #include "FluidSim/TwoStrokeScenario.hpp" #include #include namespace fluidsim::port { -// Shared desktop controls/snapshot contract. Two-stroke access is serialized; -// the twin already protects its own simulation state. +// Shared desktop controls and snapshots. Each simulation owns its state lock. class DesktopScenario { public: - explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, V8ExhaustConfig v8 = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)) { + explicit DesktopScenario(const ScenarioExample& e) + : DesktopScenario(e.kind,e.two,e.exhaust,e.junctions,e.engine,e.title) {} + explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)) { if (kind == ScenarioKind::TwoStroke) two_ = std::make_unique(44100, config); - else - twin_ = std::make_unique(44100, kind == ScenarioKind::SaabB205 ? - FourStrokePreset::SaabB205 : kind == ScenarioKind::AmericanV8 ? FourStrokePreset::AmericanV8 : FourStrokePreset::Twin, exhaust, v8,engine); + else { + if(!engine.cylinders)engine=preset_engine_definition(kind==ScenarioKind::SaabB205? + FourStrokePreset::SaabB205:kind==ScenarioKind::AmericanV8?FourStrokePreset::AmericanV8:FourStrokePreset::Twin).engine; + four_ = std::make_unique(44100,std::move(engine),exhaust,junctions); + } } AudioFrame process() { - if (twin_) - return twin_->process(); + if (four_) + return four_->process(); const std::lock_guard lock(mutex_); return two_->process(); } @@ -33,8 +37,11 @@ public: void set_audio_scene(OutdoorScene scene) { visit([&](auto& scenario) { scenario.set_audio_scene(scene); }); } + void set_listener(AudioListener listener) { + visit([&](auto& scenario) { scenario.set_listener(listener); }); + } ScenarioSnapshot snapshot() const { - if (twin_) { auto s=twin_->snapshot();s.example_name=name_;return s; } + if (four_) { auto s=four_->snapshot();s.example_name=name_;return s; } const std::lock_guard lock(mutex_); const auto source = two_->snapshot(); ScenarioSnapshot s; @@ -65,6 +72,7 @@ public: s.valve1_exhaust_area = source.exhaust_area; s.reed_open = source.reed_open; s.fuel_cut = source.fuel_cut; + s.ignition_cut = source.ignition_cut; s.idle_control = two_->ecu.idle_active(); s.rev_limit1 = two_->ecu.rev_limited(); s.audio_scene = source.audio_scene; @@ -77,11 +85,11 @@ public: private: template void visit(Function&& function) { const std::lock_guard lock(mutex_); - if (twin_) function(*twin_); + if (four_) function(*four_); else function(*two_); } std::string name_; - std::unique_ptr twin_; + std::unique_ptr four_; std::unique_ptr two_; mutable std::mutex mutex_; }; diff --git a/include/FluidSim/Ecu.hpp b/include/FluidSim/Ecu.hpp index ee15dfb..9ed97a3 100644 --- a/include/FluidSim/Ecu.hpp +++ b/include/FluidSim/Ecu.hpp @@ -4,8 +4,7 @@ namespace fluidsim::port { // Ideal sensors and injectors; calibration parameters are not engine measurements. -class Ecu { -public: +struct EcuConfig { enum class IdleControl { Feedback, FixedOpening }; IdleControl idle_control{IdleControl::Feedback}; struct IgnitionPoint { float rpm, degrees_btdc; }; @@ -18,18 +17,25 @@ public: float throttle_closing_time_constant{}, throttle_closing_rate{}; float rev_limit_rpm{10000.0f}, rev_resume_rpm{9750.0f}; bool rev_limit_enabled{true}; + bool rev_limit_fuel_cut_enabled{true}; float overrun_cut_rpm{3500.0f}, overrun_resume_rpm{2400.0f}; bool overrun_fuel_cut_enabled{true}; + bool overrun_ignition_cut_enabled{false}; float idle_spark_gain{0.0f}, idle_minimum_advance{-5.0f}; // degrees/RPM, degrees BTDC float stoichiometric_afr{14.7f}, power_afr{12.8f}; +}; + +class Ecu : public EcuConfig { +public: void step(float dt, float pedal, float rpm); float fuel_for_air(float trapped_air_mass) const; float throttle_opening() const { return opening_; } float throttle_target() const { return target_; } float target_afr() const { return afr_; } float spark_advance() const { return spark_; } - bool fuel_cut() const { return limiter_ || (overrun_ && overrun_fuel_cut_enabled) || fault_; } + bool fuel_cut() const { return (limiter_ && rev_limit_fuel_cut_enabled) || (overrun_ && overrun_fuel_cut_enabled) || fault_; } + bool ignition_cut() const { return limiter_ || (overrun_ && overrun_ignition_cut_enabled) || fault_; } bool rev_limited() const { return limiter_; } bool idle_active() const { return idle_; } bool sensor_fault() const { return fault_; } diff --git a/include/FluidSim/Engine.hpp b/include/FluidSim/Engine.hpp index 2db37e1..f93630d 100644 --- a/include/FluidSim/Engine.hpp +++ b/include/FluidSim/Engine.hpp @@ -10,12 +10,16 @@ namespace fluidsim::port { class Ecu; -class Crankshaft { -public: - float omega{400.0f * 2.0f * pi / 60.0f}; - float angle{}, previous_angle{}, inertia{0.2f}; +struct CrankshaftConfig { + float inertia{0.2f}; float friction_constant{}, friction_viscous{}; float friction_quadratic{}; // Nm / (rad/s)^2, dissipative windage term +}; + +class Crankshaft : public CrankshaftConfig { +public: + float omega{400.0f * 2.0f * pi / 60.0f}; + float angle{}, previous_angle{}; float load_torque{}, drivetrain_torque{}; float average_power{}, average_torque{}; @@ -28,18 +32,22 @@ private: float power_sum_{}, torque_sum_{}; }; -class EngineCylinder : public Component { -public: - Ecu* ecu{}; // Optional ideal electronic fuel/ignition controller. - bool transported_fuel{}; // Upstream carb meters the fuel species. +struct CylinderConfig { + float intake_open{340}, intake_close{590}, exhaust_open{130}, exhaust_close{390}; float bore{0.075f}, stroke{0.068f}, rod{0.112f}, compression{11.5f}; float intake_valve_diameter{0.030f}, exhaust_valve_diameter{0.028f}; int intake_valve_count{1}, exhaust_valve_count{1}; float intake_valve_lift{0.005f}, exhaust_valve_lift{0.005f}; - float phase{}; float spark_advance{20.0f}, wiebe_a{5.0f}, wiebe_m{2.0f}, burn_duration{60.0f}, burn_start{5.0f}; float afr{14.7f}, fuel_lhv{44.0e6f}, energy_variation{0.05f}, misfire_probability{}; float wall_area{0.02f}, heat_transfer{100.0f}, ambient_temperature{300.0f}; +}; + +class EngineCylinder : public Component, public CylinderConfig { +public: + Ecu* ecu{}; + bool transported_fuel{}; + float phase{}; bool rev_limit_enabled{}; float rev_limit_rpm{7500.0f}, rev_limit_cut_time{0.05f}, rev_limit_hysteresis{200.0f}; float air_mass{}, exhaust_mass{}, energy{}, volume{}, pressure{101325.0f}; @@ -78,7 +86,6 @@ protected: class FourStrokeCylinder final : public EngineCylinder { public: - float intake_open{340.0f}, intake_close{590.0f}, exhaust_open{130.0f}, exhaust_close{390.0f}; float valve_area(float cycle_degrees, bool inlet) const override; void pre_step(Crankshaft& crank, float dt, float throttle) override; }; diff --git a/include/FluidSim/EngineConfig.hpp b/include/FluidSim/EngineConfig.hpp new file mode 100644 index 0000000..7a6cc13 --- /dev/null +++ b/include/FluidSim/EngineConfig.hpp @@ -0,0 +1,53 @@ +#pragma once +#include "FluidSim/Engine.hpp" +#include "FluidSim/Ecu.hpp" +#include "FluidSim/Vehicle.hpp" +#include "FluidSim/Carburetor.hpp" +#include +#include + +namespace fluidsim::port { +enum class ScenarioKind { Twin, TwoStroke, SaabB205, AmericanV8 }; +enum class FourStrokePreset { Twin, SaabB205, AmericanV8 }; +enum class EngineLayout { Inline, V, Boxer }; +enum class ExhaustTopology { Collector, Staged, AmericanV8 }; + +struct PipeConfig { + std::string id; + float diameter{}, length{}; // metres + int cells{}, count{1}; + bool silenced{}; +}; + +// Parsed calibration only. Runtime state belongs to the scenario components. +// Dimensional fields use SI units after loading. +struct EngineConfig { + int cylinders{}; + EngineLayout layout{EngineLayout::Inline}; + ExhaustTopology topology{ExhaustTopology::Collector}; + float bank_angle{60}; + CylinderConfig cylinder; + CrankshaftConfig crankshaft; + EcuConfig ecu; + VehicleConfig vehicle; + CarburetorConfig fuel; + bool carbureted{}, initialize_phased_charge{true}; + float initial_rpm{}, starter_torque{}, maximum_load_torque{}; + float plenum_volume{}, collector_volume{}, throttle_diameter{}, pipe_damping{}; + int minimum_substeps{1}; + std::vector phase_degrees; + std::vector pipes; + + bool has_silenced_exhaust() const { for(const auto& p:pipes)if(p.silenced)return true;return false; } + bool american_headers() const { return topology==ExhaustTopology::AmericanV8; } + bool staged_headers() const { return topology==ExhaustTopology::Staged; } + bool dual_outlets() const { return american_headers() || (staged_headers() && layout==EngineLayout::V); } + int header_groups() const { return cylinders==3?1:2; } +}; + +struct JunctionConfig { + bool h_pipe{true}; + float junction_volume{50e-6f}; +}; +void validate_engine_config(const EngineConfig& config,const std::string& context="engine config"); +} diff --git a/include/FluidSim/OutdoorAudio.hpp b/include/FluidSim/OutdoorAudio.hpp index 2408ce4..85f81a0 100644 --- a/include/FluidSim/OutdoorAudio.hpp +++ b/include/FluidSim/OutdoorAudio.hpp @@ -25,7 +25,7 @@ struct OutdoorAudioConfig { float monitor_cutoff_hz{6000.0f}; // Second-order acquisition/rendering filter. }; -// Static geometric direct/first-order reflection paths plus an explicitly +// Moving geometric direct/first-order reflection paths plus an explicitly // approximate diffuse tail. No feedback into the fluid/combustion simulation. class OutdoorAudio { public: @@ -35,6 +35,9 @@ public: AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0); void set_scene(OutdoorScene scene) { requested_scene_.store(scene, std::memory_order_relaxed); } OutdoorScene scene() const { return requested_scene_.load(std::memory_order_relaxed); } + // Call on the simulation thread or under the scenario's simulation lock. + void set_listener(AudioListener listener); + void set_source_positions(const std::array& positions); void set_monitor_filter(bool enabled) { monitor_filter_ = enabled; } // Set before playback/capture. AudioFrame dry_frame() const { return last_dry_; } AudioFrame unfiltered_frame() const { return last_raw_; } @@ -45,8 +48,8 @@ private: float process(float x); }; struct Path { - std::size_t delay_left{}, delay_right{}; - float fraction_left{}, fraction_right{}, gain_left{}, gain_right{}, alpha{1}; + float delay_left{}, delay_right{}, gain_left{}, gain_right{}, alpha{1}; + float target_delay_left{}, target_delay_right{}, target_gain_left{}, target_gain_right{}; float state_left{}, state_right{}; int group{}; // direct, ground, wall, trees, tunnel }; @@ -55,6 +58,7 @@ private: std::vector paths; std::size_t position{}; float previous_flow{}; + float center_delay{}, target_center_delay{}, center_gain{}, target_center_gain{}; LowPass filter; }; int rate_{}; @@ -66,8 +70,10 @@ private: OutdoorExhaustReverb late_reverb_; std::array weights_{1, 0, 0, 0, 0}; float scene_alpha_{}, tail_mix_{}; + float geometry_alpha_{}, reverb_input_{}; AudioFrame last_dry_{}, last_raw_{}; void configure_emitter(Emitter &emitter, Vec3 position); + void update_geometry(); AudioFrame render(Emitter &emitter, float flow, AudioFrame &dry, AudioFrame &raw); }; } // namespace fluidsim::port diff --git a/include/FluidSim/Scenario.hpp b/include/FluidSim/Scenario.hpp index 29ee749..6abe8f0 100644 --- a/include/FluidSim/Scenario.hpp +++ b/include/FluidSim/Scenario.hpp @@ -1,5 +1,7 @@ #pragma once #include "FluidSim/Silencer.hpp" +#include "FluidSim/EngineConfig.hpp" +#include "FluidSim/ScenarioSnapshot.hpp" #include "FluidSim/Carburetor.hpp" #include #include "FluidSim/Junction.hpp" @@ -19,85 +21,24 @@ #include namespace fluidsim::port { +struct ScenarioExample; -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. +// Calibrated four-stroke assembly with independent runners and shared plenums. 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; + explicit FourStrokeScenario(int sample_rate = 44100, FourStrokePreset preset = FourStrokePreset::Twin); + FourStrokeScenario(int sample_rate, const ScenarioExample& definition); + FourStrokeScenario(int sample_rate, EngineConfig engine, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}); const ExhaustConfig exhaust_config; - const V8ExhaustConfig v8_exhaust_config; + const JunctionConfig junction_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; } + int cylinder_count() const { return engine_config.cylinders; } 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_listener(AudioListener listener); void set_pipe_solver(PipeSolver solver); void set_monitor_filter(bool enabled); void set_starter(bool engaged); @@ -115,8 +56,8 @@ public: 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}; + Volume0D intake_plenum; + Junction exhaust_collector; std::vector pipes; float intake_flow{}, exhaust_flow{}; std::uint64_t sample_count{}; @@ -134,7 +75,7 @@ private: 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); + static std::vector create_pipes(const EngineConfig& engine, const ExhaustConfig& exhaust); }; using FourStrokeTwinScenario = FourStrokeScenario; diff --git a/include/FluidSim/ScenarioCatalog.hpp b/include/FluidSim/ScenarioCatalog.hpp index 8b4c018..5a2665b 100644 --- a/include/FluidSim/ScenarioCatalog.hpp +++ b/include/FluidSim/ScenarioCatalog.hpp @@ -1,83 +1,28 @@ #pragma once -#include "FluidSim/Scenario.hpp" +#include "FluidSim/EngineConfig.hpp" +#include "FluidSim/Silencer.hpp" #include "FluidSim/TwoStrokeConfig.hpp" +#include #include +#include + namespace fluidsim::port { struct ScenarioExample { std::string id,title,description; - ScenarioKind kind; + ScenarioKind kind{ScenarioKind::Twin}; TwoStrokeConfig two{}; ExhaustConfig exhaust{}; - V8ExhaustConfig v8{}; + JunctionConfig junctions{}; EngineConfig engine{}; std::string category; + int order{}; }; -inline std::vector scenario_examples() { - std::vector examples; - auto add=[&](const char* id,const char* title,const char* description,ScenarioKind kind)->ScenarioExample& { - examples.push_back({id,title,description,kind,{},{},{},{},{}});return examples.back(); - }; - add("twin","600 cc parallel twin","EFI motorcycle baseline",ScenarioKind::Twin); - add("yz-racing","YZ125 / expansion chamber","Racing pipe, fixed 4.5% carb idle",ScenarioKind::TwoStroke); - add("yz-straight","YZ125 / straight exhaust","Same engine and carb, straight exhaust comparison",ScenarioKind::TwoStroke).two.exhaust=TwoStrokeExhaust::Straight; - add("saab-straight","Saab B205 / straight-through","6 L resonator and 18 L straight-through rear muffler",ScenarioKind::SaabB205); - auto& chambered=add("saab-chambered","Saab B205 / chambered","Expansion resonator and two-chamber rear muffler",ScenarioKind::SaabB205); - chambered.exhaust.resonator.type=SilencerType::ExpansionResonator; - chambered.exhaust.muffler.type=SilencerType::Chambered;chambered.exhaust.muffler.chambers=2; - auto& resonator=add("saab-expansion","Saab B205 / expansion silencers","Two single expansion chambers, same body volumes",ScenarioKind::SaabB205); - resonator.exhaust.resonator.type=SilencerType::ExpansionResonator;resonator.exhaust.muffler.type=SilencerType::ExpansionResonator; - add("saab-open","Saab B205 / open exhaust","Silencers bypassed; naturally aspirated baseline",ScenarioKind::SaabB205).exhaust.enabled=false; - add("v8-h","American 5.0 / 4-2-1 + H","750 mm primaries, 50 cc junctions, H crossover",ScenarioKind::AmericanV8); - add("v8-dual","American 5.0 / independent banks","Same 4-2-1 headers with H crossover disconnected",ScenarioKind::AmericanV8).v8.h_pipe=false; - add("v8-short","American 5.0 / short headers","600 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_length=.60f; - add("v8-long","American 5.0 / long headers","900 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_length=.90f; - add("v8-large","American 5.0 / larger primaries","42 mm x 750 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_diameter=.042f; - for(auto& e:examples) e.category=e.kind==ScenarioKind::TwoStroke?"Two-stroke / 125 cc":e.kind==ScenarioKind::SaabB205?"Inline four / Saab":e.kind==ScenarioKind::AmericanV8?"V8 / American 5.0":"Twins / parallel"; - auto chamber=[&](const char* id,const char* title,float length,float width) { - auto& e=add(id,title,"Estimated chamber geometry / same 125 cc engine",ScenarioKind::TwoStroke); - e.category="Two-stroke / 125 cc"; - for(auto& section:e.two.racing_pipe) {section.length*=length;section.inlet_diameter*=width;section.outlet_diameter*=width;} - }; - chamber("yz-torque","125 / long torque chamber",1.18f,1.f); - chamber("yz-short","125 / short high-RPM chamber",.82f,1.f); - chamber("yz-fat","125 / wide belly chamber",1.f,1.18f); - auto two=[&](const char* id,const char* title,const char* category,float bore,float stroke,float carb) { - auto& e=add(id,title,"Concept single / transported carb fuel",ScenarioKind::TwoStroke);e.category=category; - const float scale=std::cbrt((bore*bore*stroke)/(.054f*.054f*.0545f)); - e.two.jet_scale=bore<.05f?.5f:1.f; - e.two.bore=bore;e.two.stroke=stroke;e.two.rod*=scale;e.two.carburetor_diameter=carb; - e.two.transfer_width*=scale;e.two.exhaust_width*=scale;e.two.crankcase_clearance*=scale*scale*scale; - e.two.minimum_burn_seconds*=scale; - for(auto& section:e.two.racing_pipe) {section.length*=scale;section.inlet_diameter*=scale;section.outlet_diameter*=scale;} - e.two.exhaust_cell_length=.015f; - }; - two("fifty","50 cc / little screamer","Two-stroke / 50 cc",.040f,.0398f,.019f); - two("fivehundred","500 cc / big single","Two-stroke / 500 cc",.086f,.086f,.041f); - auto custom=[&](const char* id,const char* title,const char* category,int count,EngineLayout layout,float bore,float stroke)->ScenarioExample& { - auto& e=add(id,title,"Estimated concept calibration / EFI",ScenarioKind::Twin);e.category=category; - e.engine.cylinders=count;e.engine.layout=layout;e.engine.bore=bore;e.engine.stroke=stroke; - e.engine.rod=std::max(.11f,stroke*1.8f);return e; - }; - custom("single-450","450 cc / four-stroke thumper","Singles / four-stroke",1,EngineLayout::Inline,.096f,.062f); - custom("single-650","650 cc / big thumper","Singles / four-stroke",1,EngineLayout::Inline,.100f,.083f).engine.rev_limit=6500; - auto& twin270=custom("twin-270","700 cc / 270-degree parallel twin","Twins / parallel",2,EngineLayout::Inline,.080f,.068f); - twin270.engine.custom_phases=true;twin270.engine.phase_degrees[1]=450; - auto& ducati=custom("ltwin","916-inspired / 90-degree V twin","Twins / V",2,EngineLayout::V,.094f,.066f); - ducati.engine.bank_angle=90;ducati.engine.custom_phases=true;ducati.engine.phase_degrees[1]=450; - custom("triple","1.0 litre / inline triple","Inline three",3,EngineLayout::Inline,.079f,.068f); - auto& four=custom("f20","F20C-inspired / 2.0 high-rev four","Inline four / specials",4,EngineLayout::Inline,.087f,.084f);four.engine.rev_limit=9000; - custom("five","Audi-inspired / 2.2 inline five","Inline five",5,EngineLayout::Inline,.081f,.0864f); - custom("rb26","RB26-inspired / 2.6 inline six NA","Inline six",6,EngineLayout::Inline,.086f,.0737f); - custom("six-long","3.0 litre / long-stroke inline six","Inline six",6,EngineLayout::Inline,.086f,.086f); - custom("busso","Busso-inspired / 3.0 V6","V6",6,EngineLayout::V,.093f,.0726f); - auto& flat4=custom("ej20","EJ20-inspired / 2.0 flat four NA","Boxer / flat four",4,EngineLayout::Boxer,.092f,.075f); - flat4.engine.bank_angle=180; - custom("flat6","911-inspired / 3.0 flat six","Boxer / flat six",6,EngineLayout::Boxer,.095f,.0704f).engine.bank_angle=180; - auto& big=custom("big-carb","8.0 litre / carbureted big-block","V8 / big-block",8,EngineLayout::V,.1143f,.098f); - big.engine.carb_pilot_scale=.4f;big.engine.carb_jet_scale=.47f;big.engine.bank_angle=90;big.engine.compression=9;big.engine.carbureted=true;big.engine.idle_rpm=800;big.engine.idle_opening=.08f; - big.description="Carb fuel transport / fixed configurable idle stop"; - custom("v10","5.0 litre / even-fire V10","V10",10,EngineLayout::V,.092f,.0752f).engine.bank_angle=72; - custom("v12","250-inspired / 3.0 V12","V12",12,EngineLayout::V,.073f,.0588f).engine.bank_angle=60; - return examples; -} +ScenarioExample load_engine_file(const std::filesystem::path& path); +std::vector load_engine_directory(const std::filesystem::path& directory); +// FLUIDSIM_ENGINE_DIR overrides discovery. Otherwise search the working directory, +// beside the executable, then the source tree for developer tools. +std::filesystem::path engine_directory(); +const std::vector& scenario_examples(); +const ScenarioExample& scenario_example(const std::string& id); +const ScenarioExample& preset_engine_definition(FourStrokePreset preset); } diff --git a/include/FluidSim/ScenarioGeometry.hpp b/include/FluidSim/ScenarioGeometry.hpp new file mode 100644 index 0000000..a0a92b2 --- /dev/null +++ b/include/FluidSim/ScenarioGeometry.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "FluidSim/AudioProcessing.hpp" +#include + +namespace fluidsim::port { +struct ScenarioSnapshot; +// Fractions refer to simulated pipe length; positions are world metres. +// Both the pipe mesh and its acoustic mouths use these same routes. +struct PipeRoute { + std::array points{}; + std::array fractions{}; + unsigned count{}; +}; +std::array scenario_pipe_routes(const ScenarioSnapshot& state); +std::array scenario_sound_sources(const ScenarioSnapshot& state); +} diff --git a/include/FluidSim/ScenarioSnapshot.hpp b/include/FluidSim/ScenarioSnapshot.hpp new file mode 100644 index 0000000..496cd0c --- /dev/null +++ b/include/FluidSim/ScenarioSnapshot.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "FluidSim/EngineConfig.hpp" +#include "FluidSim/Fluid.hpp" +#include "FluidSim/OutdoorAudio.hpp" +#include "FluidSim/Silencer.hpp" +#include +#include +#include +#include + +namespace fluidsim::port { +struct ScenarioSnapshot { + std::string example_name; + EngineLayout layout{EngineLayout::Inline}; + float bank_angle{}; + bool v8_headers{}; + bool staged_headers{}, dual_exhaust{}; + 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{}, ignition_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))); + } + } +}; + +} diff --git a/include/FluidSim/TwoStrokeConfig.hpp b/include/FluidSim/TwoStrokeConfig.hpp index 3d2f59b..f2d2cd9 100644 --- a/include/FluidSim/TwoStrokeConfig.hpp +++ b/include/FluidSim/TwoStrokeConfig.hpp @@ -1,5 +1,7 @@ #pragma once #include "FluidSim/PipeProfile.hpp" +#include "FluidSim/Ecu.hpp" +#include "FluidSim/Vehicle.hpp" #include namespace fluidsim::port { @@ -8,14 +10,40 @@ enum class TwoStrokeExhaust { Racing, Straight }; // YZ125-sized baseline. Port/chamber/combustion dimensions are explicit design // estimates, not a measured Yamaha cylinder or factory exhaust/ignition map. struct TwoStrokeConfig { + EcuConfig ecu=[] { + EcuConfig e; + e.idle_control=EcuConfig::IdleControl::FixedOpening; + e.throttle_time_constant=.0002f;e.throttle_rate=5000; + e.ignition_map={{0,10},{3000,18},{6000,24},{9000,18},{11000,12},{13000,6},{15000,2},{18000,0}}; + e.idle_rpm=3000;e.idle_max_opening=.3f;e.rev_limit_enabled=false; + e.overrun_cut_rpm=5000;e.overrun_resume_rpm=3500; + e.rev_limit_fuel_cut_enabled=false;e.overrun_fuel_cut_enabled=false;e.overrun_ignition_cut_enabled=true; + return e; + }(); + VehicleConfig vehicle=[] { + VehicleConfig v; + v.primary_reduction=64.f/19;v.final_drive_ratio=49.f/13; + v.gear_ratios={31.f/13,29.f/15,27.f/17,23.f/17,20.f/17,19.f/18}; + v.mass=170;v.wheel_radius=.3313f; + return v; + }(); + float initial_rpm{3000}, starter_torque{8}, maximum_load_torque{10}; + float crankshaft_inertia{.01f}; TwoStrokeExhaust exhaust{TwoStrokeExhaust::Racing}; float bore{.054f}, stroke{.0545f}, rod{.110f}; float trapped_compression{8.2f}; // exhaust-closure volume / clearance volume float transfer_duration{130}, exhaust_duration{190}; float transfer_width{.050f}, exhaust_width{.036f}; // summed effective window widths + float pilot_jet_scale{.75f}, main_jet_scale{1.6f}, needle_jet_scale{1.f}; + float needle_low_scale{2.72f}, needle_mid_scale{.8f}; + float needle_off_idle_scale{12.f}, needle_transition_scale{9.f}, needle_tip_scale{5.f}; + float needle_quarter_scale{1.4f}, needle_upper_scale{.76f}; + float pilot_transition_scale{1.f}, pilot_tip_scale{1.f}; float jet_scale{1.f}; // multiplier on effective jet diameters float crankcase_clearance{150e-6f}; float carburetor_diameter{.0381f}; + float idle_spark_gain{.05f}; // spark degrees per RPM error at the fixed carb stop + float idle_minimum_advance{-10.f}; float idle_slide_opening{.045f}; // fixed mechanical stop, normalized 0..1 (4.5%) float minimum_burn_seconds{.0008f}; // Empirical mechanical FMEP = A + B*mean_piston_speed + C*speed^2. diff --git a/include/FluidSim/TwoStrokeScenario.hpp b/include/FluidSim/TwoStrokeScenario.hpp index 24a018b..8c79266 100644 --- a/include/FluidSim/TwoStrokeScenario.hpp +++ b/include/FluidSim/TwoStrokeScenario.hpp @@ -19,7 +19,7 @@ namespace fluidsim::port { struct TwoStrokeSnapshot { float rpm{}, cylinder_pressure{}, crankcase_pressure{}, intake_pressure{}; float throttle{}, transfer_area{}, exhaust_area{}; - bool reed_open{}, fuel_cut{}; + bool reed_open{}, fuel_cut{}, ignition_cut{}; float pedal{}; OutdoorScene audio_scene{OutdoorScene::Wall}; }; @@ -28,6 +28,7 @@ struct TwoStrokeSnapshot { // the plenum / reed / crankcase / transfer / cylinder / exhaust topology. class TwoStrokeScenario : public MotorcycleTest { public: + void set_listener(AudioListener listener) { audio_.set_listener(listener); } explicit TwoStrokeScenario(int sample_rate = 44100, TwoStrokeConfig config = {}); AudioFrame process(); void set_throttle(float value); diff --git a/include/FluidSim/Vehicle.hpp b/include/FluidSim/Vehicle.hpp index 94a96f3..ac1df95 100644 --- a/include/FluidSim/Vehicle.hpp +++ b/include/FluidSim/Vehicle.hpp @@ -5,16 +5,19 @@ namespace fluidsim::port { // Longitudinal vehicle model and the friction clutch coupling it to the crank. -class Vehicle { -public: - int current_gear{}; +struct VehicleConfig { std::vector gear_ratios{4.1f, 2.7f, 2.0f, 1.55f, 1.25f, 0.98f}; float final_drive_ratio{2.7f}, primary_reduction{1.8f}; - float clutch_input{1.0f}, clutch_capacity{80.0f}, clutch_engagement_rate{8.0f}; + float clutch_capacity{80.0f}, clutch_engagement_rate{8.0f}; float mass{160.0f}, wheel_radius{0.32f}, rotational_inertia{1.2f}; float rolling_resistance{0.015f}, drag_coefficient{0.7f}, frontal_area{0.65f}; float air_density{1.225f}, gravity{9.80665f}; - float speed{}, engagement{}; +}; + +class Vehicle : public VehicleConfig { +public: + int current_gear{}; + float clutch_input{1.0f}, speed{}, engagement{}; float total_gear_ratio() const; float kinetic_energy() const; diff --git a/src/carburetor.cpp b/src/carburetor.cpp index 87848b6..2515a2f 100644 --- a/src/carburetor.cpp +++ b/src/carburetor.cpp @@ -61,14 +61,31 @@ void Carburetor::step(float dt, float pedal, float slide, float rpm, float air_m const float pilot_area = circle_area(pilot_jet_diameter); const float main_area = circle_area(main_jet_diameter); const float needle_jet_area = circle_area(needle_jet_diameter); - const float needle_lift = smoothstep((slide - 0.04f) / 0.86f); + // Piecewise needle annulus area, interpolated continuously between configured + // slide positions. A monotone area profile avoids artificial lean/rich holes + // from interpolating a multiplier over a curved base taper. + const std::array positions{.04f,.045f,.05f,.07f,.1f,.15f,.25f,.5f,.75f,1.f}; + const std::array scales{1.f,std::lerp(1.f,needle_low_scale,.005f/.11f), + needle_off_idle_scale,needle_transition_scale,needle_tip_scale,needle_low_scale,needle_quarter_scale, + needle_mid_scale,needle_upper_scale,1.f}; + float needle_lift=0; + for(std::size_t i=1;i 1e-12f ? 1.0f / std::sqrt(1.0f / (main_area * main_area) + 1.0f / (needle_area * needle_area)) : 0.0f; const float pilot_depression = bowl_head_pressure + (0.62f * static_drop + 0.25f * dynamic_pressure) * (1.0f + 0.25f * lift_enrichment_); const float main_depression = bowl_head_pressure + venturi_depression_; - const float pilot_flow = pilot_discharge_coefficient * pilot_area * + const float pilot_progression = slide<.05f ? std::lerp(1.f,pilot_transition_scale, + std::clamp((slide-.03f)/.02f,0.f,1.f)) : slide<.1f ? + std::lerp(pilot_transition_scale,pilot_tip_scale,(slide-.05f)/.05f) : + std::lerp(pilot_tip_scale,1.f,std::clamp((slide-.1f)/.1f,0.f,1.f)); + const float pilot_flow = pilot_progression * pilot_discharge_coefficient * pilot_area * std::sqrt(2.0f * fuel_density * std::max(pilot_depression, 0.0f)); const float main_flow = main_discharge_coefficient * main_path_area * std::sqrt(2.0f * fuel_density * std::max(main_depression, 0.0f)); @@ -96,7 +113,7 @@ void Carburetor::step(float dt, float pedal, float slide, float rpm, float air_m const float fuel_alpha = -std::expm1(-dt / 0.025f); filtered_fuel_flow_ += (delivered_fuel_flow_ - filtered_fuel_flow_) * fuel_alpha; if (filtered_air_flow_ > 1e-7f && filtered_fuel_flow_ > 1e-9f) - indicated_afr_ = std::clamp(filtered_air_flow_ / filtered_fuel_flow_, 4.0f, 40.0f); + indicated_afr_ = filtered_air_flow_ / filtered_fuel_flow_; } } // namespace fluidsim::port diff --git a/src/desktop_main.cpp b/src/desktop_main.cpp index 616f149..deeff48 100644 --- a/src/desktop_main.cpp +++ b/src/desktop_main.cpp @@ -28,14 +28,11 @@ constexpr float throttle_lerp_rate = 10.0f; } // namespace -int main(int argc, char** argv) { +int main(int argc, char** argv) try { using namespace fluidsim::port; - ScenarioKind selected_kind=ScenarioKind::Twin; bool scenario_from_args=false; - TwoStrokeConfig two_stroke_config; - ExhaustConfig exhaust_config; V8ExhaustConfig v8_config; EngineConfig engine_config; - const auto examples=scenario_examples(); - std::string example_title; + const auto& examples=scenario_examples(); + auto selected=examples.front(); PipeSolver selected_solver=PipeSolver::Rusanov; OutdoorScene selected_scene=OutdoorScene::Wall; for(int i=1;ikind;two_stroke_config=found->two;exhaust_config=found->exhaust;v8_config=found->v8;engine_config=found->engine;example_title=found->title;scenario_from_args=true; + selected=*found;scenario_from_args=true; } else if(option=="--scenario"){ if(value!="twin"&&value!="two-stroke"&&value!="b205"&&value!="v8"){std::cerr<<"Choose twin, two-stroke, b205 or v8\n";return 2;} - selected_kind=value=="v8" ? ScenarioKind::AmericanV8 : value=="b205" ? ScenarioKind::SaabB205 : - (value=="two-stroke" ? ScenarioKind::TwoStroke : ScenarioKind::Twin); + selected=scenario_example(value=="v8"?"v8-h":value=="b205"?"saab-straight":value=="two-stroke"?"yz-racing":"twin"); scenario_from_args=true; }else if(option=="--solver"){ try { selected_solver=parse_pipe_solver(value); } catch(const std::exception& e) { std::cerr<((static_cast(selected_scene)+1)%5); - scenario.set_audio_scene(selected_scene); - break; - case sf::Keyboard::Key::G: audio.set_auto_gain(!audio.auto_gain()); break; - default: break; + if (!scenario_from_args) { + window.setTitle("FluidSim - Examples"); + window.setMinimumSize(sf::Vector2u{720,606}); + window.setSize({720,606}); + const auto choice = desktop::choose_scenario(window, font_ptr,examples); + if (!choice) return 0; + selected=examples[*choice]; + window.setSize({window_width, window_height}); + window.setView(sf::View(sf::FloatRect({0, 0}, {float(window_width), float(window_height)}))); + } + scenario_from_args=false; + window.setTitle("FluidSim - "+selected.title); + window.setMinimumSize(sf::Vector2u{1040,800}); + DesktopScenario scenario(selected); + scenario.set_pipe_solver(selected_solver); + scenario.set_audio_scene(selected_scene); + desktop::DesktopUi ui; + scenario.set_listener(ui.listener(scenario.snapshot(),window.getSize())); + std::cout << "Desktop started: scenario=" << selected.id + << " solver=" << pipe_solver_name(selected_solver) << " scene=" << scene_name(selected_scene) << std::endl; + SimulationRingBuffer ring(ring_capacity); + for (std::size_t i = 0; i < preroll_samples; ++i) { + const AudioFrame audio = scenario.process(); + ring.write(audio.left, audio.right); + } + ring.reset_consumer(); + + SoundEngine audio(ring); + audio.set_volume(100.0f); + audio.start(); + std::jthread simulation([&](std::stop_token stop) { + constexpr std::size_t batch_size = 512; + while (ring.wait_for_space(target_fill - batch_size, stop)) { + for (std::size_t i = 0; i < batch_size; ++i) { + const AudioFrame frame = scenario.process(); + if (!ring.write(frame.left, frame.right, stop)) return; } } - if (const auto* key = event->getIf()) { - if (key->code == sf::Keyboard::Key::V) scenario.set_starter(false); - if (key->code == sf::Keyboard::Key::W) throttle_held = false; - if (key->code == sf::Keyboard::Key::S) throttle_down = false; - if (key->code == sf::Keyboard::Key::C) clutch_held = false; + }); + + double desired_speed = 1.0; + double current_speed = desired_speed; + double last_normal_speed = 1.0; + bool time_warp = false; + float throttle_current = 0.0f; + float brake_target = 0.0f, brake_current = 0.0f; + float clutch_current = 0.0f; + bool throttle_held = false, throttle_down = false, clutch_held = false; + auto previous_frame = std::chrono::steady_clock::now(); + + bool return_to_menu=false; + while (window.isOpen() && !return_to_menu) { + while (const std::optional event = window.pollEvent()) { + if (const auto* key = event->getIf(); + key && key->code == sf::Keyboard::Key::Escape) { + return_to_menu=true; + break; + } + if(ui.handle_event(*event,window.getSize())) continue; + if(event->is()) { throttle_held=false; throttle_down=false; clutch_held=false; scenario.set_starter(false); } + if (event->is()) window.close(); + if (const auto* key = event->getIf()) { + switch (key->code) { + case sf::Keyboard::Key::Space: + time_warp = !time_warp; + if (!time_warp) { desired_speed = last_normal_speed; } + break; + case sf::Keyboard::Key::PageUp: + if(!time_warp) {desired_speed=std::min(1.,desired_speed*1.1);last_normal_speed=desired_speed;} break; + case sf::Keyboard::Key::PageDown: + if(!time_warp) {desired_speed=std::max(.0001,desired_speed/1.1);last_normal_speed=desired_speed;} break; + case sf::Keyboard::Key::W: throttle_held = true; break; + case sf::Keyboard::Key::V: scenario.set_starter(true); break; + case sf::Keyboard::Key::C: clutch_held = true; break; + case sf::Keyboard::Key::Up: ui.nudge_pedal(.05f); break; + case sf::Keyboard::Key::Down: ui.nudge_pedal(-.05f); break; + case sf::Keyboard::Key::Left: brake_target = std::max(0.0f, brake_target - 0.05f); break; + case sf::Keyboard::Key::Right: brake_target = std::min(1.0f, brake_target + 0.05f); break; + case sf::Keyboard::Key::D: scenario.shift_up(); break; + case sf::Keyboard::Key::A: scenario.shift_down(); break; + case sf::Keyboard::Key::Z: scenario.set_neutral(); break; + case sf::Keyboard::Key::S: throttle_down=true; break; + case sf::Keyboard::Key::F3: + selected_solver=static_cast((static_cast(selected_solver)+1)%3); + scenario.set_pipe_solver(selected_solver); + std::cout << "Pipe solver: " << pipe_solver_name(selected_solver) << std::endl; + break; + case sf::Keyboard::Key::R: + selected_scene=static_cast((static_cast(selected_scene)+1)%5); + scenario.set_audio_scene(selected_scene); + break; + case sf::Keyboard::Key::G: audio.set_auto_gain(!audio.auto_gain()); break; + default: break; + } + } + if (const auto* key = event->getIf()) { + if (key->code == sf::Keyboard::Key::V) scenario.set_starter(false); + if (key->code == sf::Keyboard::Key::W) throttle_held = false; + if (key->code == sf::Keyboard::Key::S) throttle_down = false; + if (key->code == sf::Keyboard::Key::C) clutch_held = false; + } + } + if (!window.isOpen() || return_to_menu) break; + + const auto now = std::chrono::steady_clock::now(); + const float frame_dt = std::chrono::duration(now - previous_frame).count(); + previous_frame = now; + const double target_speed = time_warp ? 1.0 : desired_speed; + current_speed += (target_speed - current_speed) * (1.0 - std::exp(-8.0 * frame_dt)); + audio.set_speed(current_speed); + + ui.update_pedal(frame_dt,throttle_held,throttle_down); + const float wanted_throttle = ui.pedal(); + throttle_current = wanted_throttle; // ECU actuator evolves at the simulation sample rate. + brake_current += (brake_target - brake_current) * (1.0f - std::exp(-throttle_lerp_rate * frame_dt)); + const float wanted_clutch = clutch_held ? 1.0f : 0.0f; + clutch_current += (wanted_clutch - clutch_current) * (1.0f - std::exp(-throttle_lerp_rate * frame_dt)); + scenario.set_controls(throttle_current, brake_current, clutch_current); + + const desktop::PlaybackInfo playback{throttle_current,brake_current,current_speed, + ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),time_warp}; + const auto state=scenario.snapshot(); + scenario.set_listener(ui.listener(state,window.getSize())); + ui.draw(window,font_ptr,state,playback); + } - const auto now = std::chrono::steady_clock::now(); - const float frame_dt = std::chrono::duration(now - previous_frame).count(); - previous_frame = now; - const double target_speed = time_warp ? 1.0 : desired_speed; - current_speed += (target_speed - current_speed) * (1.0 - std::exp(-8.0 * frame_dt)); - audio.set_speed(current_speed); - - ui.update_pedal(frame_dt,throttle_held,throttle_down); - const float wanted_throttle = ui.pedal(); - throttle_current = wanted_throttle; // ECU actuator evolves at the simulation sample rate. - brake_current += (brake_target - brake_current) * (1.0f - std::exp(-throttle_lerp_rate * frame_dt)); - const float wanted_clutch = clutch_held ? 1.0f : 0.0f; - clutch_current += (wanted_clutch - clutch_current) * (1.0f - std::exp(-throttle_lerp_rate * frame_dt)); - scenario.set_controls(throttle_current, brake_current, clutch_current); - - const desktop::PlaybackInfo playback{throttle_current,brake_current,current_speed, - ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),time_warp}; - ui.draw(window,font_ptr,scenario.snapshot(),playback); - + simulation.request_stop(); + audio.stop(); + simulation.join(); } - - simulation.request_stop(); - audio.stop(); return 0; +} catch(const std::exception& error) { + std::cerr<<"Startup failed: "<=800) label(w,font,"W/S Raise / lower pedal\nA/D Shift Z Neutral\nC Clutch V Starter\nArrows Load End Release\nPgUp/Dn Speed Space 1x",x,height-141,11,muted); label(w,font,three_d_?"ENGINE VIEW / 3D":"ENGINE VIEW / SCHEMATIC",l.viewport.position.x+16,93,11,accent); label(w,font,three_d_?"Drag: orbit Right: pan Scroll: zoom Home: fit Tab: 2D":"Drag: pan Scroll: zoom Home: reset Tab: 3D",l.viewport.position.x+16,117,11,muted); @@ -112,7 +121,7 @@ void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnap const bool exhaust=s.two_stroke?s.valve1_exhaust_area>0:s.exhaust_areas[i]>0; label(w,font,std::to_string(i+1)+" "+number(pressure/1000)+" "+number(stroke*100)+"% "+(intake?"O":"-")+" / "+(exhaust?"O":"-"),ix+(s.cylinder_count>8?i/6*(l.inspector.size.x/2-4):0),iy+42+(s.cylinder_count>8?i%6:i)*15,10); } - label(w,font,"End: release pedal",20,height-29,11,muted); + label(w,font,"Esc: menu End: release pedal",20,height-29,11,muted); label(w,font,"Pressure: blue vacuum / green atmosphere / red 1 MPa",l.viewport.position.x+2,height-29,11,muted); w.display(); } diff --git a/src/ecu.cpp b/src/ecu.cpp index 6f6960e..7d47df1 100644 --- a/src/ecu.cpp +++ b/src/ecu.cpp @@ -51,7 +51,7 @@ void Ecu::step(float dt, float pedal, float rpm) { if (rpm <= b.rpm) break; } } - if(idle_ && !fixed_idle && idle_spark_gain>0) spark_=std::clamp(spark_+idle_spark_gain*error,idle_minimum_advance,35.0f); + if(idle_ && idle_spark_gain>0) spark_=std::clamp(spark_+idle_spark_gain*error,idle_minimum_advance,35.0f); } float Ecu::fuel_for_air(float air) const { diff --git a/src/engine.cpp b/src/engine.cpp index b108f49..0c4c956 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -150,7 +150,7 @@ void FourStrokeCylinder::pre_step(Crankshaft& crank, float dt, float /*throttle* const float spark_angle = wrap_degrees(720.0f - spark_advance); const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle); - if (crossed_spark && fuel_injected && !combustion_active && !(ecu ? ecu->fuel_cut() : rev_limit_active) && random_unit() >= misfire_probability) { + if (crossed_spark && fuel_injected && !combustion_active && !(ecu ? ecu->ignition_cut() : rev_limit_active) && random_unit() >= misfire_probability) { combustion_active = true; burn_fraction = 0.0f; active_fuel_mass_ = transported_fuel ? std::min(fuel_mass, air_mass / 14.7f) : fuel_mass; diff --git a/src/engine_config.cpp b/src/engine_config.cpp new file mode 100644 index 0000000..4b5a4f4 --- /dev/null +++ b/src/engine_config.cpp @@ -0,0 +1,526 @@ +#include "FluidSim/ScenarioCatalog.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace fluidsim::port { +namespace { +class Reader { + const toml::table& table_; + std::set used_; + std::string path_; +public: + Reader(const toml::table& table,std::string path):table_(table),path_(std::move(path)) {} + [[noreturn]] void fail(const std::string& message) const { throw std::runtime_error(path_+": "+message); } + void required(std::initializer_list keys) const { + for(const auto* key:keys)if(!table_.contains(key))fail(std::string("missing required field '")+key+"'"); + } + const toml::node* node(const std::string& key) { used_.insert(key);return table_.get(key); } + void field(const char* key,float& value,double scale=1) { + if(const auto* n=node(key)) { + auto number=n->value(); + if(!number || !std::isfinite(*number) || std::abs(*number*scale)>std::numeric_limits::max()) + fail(std::string(key)+" must be a finite number"); + value=static_cast(*number*scale); + } + } + void field(const char* key,int& value) { + if(const auto* n=node(key)) { + auto number=n->value(); + if(!n->is_integer() || !number || *number::min() || *number>std::numeric_limits::max()) + fail(std::string(key)+" must be an integer"); + value=static_cast(*number); + } + } + void field(const char* key,bool& value) { + if(const auto* n=node(key)) { + auto b=n->value();if(!b)fail(std::string(key)+" must be true or false");value=*b; + } + } + void field(const char* key,std::string& value) { + if(const auto* n=node(key)) { + auto s=n->value();if(!s)fail(std::string(key)+" must be a string");value=*s; + } + } + std::string text(const char* key,std::string fallback={}) {field(key,fallback);return fallback;} + Reader section(const char* key,bool required=false) { + static const toml::table empty; + const auto* n=node(key); + if(n && !n->is_table())fail(std::string(key)+" must be a table"); + if(!n && required)fail(std::string("missing [")+key+"]"); + return Reader(n?*n->as_table():empty,path_+"."+key); + } + const toml::array* array(const char* key) { + const auto* n=node(key); + if(n && !n->is_array())fail(std::string(key)+" must be an array"); + if(n && n->as_array()->size()>4096)fail(std::string(key)+" has too many entries"); + return n?n->as_array():nullptr; + } + float number(const toml::node& n) const { + auto value=n.value(); + if(!value || !std::isfinite(*value) || std::abs(*value)>std::numeric_limits::max())fail("array entries must be finite numbers"); + return static_cast(*value); + } + void field(const char* key,std::vector& values) { + if(const auto* a=array(key)) {values.clear();for(const auto& n:*a)values.push_back(number(n));} + } + void finish() const { + for(const auto& [key,value]:table_)if(!used_.contains(std::string(key.str())))fail("unknown field '"+std::string(key.str())+"'"); + } +}; + +template void require(bool condition,const T& where,const char* message) { + if(!condition)throw std::runtime_error(std::string(where)+": "+message); +} + +// Scalar readers below keep the file units explicit at the conversion boundary. +void read_cylinder(Reader r,CylinderConfig& c) { + r.required({"bore_mm","stroke_mm","rod_mm","compression"}); + r.field("afr",c.afr); + r.field("ambient_temperature_k",c.ambient_temperature); + r.field("bore_mm",c.bore,0.001); + r.field("burn_duration_deg",c.burn_duration); + r.field("burn_start_deg",c.burn_start); + r.field("compression",c.compression); + r.field("energy_variation",c.energy_variation); + r.field("exhaust_close_deg",c.exhaust_close); + r.field("exhaust_open_deg",c.exhaust_open); + r.field("exhaust_valve_count",c.exhaust_valve_count); + r.field("exhaust_valve_diameter_mm",c.exhaust_valve_diameter,0.001); + r.field("exhaust_valve_lift_mm",c.exhaust_valve_lift,0.001); + r.field("fuel_lhv_j_per_kg",c.fuel_lhv); + r.field("heat_transfer",c.heat_transfer); + r.field("intake_close_deg",c.intake_close); + r.field("intake_open_deg",c.intake_open); + r.field("intake_valve_count",c.intake_valve_count); + r.field("intake_valve_diameter_mm",c.intake_valve_diameter,0.001); + r.field("intake_valve_lift_mm",c.intake_valve_lift,0.001); + r.field("misfire_probability",c.misfire_probability); + r.field("rod_mm",c.rod,0.001); + r.field("stroke_mm",c.stroke,0.001); + r.field("wall_area",c.wall_area); + r.field("wiebe_a",c.wiebe_a); + r.field("wiebe_m",c.wiebe_m); + r.finish(); +} +void read_crankshaft(Reader r,CrankshaftConfig& c) { + r.field("friction_constant_nm",c.friction_constant); + r.field("friction_quadratic_nm_s2",c.friction_quadratic); + r.field("friction_viscous_nm_s",c.friction_viscous); + r.field("inertia_kg_m2",c.inertia); + r.finish(); +} +void read_ecu(Reader r,EcuConfig& c) { + r.field("idle_base_opening",c.idle_base_opening); + r.field("idle_ki",c.idle_ki); + r.field("idle_kp",c.idle_kp); + r.field("idle_max_opening",c.idle_max_opening); + r.field("idle_minimum_advance_deg",c.idle_minimum_advance); + r.field("idle_rpm",c.idle_rpm); + r.field("idle_spark_gain",c.idle_spark_gain); + r.field("overrun_cut_rpm",c.overrun_cut_rpm); + r.field("overrun_fuel_cut_enabled",c.overrun_fuel_cut_enabled); + r.field("overrun_ignition_cut_enabled",c.overrun_ignition_cut_enabled); + r.field("overrun_resume_rpm",c.overrun_resume_rpm); + r.field("power_afr",c.power_afr); + r.field("rev_limit_enabled",c.rev_limit_enabled); + r.field("rev_limit_fuel_cut_enabled",c.rev_limit_fuel_cut_enabled); + r.field("rev_limit_rpm",c.rev_limit_rpm); + r.field("rev_resume_rpm",c.rev_resume_rpm); + r.field("stoichiometric_afr",c.stoichiometric_afr); + r.field("throttle_closing_rate",c.throttle_closing_rate); + r.field("throttle_closing_time_constant_s",c.throttle_closing_time_constant); + r.field("throttle_rate",c.throttle_rate); + r.field("throttle_time_constant_s",c.throttle_time_constant); + const auto mode=r.text("idle_control","feedback"); + if(mode!="fixed" && mode!="feedback")r.fail("idle_control must be fixed or feedback"); + c.idle_control=mode=="fixed"?EcuConfig::IdleControl::FixedOpening:EcuConfig::IdleControl::Feedback; + if(const auto* curve=r.array("ignition_map")) { + c.ignition_map.clear();float previous=-1; + for(const auto& n:*curve) { + const auto* point=n.as_array(); + if(!point || point->size()!=2)r.fail("ignition_map entries must be [RPM, degrees BTDC]"); + const float rpm=r.number(*point->get(0)),advance=r.number(*point->get(1)); + if(rpm<0 || rpm<=previous || advance < -90 || advance>90)r.fail("ignition_map needs increasing nonnegative RPM and advances within +/-90 degrees"); + c.ignition_map.push_back({rpm,advance});previous=rpm; + } + if(c.ignition_map.empty())r.fail("ignition_map must not be empty"); + } + r.finish(); +} +void read_vehicle(Reader r,VehicleConfig& c) { + r.field("air_density",c.air_density); + r.field("clutch_capacity",c.clutch_capacity); + r.field("clutch_engagement_rate",c.clutch_engagement_rate); + r.field("drag_coefficient",c.drag_coefficient); + r.field("final_drive_ratio",c.final_drive_ratio); + r.field("frontal_area",c.frontal_area); + r.field("gear_ratios",c.gear_ratios); + r.field("gravity",c.gravity); + r.field("mass_kg",c.mass); + r.field("primary_reduction",c.primary_reduction); + r.field("rolling_resistance",c.rolling_resistance); + r.field("rotational_inertia_kg_m2",c.rotational_inertia); + r.field("wheel_radius_mm",c.wheel_radius,0.001); + r.finish(); +} +void read_silencer(Reader r,SilencerConfig& c) { + r.field("chambers",c.chambers); + r.field("core_diameter_mm",c.core_diameter,0.001); + r.field("length_mm",c.length,0.001); + r.field("loss_coefficient",c.loss_coefficient); + r.field("passage_diameter_mm",c.passage_diameter,0.001); + r.field("resistance",c.resistance); + r.field("volume_litres",c.volume,0.001); + const auto type=r.text("type","straight_through"); + if(type=="straight_through")c.type=SilencerType::StraightThrough; + else if(type=="chambered")c.type=SilencerType::Chambered; + else if(type=="expansion")c.type=SilencerType::ExpansionResonator; + else r.fail("unknown silencer type '"+type+"'"); + r.finish(); +} +void read_silencers(Reader r,ExhaustConfig& c) { + r.field("cell_length_mm",c.cell_length,0.001); + r.field("connecting_length_mm",c.connecting_length,0.001); + r.field("diameter_mm",c.diameter,0.001); + r.field("enabled",c.enabled); + r.field("inlet_length_mm",c.inlet_length,0.001); + r.field("outlet_length_mm",c.outlet_length,0.001); + read_silencer(r.section("resonator"),c.resonator); + read_silencer(r.section("muffler"),c.muffler); + r.finish(); +} +void read_two_stroke(Reader r,TwoStrokeConfig& c) { + r.required({"bore_mm","stroke_mm","rod_mm","trapped_compression","carburetor_diameter_mm"}); + r.field("initial_rpm",c.initial_rpm); + r.field("starter_torque_nm",c.starter_torque); + r.field("maximum_load_torque_nm",c.maximum_load_torque); + r.field("crankshaft_inertia_kg_m2",c.crankshaft_inertia); + r.field("bore_mm",c.bore,0.001); + r.field("carburetor_diameter_mm",c.carburetor_diameter,0.001); + r.field("crankcase_clearance_cc",c.crankcase_clearance,1e-06); + r.field("exhaust_cell_length_mm",c.exhaust_cell_length,0.001); + r.field("exhaust_duration_deg",c.exhaust_duration); + r.field("exhaust_width_mm",c.exhaust_width,0.001); + r.field("friction_bar",c.friction_bar); + r.field("friction_bar_per_mps",c.friction_bar_per_mps); + r.field("friction_bar_per_mps2",c.friction_bar_per_mps2); + r.field("idle_minimum_advance_deg",c.idle_minimum_advance); + r.field("idle_slide_opening",c.idle_slide_opening); + r.field("idle_spark_gain",c.idle_spark_gain); + r.field("jet_scale",c.jet_scale); + r.field("main_jet_scale",c.main_jet_scale); + r.field("minimum_burn_s",c.minimum_burn_seconds); + r.field("needle_jet_scale",c.needle_jet_scale); + r.field("needle_low_scale",c.needle_low_scale); + r.field("needle_mid_scale",c.needle_mid_scale); + r.field("needle_off_idle_scale",c.needle_off_idle_scale); + r.field("needle_quarter_scale",c.needle_quarter_scale); + r.field("needle_tip_scale",c.needle_tip_scale); + r.field("needle_transition_scale",c.needle_transition_scale); + r.field("needle_upper_scale",c.needle_upper_scale); + r.field("pilot_jet_scale",c.pilot_jet_scale); + r.field("pilot_tip_scale",c.pilot_tip_scale); + r.field("pilot_transition_scale",c.pilot_transition_scale); + r.field("rod_mm",c.rod,0.001); + r.field("stroke_mm",c.stroke,0.001); + r.field("transfer_duration_deg",c.transfer_duration); + r.field("transfer_width_mm",c.transfer_width,0.001); + r.field("trapped_compression",c.trapped_compression); + const auto exhaust=r.text("exhaust","racing"); + if(exhaust!="racing" && exhaust!="straight")r.fail("exhaust must be racing or straight"); + c.exhaust=exhaust=="racing"?TwoStrokeExhaust::Racing:TwoStrokeExhaust::Straight; + if(const auto* sections=r.array("chamber_sections_mm")) { + if(sections->size()!=c.racing_pipe.size())r.fail("chamber_sections_mm must have six sections"); + for(std::size_t i=0;isize();++i) { + const auto* section=sections->get(i)->as_array(); + if(!section || section->size()!=3)r.fail("chamber sections must be [length, inlet diameter, outlet diameter] in mm"); + auto& p=c.racing_pipe[i]; + p.length=r.number(*section->get(0))*.001f; + p.inlet_diameter=r.number(*section->get(1))*.001f; + p.outlet_diameter=r.number(*section->get(2))*.001f; + } + } + r.finish(); +} +void read_engine(Reader r,EngineConfig& c) { + r.required({"cylinders","layout","topology","phase_degrees"}); + r.field("bank_angle_deg",c.bank_angle); + r.field("collector_volume_litres",c.collector_volume,0.001); + r.field("cylinders",c.cylinders); + r.field("initial_rpm",c.initial_rpm); + r.field("initialize_phased_charge",c.initialize_phased_charge); + r.field("maximum_load_torque_nm",c.maximum_load_torque); + r.field("minimum_substeps",c.minimum_substeps); + r.field("phase_degrees",c.phase_degrees); + r.field("pipe_damping",c.pipe_damping); + r.field("plenum_volume_litres",c.plenum_volume,0.001); + r.field("starter_torque_nm",c.starter_torque); + r.field("throttle_diameter_mm",c.throttle_diameter,0.001); + const auto layout=r.text("layout","inline"),topology=r.text("topology","collector"); + if(layout=="inline")c.layout=EngineLayout::Inline; + else if(layout=="v")c.layout=EngineLayout::V; + else if(layout=="boxer")c.layout=EngineLayout::Boxer; + else r.fail("layout must be inline, v or boxer"); + if(topology=="collector")c.topology=ExhaustTopology::Collector; + else if(topology=="staged")c.topology=ExhaustTopology::Staged; + else if(topology=="american_v8")c.topology=ExhaustTopology::AmericanV8; + else r.fail("unknown exhaust topology '"+topology+"'"); + r.finish(); +} +void read_fuel(Reader r,EngineConfig& c) { + const auto system=r.text("system","injection"); + if(system!="injection" && system!="carburetor")r.fail("system must be injection or carburetor"); + c.carbureted=system=="carburetor"; + r.field("pilot_jet_diameter_mm",c.fuel.pilot_jet_diameter,0.001); + r.field("main_jet_diameter_mm",c.fuel.main_jet_diameter,0.001); + r.field("needle_jet_diameter_mm",c.fuel.needle_jet_diameter,0.001); + r.field("needle_low_scale",c.fuel.needle_low_scale); + r.field("needle_mid_scale",c.fuel.needle_mid_scale); + r.field("needle_off_idle_scale",c.fuel.needle_off_idle_scale); + r.field("needle_transition_scale",c.fuel.needle_transition_scale); + r.field("needle_tip_scale",c.fuel.needle_tip_scale); + r.field("needle_quarter_scale",c.fuel.needle_quarter_scale); + r.field("needle_upper_scale",c.fuel.needle_upper_scale); + r.field("pilot_transition_scale",c.fuel.pilot_transition_scale); + r.field("pilot_tip_scale",c.fuel.pilot_tip_scale); + r.field("fuel_density",c.fuel.fuel_density); + r.field("pilot_discharge_coefficient",c.fuel.pilot_discharge_coefficient); + r.field("main_discharge_coefficient",c.fuel.main_discharge_coefficient); + r.field("bowl_head_pressure",c.fuel.bowl_head_pressure); + r.field("film_fraction",c.fuel.film_fraction); + r.field("film_time_constant",c.fuel.film_time_constant); + r.field("overrun_rpm",c.fuel.overrun_rpm); + r.field("overrun_exit_rpm",c.fuel.overrun_exit_rpm); + r.field("bowl_capacity_kg",c.fuel.bowl_capacity); + r.field("bowl_refill_kg_per_s",c.fuel.bowl_refill_rate); + r.finish(); +} + +void validate_ecu(const EcuConfig& c,const std::string& path) { + require(c.idle_rpm>0 && c.idle_base_opening>=0 && c.idle_base_opening<=1 && + c.idle_max_opening>=c.idle_base_opening && c.idle_max_opening<=1,path,"ecu: invalid idle calibration"); + require(c.rev_limit_rpm>c.rev_resume_rpm && c.rev_resume_rpm>0 && + c.overrun_cut_rpm>c.overrun_resume_rpm && c.overrun_resume_rpm>0,path,"ecu: invalid cut/resume RPM"); + require(c.throttle_time_constant>0 && c.throttle_rate>0 && c.idle_spark_gain>=0 && + c.throttle_closing_time_constant>=0 && c.throttle_closing_rate>=0,path,"ecu: invalid actuator calibration"); + require(c.stoichiometric_afr>0 && c.power_afr>0,path,"ecu: AFR targets must be positive"); +} +void validate_vehicle(const VehicleConfig& c,const std::string& path) { + require(c.mass>0 && c.wheel_radius>0 && c.rotational_inertia>=0 && + c.primary_reduction>0 && c.final_drive_ratio>0 && c.clutch_capacity>0 && + c.clutch_engagement_rate>0 && c.rolling_resistance>=0 && + c.drag_coefficient>=0 && c.frontal_area>0 && c.air_density>0 && c.gravity>0, + path,"vehicle: invalid dimensions, ratios or resistance"); + require(!c.gear_ratios.empty() && c.gear_ratios.size()<=16,path,"vehicle: expected 1..16 gear ratios"); + for(float ratio:c.gear_ratios)require(ratio>0,path,"vehicle: gear ratios must be positive"); +} +void validate_two(const TwoStrokeConfig& c,const std::string& path) { + require(c.bore>0 && c.bore<=1 && c.stroke>0 && c.stroke<=1 && c.rod>c.stroke*.5f && + c.trapped_compression>1 && c.trapped_compression<=50,path,"two_stroke: invalid cylinder geometry"); + require(c.transfer_duration>0 && c.transfer_duration0 && c.exhaust_width>0 && c.crankcase_clearance>0, + path,"two_stroke: invalid port timing or crankcase geometry"); + require(c.idle_slide_opening>=0 && c.idle_slide_opening<=1 && c.initial_rpm>0 && c.initial_rpm<=30000 && + c.crankshaft_inertia>0 && c.starter_torque>=0 && c.maximum_load_torque>=0 && c.idle_spark_gain>=0, + path,"two_stroke: invalid idle or crankshaft calibration"); + require(c.carburetor_diameter>0 && c.minimum_burn_seconds>0 && + c.friction_bar>=0 && c.friction_bar_per_mps>=0 && c.friction_bar_per_mps2>=0, + path,"two_stroke: invalid carburetor, burn time or friction"); + for(float value:{c.jet_scale,c.pilot_jet_scale,c.main_jet_scale,c.needle_jet_scale, + c.needle_low_scale,c.needle_mid_scale,c.needle_off_idle_scale,c.needle_transition_scale, + c.needle_tip_scale,c.needle_quarter_scale,c.needle_upper_scale,c.pilot_transition_scale,c.pilot_tip_scale}) + require(value>0,path,"two_stroke: jet and needle scales must be positive"); + require(c.exhaust_cell_length>=.002f && c.exhaust_cell_length<=1,path,"two_stroke: exhaust cell length outside 2..1000 mm"); + float total=0; + for(auto p:c.racing_pipe) { + require(p.length>0 && p.inlet_diameter>0 && p.outlet_diameter>0,path,"two_stroke: invalid chamber section"); + total+=p.length; + } + require(total/c.exhaust_cell_length<=4096,path,"two_stroke: chamber exceeds 4096 cells"); + validate_ecu(c.ecu,path);validate_vehicle(c.vehicle,path); +} +} // namespace + +void validate_engine_config(const EngineConfig& c,const std::string& path) { + require(c.cylinders>=1 && c.cylinders<=12 && (c.layout==EngineLayout::Inline || c.cylinders%2==0), + path,"engine: expected 1..12 cylinders and even banked cylinder counts"); + const auto& cylinder=c.cylinder; + require(cylinder.bore>0 && cylinder.bore<=1 && cylinder.stroke>0 && cylinder.stroke<=1 && + cylinder.rod>cylinder.stroke*.5f && cylinder.compression>1 && cylinder.compression<=50, + path,"cylinder: invalid bore, stroke, rod or compression"); + require(cylinder.intake_valve_count>=1 && cylinder.intake_valve_count<=8 && + cylinder.exhaust_valve_count>=1 && cylinder.exhaust_valve_count<=8 && + cylinder.intake_valve_diameter>0 && cylinder.exhaust_valve_diameter>0 && + cylinder.intake_valve_lift>0 && cylinder.exhaust_valve_lift>0,path,"cylinder: invalid valve geometry"); + for(float angle:{cylinder.intake_open,cylinder.intake_close,cylinder.exhaust_open,cylinder.exhaust_close}) + require(angle>=0 && angle<720,path,"cylinder: valve events must be in [0,720) degrees"); + require(cylinder.intake_open!=cylinder.intake_close && cylinder.exhaust_open!=cylinder.exhaust_close && + cylinder.burn_duration>0 && cylinder.wiebe_a>0 && cylinder.wiebe_m>=0 && + cylinder.energy_variation>=0 && cylinder.energy_variation<=1 && + cylinder.misfire_probability>=0 && cylinder.misfire_probability<=1 && + cylinder.fuel_lhv>0 && cylinder.wall_area>0 && cylinder.heat_transfer>=0 && cylinder.ambient_temperature>0, + path,"cylinder: invalid timing or combustion calibration"); + require(c.crankshaft.inertia>0 && c.crankshaft.friction_constant>=0 && c.crankshaft.friction_viscous>=0 && + c.crankshaft.friction_quadratic>=0,path,"crankshaft: invalid inertia or friction"); + require(c.initial_rpm>0 && c.initial_rpm<=30000 && c.starter_torque>=0 && c.maximum_load_torque>=0 && + c.plenum_volume>0 && c.collector_volume>0 && c.throttle_diameter>0 && + c.pipe_damping>=0 && c.minimum_substeps>=1 && c.minimum_substeps<=64 && + c.bank_angle>0 && c.bank_angle<=180,path,"engine: invalid startup, intake or solver calibration"); + require(c.phase_degrees.size()==static_cast(c.cylinders),path,"engine: phase_degrees needs one value per cylinder"); + for(float phase:c.phase_degrees)require(std::isfinite(phase) && phase>=0 && phase<720,path,"engine: invalid cylinder phase"); + require(!c.american_headers() || (c.cylinders==8 && c.layout==EngineLayout::V),path,"engine: american_v8 topology requires a V8"); + require(!c.staged_headers() || c.cylinders>=3,path,"engine: staged topology requires at least three cylinders"); + const int expected=c.american_headers()?26:1+2*c.cylinders+(c.dual_outlets()?2:1)+(c.staged_headers()?c.header_groups():0); + int count=0,cells=0,silenced=0;std::set ids; + for(const auto& p:c.pipes) { + require(!p.id.empty() && ids.insert(p.id).second,path,"pipes: missing or duplicate pipe group id"); + require(p.count>=1 && p.count<=12,path,"pipes: count must be in 1..12"); + if(p.silenced) { + require(c.has_silenced_exhaust() && c.topology==ExhaustTopology::Collector && + count==1+2*c.cylinders && p.count==1,path,"pipes: silenced profile must be the collector's final outlet"); + ++silenced; + } else { + require(p.diameter>0 && p.diameter<=2 && p.length>0 && p.length<=100 && p.cells>=1 && p.cells<=4096, + path,"pipes: invalid diameter, length or cell count"); + cells+=p.cells*p.count; + } + count+=p.count; + } + require(count==expected && count<=32 && cells<=32768,path,"pipes: groups do not match the selected topology or exceed mesh limits"); + require(silenced<=1,path,"pipes: only one silenced outlet is supported"); + if(c.carbureted) { + const auto& f=c.fuel; + for(float value:{f.pilot_jet_diameter,f.main_jet_diameter,f.needle_jet_diameter,f.needle_low_scale, + f.needle_mid_scale,f.needle_off_idle_scale,f.needle_transition_scale,f.needle_tip_scale, + f.needle_quarter_scale,f.needle_upper_scale,f.pilot_transition_scale,f.pilot_tip_scale, + f.fuel_density,f.pilot_discharge_coefficient,f.main_discharge_coefficient,f.bowl_capacity,f.bowl_refill_rate,f.film_time_constant}) + require(value>0,path,"fuel: jets, scales, density and time constants must be positive"); + require(f.film_fraction>=0 && f.film_fraction<=.8f && f.bowl_head_pressure>=0 && + f.overrun_rpm>f.overrun_exit_rpm && f.overrun_exit_rpm>0,path,"fuel: invalid film or overrun calibration"); + } + validate_ecu(c.ecu,path);validate_vehicle(c.vehicle,path); +} + +ScenarioExample load_engine_file(const std::filesystem::path& path) { + toml::table table; + try { table=toml::parse_file(path.string()); } + catch(const toml::parse_error& error) {std::ostringstream message;message<0 && e.junctions.junction_volume<=1,path.string(),"junction_volume_cc outside (0,1000000]"); + if(e.engine.has_silenced_exhaust()) { + require(e.exhaust.cell_length>=.002f && e.exhaust.cell_length<=1,path.string(),"silencers: cell length outside 2..1000 mm"); + require(e.exhaust.diameter>0 && e.exhaust.diameter<=2 && e.exhaust.inlet_length>0 && + e.exhaust.outlet_length>0 && e.exhaust.connecting_length>0, + path.string(),"silencers: invalid connecting pipe geometry"); + float length=e.exhaust.inlet_length+e.exhaust.outlet_length+e.exhaust.connecting_length; + for(const auto& s:{e.exhaust.resonator,e.exhaust.muffler}) { + require(s.volume>0 && s.volume<=1 && s.length>0 && s.core_diameter>0 && s.core_diameter<=2 && + s.passage_diameter>0 && s.passage_diameter<=2 && s.chambers>=1 && s.chambers<=16 && + s.resistance>=0 && s.loss_coefficient>=0,path.string(),"silencers: invalid chamber geometry or resistance"); + length+=s.length; + } + require(length/e.exhaust.cell_length+6*(e.exhaust.resonator.chambers+e.exhaust.muffler.chambers)+6<=4096, + path.string(),"silencers: mesh exceeds 4096 cells"); + try {const auto pipe=make_silenced_exhaust(e.exhaust);require(pipe.cells.size()<=4096,path.string(),"silencers: mesh exceeds 4096 cells");} + catch(const std::exception& error) {root.fail(std::string("silencers: ")+error.what());} + } + } else root.fail("cycle must be two_stroke or four_stroke"); + root.finish();return e; +} + +std::vector load_engine_directory(const std::filesystem::path& directory) { + require(std::filesystem::is_directory(directory),directory.string(),"engine directory does not exist"); + std::vector files; + for(const auto& entry:std::filesystem::directory_iterator(directory)) + if(entry.is_regular_file() && entry.path().extension()==".toml")files.push_back(entry.path()); + std::sort(files.begin(),files.end()); + std::vector engines;std::set ids; + for(const auto& file:files) { + auto engine=load_engine_file(file); + require(ids.insert(engine.id).second,file.string(),"duplicate engine id"); + engines.push_back(std::move(engine)); + } + require(!engines.empty(),directory.string(),"no .toml engine definitions found"); + std::sort(engines.begin(),engines.end(),[](const auto& a,const auto& b) { + return a.order==b.order?a.id candidates{std::filesystem::current_path()/"engines"}; +#ifdef _WIN32 + std::wstring executable(32768,L'\0'); + const auto size=GetModuleFileNameW(nullptr,executable.data(),static_cast(executable.size())); + if(size>0 && size& scenario_examples() { + static const auto engines=load_engine_directory(engine_directory());return engines; +} +const ScenarioExample& scenario_example(const std::string& id) { + const auto& engines=scenario_examples(); + const auto found=std::find_if(engines.begin(),engines.end(),[&](const auto& e) {return e.id==id;}); + if(found==engines.end())throw std::runtime_error("Unknown engine id '"+id+"' in "+engine_directory().string()); + return *found; +} +const ScenarioExample& preset_engine_definition(FourStrokePreset preset) { + return scenario_example(preset==FourStrokePreset::AmericanV8?"v8-h":preset==FourStrokePreset::SaabB205?"saab-straight":"twin"); +} +} // namespace fluidsim::port diff --git a/src/engine_renderer_3d.cpp b/src/engine_renderer_3d.cpp index 41fe531..e656032 100644 --- a/src/engine_renderer_3d.cpp +++ b/src/engine_renderer_3d.cpp @@ -1,4 +1,5 @@ #include "FluidSim/Desktop/EngineRenderer3D.hpp" +#include "FluidSim/ScenarioGeometry.hpp" #include "FluidSim/Desktop/PressurePalette.hpp" #include "FluidSim/Desktop/PipePath.hpp" #include @@ -61,12 +62,7 @@ Vec3 sub(Vec3 a,Vec3 b) {return {a.x-b.x,a.y-b.y,a.z-b.z};} float length(Vec3 a) {return std::sqrt(a.x*a.x+a.y*a.y+a.z*a.z);} Vec3 pressure_color(float pressure) {return pressure_tint(pressure);} void color(Vec3 c) {glColor3f(c.x,c.y,c.z);} -struct Route {std::array points{};std::array fractions{};unsigned count{};}; -// Scene points specify a fraction of the pipe's physical length, converted -// to metre-based PipeAnchor distances when constructing the cached surface. -Route route(std::initializer_list p) { - Route r;for(auto v:p) {r.points[r.count]=v.position;r.fractions[r.count++]=v.distance;}return r; -} +using Route = PipeRoute; Vec3 cross(Vec3 a,Vec3 b) {return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};} float dot(Vec3 a,Vec3 b) {return a.x*b.x+a.y*b.y+a.z*b.z;} Vec3 unit(Vec3 a) {float l=length(a);return l>1e-7f?Vec3{a.x/l,a.y/l,a.z/l}:Vec3{1,0,0};} @@ -162,55 +158,58 @@ struct EngineRenderer3D::Impl { glPopMatrix(); } void engine(const ScenarioSnapshot& s) { + const auto routes=scenario_pipe_routes(s); if(s.two_stroke) { cylinder_part({0,0,0},s.cylinder_piston_fraction1,s.cylinder_pressure1,s.cylinder_angles[0]); box({0,.07f,0},{.21f,.14f,.19f},pressure_color(s.crankcase_pressure)); box({-.55f,.35f,0},{.18f,.12f,.15f},pressure_color(s.intake_pressure)); - pipe(s,0,route({{0.0f,{-1.05f,.35f,0}},{1.0f,{-.64f,.35f,0}}})); - pipe(s,1,route({{0.0f,{-.46f,.35f,0}},{0.3333333333333333f,{-.28f,.35f,0}},{0.6666666666666666f,{-.28f,.1f,0}},{1.0f,{-.105f,.1f,0}}})); - pipe(s,2,route({{0.0f,{.105f,.09f,0}},{0.3333333333333333f,{.18f,.09f,-.12f}},{0.6666666666666666f,{.18f,.35f,-.12f}},{1.0f,{.05f,.35f,0}}})); - pipe(s,3,route({{0.0f,{.055f,.40f,0}},{0.3333333333333333f,{.3f,.40f,0}},{0.6666666666666666f,{.58f,.23f,0}},{1.0f,{1.45f,.23f,0}}})); + pipe(s,0,routes[0]); + pipe(s,1,routes[1]); + pipe(s,2,routes[2]); + pipe(s,3,routes[3]); } else { const int n=s.cylinder_count;const bool v8=s.v8_headers; const bool banked=s.layout!=EngineLayout::Inline;const int per_bank=banked?n/2:n; const float spacing=.21f,first=-(per_bank-1)*spacing*.5f; box({0,.64f,0},{per_bank*.21f,.12f,.15f},pressure_color(s.intake_pressure)); - pipe(s,0,route({{0.0f,{-1.05f,.64f,0}},{0.25f,{-.5f,.64f,0}},{0.5f,{-.5f,.64f,-.1f}},{0.75f,{0,.64f,-.1f}},{1.0f,{0,.64f,0}}})); + pipe(s,0,routes[0]); for(int i=0;i pair{0,0,1,1,2,3,2,3}; for(int i=0;i<8;++i) { - const int bank=i/4, group=pair[i]%2; - const float x=first+(i%4)*spacing, z=bank? .39f:-.39f; - const float a=(bank?28.f:-28.f)*pi3/180; - const Vec3 port{x+.055f,.1f+.33f*std::cos(a),.33f*std::sin(a)}; - const float end_z=z+(group? .09f:-.09f); - pipe(s,9+i,route({{0,port},{.3f,{x+.12f,.34f,z}},{.65f,{.35f,.20f,end_z}},{1,{.74f,.17f,end_z}}})); + pipe(s,9+i,routes[9+i]); } for(int i=0;i<4;++i) { - const float z=i<2?-.39f:.39f; - pipe(s,17+i,route({{0,{.74f,.17f,z+(i%2?.09f:-.09f)}},{.5f,{.94f,.17f,z+(i%2?.06f:-.06f)}},{1,{1.14f,.17f,z}}})); + pipe(s,17+i,routes[17+i]); } - if(s.h_pipe) pipe(s,25,route({{0,{1.69f,.17f,-.39f}},{1,{1.69f,.17f,.39f}}})); + if(s.h_pipe) pipe(s,25,routes[25]); + } + if(s.staged_headers) { + const int exit=1+2*n, groups=n==3?1:2, secondary=exit+(s.dual_exhaust?2:1); + for(int group=0;group1e-7f,state.exhaust_areas[i]>1e-7f,x); + draw_pipe(window,state,1+i,top-15,x-10,x+90); + draw_pipe(window,state,1+n+i,top+155,x-10,x+90); + draw_graph_line(window,{x+90,top+155},{950,top+155},{200,120,80}); + } + for(int group=0;group #include -int main(int argc, char** argv) { +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"; - TwoStrokeConfig two_stroke_config; + 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]; @@ -107,4 +108,7 @@ int main(int argc, char** argv) { return 2; } return 0; +} catch(const std::exception& error) { + std::cerr<& positions) { + for(auto p:positions)if(!finite(p))return; + config_.intake_position=positions[0]; + config_.exhaust_position=positions[1]; + config_.second_exhaust_position=positions[2]; + update_geometry(); +} +void OutdoorAudio::update_geometry() { + 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); } void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) { + const bool initial=emitter.filtered.empty(); emitter.filtered.resize(std::bit_ceil(std::size_t(rate_))); - emitter.raw.resize(emitter.filtered.size()); // One second, validated path range. + emitter.raw.resize(emitter.filtered.size()); // Power-of-two propagation history, at least one second. + std::size_t path_index=0; + const float center_distance=std::max(distance(source,config_.listener),.1f); + const float max_delay=float(emitter.filtered.size()-2); + const float center_samples=center_distance*rate_/config_.sound_speed; + emitter.target_center_delay=std::min(center_samples,max_delay); + emitter.target_center_gain=center_samples<=max_delay?1/center_distance:0; + if(initial) {emitter.center_delay=emitter.target_center_delay;emitter.center_gain=emitter.target_center_gain;} const auto ear = [&](float sign) { return Vec3{config_.listener.x + sign * config_.listener_right.x * config_.ear_spacing * .5f, config_.listener.y + sign * config_.listener_right.y * config_.ear_spacing * .5f, @@ -71,26 +105,31 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) { const float dl = std::max(distance(image, ear(-1)), .1f), dr = std::max(distance(image, ear(1)), .1f); const float sl = (dl + extra) * rate_ / config_.sound_speed, sr = (dr + extra) * rate_ / config_.sound_speed; - if (!std::isfinite(sl) || !std::isfinite(sr) || sl >= rate_ - 2 || sr >= rate_ - 2) - throw std::invalid_argument("outdoor propagation path exceeds one second"); - p.delay_left = std::size_t(sl); - p.delay_right = std::size_t(sr); - p.fraction_left = sl - p.delay_left; - p.fraction_right = sr - p.delay_right; + // Very distant camera pans fade out instead of wrapping the delay line. + p.delay_left = p.target_delay_left = std::min(sl,max_delay); + p.delay_right = p.target_delay_right = std::min(sr,max_delay); // 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.gain_left = p.target_gain_left = sl<=max_delay?coefficient / path_left:0; + p.gain_right = p.target_gain_right = sr<=max_delay?coefficient / path_right:0; p.alpha = cutoff > 0 ? 1 - std::exp(-2 * pi * cutoff / rate_) : 1; - emitter.paths.push_back(p); + if(initial)emitter.paths.push_back(p); + else { + auto& current=emitter.paths[path_index]; + current.target_delay_left=p.delay_left;current.target_delay_right=p.delay_right; + current.target_gain_left=p.gain_left;current.target_gain_right=p.gain_right; + } + ++path_index; }; path(source, 1, 0, 0); - path({source.x, 2 * config_.ground_y - source.y, source.z}, config_.ground_reflection, 1, 1800); - path({2 * config_.wall_x - source.x, source.y, source.z}, config_.wall_reflection, 2, 4500); + const bool ground_visible=(source.y-config_.ground_y)*(config_.listener.y-config_.ground_y)>=0; + const bool wall_visible=(source.x-config_.wall_x)*(config_.listener.x-config_.wall_x)>=0; + path({source.x, 2 * config_.ground_y - source.y, source.z}, ground_visible?config_.ground_reflection:0, 1, 1800); + path({2 * config_.wall_x - source.x, source.y, source.z}, wall_visible?config_.wall_reflection:0, 2, 4500); // Single-bounce point scatterers: geometric paths, empirical scattering strengths. for (Vec3 tree : std::array{{{-4, 1, 3}, {3, 1, 5}, {5, 1, -2}, {-6, 2, -3}, {-2, 1, 7}, {6, 2, 6}}}) { @@ -102,36 +141,54 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) { // distinct before the diffuse FDN tail builds up. constexpr float tunnel_width = 6.0f; constexpr float tunnel_height = 4.0f; + const float tunnel_visible=config_.listener.y>=config_.ground_y && config_.listener.y<=tunnel_height && + std::abs(config_.listener.z)<=tunnel_width*.5f?1.f:0.f; 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); + path(ceiling, .82f*tunnel_visible, 4, 7200); + path(left_wall, .82f*tunnel_visible, 4, 6200); + path(right_wall, .82f*tunnel_visible, 4, 6200); + path({ceiling.x, ceiling.y, left_wall.z}, .68f*tunnel_visible, 4, 5600); + path({ceiling.x, ceiling.y, right_wall.z}, .68f*tunnel_visible, 4, 5600); + path({source.x, source.y, -2 * tunnel_width + source.z}, .56f*tunnel_visible, 4, 4800); + path({source.x, source.y, 2 * tunnel_width + source.z}, .56f*tunnel_visible, 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; - emitter.filter = {(1 - cs) * .5f / a0, (1 - cs) / a0, (1 - cs) * .5f / a0, -2 * cs / a0, - (1 - alpha) / a0}; + if(initial)emitter.filter = {(1 - cs) * .5f / a0, (1 - cs) / a0, (1 - cs) * .5f / a0, -2 * cs / a0, + (1 - alpha) / a0}; } 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; e.raw[e.position] = pressure; e.filtered[e.position] = monitor_filter_ ? e.filter.process(pressure) : pressure; - const auto read = [&](const std::vector &data, std::size_t delay, float fraction) { + const auto read = [&](const std::vector &data, float samples) { + const auto delay=std::size_t(samples); + const float fraction=samples-delay; const auto i = (e.position + data.size() - delay) & (data.size() - 1); const auto j = (i + data.size() - 1) & (data.size() - 1); return data[i] + (data[j] - data[i]) * fraction; }; AudioFrame result{}; + const auto smooth=[&](float& current,float target) { + const float next=current+(target-current)*geometry_alpha_; + // Finish the movement when float precision would stall the smoother. + current=next==current?target:next; + }; + smooth(e.center_delay,e.target_center_delay); + smooth(e.center_gain,e.target_center_gain); + // The diffuse field is excited by a centre microphone, avoiding comb + // filtering from summing the differently delayed left and right ears. + reverb_input_+=read(e.filtered,e.center_delay)*e.center_gain; for (auto &p : e.paths) { - const float l = read(e.filtered, p.delay_left, p.fraction_left) * p.gain_left; - const float r = read(e.filtered, p.delay_right, p.fraction_right) * p.gain_right; + smooth(p.delay_left,p.target_delay_left); + smooth(p.delay_right,p.target_delay_right); + smooth(p.gain_left,p.target_gain_left); + smooth(p.gain_right,p.target_gain_right); + const float l = read(e.filtered, p.delay_left) * p.gain_left; + const float r = read(e.filtered, p.delay_right) * p.gain_right; p.state_left += p.alpha * (l - p.state_left); p.state_right += p.alpha * (r - p.state_right); if (std::abs(p.state_left) < 1e-25f) @@ -142,8 +199,8 @@ AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFr result.right += p.state_right * weights_[p.group]; if (p.group == 0) { dry = add(dry, {l, r}); - raw = add(raw, {read(e.raw, p.delay_left, p.fraction_left) * p.gain_left, - read(e.raw, p.delay_right, p.fraction_right) * p.gain_right}); + raw = add(raw, {read(e.raw, p.delay_left) * p.gain_left, + read(e.raw, p.delay_right) * p.gain_right}); } } e.position = (e.position + 1) & (e.filtered.size() - 1); @@ -189,12 +246,13 @@ AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhau tail_mix_ += (target_tail - tail_mix_) * scene_alpha_; last_dry_ = {}; last_raw_ = {}; + reverb_input_ = 0; 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 = late_reverb_.process_stereo((last_dry_.left + last_dry_.right) * .5f); + const auto tail = late_reverb_.process_stereo(reverb_input_); return {a.left + b.left + c.left + tail.left * tail_mix_, a.right + b.right + c.right + tail.right * tail_mix_}; } } // namespace fluidsim::port diff --git a/src/scenario.cpp b/src/scenario.cpp index 0e71a31..e09537a 100644 --- a/src/scenario.cpp +++ b/src/scenario.cpp @@ -1,141 +1,62 @@ #include "FluidSim/Scenario.hpp" #include "FluidSim/Constants.hpp" +#include "FluidSim/ScenarioGeometry.hpp" +#include "FluidSim/ScenarioCatalog.hpp" #include #include namespace fluidsim::port { +FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset preset) + : FourStrokeScenario(sample_rate,preset_engine_definition(preset)) {} -FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selected_preset, ExhaustConfig exhaust, V8ExhaustConfig v8_exhaust, EngineConfig engine) - : preset(selected_preset), exhaust_config(exhaust), v8_exhaust_config(v8_exhaust), engine_config(engine), - intake_plenum(engine.cylinders ? .001f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 4e-3f : selected_preset == FourStrokePreset::SaabB205 ? 3e-3f : 1e-3f, 101325, 300), - exhaust_collector(engine.cylinders ? .00025f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 1.5e-3f : selected_preset == FourStrokePreset::SaabB205 ? .6e-3f : .15e-3f, 101325, 700), - pipes(create_pipes(selected_preset, exhaust, v8_exhaust, engine)), dt_(1.f / std::max(sample_rate, 1)), - boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate, [&] { +FourStrokeScenario::FourStrokeScenario(int sample_rate,const ScenarioExample& definition) + : FourStrokeScenario(sample_rate,definition.engine,definition.exhaust,definition.junctions) {} + +FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configuration, ExhaustConfig exhaust, JunctionConfig fittings) + : exhaust_config(exhaust), junction_config(fittings), engine_config(std::move(configuration)), + intake_plenum(engine_config.plenum_volume,101325,300), + exhaust_collector(engine_config.collector_volume,101325,700), + pipes(create_pipes(engine_config,exhaust)), dt_(1.f/std::max(sample_rate,1)), + boundaries_(pipes), solver_(pipes,boundaries_), audio_(sample_rate,[&] { OutdoorAudioConfig config; - config.dual_exhaust = selected_preset == FourStrokePreset::AmericanV8; + config.dual_exhaust=engine_config.dual_outlets(); return config; }()) { - const bool saab = preset == FourStrokePreset::SaabB205; - const bool v8 = preset == FourStrokePreset::AmericanV8; - const int count = cylinder_count(); - if(engine.cylinders<0 || engine.cylinders>12 || (engine.layout!=EngineLayout::Inline && engine.cylinders%2)) throw std::invalid_argument("invalid engine cylinder layout"); - starter_torque = v8 ? 180.f : saab ? 65.f : 18.f; - crankshaft.omega = (saab ? 1200 : 2000) * 2 * pi / 60; - crankshaft.inertia = saab ? .18f : .04f; - crankshaft.friction_constant = saab ? 12.f : 5.f; - crankshaft.friction_viscous = saab ? .012f : .003f; - if (saab) { - solver_.minimum_substeps = 2; // Acoustic CFL may subdivide further. - // Rough 9-5 wagon with driver; FM55 B05 overall reductions from WIS. - vehicle.mass = 1600 + 75; - vehicle.primary_reduction = 1; - vehicle.final_drive_ratio = 4.05f; - vehicle.gear_ratios = {13.70f/4.05f, 7.12f/4.05f, 4.77f/4.05f, 3.62f/4.05f, 2.67f/4.05f}; - vehicle.wheel_radius = .317f; - vehicle.rotational_inertia = 3.6f; - vehicle.drag_coefficient = .32f; - vehicle.frontal_area = 2.2f; - vehicle.clutch_capacity = 300; - ecu.idle_rpm = 850; - ecu.idle_spark_gain = .025f; - ecu.idle_minimum_advance = -10; - ecu.rev_limit_rpm = 6500; - ecu.rev_resume_rpm = 6250; - ecu.overrun_cut_rpm = 2000; - ecu.overrun_resume_rpm = 1300; - } - if (v8) { - // Estimated mild 1980s 302-class pushrod small block and manual coupe. - crankshaft.omega = 1000 * 2*pi/60; - crankshaft.inertia = 1.1f; - crankshaft.friction_constant = 22; - crankshaft.friction_viscous = .025f; - vehicle.mass = 3000; - vehicle.primary_reduction = 1; - vehicle.final_drive_ratio = 6.16f / 1.7f; // 1.7x taller gearing in every gear. - vehicle.gear_ratios = {3.35f, 1.93f, 1.29f, 1.f, .68f}; - vehicle.wheel_radius = .32f; - vehicle.clutch_capacity = 1100; // Hold V8 torque with the heavy flywheel/car. - vehicle.drag_coefficient = .4f; - vehicle.frontal_area = 2.f; - ecu.idle_rpm = 750; - ecu.idle_base_opening = .08f; - ecu.idle_spark_gain = .02f; - ecu.ignition_map = {{0, 10}, {750, 12}, {2500, 28}, {4000, 32}, {6000, 30}}; - ecu.rev_limit_rpm = 7000; - ecu.rev_resume_rpm = 6750; - ecu.overrun_cut_rpm = 2000; - ecu.overrun_resume_rpm = 1100; - // Port-coupled fittings use one minimum substep; acoustic CFL may subdivide further. - solver_.minimum_substeps = 1; - } - if(engine.cylinders) { - solver_.minimum_substeps=1; - const float litres=count*pi*.25f*engine.bore*engine.bore*engine.stroke*1000; - crankshaft.omega=engine.idle_rpm*1.4f*2*pi/60;crankshaft.inertia=std::max(.04f,litres*.12f); - crankshaft.friction_constant=litres*4;crankshaft.friction_viscous=litres*.004f; - starter_torque=litres*25;vehicle.mass=count<=2?240:1400;vehicle.clutch_capacity=litres*220; - ecu.idle_rpm=engine.idle_rpm;ecu.rev_limit_rpm=engine.rev_limit;ecu.rev_resume_rpm=engine.rev_limit-250; - ecu.idle_spark_gain=.02f;ecu.idle_base_opening=.05f; - if(engine.carbureted) { - ecu.idle_control=Ecu::IdleControl::FixedOpening;ecu.idle_base_opening=engine.idle_opening; - ecu.overrun_fuel_cut_enabled=false; - carb_=std::make_unique(.085f); - const float jet_scale=engine.carb_jet_scale*std::sqrt(litres/.5f); - carb_->pilot_jet_diameter*=jet_scale*engine.carb_pilot_scale;carb_->main_jet_diameter*=jet_scale;carb_->needle_jet_diameter*=jet_scale; - carb_->bowl_capacity=.1f;carb_->bowl_refill_rate=.1f; - intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f); - } + const auto& engine=engine_config; + const auto& junctions=junction_config; + const bool v8=engine.american_headers(),staged=engine.staged_headers(); + const int count=cylinder_count(); + static_cast(crankshaft)=engine.crankshaft; + static_cast(ecu)=engine.ecu; + static_cast(vehicle)=engine.vehicle; + crankshaft.omega=engine.initial_rpm*2*pi/60; + starter_torque=engine.starter_torque; + solver_.minimum_substeps=engine.minimum_substeps; + if(engine.carbureted) { + carb_=std::make_unique(engine.throttle_diameter); + static_cast(*carb_)=engine.fuel; + // Carburetors keep metering fuel during both limiter and overrun cuts. + ecu.rev_limit_fuel_cut_enabled=false; + ecu.overrun_fuel_cut_enabled=false; + ecu.overrun_ignition_cut_enabled=true; + intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f); } vehicle.set_neutral(); vehicle.clutch_input = clutch; const auto engines = cylinders(); - const std::array phases{0, pi, 3*pi, 2*pi}; // I4 firing: 1-3-4-2 - const std::array firing_order{1, 5, 4, 2, 6, 3, 7, 8}; - for (int i = 0; i < count; ++i) { - auto& c = *engines[i]; - c.phase = phases[i%4]; - if (v8) { - const auto position = std::find(firing_order.begin(), firing_order.end(), i+1) - firing_order.begin(); - c.phase = std::fmod(4*pi - float(position)*pi*.5f, 4*pi); - c.bore = .1016f; - c.stroke = .0762f; - c.rod = .129f; - c.compression = 7.0f; - c.intake_open = 350; c.intake_close = 570; - c.exhaust_open = 140; c.exhaust_close = 370; - c.burn_duration = 65; - } - if (saab) { - c.bore = .090f; - c.stroke = .078f; - c.rod = .153f; // estimated, not a verified B205 rod measurement - c.compression = 8.8f; - c.intake_valve_count = c.exhaust_valve_count = 2; - } - c.intake_valve_diameter = saab ? .032f : .030f; - c.intake_valve_lift = saab ? .0085f : .008f; - c.exhaust_valve_diameter = saab ? .028f : .026f; - c.exhaust_valve_lift = saab ? .008f : .0075f; - if (v8) { - c.intake_valve_diameter = .045f; c.exhaust_valve_diameter = .037f; - c.intake_valve_lift = .010f; c.exhaust_valve_lift = .010f; - } - if(engine.cylinders) { - c.phase=engine.custom_phases?engine.phase_degrees[i]*pi/180:std::fmod(4*pi-i*4*pi/count,4*pi); - c.bore=engine.bore;c.stroke=engine.stroke;c.rod=engine.rod;c.compression=engine.compression; - c.intake_valve_diameter=engine.bore*.38f;c.exhaust_valve_diameter=engine.bore*.32f; - c.intake_valve_lift=engine.bore*.10f;c.exhaust_valve_lift=engine.bore*.09f; - c.transported_fuel=engine.carbureted; - } - c.rev_limit_enabled = true; + for (int i=0;i(c)=engine.cylinder; + c.phase=engine.phase_degrees.at(i)*pi/180; + c.transported_fuel=engine.carbureted; + c.rev_limit_enabled = ecu.rev_limit_enabled; c.rev_limit_rpm = ecu.rev_limit_rpm; c.rev_limit_hysteresis = 250; c.rev_limit_cut_time = .020f; c.ecu = &ecu; c.initialize(crankshaft); - if (saab || v8 || engine.cylinders) { + if (engine.initialize_phased_charge) { // Initialize each phased cylinder at its actual piston position. c.volume = c.compute_volume(c.phase); c.air_mass = ambient_density * c.volume; @@ -148,13 +69,13 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte header_area_ = pipes[1+count].area; tail_area_ = pipes[1+2*count].area; for (auto& pipe : pipes) { - pipe.damping_multiplier = saab ? 0.f : 1.f; + pipe.damping_multiplier = engine.pipe_damping; pipe.energy_relaxation_rate = 0; } auto& throttle_port = intake_plenum.create_port(); throttle_port_=&throttle_port; solver_.add_component(intake_plenum); - if(!v8) solver_.add_component(exhaust_collector); + if(!v8 && !staged) solver_.add_component(exhaust_collector); boundaries_.add_orifice(throttle_port, 0, false, throttle_area_, .7f); for (int i = 0; i < count; ++i) { auto& feed = intake_plenum.create_port(); @@ -164,11 +85,11 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte boundaries_.add_orifice(feed, 1+i, true, runner_area_); boundaries_.add_orifice(engines[i]->intake, 1+i, false, intake_valve_areas_[i]); boundaries_.add_orifice(engines[i]->exhaust, 1+count+i, true, exhaust_valve_areas_[i]); - if (!v8) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_); + if (!v8 && !staged) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_); } if(v8) { auto junction=[&](std::initializer_list ends) { - auto fitting=std::make_unique(v8_exhaust.junction_volume,101325,700); + auto fitting=std::make_unique(junctions.junction_volume,101325,700); fitting->connect(boundaries_,pipes,std::span(ends.begin(),ends.size())); solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting)); }; @@ -179,7 +100,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte junction({{14,false},{16,false},{20,true}}); junction({{17,false},{18,false},{21,true}}); junction({{19,false},{20,false},{22,true}}); - if(v8_exhaust.h_pipe) { + if(junctions.h_pipe) { junction({{21,false},{23,true},{25,true}}); junction({{22,false},{24,true},{25,false}}); } else { @@ -189,6 +110,33 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte } boundaries_.add_radiating_open_end(23,false,101325,pipes[23].area); boundaries_.add_radiating_open_end(24,false,101325,pipes[24].area); + } else if(staged) { + const int exit=1+2*count; + const bool dual=engine.dual_outlets(); + const int groups=engine.header_groups(), secondary=exit+(dual?2:1); + auto junction=[&](const std::vector& ends) { + auto fitting=std::make_unique(junctions.junction_volume,101325,700); + fitting->connect(boundaries_,pipes,ends); + solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting)); + }; + for(int group=0;group ends; + for(int i=0;i ends{{exit,true}}; + for(int group=0;groupstep(dt_,throttle,opening,crankshaft.omega*60/(2*pi),std::max(throttle_port_->mass_flow,0.f),inlet.rho,inlet.pressure,intake_plenum.pressure()); intake_plenum.add_fuel_mass(carb_->fuel_flow()*dt_); } solver_.step(dt_); - intake_flow = boundaries_.open_end_mass_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1); + intake_flow = boundaries_.open_end_mass_flow(engine_config.dual_outlets() ? 2 : 1); exhaust_flow = boundaries_.open_end_mass_flow(0); - const float other_exhaust = preset == FourStrokePreset::AmericanV8 ? boundaries_.open_end_mass_flow(1) : 0.f; + const float other_exhaust = engine_config.dual_outlets() ? boundaries_.open_end_mass_flow(1) : 0.f; // Compact mouth radiation responds to volume velocity. Use ambient-equivalent // mass flow so hot/cold donor-density changes do not enter the derivative. - const float intake_source = ambient_density * boundaries_.open_end_volume_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1); + const float intake_source = ambient_density * boundaries_.open_end_volume_flow(engine_config.dual_outlets() ? 2 : 1); const float exhaust_source = ambient_density * boundaries_.open_end_volume_flow(0); - const float other_source = preset == FourStrokePreset::AmericanV8 ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f; + const float other_source = engine_config.dual_outlets() ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f; const auto rendered = audio_.process(intake_source, exhaust_source, other_source); exhaust_flow += other_exhaust; ++sample_count; @@ -241,6 +190,9 @@ void FourStrokeScenario::set_pipe_solver(PipeSolver solver) { void FourStrokeScenario::set_audio_scene(OutdoorScene scene) { const std::lock_guard guard(mutex_); audio_.set_scene(scene); } +void FourStrokeScenario::set_listener(AudioListener listener) { + const std::lock_guard guard(mutex_); audio_.set_listener(listener); +} void FourStrokeScenario::set_monitor_filter(bool enabled) { const std::lock_guard guard(mutex_); audio_.set_monitor_filter(enabled); } @@ -283,107 +235,18 @@ void FourStrokeScenario::set_first_gear() { vehicle.set_first_gear(); } -ScenarioSnapshot FourStrokeScenario::snapshot() const { - const std::lock_guard guard(mutex_); - ScenarioSnapshot result; - result.layout=engine_config.cylinders?engine_config.layout:preset==FourStrokePreset::AmericanV8?EngineLayout::V:EngineLayout::Inline; - result.bank_angle=engine_config.cylinders?engine_config.bank_angle:56; - result.v8_headers=preset==FourStrokePreset::AmericanV8; - result.silencers = preset == FourStrokePreset::SaabB205 && exhaust_config.enabled; - result.exhaust_config = exhaust_config; - result.h_pipe = preset==FourStrokePreset::AmericanV8 && v8_exhaust_config.h_pipe; - result.crossover_pressures = preset==FourStrokePreset::AmericanV8 ? std::array{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array{}; - result.pipe_solver = pipes.front().solver; - result.solver_substeps = solver_.substep_count(); - result.boundary_iterations = boundaries_.nonlinear_iterations; - result.rpm = crankshaft.omega * 60.0f / (2.0f * pi); - result.speed_kmh = vehicle.speed * 3.6f; - result.kinetic_energy = vehicle.kinetic_energy(); - result.average_power = crankshaft.average_power; - result.average_torque = crankshaft.average_torque; - result.cylinder_pressure1 = cylinder1.pressure; - result.cylinder_pressure2 = cylinder2.pressure; - result.cylinder_temperature1 = cylinder1.temperature(); - result.cylinder_temperature2 = cylinder2.temperature(); - result.cylinder_piston_fraction1 = (cylinder1.volume - cylinder1.clearance_volume()) / cylinder1.swept_volume(); - result.cylinder_piston_fraction2 = (cylinder2.volume - cylinder2.clearance_volume()) / cylinder2.swept_volume(); - result.valve1_intake_area = intake_valve_areas_[0]; - result.valve1_exhaust_area = exhaust_valve_areas_[0]; - result.valve2_intake_area = intake_valve_areas_[1]; - result.valve2_exhaust_area = exhaust_valve_areas_[1]; - result.intake_pressure = intake_plenum.pressure(); - result.exhaust_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[21].state(0).pressure : exhaust_collector.pressure(); - result.second_bank_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[22].state(0).pressure : 101325; - result.throttle = ecu.throttle_opening(); - result.pedal = throttle; - result.target_afr = carb_?carb_->indicated_afr():ecu.target_afr(); - result.idle_control = ecu.idle_active(); - result.fuel_cut = ecu.fuel_cut(); - result.audio_scene = audio_.scene(); - result.clutch = clutch; - result.intake_flow = intake_flow; - result.exhaust_flow = exhaust_flow; - result.gear = vehicle.current_gear; - result.rev_limit1 = cylinder1.rev_limit_active; - result.rev_limit2 = cylinder2.rev_limit_active; - result.cylinder_count = cylinder_count(); - result.rev_limit_rpm = ecu.rev_limit_rpm; - const std::array engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4, - &cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; - for (int i = 0; i < cylinder_count(); ++i) { - result.cylinder_angles[i] = crankshaft.angle + engines[i]->phase; - result.cylinder_pressures[i] = engines[i]->pressure; - result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume(); - result.intake_areas[i] = intake_valve_areas_[i]; - result.exhaust_areas[i] = exhaust_valve_areas_[i]; - } - result.capture_pipes(pipes); - return result; -} - float FourStrokeScenario::area_for_diameter(float diameter) { return pi * diameter * diameter * 0.25f; } -std::vector FourStrokeScenario::create_pipes(FourStrokePreset preset, const ExhaustConfig& exhaust, const V8ExhaustConfig& v8_exhaust, const EngineConfig& engine) { - if(engine.cylinders) { - if(engine.cylinders<1||engine.cylinders>12||engine.bore<=0||engine.stroke<=0||engine.rod<=engine.stroke*.5f)throw std::invalid_argument("invalid custom engine dimensions"); - std::vector result; - result.emplace_back(area_for_diameter(.075f),.20f,4); - for(int i=0;i FourStrokeScenario::create_pipes(const EngineConfig& engine,const ExhaustConfig& exhaust) { + validate_engine_config(engine); + std::vector pipes; + for(const auto& pipe:engine.pipes)for(int i=0;i shared plenum -> short individual runners. - // Bank collectors feed twin pipes joined by a finite-length H crossover. - // Cells stay long enough to avoid routine - // extra CFL substeps during high-vacuum coast-down. - std::vector result; - result.emplace_back(area_for_diameter(.070f), .18f, 3); - for (int i = 0; i < 8; ++i) - result.emplace_back(area_for_diameter(.038f), .12f, 2); - for (int i = 0; i < 8; ++i) - result.emplace_back(area_for_diameter(v8_exhaust.primary_diameter),v8_exhaust.primary_length,int(std::ceil(v8_exhaust.primary_length/.05f))); - for(int i=0;i<4;++i) result.emplace_back(area_for_diameter(.045f),.40f,8); - for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),.55f,11); - for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),1.25f,25); - result.emplace_back(area_for_diameter(.038f),.78f,16); - return result; - } - const int count = saab ? 4 : 2; - std::vector result; - result.reserve(4+2*count); - result.emplace_back(area_for_diameter(saab ? .060f : .0275f), saab ? .15f : .06f, saab ? 8 : 10); - for (int i = 0; i < count; ++i) - result.emplace_back(area_for_diameter(saab ? .032f : .0275f), saab ? .25f : .12f, 10); - for (int i = 0; i < count; ++i) - result.emplace_back(area_for_diameter(saab ? .034f : .028f), saab ? .28f : .20f, saab ? 10 : 6); - result.emplace_back(area_for_diameter(saab ? .060f : .028f), saab ? 1.4f : .40f, saab ? 40 : 10); - if (saab) result.back() = make_silenced_exhaust(exhaust); - return result; + return pipes; } } // namespace fluidsim::port diff --git a/src/scenario_geometry.cpp b/src/scenario_geometry.cpp new file mode 100644 index 0000000..b5bbee8 --- /dev/null +++ b/src/scenario_geometry.cpp @@ -0,0 +1,84 @@ +#include "FluidSim/ScenarioGeometry.hpp" +#include "FluidSim/ScenarioSnapshot.hpp" +#include +#include + +namespace fluidsim::port { +namespace { +struct Anchor { float fraction; Vec3 point; }; +PipeRoute route(std::initializer_list anchors) { + PipeRoute result; + for (auto a : anchors) { + result.fractions[result.count] = a.fraction; + result.points[result.count++] = a.point; + } + return result; +} +} +std::array scenario_pipe_routes(const ScenarioSnapshot& s) { + std::array routes{}; + if (s.two_stroke) { + routes[0]=route({{0,{-1.05f,.35f,0}},{1,{-.64f,.35f,0}}}); + routes[1]=route({{0,{-.46f,.35f,0}},{1.f/3,{-.28f,.35f,0}},{2.f/3,{-.28f,.1f,0}},{1,{-.105f,.1f,0}}}); + routes[2]=route({{0,{.105f,.09f,0}},{1.f/3,{.18f,.09f,-.12f}},{2.f/3,{.18f,.35f,-.12f}},{1,{.05f,.35f,0}}}); + routes[3]=route({{0,{.055f,.40f,0}},{1.f/3,{.3f,.40f,0}},{2.f/3,{.58f,.23f,0}},{1,{1.45f,.23f,0}}}); + return routes; + } + const int n=s.cylinder_count; + const bool banked=s.layout!=EngineLayout::Inline; + const int per_bank=banked?n/2:n; + const float first=-(per_bank-1)*.21f*.5f; + routes[0]=route({{0,{-1.05f,.64f,0}},{.25f,{-.5f,.64f,0}},{.5f,{-.5f,.64f,-.1f}},{.75f,{0,.64f,-.1f}},{1,{0,.64f,0}}}); + for(int i=0;i pair{0,0,1,1,2,3,2,3}; + for(int i=0;i<8;++i) { + const float x=first+(i%4)*.21f,z=i/4?.39f:-.39f,a=(i/4?28.f:-28.f)*.017453293f; + const float end_z=z+(pair[i]%2?.09f:-.09f); + routes[9+i]=route({{0,{x+.055f,.1f+.33f*std::cos(a),.33f*std::sin(a)}},{.3f,{x+.12f,.34f,z}},{.65f,{.35f,.20f,end_z}},{1,{.74f,.17f,end_z}}}); + } + for(int i=0;i<4;++i) { + const float z=i<2?-.39f:.39f; + routes[17+i]=route({{0,{.74f,.17f,z+(i%2?.09f:-.09f)}},{.5f,{.94f,.17f,z+(i%2?.06f:-.06f)}},{1,{1.14f,.17f,z}}}); + } + if(s.h_pipe)routes[25]=route({{0,{1.69f,.17f,-.39f}},{1,{1.69f,.17f,.39f}}}); + } + if(s.staged_headers) { + const int exit=1+2*n,groups=n==3?1:2,secondary=exit+(s.dual_exhaust?2:1); + for(int group=0;group scenario_sound_sources(const ScenarioSnapshot& s) { + const auto routes=scenario_pipe_routes(s); + const int exhaust=s.two_stroke?3:s.v8_headers?23:1+2*s.cylinder_count; + const auto end=[&](int index) {const auto& r=routes[index];return r.points[r.count-1];}; + return {routes[0].points[0],end(exhaust),s.dual_exhaust?end(exhaust+1):end(exhaust)}; +} +} diff --git a/src/scenario_snapshot.cpp b/src/scenario_snapshot.cpp new file mode 100644 index 0000000..0ea3e5c --- /dev/null +++ b/src/scenario_snapshot.cpp @@ -0,0 +1,69 @@ +#include "FluidSim/Scenario.hpp" +#include + +namespace fluidsim::port { +ScenarioSnapshot FourStrokeScenario::snapshot() const { + const std::lock_guard guard(mutex_); + ScenarioSnapshot result; + result.layout=engine_config.layout; + result.bank_angle=engine_config.bank_angle; + result.v8_headers=engine_config.american_headers(); + result.staged_headers=engine_config.staged_headers(); + result.dual_exhaust=engine_config.dual_outlets(); + result.silencers = engine_config.has_silenced_exhaust() && exhaust_config.enabled; + result.exhaust_config = exhaust_config; + result.h_pipe = engine_config.american_headers() && junction_config.h_pipe; + result.crossover_pressures = engine_config.american_headers() ? std::array{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array{}; + result.pipe_solver = pipes.front().solver; + result.solver_substeps = solver_.substep_count(); + result.boundary_iterations = boundaries_.nonlinear_iterations; + result.rpm = crankshaft.omega * 60.0f / (2.0f * pi); + result.speed_kmh = vehicle.speed * 3.6f; + result.kinetic_energy = vehicle.kinetic_energy(); + result.average_power = crankshaft.average_power; + result.average_torque = crankshaft.average_torque; + result.cylinder_pressure1 = cylinder1.pressure; + result.cylinder_pressure2 = cylinder2.pressure; + result.cylinder_temperature1 = cylinder1.temperature(); + result.cylinder_temperature2 = cylinder2.temperature(); + result.cylinder_piston_fraction1 = (cylinder1.volume - cylinder1.clearance_volume()) / cylinder1.swept_volume(); + result.cylinder_piston_fraction2 = (cylinder2.volume - cylinder2.clearance_volume()) / cylinder2.swept_volume(); + result.valve1_intake_area = intake_valve_areas_[0]; + result.valve1_exhaust_area = exhaust_valve_areas_[0]; + result.valve2_intake_area = intake_valve_areas_[1]; + result.valve2_exhaust_area = exhaust_valve_areas_[1]; + result.intake_pressure = intake_plenum.pressure(); + const int exit=1+2*cylinder_count(); + result.exhaust_pressure = result.v8_headers ? pipes[21].state(0).pressure : + result.staged_headers ? pipes[exit].state(0).pressure : exhaust_collector.pressure(); + result.second_bank_pressure = result.v8_headers ? pipes[22].state(0).pressure : + result.dual_exhaust ? pipes[exit+1].state(0).pressure : 101325; + result.throttle = ecu.throttle_opening(); + result.pedal = throttle; + result.target_afr = carb_?carb_->indicated_afr():ecu.target_afr(); + result.idle_control = ecu.idle_active(); + result.fuel_cut = ecu.fuel_cut(); + result.ignition_cut = ecu.ignition_cut(); + result.audio_scene = audio_.scene(); + result.clutch = clutch; + result.intake_flow = intake_flow; + result.exhaust_flow = exhaust_flow; + result.gear = vehicle.current_gear; + result.rev_limit1 = cylinder1.rev_limit_active; + result.rev_limit2 = cylinder2.rev_limit_active; + result.cylinder_count = cylinder_count(); + result.rev_limit_rpm = ecu.rev_limit_rpm; + const std::array engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4, + &cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; + for (int i = 0; i < cylinder_count(); ++i) { + result.cylinder_angles[i] = crankshaft.angle + engines[i]->phase; + result.cylinder_pressures[i] = engines[i]->pressure; + result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume(); + result.intake_areas[i] = intake_valve_areas_[i]; + result.exhaust_areas[i] = exhaust_valve_areas_[i]; + } + result.capture_pipes(pipes); + return result; +} + +} diff --git a/src/two_stroke_cylinder.cpp b/src/two_stroke_cylinder.cpp index 18c109a..9d6a263 100644 --- a/src/two_stroke_cylinder.cpp +++ b/src/two_stroke_cylinder.cpp @@ -80,7 +80,7 @@ void TwoStrokeCylinder::handle_cycle_events(float previous_degrees, float curren const float spark_angle = wrap_degrees(360.0f - spark_advance, 360.0f); const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle); if (crossed_spark && fuel_injected && !combustion_active && - !(ecu ? ecu->fuel_cut() : rev_limit_active) && + !(ecu ? ecu->ignition_cut() : rev_limit_active) && random_unit() >= misfire_probability) { combustion_active = true; burn_fraction = 0.0f; diff --git a/src/two_stroke_scenario.cpp b/src/two_stroke_scenario.cpp index 6c7418f..ce467dd 100644 --- a/src/two_stroke_scenario.cpp +++ b/src/two_stroke_scenario.cpp @@ -1,5 +1,7 @@ #include "FluidSim/TwoStrokeScenario.hpp" #include "FluidSim/Constants.hpp" +#include "FluidSim/ScenarioGeometry.hpp" +#include "FluidSim/Scenario.hpp" #include #include @@ -18,15 +20,20 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config) boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate) { if (!std::isfinite(config.idle_slide_opening) || config.idle_slide_opening<0 || config.idle_slide_opening>1) throw std::invalid_argument("carb idle slide opening must be in [0,1]"); + static_cast(ecu)=config.ecu; + static_cast(vehicle)=config.vehicle; ecu.idle_control = Ecu::IdleControl::FixedOpening; - carburetor.pilot_jet_diameter*=config.jet_scale; - carburetor.main_jet_diameter*=config.jet_scale; - carburetor.needle_jet_diameter*=config.jet_scale; - ecu.throttle_time_constant = .0002f; - ecu.throttle_rate = 5000; - crankshaft.omega = 3000.0f * 2.0f * pi / 60.0f; - crankshaft.inertia = 0.01f*(config.bore*config.bore*config.stroke)/(.054f*.054f*.0545f); - starter_torque = 8.f; + carburetor.pilot_jet_diameter*=config.jet_scale*config.pilot_jet_scale; + carburetor.main_jet_diameter*=config.jet_scale*config.main_jet_scale; + carburetor.needle_jet_diameter*=config.jet_scale*config.needle_jet_scale; + carburetor.needle_low_scale=config.needle_low_scale;carburetor.needle_mid_scale=config.needle_mid_scale; + carburetor.needle_transition_scale=config.needle_transition_scale; + carburetor.needle_off_idle_scale=config.needle_off_idle_scale;carburetor.needle_tip_scale=config.needle_tip_scale; + carburetor.pilot_transition_scale=config.pilot_transition_scale;carburetor.pilot_tip_scale=config.pilot_tip_scale; + carburetor.needle_quarter_scale=config.needle_quarter_scale;carburetor.needle_upper_scale=config.needle_upper_scale; + crankshaft.omega=config.initial_rpm*2*pi/60; + crankshaft.inertia=config.crankshaft_inertia; + starter_torque=config.starter_torque; const float pressure_to_torque = cylinder.swept_volume() * 1e5f / (2 * pi); const float piston_speed_per_omega = config.stroke / pi; crankshaft.friction_constant = pressure_to_torque * config.friction_bar; @@ -37,24 +44,14 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config) cylinder.transfer_width = config.transfer_width; cylinder.exhaust_width = config.exhaust_width; cylinder.minimum_burn_seconds = config.minimum_burn_seconds; - // Yamaha's 2022 YZ125 published ratios; mass includes a nominal 75 kg rider. - vehicle.primary_reduction = 64.f / 19; - vehicle.final_drive_ratio = 49.f / 13; - vehicle.gear_ratios = {31.f/13, 29.f/15, 27.f/17, 23.f/17, 20.f/17, 19.f/18}; - vehicle.mass = 95 + 75; - vehicle.wheel_radius = .3313f; // nominal 100/90-19 geometry; loaded radius unmeasured - // Estimated two-stroke ignition calibration, not a factory CDI map. - ecu.ignition_map = {{0, 10}, {3000, 18}, {6000, 24}, {9000, 18}, - {11000, 12}, {13000, 6}, {15000, 2}, {18000, 0}}; - ecu.idle_rpm = 3000; - ecu.rev_limit_enabled = false; - cylinder.rev_limit_enabled = false; + cylinder.rev_limit_enabled=ecu.rev_limit_enabled; ecu.idle_base_opening = config.idle_slide_opening; ecu.idle_max_opening = .3f; - ecu.idle_spark_gain=0; - ecu.overrun_cut_rpm = 5000; - ecu.overrun_resume_rpm = 3500; + ecu.idle_spark_gain=config.idle_spark_gain; + ecu.idle_minimum_advance=config.idle_minimum_advance; + ecu.rev_limit_fuel_cut_enabled = false; ecu.overrun_fuel_cut_enabled = false; + ecu.overrun_ignition_cut_enabled = true; cylinder.ecu = &ecu; cylinder.initialize(crankshaft); cylinder.set_crankcase(crankcase); @@ -92,6 +89,9 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config) solver_.add_component(intake_plenum); solver_.add_component(crankcase); solver_.add_component(cylinder); + ScenarioSnapshot geometry; + geometry.two_stroke=true; + audio_.set_source_positions(scenario_sound_sources(geometry)); } AudioFrame TwoStrokeScenario::process() { @@ -105,7 +105,7 @@ AudioFrame TwoStrokeScenario::process() { carb_inlet.pressure, intake_plenum.pressure()); intake_plenum.add_fuel_mass(carburetor.fuel_flow() * dt_); max_throttle_area_ = slide_area(configuration.carburetor_diameter, ecu.throttle_opening()); - step_motorcycle(crankshaft, dt_, 10.0f); + step_motorcycle(crankshaft, dt_, configuration.maximum_load_torque); crankcase.pre_step(dt_); cylinder.pre_step(crankshaft, dt_, ecu.throttle_opening()); transfer_valve_area_ = cylinder.valve_area(crankshaft.angle * 180.0f / pi, true); @@ -137,6 +137,7 @@ TwoStrokeSnapshot TwoStrokeScenario::snapshot() const { cylinder.valve_area(crankshaft.angle * 180.0f / pi, false), reed_open_, ecu.fuel_cut(), + ecu.ignition_cut(), throttle, audio_.scene()}; } diff --git a/tests/desktop_ui_tests.cpp b/tests/desktop_ui_tests.cpp index d5017ad..bbb5727 100644 --- a/tests/desktop_ui_tests.cpp +++ b/tests/desktop_ui_tests.cpp @@ -33,5 +33,18 @@ int main() { camera.orbit({40,20});camera.pan({20,15},500);camera.zoom(2); check(camera.pitch<=80 && camera.pitch>=-75 && camera.distance>.35f,"camera bounds"); check(camera.target.x!=before.x || camera.target.y!=before.y,"camera panning must move target"); + for(float yaw:{-90.f,-28.f,0.f,90.f,180.f})for(float pitch:{-60.f,0.f,70.f}) { + camera.yaw=yaw;camera.pitch=pitch;camera.target={.7f,.4f,-.2f};camera.distance=3; + const auto listener=camera.listener(1.8f,1.1f); + const float y=yaw*.017453293f,p=pitch*.017453293f; + const float dx=listener.position.x-camera.target.x-1.1f; + const float dy=listener.position.y-camera.target.y,dz=listener.position.z-camera.target.z; + const float x1=std::cos(y)*dx+std::sin(y)*dz,z1=-std::sin(y)*dx+std::cos(y)*dz; + check(std::abs(x1)<2e-6f && std::abs(std::cos(p)*dy-std::sin(p)*z1)<2e-6f && + std::abs(std::sin(p)*dy+std::cos(p)*z1-5.4f)<2e-6f, + "audio listener must coincide with the rendered camera including fit offsets"); + check(std::abs(std::cos(y)*listener.right.x+std::sin(y)*listener.right.z-1)<1e-6f, + "audio ears must rotate with the camera right axis"); + } std::cout<<"Desktop layout, spring pedal, keyboard travel and orbit camera checks passed\n"; } diff --git a/tests/engine_config_tests.cpp b/tests/engine_config_tests.cpp new file mode 100644 index 0000000..a601581 --- /dev/null +++ b/tests/engine_config_tests.cpp @@ -0,0 +1,78 @@ +#include "FluidSim/ScenarioCatalog.hpp" +#include "FluidSim/DesktopScenario.hpp" +#include +#include +#include +#include +#include +#include + +using namespace fluidsim::port; +namespace { +void check(bool condition,const std::string& message) {if(!condition)throw std::runtime_error(message);} +std::string read(const std::filesystem::path& path) { + std::ifstream file(path);std::ostringstream text;text<=32,"bundled engines missing"); + const auto& thumper=scenario_example("single-450"); + check(thumper.engine.carbureted && std::abs(thumper.engine.cylinder.bore-.096f)<1e-7f, + "four-stroke units or fuel type changed"); + check(std::abs(scenario_example("yz-racing").two.idle_slide_opening-.03f)<1e-7f, + "125 idle tune lost"); + for(const auto& e:catalog) { + DesktopScenario s(e);s.set_controls(.35f,0,1); + for(int i=0;i<256;++i) { + const auto audio=s.process(); + check(std::isfinite(audio.left) && std::isfinite(audio.right),e.id+": invalid audio after TOML load"); + } + const auto state=s.snapshot(); + check(state.rpm>0 && state.pipe_positivity_repairs==0,e.id+": startup changed"); + } + + TemporaryDirectory temp; + const auto file=temp.path/"test.toml"; + const auto original=read(engine_directory()/"twin.toml"); + const auto reject=[&](std::string text,const std::string& expected) { + {std::ofstream output(file);output< #include #include @@ -98,6 +102,85 @@ int main() { } check(right < left, "source on right does not reach right ear first"); + // A moved listener must converge to the same propagation as one created + // there, including the reversed ear orientation and the reflected field. + auto moved_config=stereo_config; + moved_config.listener={-.5f,1.5f,4}; + moved_config.listener_right={-1,0,0}; + OutdoorAudio moving(44100,stereo_config),destination(44100,moved_config); + moving.set_listener({moved_config.listener,moved_config.listener_right}); + for(auto* p:{&moving,&destination})p->set_monitor_filter(false); + for(int i=0;i<44100;++i) {moving.process(0,0);destination.process(0,0);} + for(int i=0;i<6000;++i) { + const auto a=moving.process(0,.001f),b=destination.process(0,.001f); + check(std::abs(a.left-b.left)<2e-7f && std::abs(a.right-b.right)<2e-7f, + "moving listener does not update direct/reflected stereo paths"); + } + // Exercise rapid pose changes while a wet tail is still ringing. + for(int i=0;i<22050;++i) { + if(i%735==0) { + const float angle=i*.0002f; + moving.set_listener({{3*std::sin(angle),1,3*std::cos(angle)}, + {std::cos(angle),0,-std::sin(angle)}}); + } + const auto a=moving.process(0,.001f); + check(std::isfinite(a.left) && std::isfinite(a.right) && + std::abs(a.left)<1 && std::abs(a.right)<1,"moving wet paths became unstable"); + } + + // Source relocation must use geometry without resetting audio state. + auto source_config=config();source_config.exhaust_position={2,1,-1}; + OutdoorAudio relocated(44100,config()),fixed_source(44100,source_config); + relocated.set_source_positions({source_config.intake_position,source_config.exhaust_position, + source_config.second_exhaust_position}); + for(int i=0;i<44100;++i) {relocated.process(0,0);fixed_source.process(0,0);} + for(int i=0;i<3000;++i) { + const auto a=relocated.process(0,.001f),b=fixed_source.process(0,.001f); + check(std::abs(a.left-b.left)<2e-7f && std::abs(a.right-b.right)<2e-7f, + "relocated mouth does not match world source position"); + } + ScenarioSnapshot geometry; + geometry.two_stroke=true; + auto mouths=scenario_sound_sources(geometry); + check(mouths[0].x==-1.05f && mouths[1].x==1.45f && mouths[1].y==.23f, + "two-stroke mouths are not on the drawn pipe ends"); + geometry.two_stroke=false;geometry.cylinder_count=8;geometry.layout=EngineLayout::V; + geometry.v8_headers=true;geometry.dual_exhaust=true; + mouths=scenario_sound_sources(geometry); + check(mouths[1].x==2.94f && mouths[1].z==-.39f && mouths[2].z==.39f, + "dual V8 outlets are not at their separate tailpipe mouths"); + geometry.v8_headers=false;geometry.dual_exhaust=false;geometry.silencers=true; + geometry.cylinder_count=4;geometry.layout=EngineLayout::Inline; + const auto& e=geometry.exhaust_config; + mouths=scenario_sound_sources(geometry); + check(std::abs(mouths[1].x-(.74f+e.inlet_length+e.resonator.length+e.connecting_length+ + e.muffler.length+e.outlet_length))<1e-6f, + "silenced exhaust source does not follow the configured pipe length"); + // Crossing a reflecting plane must not turn its virtual image into a + // source that arrives before the actual pipe mouth. + auto behind_config=config();behind_config.listener={-4,1,0}; + OutdoorAudio behind_wall(44100,behind_config); + behind_wall.set_monitor_filter(false); + for(int i=0;i<4410;++i)behind_wall.process(0,0); + int first_behind=-1; + for(int i=0;i<1000;++i) { + const auto a=behind_wall.process(0,.001f); + if(first_behind<0 && std::abs(a.left)>1e-9f)first_behind=i; + } + check(first_behind==514,"reflection crosses wall and precedes the physical source"); + + for(const auto& example:scenario_examples()) { + DesktopScenario scenario(example.kind,example.two,example.exhaust,example.junctions,example.engine); + scenario.set_listener({{2,1,3},{1,0,0}}); + const auto snapshot=scenario.snapshot(); + const auto sources=scenario_sound_sources(snapshot); + for(auto source:sources) + check(std::isfinite(source.x) && std::isfinite(source.y) && std::isfinite(source.z), + "catalog scenario has an invalid sound source"); + const auto frame=scenario.process(); + check(std::isfinite(frame.left) && std::isfinite(frame.right),"catalog listener wiring failed"); + } + ReedValve reed; for (int i = 0; i < 10000; i++) reed.step(1.f / 44100, 100); diff --git a/tests/scenario_examples.cpp b/tests/scenario_examples.cpp index d2c70fb..882112f 100644 --- a/tests/scenario_examples.cpp +++ b/tests/scenario_examples.cpp @@ -10,7 +10,7 @@ int main() { std::set ids; for(const auto& example:scenario_examples()) { if(!ids.insert(example.id).second)throw std::runtime_error("duplicate example id"); - DesktopScenario scenario(example.kind,example.two,example.exhaust,example.v8,example.engine,example.title); + DesktopScenario scenario(example.kind,example.two,example.exhaust,example.junctions,example.engine,example.title); scenario.set_audio_scene(OutdoorScene::Dry); const auto started=std::chrono::steady_clock::now(); float peak_power=0; @@ -25,9 +25,17 @@ int main() { if(example.id=="big-carb" && peak_power<10000)throw std::runtime_error("big carb V8 failed to produce power"); if(!std::isfinite(snapshot.rpm)||snapshot.rpm<=0)throw std::runtime_error(example.id+" invalid engine speed"); if(example.engine.cylinders && (snapshot.cylinder_count!=example.engine.cylinders || snapshot.layout!=example.engine.layout))throw std::runtime_error("custom cylinder layout lost"); + const bool custom_v8=example.engine.american_headers(); + if(custom_v8 && (!snapshot.v8_headers || snapshot.h_pipe!=example.junctions.h_pipe || snapshot.pipe_states[25].empty())) + throw std::runtime_error("custom V8 lost American junction headers/crossover"); + if(example.engine.staged_headers() && example.engine.layout==EngineLayout::Inline) { + if(!snapshot.staged_headers || snapshot.dual_exhaust) + throw std::runtime_error("inline engine lost staged junction collectors"); + } + if(example.engine.staged_headers() && example.engine.layout==EngineLayout::V && (!snapshot.dual_exhaust || !snapshot.staged_headers)) + throw std::runtime_error("large V engine lost separate bank outlets"); if(snapshot.example_name!=example.title)throw std::runtime_error("example name lost"); - if(example.kind==ScenarioKind::AmericanV8 && snapshot.h_pipe!=example.v8.h_pipe)throw std::runtime_error("example crossover selection lost"); - if(example.kind==ScenarioKind::SaabB205 && snapshot.silencers!=example.exhaust.enabled)throw std::runtime_error("example silencer selection lost"); + if(example.engine.has_silenced_exhaust() && snapshot.silencers!=example.exhaust.enabled)throw std::runtime_error("example silencer selection lost"); std::cout< +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//---------------------------------------------------------------------------------------------------------------------- +#ifndef TOMLPLUSPLUS_HPP +#define TOMLPLUSPLUS_HPP + +#define INCLUDE_TOMLPLUSPLUS_H // old guard name used pre-v3 +#define TOMLPLUSPLUS_H // guard name used in the legacy toml.h + +//******** impl/preprocessor.hpp ************************************************************************************* + +#ifndef __cplusplus +#error toml++ is a C++ library. +#endif + +#ifndef TOML_CPP +#ifdef _MSVC_LANG +#if _MSVC_LANG > __cplusplus +#define TOML_CPP _MSVC_LANG +#endif +#endif +#ifndef TOML_CPP +#define TOML_CPP __cplusplus +#endif +#if TOML_CPP >= 202900L +#undef TOML_CPP +#define TOML_CPP 29 +#elif TOML_CPP >= 202600L +#undef TOML_CPP +#define TOML_CPP 26 +#elif TOML_CPP >= 202302L +#undef TOML_CPP +#define TOML_CPP 23 +#elif TOML_CPP >= 202002L +#undef TOML_CPP +#define TOML_CPP 20 +#elif TOML_CPP >= 201703L +#undef TOML_CPP +#define TOML_CPP 17 +#elif TOML_CPP >= 201402L +#undef TOML_CPP +#define TOML_CPP 14 +#elif TOML_CPP >= 201103L +#undef TOML_CPP +#define TOML_CPP 11 +#else +#undef TOML_CPP +#define TOML_CPP 0 +#endif +#endif + +#if !TOML_CPP +#error toml++ requires C++17 or higher. For a pre-C++11 TOML library see https://github.com/ToruNiina/Boost.toml +#elif TOML_CPP < 17 +#error toml++ requires C++17 or higher. For a C++11 TOML library see https://github.com/ToruNiina/toml11 +#endif + +#ifndef TOML_MAKE_VERSION +#define TOML_MAKE_VERSION(major, minor, patch) (((major)*10000) + ((minor)*100) + ((patch))) +#endif + +#ifndef TOML_INTELLISENSE +#ifdef __INTELLISENSE__ +#define TOML_INTELLISENSE 1 +#else +#define TOML_INTELLISENSE 0 +#endif +#endif + +#ifndef TOML_DOXYGEN +#if defined(DOXYGEN) || defined(__DOXYGEN) || defined(__DOXYGEN__) || defined(__doxygen__) || defined(__POXY__) \ + || defined(__poxy__) +#define TOML_DOXYGEN 1 +#else +#define TOML_DOXYGEN 0 +#endif +#endif + +#ifndef TOML_CLANG +#ifdef __clang__ +#define TOML_CLANG __clang_major__ +#else +#define TOML_CLANG 0 +#endif + +// special handling for apple clang; see: +// - https://github.com/marzer/tomlplusplus/issues/189 +// - https://en.wikipedia.org/wiki/Xcode +// - +// https://stackoverflow.com/questions/19387043/how-can-i-reliably-detect-the-version-of-clang-at-preprocessing-time +#if TOML_CLANG && defined(__apple_build_version__) +#undef TOML_CLANG +#define TOML_CLANG_VERSION TOML_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#if TOML_CLANG_VERSION >= TOML_MAKE_VERSION(15, 0, 0) +#define TOML_CLANG 16 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 3, 0) +#define TOML_CLANG 15 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 0, 0) +#define TOML_CLANG 14 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 1, 6) +#define TOML_CLANG 13 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 0, 0) +#define TOML_CLANG 12 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 5) +#define TOML_CLANG 11 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 0) +#define TOML_CLANG 10 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 3) +#define TOML_CLANG 9 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 0) +#define TOML_CLANG 8 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(10, 0, 1) +#define TOML_CLANG 7 +#else +#define TOML_CLANG 6 // not strictly correct but doesn't matter below this +#endif +#undef TOML_CLANG_VERSION +#endif +#endif + +#ifndef TOML_ICC +#ifdef __INTEL_COMPILER +#define TOML_ICC __INTEL_COMPILER +#ifdef __ICL +#define TOML_ICC_CL TOML_ICC +#else +#define TOML_ICC_CL 0 +#endif +#else +#define TOML_ICC 0 +#define TOML_ICC_CL 0 +#endif +#endif + +#ifndef TOML_MSVC_LIKE +#ifdef _MSC_VER +#define TOML_MSVC_LIKE _MSC_VER +#else +#define TOML_MSVC_LIKE 0 +#endif +#endif + +#ifndef TOML_MSVC +#if TOML_MSVC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_MSVC TOML_MSVC_LIKE +#else +#define TOML_MSVC 0 +#endif +#endif + +#ifndef TOML_GCC_LIKE +#ifdef __GNUC__ +#define TOML_GCC_LIKE __GNUC__ +#else +#define TOML_GCC_LIKE 0 +#endif +#endif + +#ifndef TOML_GCC +#if TOML_GCC_LIKE && !TOML_CLANG && !TOML_ICC +#define TOML_GCC TOML_GCC_LIKE +#else +#define TOML_GCC 0 +#endif +#endif + +#ifndef TOML_CUDA +#if defined(__CUDACC__) || defined(__CUDA_ARCH__) || defined(__CUDA_LIBDEVICE__) +#define TOML_CUDA 1 +#else +#define TOML_CUDA 0 +#endif +#endif + +#ifndef TOML_ARCH_ITANIUM +#if defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +#define TOML_ARCH_ITANIUM 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_ITANIUM 0 +#endif +#endif + +#ifndef TOML_ARCH_AMD64 +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) +#define TOML_ARCH_AMD64 1 +#define TOML_ARCH_BITNESS 64 +#else +#define TOML_ARCH_AMD64 0 +#endif +#endif + +#ifndef TOML_ARCH_X86 +#if defined(__i386__) || defined(_M_IX86) +#define TOML_ARCH_X86 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_X86 0 +#endif +#endif + +#ifndef TOML_ARCH_ARM +#if defined(__aarch64__) || defined(__ARM_ARCH_ISA_A64) || defined(_M_ARM64) || defined(__ARM_64BIT_STATE) \ + || defined(_M_ARM64EC) +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 1 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 64 +#elif defined(__arm__) || defined(_M_ARM) || defined(__ARM_32BIT_STATE) +#define TOML_ARCH_ARM32 1 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 1 +#define TOML_ARCH_BITNESS 32 +#else +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 0 +#endif +#endif + +#ifndef TOML_ARCH_BITNESS +#define TOML_ARCH_BITNESS 0 +#endif + +#ifndef TOML_ARCH_X64 +#if TOML_ARCH_BITNESS == 64 +#define TOML_ARCH_X64 1 +#else +#define TOML_ARCH_X64 0 +#endif +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) +#define TOML_WINDOWS 1 +#else +#define TOML_WINDOWS 0 +#endif + +#ifdef __unix__ +#define TOML_UNIX 1 +#else +#define TOML_UNIX 0 +#endif + +#ifdef __linux__ +#define TOML_LINUX 1 +#else +#define TOML_LINUX 0 +#endif + +// TOML_HAS_INCLUDE +#ifndef TOML_HAS_INCLUDE +#ifdef __has_include +#define TOML_HAS_INCLUDE(header) __has_include(header) +#else +#define TOML_HAS_INCLUDE(header) 0 +#endif +#endif + +// TOML_HAS_BUILTIN +#ifndef TOML_HAS_BUILTIN +#ifdef __has_builtin +#define TOML_HAS_BUILTIN(name) __has_builtin(name) +#else +#define TOML_HAS_BUILTIN(name) 0 +#endif +#endif + +// TOML_HAS_FEATURE +#ifndef TOML_HAS_FEATURE +#ifdef __has_feature +#define TOML_HAS_FEATURE(name) __has_feature(name) +#else +#define TOML_HAS_FEATURE(name) 0 +#endif +#endif + +// TOML_HAS_ATTR +#ifndef TOML_HAS_ATTR +#ifdef __has_attribute +#define TOML_HAS_ATTR(attr) __has_attribute(attr) +#else +#define TOML_HAS_ATTR(attr) 0 +#endif +#endif + +// TOML_HAS_CPP_ATTR +#ifndef TOML_HAS_CPP_ATTR +#ifdef __has_cpp_attribute +#define TOML_HAS_CPP_ATTR(attr) __has_cpp_attribute(attr) +#else +#define TOML_HAS_CPP_ATTR(attr) 0 +#endif +#endif + +// TOML_ATTR (gnu attributes) +#ifndef TOML_ATTR +#if TOML_CLANG || TOML_GCC_LIKE +#define TOML_ATTR(...) __attribute__((__VA_ARGS__)) +#else +#define TOML_ATTR(...) +#endif +#endif + +// TOML_DECLSPEC (msvc attributes) +#ifndef TOML_DECLSPEC +#if TOML_MSVC_LIKE +#define TOML_DECLSPEC(...) __declspec(__VA_ARGS__) +#else +#define TOML_DECLSPEC(...) +#endif +#endif + +// TOML_COMPILER_HAS_EXCEPTIONS +#ifndef TOML_COMPILER_HAS_EXCEPTIONS +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) || defined(__cpp_exceptions) +#define TOML_COMPILER_HAS_EXCEPTIONS 1 +#else +#define TOML_COMPILER_HAS_EXCEPTIONS 0 +#endif +#endif + +// TOML_COMPILER_HAS_RTTI +#ifndef TOML_COMPILER_HAS_RTTI +#if defined(_CPPRTTI) || defined(__GXX_RTTI) || TOML_HAS_FEATURE(cxx_rtti) +#define TOML_COMPILER_HAS_RTTI 1 +#else +#define TOML_COMPILER_HAS_RTTI 0 +#endif +#endif + +// TOML_CONCAT +#define TOML_CONCAT_1(x, y) x##y +#define TOML_CONCAT(x, y) TOML_CONCAT_1(x, y) + +// TOML_MAKE_STRING +#define TOML_MAKE_STRING_1(s) #s +#define TOML_MAKE_STRING(s) TOML_MAKE_STRING_1(s) + +// TOML_PRAGMA_XXXX (compiler-specific pragmas) +#if TOML_CLANG +#define TOML_PRAGMA_CLANG(decl) _Pragma(TOML_MAKE_STRING(clang decl)) +#else +#define TOML_PRAGMA_CLANG(decl) +#endif +#if TOML_CLANG >= 8 +#define TOML_PRAGMA_CLANG_GE_8(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_8(decl) +#endif +#if TOML_CLANG >= 9 +#define TOML_PRAGMA_CLANG_GE_9(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_9(decl) +#endif +#if TOML_CLANG >= 10 +#define TOML_PRAGMA_CLANG_GE_10(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_10(decl) +#endif +#if TOML_CLANG >= 11 +#define TOML_PRAGMA_CLANG_GE_11(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_11(decl) +#endif +#if TOML_GCC +#define TOML_PRAGMA_GCC(decl) _Pragma(TOML_MAKE_STRING(GCC decl)) +#else +#define TOML_PRAGMA_GCC(decl) +#endif +#if TOML_MSVC +#define TOML_PRAGMA_MSVC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_MSVC(...) +#endif +#if TOML_ICC +#define TOML_PRAGMA_ICC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_ICC(...) +#endif + +// TOML_ALWAYS_INLINE +#ifdef _MSC_VER +#define TOML_ALWAYS_INLINE __forceinline +#elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__always_inline__) +#define TOML_ALWAYS_INLINE \ + TOML_ATTR(__always_inline__) \ + inline +#else +#define TOML_ALWAYS_INLINE inline +#endif + +// TOML_NEVER_INLINE +#ifdef _MSC_VER +#define TOML_NEVER_INLINE TOML_DECLSPEC(noinline) +#elif TOML_CUDA // https://gitlab.gnome.org/GNOME/glib/-/issues/2555 +#define TOML_NEVER_INLINE TOML_ATTR(noinline) +#else +#if TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__noinline__) +#define TOML_NEVER_INLINE TOML_ATTR(__noinline__) +#endif +#endif +#ifndef TOML_NEVER_INLINE +#define TOML_NEVER_INLINE +#endif + +// MSVC attributes +#define TOML_ABSTRACT_INTERFACE TOML_DECLSPEC(novtable) +#define TOML_EMPTY_BASES TOML_DECLSPEC(empty_bases) + +// TOML_TRIVIAL_ABI +#if TOML_CLANG || TOML_HAS_ATTR(__trivial_abi__) +#define TOML_TRIVIAL_ABI TOML_ATTR(__trivial_abi__) +#else +#define TOML_TRIVIAL_ABI +#endif + +// TOML_NODISCARD +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201603 +#define TOML_NODISCARD [[nodiscard]] +#elif TOML_CLANG || TOML_GCC || TOML_HAS_ATTR(__warn_unused_result__) +#define TOML_NODISCARD TOML_ATTR(__warn_unused_result__) +#else +#define TOML_NODISCARD +#endif + +// TOML_NODISCARD_CTOR +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201907 +#define TOML_NODISCARD_CTOR [[nodiscard]] +#else +#define TOML_NODISCARD_CTOR +#endif + +// pure + const +#ifndef TOML_PURE +#ifdef NDEBUG +#define TOML_PURE \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(pure) +#else +#define TOML_PURE +#endif +#endif +#ifndef TOML_CONST +#ifdef NDEBUG +#define TOML_CONST \ + TOML_DECLSPEC(noalias) \ + TOML_ATTR(const) +#else +#define TOML_CONST +#endif +#endif +#ifndef TOML_INLINE_GETTER +#define TOML_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE +#endif +#ifndef TOML_PURE_GETTER +#define TOML_PURE_GETTER \ + TOML_NODISCARD \ + TOML_PURE +#endif +#ifndef TOML_PURE_INLINE_GETTER +#define TOML_PURE_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_PURE +#endif +#ifndef TOML_CONST_GETTER +#define TOML_CONST_GETTER \ + TOML_NODISCARD \ + TOML_CONST +#endif +#ifndef TOML_CONST_INLINE_GETTER +#define TOML_CONST_INLINE_GETTER \ + TOML_NODISCARD \ + TOML_ALWAYS_INLINE \ + TOML_CONST +#endif + +// TOML_ASSUME +#ifdef _MSC_VER +#define TOML_ASSUME(expr) __assume(expr) +#elif TOML_ICC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_assume) +#define TOML_ASSUME(expr) __builtin_assume(expr) +#elif TOML_HAS_CPP_ATTR(assume) >= 202207 +#define TOML_ASSUME(expr) [[assume(expr)]] +#elif TOML_HAS_ATTR(__assume__) +#define TOML_ASSUME(expr) __attribute__((__assume__(expr))) +#else +#define TOML_ASSUME(expr) static_cast(0) +#endif + +// TOML_UNREACHABLE +#ifdef _MSC_VER +#define TOML_UNREACHABLE __assume(0) +#elif TOML_ICC || TOML_CLANG || TOML_GCC || TOML_HAS_BUILTIN(__builtin_unreachable) +#define TOML_UNREACHABLE __builtin_unreachable() +#else +#define TOML_UNREACHABLE static_cast(0) +#endif + +// TOML_LIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(likely) >= 201803 +#define TOML_LIKELY(...) (__VA_ARGS__) [[likely]] +#define TOML_LIKELY_CASE [[likely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1)) +#else +#define TOML_LIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_LIKELY_CASE +#define TOML_LIKELY_CASE +#endif + +// TOML_UNLIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(unlikely) >= 201803 +#define TOML_UNLIKELY(...) (__VA_ARGS__) [[unlikely]] +#define TOML_UNLIKELY_CASE [[unlikely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0)) +#else +#define TOML_UNLIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_UNLIKELY_CASE +#define TOML_UNLIKELY_CASE +#endif + +// TOML_FLAGS_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(flag_enum) +#define TOML_FLAGS_ENUM __attribute__((flag_enum)) +#else +#define TOML_FLAGS_ENUM +#endif + +// TOML_OPEN_ENUM + TOML_CLOSED_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(enum_extensibility) +#define TOML_OPEN_ENUM __attribute__((enum_extensibility(open))) +#define TOML_CLOSED_ENUM __attribute__((enum_extensibility(closed))) +#else +#define TOML_OPEN_ENUM +#define TOML_CLOSED_ENUM +#endif + +// TOML_OPEN_FLAGS_ENUM + TOML_CLOSED_FLAGS_ENUM +#define TOML_OPEN_FLAGS_ENUM TOML_OPEN_ENUM TOML_FLAGS_ENUM +#define TOML_CLOSED_FLAGS_ENUM TOML_CLOSED_ENUM TOML_FLAGS_ENUM + +// TOML_MAKE_FLAGS +#define TOML_MAKE_FLAGS_2(T, op, linkage) \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator op(T lhs, T rhs) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(static_cast(lhs) op static_cast(rhs)); \ + } \ + \ + linkage constexpr T& operator TOML_CONCAT(op, =)(T & lhs, T rhs) noexcept \ + { \ + return lhs = (lhs op rhs); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS_1(T, linkage) \ + static_assert(std::is_enum_v); \ + \ + TOML_MAKE_FLAGS_2(T, &, linkage); \ + TOML_MAKE_FLAGS_2(T, |, linkage); \ + TOML_MAKE_FLAGS_2(T, ^, linkage); \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator~(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(~static_cast(val)); \ + } \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr bool operator!(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return !static_cast(val); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS(T) TOML_MAKE_FLAGS_1(T, ) + +#define TOML_UNUSED(...) static_cast(__VA_ARGS__) + +#define TOML_DELETE_DEFAULTS(T) \ + T(const T&) = delete; \ + T(T&&) = delete; \ + T& operator=(const T&) = delete; \ + T& operator=(T&&) = delete + +#define TOML_ASYMMETRICAL_EQUALITY_OPS(LHS, RHS, ...) \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator==(RHS rhs, LHS lhs) noexcept \ + { \ + return lhs == rhs; \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(LHS lhs, RHS rhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(RHS rhs, LHS lhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + static_assert(true) + +#define TOML_EVAL_BOOL_1(T, F) T +#define TOML_EVAL_BOOL_0(T, F) F + +#if !defined(__POXY__) && !defined(POXY_IMPLEMENTATION_DETAIL) +#define POXY_IMPLEMENTATION_DETAIL(...) __VA_ARGS__ +#endif + +// COMPILER-SPECIFIC WARNING MANAGEMENT + +#if TOML_CLANG + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wunknown-warning-option") \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wswitch") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wimplicit-int-float-conversion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wshift-sign-overflow") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_CLANG_GE_8(diagnostic ignored "-Wdefaulted-function-deleted") \ + TOML_PRAGMA_CLANG_GE_9(diagnostic ignored "-Wctad-maybe-unsupported") \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wzero-as-null-pointer-constant") \ + TOML_PRAGMA_CLANG_GE_11(diagnostic ignored "-Wsuggest-destructor-override") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-template-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wpadded") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Weverything") \ + static_assert(true, "") + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 1 + +#elif TOML_MSVC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#if TOML_HAS_INCLUDE() +#pragma warning(push, 0) +#include +#pragma warning(pop) +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS \ + __pragma(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) \ + static_assert(true) +#else +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif + +#define TOML_DISABLE_SWITCH_WARNINGS \ + __pragma(warning(disable : 4061)) \ + __pragma(warning(disable : 4062)) \ + __pragma(warning(disable : 4063)) \ + __pragma(warning(disable : 5262)) /* switch-case implicit fallthrough (false-positive) */ \ + __pragma(warning(disable : 26819)) /* cg: unannotated fallthrough */ \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 4127)) /* conditional expr is constant */ \ + __pragma(warning(disable : 4324)) /* structure was padded due to alignment specifier */ \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4464)) /* relative include path contains '..' */ \ + __pragma(warning(disable : 4505)) /* unreferenced local function removed */ \ + __pragma(warning(disable : 4514)) /* unreferenced inline function has been removed */ \ + __pragma(warning(disable : 4582)) /* constructor is not implicitly called */ \ + __pragma(warning(disable : 4619)) /* there is no warning number 'XXXX' */ \ + __pragma(warning(disable : 4623)) /* default constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4625)) /* copy constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4626)) /* assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 4710)) /* function not inlined */ \ + __pragma(warning(disable : 4711)) /* function selected for automatic expansion */ \ + __pragma(warning(disable : 4820)) /* N bytes padding added */ \ + __pragma(warning(disable : 4946)) /* reinterpret_cast used between related classes */ \ + __pragma(warning(disable : 5026)) /* move constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 5027)) /* move assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 5039)) /* potentially throwing function passed to 'extern "C"' function */ \ + __pragma(warning(disable : 5045)) /* Compiler will insert Spectre mitigation */ \ + __pragma(warning(disable : 5264)) /* const variable is not used (false-positive) */ \ + __pragma(warning(disable : 26451)) \ + __pragma(warning(disable : 26490)) \ + __pragma(warning(disable : 26495)) \ + __pragma(warning(disable : 26812)) \ + __pragma(warning(disable : 26819)) \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + __pragma(warning(disable : 4365)) /* argument signed/unsigned mismatch */ \ + __pragma(warning(disable : 4738)) /* storing 32-bit float result in memory */ \ + __pragma(warning(disable : 5219)) /* implicit conversion from integral to float */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4668)) \ + __pragma(warning(disable : 5105)) \ + __pragma(warning(disable : 5264)) \ + TOML_DISABLE_CODE_ANALYSIS_WARNINGS; \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS TOML_POP_WARNINGS + +#elif TOML_ICC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 82)) /* storage class is not first */ \ + __pragma(warning(disable : 111)) /* statement unreachable (false-positive) */ \ + __pragma(warning(disable : 869)) /* unreferenced parameter */ \ + __pragma(warning(disable : 1011)) /* missing return (false-positive) */ \ + __pragma(warning(disable : 2261)) /* assume expr side-effects discarded */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + TOML_DISABLE_SPAM_WARNINGS + +#define TOML_ENABLE_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#elif TOML_GCC + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-enum") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-default") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsign-conversion") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + static_assert(true) + +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=const") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=pure") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpadded") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcast-align") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcomment") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wtype-limits") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wuseless-cast") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsubobject-linkage") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmaybe-uninitialized") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnoexcept") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnull-dereference") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wduplicated-branches") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wall") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wextra") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpedantic") \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + TOML_DISABLE_SUGGEST_ATTR_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#endif + +#ifndef TOML_PUSH_WARNINGS +#define TOML_PUSH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_CODE_ANALYSIS_WARNINGS +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SWITCH_WARNINGS +#define TOML_DISABLE_SWITCH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SUGGEST_ATTR_WARNINGS +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SPAM_WARNINGS +#define TOML_DISABLE_SPAM_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_ARITHMETIC_WARNINGS +#define TOML_DISABLE_ARITHMETIC_WARNINGS static_assert(true) +#endif +#ifndef TOML_POP_WARNINGS +#define TOML_POP_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_WARNINGS +#define TOML_DISABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_ENABLE_WARNINGS +#define TOML_ENABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_SIMPLE_STATIC_ASSERT_MESSAGES +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 0 +#endif + +#ifdef TOML_CONFIG_HEADER +#include TOML_CONFIG_HEADER +#endif + +// is the library being built as a shared lib/dll using meson and friends? +#ifndef TOML_SHARED_LIB +#define TOML_SHARED_LIB 0 +#endif + +// header-only mode +#if !defined(TOML_HEADER_ONLY) && defined(TOML_ALL_INLINE) // was TOML_ALL_INLINE pre-2.0 +#define TOML_HEADER_ONLY TOML_ALL_INLINE +#endif +#if !defined(TOML_HEADER_ONLY) || (defined(TOML_HEADER_ONLY) && TOML_HEADER_ONLY) || TOML_INTELLISENSE +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 1 +#endif +#if TOML_DOXYGEN || TOML_SHARED_LIB +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 0 +#endif + +// internal implementation switch +#if defined(TOML_IMPLEMENTATION) || TOML_HEADER_ONLY +#undef TOML_IMPLEMENTATION +#define TOML_IMPLEMENTATION 1 +#else +#define TOML_IMPLEMENTATION 0 +#endif + +// dll/shared lib function exports (legacy - TOML_API was the old name for this setting) +#if !defined(TOML_EXPORTED_MEMBER_FUNCTION) && !defined(TOML_EXPORTED_STATIC_FUNCTION) \ + && !defined(TOML_EXPORTED_FREE_FUNCTION) && !defined(TOML_EXPORTED_CLASS) && defined(TOML_API) +#define TOML_EXPORTED_MEMBER_FUNCTION TOML_API +#define TOML_EXPORTED_STATIC_FUNCTION TOML_API +#define TOML_EXPORTED_FREE_FUNCTION TOML_API +#endif + +// dll/shared lib exports +#if TOML_SHARED_LIB +#undef TOML_API +#undef TOML_EXPORTED_CLASS +#undef TOML_EXPORTED_MEMBER_FUNCTION +#undef TOML_EXPORTED_STATIC_FUNCTION +#undef TOML_EXPORTED_FREE_FUNCTION +#if TOML_WINDOWS +#if TOML_IMPLEMENTATION +#define TOML_EXPORTED_CLASS __declspec(dllexport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllexport) +#else +#define TOML_EXPORTED_CLASS __declspec(dllimport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllimport) +#endif +#ifndef TOML_CALLCONV +#define TOML_CALLCONV __cdecl +#endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define TOML_EXPORTED_CLASS __attribute__((visibility("default"))) +#define TOML_EXPORTED_MEMBER_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_STATIC_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_FREE_FUNCTION __attribute__((visibility("default"))) +#endif +#endif +#ifndef TOML_EXPORTED_CLASS +#define TOML_EXPORTED_CLASS +#endif +#ifndef TOML_EXPORTED_MEMBER_FUNCTION +#define TOML_EXPORTED_MEMBER_FUNCTION +#endif +#ifndef TOML_EXPORTED_STATIC_FUNCTION +#define TOML_EXPORTED_STATIC_FUNCTION +#endif +#ifndef TOML_EXPORTED_FREE_FUNCTION +#define TOML_EXPORTED_FREE_FUNCTION +#endif + +// experimental language features +#if !defined(TOML_ENABLE_UNRELEASED_FEATURES) && defined(TOML_UNRELEASED_FEATURES) // was TOML_UNRELEASED_FEATURES + // pre-3.0 +#define TOML_ENABLE_UNRELEASED_FEATURES TOML_UNRELEASED_FEATURES +#endif +#if (defined(TOML_ENABLE_UNRELEASED_FEATURES) && TOML_ENABLE_UNRELEASED_FEATURES) || TOML_INTELLISENSE +#undef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 1 +#endif +#ifndef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 0 +#endif + +// parser +#if !defined(TOML_ENABLE_PARSER) && defined(TOML_PARSER) // was TOML_PARSER pre-3.0 +#define TOML_ENABLE_PARSER TOML_PARSER +#endif +#if !defined(TOML_ENABLE_PARSER) || (defined(TOML_ENABLE_PARSER) && TOML_ENABLE_PARSER) || TOML_INTELLISENSE +#undef TOML_ENABLE_PARSER +#define TOML_ENABLE_PARSER 1 +#endif + +// formatters +#if !defined(TOML_ENABLE_FORMATTERS) || (defined(TOML_ENABLE_FORMATTERS) && TOML_ENABLE_FORMATTERS) || TOML_INTELLISENSE +#undef TOML_ENABLE_FORMATTERS +#define TOML_ENABLE_FORMATTERS 1 +#endif + +// SIMD +#if !defined(TOML_ENABLE_SIMD) || (defined(TOML_ENABLE_SIMD) && TOML_ENABLE_SIMD) || TOML_INTELLISENSE +#undef TOML_ENABLE_SIMD +#define TOML_ENABLE_SIMD 1 +#endif + +// windows compat +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) && defined(TOML_WINDOWS_COMPAT) // was TOML_WINDOWS_COMPAT pre-3.0 +#define TOML_ENABLE_WINDOWS_COMPAT TOML_WINDOWS_COMPAT +#endif +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) || (defined(TOML_ENABLE_WINDOWS_COMPAT) && TOML_ENABLE_WINDOWS_COMPAT) \ + || TOML_INTELLISENSE +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 1 +#endif + +#if !TOML_WINDOWS +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 0 +#endif + +#ifndef TOML_INCLUDE_WINDOWS_H +#define TOML_INCLUDE_WINDOWS_H 0 +#endif + +// custom optional +#ifdef TOML_OPTIONAL_TYPE +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 1 +#else +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 0 +#endif + +// exceptions (library use) +#if TOML_COMPILER_HAS_EXCEPTIONS +#if !defined(TOML_EXCEPTIONS) || (defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS) +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 1 +#endif +#else +#if defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS +#error TOML_EXCEPTIONS was explicitly enabled but exceptions are disabled/unsupported by the compiler. +#endif +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 0 +#endif + +// calling convention for static/free/friend functions +#ifndef TOML_CALLCONV +#define TOML_CALLCONV +#endif + +#ifndef TOML_UNDEF_MACROS +#define TOML_UNDEF_MACROS 1 +#endif + +#ifndef TOML_MAX_NESTED_VALUES +#define TOML_MAX_NESTED_VALUES 256 +// this refers to the depth of nested values, e.g. inline tables and arrays. +// 256 is crazy high! if you're hitting this limit with real input, TOML is probably the wrong tool for the job... +#endif + +#ifdef TOML_CHAR_8_STRINGS +#if TOML_CHAR_8_STRINGS +#error TOML_CHAR_8_STRINGS was removed in toml++ 2.0.0; all value setters and getters now work with char8_t strings implicitly. +#endif +#endif + +#ifdef TOML_LARGE_FILES +#if !TOML_LARGE_FILES +#error Support for !TOML_LARGE_FILES (i.e. 'small files') was removed in toml++ 3.0.0. +#endif +#endif + +#ifndef TOML_LIFETIME_HOOKS +#define TOML_LIFETIME_HOOKS 0 +#endif + +#ifdef NDEBUG +#undef TOML_ASSERT +#define TOML_ASSERT(expr) static_assert(true) +#endif +#ifndef TOML_ASSERT +#ifndef assert +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; +#endif +#define TOML_ASSERT(expr) assert(expr) +#endif +#ifdef NDEBUG +#define TOML_ASSERT_ASSUME(expr) TOML_ASSUME(expr) +#else +#define TOML_ASSERT_ASSUME(expr) TOML_ASSERT(expr) +#endif + +#ifndef TOML_ENABLE_FLOAT16 +#define TOML_ENABLE_FLOAT16 0 +#endif + +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) +// not supported by any version of GCC or Clang as of 26/11/2020 +// not supported by any version of ICC on Linux as of 11/01/2021 +#define TOML_FLOAT_CHARCONV 0 +#endif +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) +// causes link errors on emscripten +// causes Mac OS SDK version errors on some versions of Apple Clang +#define TOML_INT_CHARCONV 0 +#endif +#ifndef TOML_INT_CHARCONV +#define TOML_INT_CHARCONV 1 +#endif +#ifndef TOML_FLOAT_CHARCONV +#define TOML_FLOAT_CHARCONV 1 +#endif +#if (TOML_INT_CHARCONV || TOML_FLOAT_CHARCONV) && !TOML_HAS_INCLUDE() +#undef TOML_INT_CHARCONV +#undef TOML_FLOAT_CHARCONV +#define TOML_INT_CHARCONV 0 +#define TOML_FLOAT_CHARCONV 0 +#endif + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907 +#define TOML_REQUIRES(...) requires(__VA_ARGS__) +#else +#define TOML_REQUIRES(...) +#endif +#define TOML_ENABLE_IF(...) , typename std::enable_if<(__VA_ARGS__), int>::type = 0 +#define TOML_CONSTRAINED_TEMPLATE(condition, ...) \ + template <__VA_ARGS__ TOML_ENABLE_IF(condition)> \ + TOML_REQUIRES(condition) +#define TOML_HIDDEN_CONSTRAINT(condition, ...) TOML_CONSTRAINED_TEMPLATE(condition, __VA_ARGS__) + +#if defined(__SIZEOF_FLOAT128__) && defined(__FLT128_MANT_DIG__) && defined(__LDBL_MANT_DIG__) \ + && __FLT128_MANT_DIG__ > __LDBL_MANT_DIG__ +#define TOML_FLOAT128 __float128 +#endif + +#ifdef __SIZEOF_INT128__ +#define TOML_INT128 __int128_t +#define TOML_UINT128 __uint128_t +#endif + +// clang-format off + +//******** impl/version.hpp ****************************************************************************************** + +#define TOML_LIB_MAJOR 3 +#define TOML_LIB_MINOR 4 +#define TOML_LIB_PATCH 0 + +#define TOML_LANG_MAJOR 1 +#define TOML_LANG_MINOR 0 +#define TOML_LANG_PATCH 0 + +//******** impl/preprocessor.hpp ************************************************************************************* + +#define TOML_LIB_SINGLE_HEADER 1 + +#if TOML_ENABLE_UNRELEASED_FEATURES + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH+1) +#else + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) +#endif + +#define TOML_LANG_HIGHER_THAN(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION > TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_AT_LEAST(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION >= TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_UNRELEASED \ + TOML_LANG_HIGHER_THAN(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) + +#ifndef TOML_ABI_NAMESPACES + #if TOML_DOXYGEN + #define TOML_ABI_NAMESPACES 0 + #else + #define TOML_ABI_NAMESPACES 1 + #endif +#endif +#if TOML_ABI_NAMESPACES + #define TOML_NAMESPACE_START namespace toml { inline namespace TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_NAMESPACE_END } static_assert(true) + #define TOML_NAMESPACE ::toml::TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_ABI_NAMESPACE_START(name) inline namespace name { static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(cond, T, F) TOML_ABI_NAMESPACE_START(TOML_CONCAT(TOML_EVAL_BOOL_, cond)(T, F)) + #define TOML_ABI_NAMESPACE_END } static_assert(true) +#else + #define TOML_NAMESPACE_START namespace toml + #define TOML_NAMESPACE_END static_assert(true) + #define TOML_NAMESPACE toml + #define TOML_ABI_NAMESPACE_START(...) static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(...) static_assert(true) + #define TOML_ABI_NAMESPACE_END static_assert(true) +#endif +#define TOML_IMPL_NAMESPACE_START TOML_NAMESPACE_START { namespace impl +#define TOML_IMPL_NAMESPACE_END } TOML_NAMESPACE_END +#if TOML_HEADER_ONLY + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); TOML_IMPL_NAMESPACE_START + #define TOML_ANON_NAMESPACE_END TOML_IMPL_NAMESPACE_END + #define TOML_ANON_NAMESPACE TOML_NAMESPACE::impl + #define TOML_EXTERNAL_LINKAGE inline + #define TOML_INTERNAL_LINKAGE inline +#else + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); \ + using namespace toml; \ + namespace + #define TOML_ANON_NAMESPACE_END static_assert(true) + #define TOML_ANON_NAMESPACE + #define TOML_EXTERNAL_LINKAGE + #define TOML_INTERNAL_LINKAGE static +#endif + +// clang-format on + +// clang-format off + +#if TOML_SIMPLE_STATIC_ASSERT_MESSAGES + + #define TOML_SA_NEWLINE " " + #define TOML_SA_LIST_SEP ", " + #define TOML_SA_LIST_BEG " (" + #define TOML_SA_LIST_END ")" + #define TOML_SA_LIST_NEW " " + #define TOML_SA_LIST_NXT ", " + +#else + + #define TOML_SA_NEWLINE "\n| " + #define TOML_SA_LIST_SEP TOML_SA_NEWLINE " - " + #define TOML_SA_LIST_BEG TOML_SA_LIST_SEP + #define TOML_SA_LIST_END + #define TOML_SA_LIST_NEW TOML_SA_NEWLINE TOML_SA_NEWLINE + #define TOML_SA_LIST_NXT TOML_SA_LIST_NEW + +#endif + +#define TOML_SA_NATIVE_VALUE_TYPE_LIST \ + TOML_SA_LIST_BEG "std::string" \ + TOML_SA_LIST_SEP "int64_t" \ + TOML_SA_LIST_SEP "double" \ + TOML_SA_LIST_SEP "bool" \ + TOML_SA_LIST_SEP "toml::date" \ + TOML_SA_LIST_SEP "toml::time" \ + TOML_SA_LIST_SEP "toml::date_time" \ + TOML_SA_LIST_END + +#define TOML_SA_NODE_TYPE_LIST \ + TOML_SA_LIST_BEG "toml::table" \ + TOML_SA_LIST_SEP "toml::array" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_END + +#define TOML_SA_UNWRAPPED_NODE_TYPE_LIST \ + TOML_SA_LIST_NEW "A native TOML value type" \ + TOML_SA_NATIVE_VALUE_TYPE_LIST \ + \ + TOML_SA_LIST_NXT "A TOML node type" \ + TOML_SA_NODE_TYPE_LIST + +// clang-format on + +TOML_PUSH_WARNINGS; +TOML_DISABLE_SPAM_WARNINGS; +TOML_DISABLE_SWITCH_WARNINGS; +TOML_DISABLE_SUGGEST_ATTR_WARNINGS; + +// misc warning false-positives +#if TOML_MSVC +#pragma warning(disable : 5031) // #pragma warning(pop): likely mismatch +#if TOML_SHARED_LIB +#pragma warning(disable : 4251) // dll exports for std lib types +#endif +#elif TOML_CLANG +TOML_PRAGMA_CLANG(diagnostic ignored "-Wheader-hygiene") +#if TOML_CLANG >= 12 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wc++20-extensions") +#endif +#if TOML_CLANG == 13 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wreserved-identifier") +#endif +#endif + +//******** impl/std_new.hpp ****************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + +#if (!defined(__apple_build_version__) && TOML_CLANG >= 8) || TOML_GCC >= 7 || TOML_ICC >= 1910 || TOML_MSVC >= 1914 +#define TOML_LAUNDER(x) __builtin_launder(x) +#elif defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 +#define TOML_LAUNDER(x) std::launder(x) +#else +#define TOML_LAUNDER(x) x +#endif + +//******** impl/std_string.hpp *************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +TOML_ENABLE_WARNINGS; + +#if TOML_DOXYGEN \ + || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811 && defined(__cpp_lib_char8_t) \ + && __cpp_lib_char8_t >= 201907) +#define TOML_HAS_CHAR8 1 +#else +#define TOML_HAS_CHAR8 0 +#endif + +namespace toml // non-abi namespace; this is not an error +{ + using namespace std::string_literals; + using namespace std::string_view_literals; +} + +#if TOML_ENABLE_WINDOWS_COMPAT + +TOML_IMPL_NAMESPACE_START +{ + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::string narrow(std::wstring_view); + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::string_view); + +#if TOML_HAS_CHAR8 + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::u8string_view); + +#endif +} +TOML_IMPL_NAMESPACE_END; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + +//******** impl/std_optional.hpp ************************************************************************************* + +TOML_DISABLE_WARNINGS; +#if !TOML_HAS_CUSTOM_OPTIONAL_TYPE +#include +#endif +TOML_ENABLE_WARNINGS; + +TOML_NAMESPACE_START +{ +#if TOML_HAS_CUSTOM_OPTIONAL_TYPE + + template + using optional = TOML_OPTIONAL_TYPE; + +#else + + template + using optional = std::optional; + +#endif +} +TOML_NAMESPACE_END; + +//******** impl/forward_declarations.hpp ***************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +TOML_ENABLE_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#ifndef __clang__ +#pragma inline_recursion(on) +#endif +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +#ifndef TOML_DISABLE_ENVIRONMENT_CHECKS +#define TOML_ENV_MESSAGE \ + "If you're seeing this error it's because you're building toml++ for an environment that doesn't conform to " \ + "one of the 'ground truths' assumed by the library. Essentially this just means that I don't have the " \ + "resources to test on more platforms, but I wish I did! You can try disabling the checks by defining " \ + "TOML_DISABLE_ENVIRONMENT_CHECKS, but your mileage may vary. Please consider filing an issue at " \ + "https://github.com/marzer/tomlplusplus/issues to help me improve support for your target environment. " \ + "Thanks!" + +static_assert(CHAR_BIT == 8, TOML_ENV_MESSAGE); +#ifdef FLT_RADIX +static_assert(FLT_RADIX == 2, TOML_ENV_MESSAGE); +#endif +static_assert('A' == 65, TOML_ENV_MESSAGE); +static_assert(sizeof(double) == 8, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::is_iec559, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits == 53, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits10 == 15, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::radix == 2, TOML_ENV_MESSAGE); + +#undef TOML_ENV_MESSAGE +#endif // !TOML_DISABLE_ENVIRONMENT_CHECKS + +// undocumented forward declarations are hidden from doxygen because they fuck it up =/ + +namespace toml // non-abi namespace; this is not an error +{ + using ::std::size_t; + using ::std::intptr_t; + using ::std::uintptr_t; + using ::std::ptrdiff_t; + using ::std::nullptr_t; + using ::std::int8_t; + using ::std::int16_t; + using ::std::int32_t; + using ::std::int64_t; + using ::std::uint8_t; + using ::std::uint16_t; + using ::std::uint32_t; + using ::std::uint64_t; + using ::std::uint_least32_t; + using ::std::uint_least64_t; +} + +TOML_NAMESPACE_START +{ + struct date; + struct time; + struct time_offset; + + TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt); + struct date_time; + TOML_ABI_NAMESPACE_END; + + struct source_position; + struct source_region; + + class node; + template + class node_view; + + class key; + class array; + class table; + template + class value; + + class path; + + class toml_formatter; + class json_formatter; + class yaml_formatter; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); +#if TOML_EXCEPTIONS + using parse_result = table; +#else + class parse_result; +#endif + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + using node_ptr = std::unique_ptr; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); + class parser; + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS + + // clang-format off + + inline constexpr std::string_view control_char_escapes[] = + { + "\\u0000"sv, + "\\u0001"sv, + "\\u0002"sv, + "\\u0003"sv, + "\\u0004"sv, + "\\u0005"sv, + "\\u0006"sv, + "\\u0007"sv, + "\\b"sv, + "\\t"sv, + "\\n"sv, + "\\u000B"sv, + "\\f"sv, + "\\r"sv, + "\\u000E"sv, + "\\u000F"sv, + "\\u0010"sv, + "\\u0011"sv, + "\\u0012"sv, + "\\u0013"sv, + "\\u0014"sv, + "\\u0015"sv, + "\\u0016"sv, + "\\u0017"sv, + "\\u0018"sv, + "\\u0019"sv, + "\\u001A"sv, + "\\u001B"sv, + "\\u001C"sv, + "\\u001D"sv, + "\\u001E"sv, + "\\u001F"sv, + }; + + inline constexpr std::string_view node_type_friendly_names[] = + { + "none"sv, + "table"sv, + "array"sv, + "string"sv, + "integer"sv, + "floating-point"sv, + "boolean"sv, + "date"sv, + "time"sv, + "date-time"sv + }; + + // clang-format on +} +TOML_IMPL_NAMESPACE_END; + +#if TOML_ABI_NAMESPACES +#if TOML_EXCEPTIONS +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_ex::parser +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_noex::parser +#endif +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::parser +#endif + +namespace toml +{ +} + +TOML_NAMESPACE_START // abi namespace +{ + inline namespace literals + { + } + + enum class TOML_CLOSED_ENUM node_type : uint8_t + { + none, + table, + array, + string, + integer, + floating_point, + boolean, + date, + time, + date_time + }; + + template + inline std::basic_ostream& operator<<(std::basic_ostream& lhs, node_type rhs) + { + const auto str = impl::node_type_friendly_names[static_cast>(rhs)]; + using str_char_t = decltype(str)::value_type; + if constexpr (std::is_same_v) + return lhs << str; + else + { + if constexpr (sizeof(Char) == sizeof(str_char_t)) + return lhs << std::basic_string_view{ reinterpret_cast(str.data()), str.length() }; + else + return lhs << str.data(); + } + } + + enum class TOML_OPEN_FLAGS_ENUM value_flags : uint16_t // being an "OPEN" flags enum is not an error + { + none, + format_as_binary = 1, + format_as_octal = 2, + format_as_hexadecimal = 3, + }; + TOML_MAKE_FLAGS(value_flags); + + inline constexpr value_flags preserve_source_value_flags = + POXY_IMPLEMENTATION_DETAIL(value_flags{ static_cast>(-1) }); + + enum class TOML_CLOSED_FLAGS_ENUM format_flags : uint64_t + { + none, + quote_dates_and_times = (1ull << 0), + quote_infinities_and_nans = (1ull << 1), + allow_literal_strings = (1ull << 2), + allow_multi_line_strings = (1ull << 3), + allow_real_tabs_in_strings = (1ull << 4), + allow_unicode_strings = (1ull << 5), + allow_binary_integers = (1ull << 6), + allow_octal_integers = (1ull << 7), + allow_hexadecimal_integers = (1ull << 8), + indent_sub_tables = (1ull << 9), + indent_array_elements = (1ull << 10), + indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), + terse_key_value_pairs = (1ull << 12), + }; + TOML_MAKE_FLAGS(format_flags); + + template + struct TOML_TRIVIAL_ABI inserter + { + static_assert(std::is_reference_v); + + T value; + }; + template + inserter(T&&) -> inserter; + template + inserter(T&) -> inserter; + + using default_formatter = toml_formatter; +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + template + using remove_cvref = std::remove_cv_t>; + + template + using common_signed_type = std::common_type_t...>; + + template + inline constexpr bool is_one_of = (false || ... || std::is_same_v); + + template + inline constexpr bool all_integral = (std::is_integral_v && ...); + + template + inline constexpr bool is_cvref = std::is_reference_v || std::is_const_v || std::is_volatile_v; + + template + inline constexpr bool is_wide_string = + is_one_of, const wchar_t*, wchar_t*, std::wstring_view, std::wstring>; + + template + inline constexpr bool value_retrieval_is_nothrow = !std::is_same_v, std::string> +#if TOML_HAS_CHAR8 + && !std::is_same_v, std::u8string> +#endif + + && !is_wide_string; + + template + struct copy_ref_; + template + using copy_ref = typename copy_ref_::type; + + template + struct copy_ref_ + { + using type = Dest; + }; + + template + struct copy_ref_ + { + using type = std::add_lvalue_reference_t; + }; + + template + struct copy_ref_ + { + using type = std::add_rvalue_reference_t; + }; + + template + struct copy_cv_; + template + using copy_cv = typename copy_cv_::type; + + template + struct copy_cv_ + { + using type = Dest; + }; + + template + struct copy_cv_ + { + using type = std::add_const_t; + }; + + template + struct copy_cv_ + { + using type = std::add_volatile_t; + }; + + template + struct copy_cv_ + { + using type = std::add_cv_t; + }; + + template + using copy_cvref = + copy_ref, std::remove_reference_t>, Dest>, Src>; + + template + inline constexpr bool always_false = false; + + template + inline constexpr bool first_is_same = false; + template + inline constexpr bool first_is_same = true; + + template > + struct underlying_type_ + { + using type = std::underlying_type_t; + }; + template + struct underlying_type_ + { + using type = T; + }; + template + using underlying_type = typename underlying_type_::type; + + // general value traits + // (as they relate to their equivalent native TOML type) + struct default_value_traits + { + using native_type = void; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = false; + static constexpr bool can_represent_native = false; + static constexpr bool can_partially_represent_native = false; + static constexpr auto type = node_type::none; + }; + + template + struct value_traits; + + template > + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = default_value_traits; + }; + template + struct value_traits_base_selector + { + static_assert(!is_cvref); + + using type = value_traits>; + }; + + template + struct value_traits : value_traits_base_selector::type + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + + // integer value_traits specializations - standard types + template + struct integer_limits + { + static constexpr T min = T{ (std::numeric_limits>::min)() }; + static constexpr T max = T{ (std::numeric_limits>::max)() }; + }; + template + struct integer_traits_base : integer_limits + { + using native_type = int64_t; + static constexpr bool is_native = std::is_same_v, native_type>; + static constexpr bool is_signed = static_cast>(-1) < underlying_type{}; + static constexpr auto type = node_type::integer; + static constexpr bool can_partially_represent_native = true; + }; + template + struct unsigned_integer_traits : integer_traits_base + { + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::max <= 9223372036854775807ULL; + static constexpr bool can_represent_native = false; + }; + template + struct signed_integer_traits : integer_traits_base + { + using native_type = int64_t; + static constexpr bool is_losslessly_convertible_to_native = + integer_limits>::min >= (-9223372036854775807LL - 1LL) + && integer_limits>::max <= 9223372036854775807LL; + static constexpr bool can_represent_native = + integer_limits>::min <= (-9223372036854775807LL - 1LL) + && integer_limits>::max >= 9223372036854775807LL; + }; + template ::is_signed> + struct integer_traits : signed_integer_traits + {}; + template + struct integer_traits : unsigned_integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + static_assert(value_traits::is_native); + static_assert(value_traits::is_signed); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // integer value_traits specializations - non-standard types +#ifdef TOML_INT128 + template <> + struct integer_limits + { + static constexpr TOML_INT128 max = + static_cast((TOML_UINT128{ 1u } << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1); + static constexpr TOML_INT128 min = -max - TOML_INT128{ 1 }; + }; + template <> + struct integer_limits + { + static constexpr TOML_UINT128 min = TOML_UINT128{}; + static constexpr TOML_UINT128 max = (2u * static_cast(integer_limits::max)) + 1u; + }; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; +#endif +#ifdef TOML_SMALL_INT_TYPE + template <> + struct value_traits : signed_integer_traits + {}; +#endif + + // floating-point traits base + template + struct float_traits_base + { + static constexpr auto type = node_type::floating_point; + using native_type = double; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_signed = true; + + static constexpr int bits = static_cast(sizeof(T) * CHAR_BIT); + static constexpr int digits = MantissaDigits; + static constexpr int digits10 = DecimalDigits; + + static constexpr bool is_losslessly_convertible_to_native = bits <= 64 // + && digits <= 53 // DBL_MANT_DIG + && digits10 <= 15; // DBL_DIG + + static constexpr bool can_represent_native = digits >= 53 // DBL_MANT_DIG + && digits10 >= 15; // DBL_DIG + + static constexpr bool can_partially_represent_native = digits > 0 && digits10 > 0; + }; + template + struct float_traits : float_traits_base::digits, std::numeric_limits::digits10> + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct float_traits<_Float16> : float_traits_base<_Float16, __FLT16_MANT_DIG__, __FLT16_DIG__> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct float_traits : float_traits_base + {}; +#endif + + // floating-point traits + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct value_traits<_Float16> : float_traits<_Float16> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct value_traits : float_traits + {}; +#endif +#ifdef TOML_SMALL_FLOAT_TYPE + template <> + struct value_traits : float_traits + {}; +#endif + static_assert(value_traits::is_native); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // string value_traits specializations - char-based strings + template + struct string_traits + { + using native_type = std::string; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = + !std::is_array_v && (!std::is_pointer_v || std::is_const_v>); + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + + // string value_traits specializations - char8_t-based strings +#if TOML_HAS_CHAR8 + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; +#endif + + // string value_traits specializations - wchar_t-based strings on Windows +#if TOML_ENABLE_WINDOWS_COMPAT + template + struct wstring_traits + { + using native_type = std::string; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = true; // narrow + static constexpr bool can_represent_native = std::is_same_v; // widen + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; +#endif + + // other 'native' value_traits specializations + template + struct native_value_traits + { + using native_type = T; + static constexpr bool is_native = true; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = true; + static constexpr bool can_partially_represent_native = true; + static constexpr auto type = NodeType; + }; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits