FluidSimCpp
Solver performance measurements and reproducible SIMD/threading experiments are documented in the optimization audit. Current scenarios use serial pipe execution because per-substep threading costs more than it saves at their graph sizes. Portable compiler SIMD remains enabled by default.
FluidSimCpp aims to reproduce sound from a physically accurate simulation of compressible flow in pipes and connected volumes, sampled at 44,100 Hz. Each audio sample advances physical time by 1/44100 second; adaptive internal substeps resolve the fluid dynamics. Every subsystem must ultimately follow physical laws with explicit units, assumptions and validation. Matching the C# reference is a regression aid, not proof of physical accuracy.
This is a work in progress. Physical correctness is the acceptance goal, not a claim about all current models. Validation must include conservation of mass, momentum and energy, analytic acoustic modes, boundary reflection and radiation, thermodynamics and convergence under time/space refinement.
Build and run (Windows and Linux)
Both platforms use CMake 3.28 or newer, Git, and a C++20 compiler. The presets
build with optimizations and debugging symbols (RelWithDebInfo). The first
desktop configure fetches SFML 3.0.2 if it is not installed and needs network
access. Visual Studio is not required.
Windows
Install CMake and Git and ensure both are on PATH. Then run from this folder in a regular PowerShell terminal:
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\Setup-Windows.ps1
cmake --preset windows
cmake --build --preset windows
ctest --preset windows
.\out\build\windows-gcc\fluidsim_desktop.exe
The setup script installs the portable WinLibs x64 UCRT
GCC bundle, including GDB and Ninja, in
%LOCALAPPDATA%/FluidSimTools/mingw64. It verifies the download's SHA-256
checksum, removes the downloaded archive, and reuses an existing installation.
It needs no administrator access and does not change your system PATH.
Each built executable gets its GCC runtime DLLs copied beside it so it also
runs directly from a terminal or Explorer.
Linux
Install CMake 3.28 or newer, GCC with C++20 support, Make, GDB, and the SFML development dependencies. For Ubuntu 24.04 / Debian with a suitable CMake:
sudo apt-get update
sudo apt-get install build-essential cmake git gdb pkg-config \
libx11-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev \
libgl1-mesa-dev libfreetype6-dev libflac-dev libogg-dev libvorbis-dev \
fonts-dejavu-core
cmake --preset linux
cmake --build --preset linux
ctest --preset linux
./build-desktop/fluidsim_desktop
Other distributions need equivalent packages; see SFML's source-build dependencies. Running the desktop app requires a graphical session and an audio device.
VS Code
Install the recommended C/C++ and CMake Tools extensions and open this
folder. Select the windows or linux CMake preset for your platform. Choose
FluidSim desktop, FluidSim two-stroke, or FluidSim Saab B205 in
Run and Debug, then press F5 or the green Run button. The launch
configurations select the platform's executable and GDB automatically; the
build task configures and builds the desktop target before launch.
Ctrl+F5 runs without debugging; Ctrl+Shift+B builds the desktop target only.
The command-line builds above also build the CLI, benchmarks, and tests.
The UI uses ../fonts/FiraCodeNerdFont-Medium.ttf when available, then tries
installed Windows or Linux system fonts. To bundle another font, add
-DFLUIDSIM_FONT_FILE=/path/to/font.ttf to the configure command. For a
headless build, add -DFLUIDSIM_BUILD_DESKTOP=OFF; this skips SFML and the
desktop's graphics/audio dependencies.
Controls: hold W for throttle, C for clutch, A / D to shift, Z for
neutral, up/down to adjust throttle target, left/right to adjust engine load,
M toggles mechanical engine noise, mouse wheel changes simulation playback
speed, and Space toggles real time.
The command-line runner is useful for repeatable checks:
.\out\build\windows-gcc\fluidsim_cpp.exe --seconds 1 --throttle 0.35
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario two-stroke --seconds 1 --throttle 0.3
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario helmholtz --seconds 1
.\out\build\windows-gcc\fluidsim_cpp.exe --scenario pulse --seconds 1
On Linux, use ./build-desktop/fluidsim_cpp with the same arguments.
To compare deterministic checkpoints from the active C# twin scenario against the C++ port (energy variation is disabled for repeatability), build the C++ trace runner and execute the PowerShell comparison script:
cmake --build build-msvc --target fluidsim_parity_trace
.\verification\Compare-ScenarioTrace.ps1
Audio fidelity and playback
Desktop playback starts at real time: one solver output frame per 44.1 kHz stereo device frame. A simulation worker fills a bounded 8192-frame queue in 512-frame batches with a 4096-frame startup reserve (about 93 ms). The device callback only reads samples, removes DC, converts to PCM and handles underruns; it performs no simulation, allocation or waiting. An underrun fades out and recovery crossfades over 64 samples rather than stepping abruptly to silence. The callback never advances physical time to conceal missing samples.
The source is a linear compact-monopole approximation,
p(r,t) = (d mass_flow/dt)/(4*pi*r). 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;
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.
The default environment is a nearby wall. Press R to cycle dry, field, wall and woodland. Ground/wall image paths provide early reflections; the woodland scatterers and decaying feedback-delay tail are approximate effects, not validated outdoor acoustics. Synthetic mechanical noise remains optional. The compact source does not implement pipe-end radiation impedance, validated directivity, or exhaust afterfire chemistry. Filtering cannot recover aliased source information. See audio verification for tests, capture commands, limitations and measured transient reductions.
Launching fluidsim_desktop without --scenario opens a small scenario menu.
Click the twin, YZ125 or Saab B205 card to start, or select with Up/Down and press Enter.
Escape closes the menu. Simulation and audio start after selection. Passing
--scenario twin, --scenario two-stroke or --scenario b205 bypasses the menu; --scene and
--exhaust options also apply when choosing through the menu.
To test the two desktop engines from the repository root:
cmake --build build-desktop --target fluidsim_desktop -j 4
./build-desktop/fluidsim_desktop --scenario two-stroke --scene wall
# Or:
./build-desktop/fluidsim_desktop --scenario twin --scene field
Hold W for throttle, use Up/Down to adjust the persistent pedal request, Space to restore real-time playback and R to change environment. The two-stroke desktop uses the existing 125 cc configuration; its dimensions and calibrations must be adapted before it represents a particular engine. Both scenarios use the same motorcycle test model, dashboard and rolling peak dyno graph (kW and Nm versus RPM). Start in neutral, hold C to disengage the clutch, press D for first gear, apply throttle and release C to pull away. D/A shift up/down; Z selects neutral. Left/right adjust an external engine load, not a wheel brake. The graph records simulated crank power/torque, not measured wheel power.
The two-stroke now uses a YZ125 baseline: published 2022 primary, gearbox and
final reductions, 54 x 54.5 mm geometry, and an estimated racing expansion
chamber. Ports use piston-uncovered window areas. The pipe display follows
physical cell diameter and length. Compare exhausts with --exhaust racing
(default) or --exhaust straight; the desktop, CLI and WAV capture tool accept
this option. See YZ125 verification for the
geometry, assumptions, speed sweep and remaining unloaded-RPM limitation.
1980s American V8
Choose 1980s American 5.0 V8 in the desktop menu, or run:
./build-desktop/fluidsim_desktop --scenario v8 --scene field
This is an estimated naturally aspirated 302-class cross-plane V8: eight 101.6 x 76.2 mm cylinders, 7:1 compression, mild pushrod-style valve timing, 1-5-4-2-6-3-7-8 firing order, 750 RPM idle target and 7000 RPM limiter (6750 RPM recovery). Crank/flywheel inertia is 1.1 kg m², versus the original .28. Eight unequal .60--.78 m headers feed two independent 1.5-litre bank collectors: cylinders 1--4 and 5--8. Each collector has its own 50 mm outlet pipe (1.8 / 1.95 m), and each outlet has a separate spatial audio emitter. A nominal 3000 kg five-speed car uses the existing clutch/gear controls. The V8 clutch has 1100 Nm capacity to shorten launch slip with the heavy flywheel and car; engagement remains progressive rather than forcibly locking. Final drive is approximately 3.624:1 (6.16 / 1.7), making gearing 1.7x taller than the previous setup: 1.7x road speed at the same RPM and selected gear. Throttle opening uses a 65 ms response and 2.3 travels/second limit; closing uses 160 ms and 1.4 travels/second for a slower release. The V8 uses the shared ideal EFI fuel dose with ECU throttle/idle control, ignition, overrun fuel cut and rev limiting. Its earlier carb setup has been removed. Independent exhaust banks retain their uneven cross-plane pulse spacing rather than blending both banks in a shared collector. Numerical diffusion in the pipe solver and audio filtering still limit high-frequency rasp. Dimensions and ignition/cam calibration are design estimates; the output is not a validated period engine dyno curve.
The intake uses one 70 mm inlet duct, one 65 mm throttle body and a shared 4-litre plenum feeding eight 38 mm diameter, 12 cm runners (previously 30 cm). The revised mesh has 19 pipes and 190 cells, down from 310. Runner cells are 6 cm long and exhaust cells approximately 5--6.5 cm long. This coarsening trades some high-frequency resolution for more real-time headroom.
The earlier lift-off slowdown came from the CFL timestep falling below the 22.7 microsecond audio interval: the whole fluid network advanced almost twice per sample, doubling boundary solves and cell updates. The revised 16-second rev/coast benchmark stayed at one substep per sample and took .51--.57 wall seconds per simulated second here, including coast-down (previously up to 1.28). It returned to 759 RPM with finite audio and zero pipe positivity repairs. The CFL limit remains enforced; other operating points may still require subdivisions. The desktop retains the 4x playback gain.
Benchmark or capture with:
./build-desktop/fluidsim_scenario_benchmark 8 transient v8
./build-desktop/fluidsim_scenario_benchmark 20 coast v8
./build-desktop/fluidsim_audio_capture --scenario v8 --seconds 6 --throttle .3 --out /tmp/v8
Twin with a VZ21-size turbo
Choose 600 cc twin + VZ21-size turbo in the startup menu, or bypass it:
./build-desktop/fluidsim_desktop --scenario twin --turbo vz21 --scene field
./build-audit/fluidsim_cpp --scenario twin --turbo vz21 --seconds 5 --throttle 0.7
Turbocharger connects to four existing pipe ends and couples turbine and
compressor work through a rigid rotor. Small housing volumes preserve unsteady
exhaust pressure. Backpressure, charge temperature and spool speed emerge from
flow restrictions and energy exchange. A pressure-can/spring wastegate opens a
bypass; no boost pressure is imposed. HUD pressures are absolute. The default
unboosted twin is unchanged unless --turbo vz21 is selected.
This is an estimated VZ21-size model, not a measured turbo map. Engine exhaust pulses are implemented; blade-passing whistle/wakes are still pending. The seven-second twin transient and all nine CTest suites passed. The optimized 7,000-RPM workload takes about 0.65 wall seconds per simulated second on the audit machine; actual playback headroom depends on the computer. See turbo verification for assumptions and results.
Saab B205 / 9-5 wagon baseline
The Saab menu entry now includes an estimated GT17 turbo and ECU wastegate boost
control (up to 0.45 bar gauge). Launch with --scenario b205 --turbo gt17, or use
--turbo none for the naturally aspirated baseline. See GT17 verification
for controller behavior, assumptions and tests. Charge heating and backpressure
are simulated; an intercooler is not yet included.
Select Saab B205 / 9-5 wagon from the desktop menu, or launch directly:
./build-desktop/fluidsim_desktop --scenario b205 --scene field
./build-audit/fluidsim_cpp --scenario b205 --seconds 4 --throttle 0.5
The shared four-stroke scenario now supports two or four cylinders. The B205
uses 90 x 78 mm geometry, 8.8:1 compression, two intake/two exhaust valves per
cylinder and 1-3-4-2 firing order. Four intake runners and four exhaust headers
connect shared plenums and a tailpipe. The vehicle approximates a 9-5 wagon:
1675 kg with driver, five-speed manual, FM55 B05 overall ratios
13.70 / 7.12 / 4.77 / 3.62 / 2.67, and 4.05 final drive. No sixth gear is offered.
Controls and dyno match the other scenarios. The original atmospheric-breathing
geometry baseline remains available with --turbo none. Pipe/cam dimensions,
friction, idle/ignition maps, clutch, drag and loaded tire radius are estimates.
Turbo calibration and acoustics are approximate; factory power is not validated.
See B205 verification for sources and checks.
Current port status
The C++ core now includes the shared EngineCylinder model with four-stroke and
two-stroke cycle implementations, crankshaft, crankcase, atmosphere and
configurable 0-D volume components (including thermal relaxation and moving
volume work), profiled compressible 1-D pipes and boundaries, adaptive solver
stepping, the impulse-clutch motorcycle/road-load model, and the active 600 cc
twin topology. Audio includes the flow-derivative source, spatialized
intake/exhaust radiation, outdoor feedback-delay reverb, optional mechanical
noise, a lock-free simulation audio buffer, a SoundEngine lifecycle wrapper,
and an SFML desktop frontend. The shared 50-RPM-bin dyno history and graph are
also ported. A crankcase-scavenged 125 cc two-stroke scenario is available in
the core and desktop, with an ECU, finite-response throttle and dynamic reed.
This is still an in-progress port, not a claimed complete replacement. All four current C# scenarios (600 cc twin, 125 cc two-stroke, Helmholtz resonator, and pressure pulse) have headless C++ counterparts and can be selected by the CLI. Remaining parity work includes completing the C# drawing/diagnostic controls in the desktop UI and resolving numerical differences reported by the comparison harness. Earlier Windows builds passed CTest, but the current audio and two-stroke changes still need Windows runtime verification. The Linux desktop builds and runs; installed-font fallbacks provide labels when no bundled font is present.
The one-second twin trace remains substantially outside its 1% tolerance. The latest comparison's largest relative difference is 40.6745 for cylinder 1 pressure at checkpoint 33075 (69.8 kPa in C# versus 2.91 MPa in C++). A dense intake trace matches closely at first, then the C# plenum pressure begins rising at checkpoint 63 while C++ remains near ambient. This divergence needs to be resolved before claiming numerical parity. CTest covers cylinder geometry and valve timing, dyno-bin interpolation, configurable 0-D volume thermodynamics, crankcase and atmosphere state, profiled-pipe geometry, inertive and closed boundaries, gearing and clutch momentum transfer, finite runs for every scenario, and finite audio/reverb output.
Fuel and throttle control
Desktop playback has automatic gain enabled by default for every scenario. Press G to toggle it; the dashboard shows its state. It tracks stereo RMS and peaks after DC removal, lifting quiet idle audio gradually (1.5-second gain-rise time constant) and reducing gain faster for loud revs (30 ms). Extra gain is capped at 12x above the existing fixed 4x playback gain, with linked stereo peak protection. Silence does not request additional boost and underruns freeze the detector. Disabling smoothly returns to fixed playback gain. Simulation pressures and WAV capture levels are unaffected.
Both desktop engines use Ecu (include/FluidSim/Ecu.hpp). The desktop and
CLI throttle values are pedal requests; the actual throttle is advanced every
simulation sample, independently of rendering. Opening and closing use an
80 ms first-order actuator response and a maximum travel rate of two full
travels per second. The four-stroke uses an ideal butterfly projection; the
two-stroke uses the circular-segment aperture uncovered by a translating slide.
Shaft obstruction and edge leakage are not modeled.
The four-strokes use ideal sensors and direct access to trapped fresh-air mass at intake-valve closure to calculate each cylinder's fuel dose. This explicitly permitted idealization replaces injector flow/duty-cycle and fuel-film models. Their nominal AFR is 14.7, enriched progressively toward 12.8 above 70% throttle. A PI idle controller targets 1800 RPM with filtered RPM feedback, bounded idle authority and anti-windup. Overrun fuel cut engages above 3500 RPM on a closed pedal and restores fuel below 2400 RPM to catch idle. The twin's rev limiter cuts fuel and spark at 10000 RPM, recovering below 9750 RPM.
The two-stroke uses a simulated 38.1 mm slide carburetor instead of the EFI
dose. Effective pilot, needle and main restrictions draw liquid fuel from a
finite float bowl with the orifice relation m_dot = Cd*A*sqrt(2*rho*delta_p).
The jet pressure signal comes from simulated mass flow, local venturi dynamic
pressure and the measured static pressure drop. A wall film deposits and
evaporates fuel during transients. Delivered fuel is added downstream of the
carb as a conserved species and travels through the plenum, reed, crankcase,
transfer duct and cylinder. Combustion is therefore limited by the fuel and
oxygen actually trapped, rather than receiving a cylinder-side AFR dose.
The two-stroke has no rev limiter and deliberately does not use electronic overrun fuel cut. On a high-RPM throttle lift, wet-wall and pilot fuel can pass through unlit cycles if the trapped mixture cannot ignite. Spark remains at the CDI timing; there is no scheduled overrun firing pattern. Unburned fuel is transported out through the exhaust without pipe combustion. No sound samples are mixed in.
All ECU parameters are accessible calibrations, not measured engine maps. There is no starter motor, automatic restart, knock sensor, lambda sensor, 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 targets 3000 RPM idle and uses idle ignition trim in addition to throttle control. 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 parts. Fuel uses the gas model after wall-film release; droplet size, atomization, evaporation heat and oil are not modeled. Reed material, combustion and controller parameters still require calibration. C# trace parity is intentionally changed by these control and physics changes.
VS Code's default build task builds the desktop through CMake. Use Ctrl+Shift+B to build, or choose FluidSim desktop in Run and Debug and press F5 to build and run. The single-file C/C++ editor Run shortcut is disabled because this project requires multiple sources and SFML; IntelliSense gets include paths from CMake Tools. Open the repository root as the VS Code folder.
Fluid physics verification and performance
See the fluid physics audit for corrected conservation/boundary equations, analytic tests, measured performance and remaining model limits. The validation executable exercises 29 cases covering walls, pressure outlets, nozzles, inertance, pipe joints, volumes, species, acoustic modes, shocks and invalid states. The default coupled solver updates components on every substep, uses area-weighted shared fluxes and matches orifice discharge to duct characteristics. These changes intentionally alter older C# trace comparisons.
Build headless with CMake, then run ctest --test-dir build-audit --output-on-failure. fluidsim_physics_benchmark and
fluidsim_thread_benchmark reproduce the performance experiments.
FLUIDSIM_ENABLE_SIMD=OFF provides a scalar comparison; fast-math is not used.
Production fluid solving remains serial because threading was slower for the
current small pipe graph. SIMD and scalar Linux checks are covered; Windows
execution is still unverified.
The twin intake is supplied by an explicit 300 K atmospheric reservoir rather
than a pressure-only boundary with extrapolated entropy. The repeated-blip
fluidsim_intake_regression checks fresh-air supply and pressure/idle recovery.
Manifold vacuum at closed throttle remains part of the simulated pumping cycle.