PHSPipe1D
PHSVolumeAcousticPHSVolumeThermoPHSCylinder
This document describes a physically‑based, real‑time audio synthesis engine for combustion‑engine sounds. The simulation is built on a Port‑Hamiltonian System (PHS) framework, guaranteeing passivity, energy conservation, and numerical stability for any interconnection of components.
The core architecture consists of:
The system runs synchronously with audio hardware at 44.1 kHz and produces the radiated sound pressure directly from physical quantities – no arbitrary gains or sample‑based playback.
Design principles (to be repeated in every source file):
A Port‑Hamiltonian System is defined by the equations: $$ \begin{aligned} \dot{\xx} &= \bigl(\JJm(\xx) - \RRm(\xx)\bigr)\,\nabla \HH(\xx) \;+\; \BB(\xx)\,\uu_{\text{ext}},\\[4pt] \yy &= \BB(\xx)^{\!\top}\,\nabla \HH(\xx) \;+\; \DD(\xx)\,\uu_{\text{ext}}, \end{aligned} $$ where the variables have the following meanings:
| Symbol | Name | Role |
|---|---|---|
| $\xx \in \mathbb{R}^n$ | State vector | Collects all independent energy‑storing variables: pressures, volume velocities, masses, internal energies. |
| $\HH(\xx) : \mathbb{R}^n \to \mathbb{R}$ | Hamiltonian | Total stored energy; for linear acoustics it is a quadratic form $\HH = \frac12 \xx^{\!\top} Q \xx$. |
| $\JJm(\xx) \in \mathbb{R}^{n\times n}$ | Interconnection matrix | Skew‑symmetric ($\JJm = -\JJm^{\!\top}$); describes lossless power routing between storage elements. |
| $\RRm(\xx) \in \mathbb{R}^{n\times n}$ | Dissipation matrix | Symmetric positive‑semidefinite ($\RRm \succeq 0$); models irreversible loss (viscosity, thermal conduction, orifice turbulence). |
| $\uu_{\text{ext}} \in \mathbb{R}^m$ | External input vector | Effort or flow variables applied at the boundary ports (e.g. volume velocity at an open end, mass flow from a cylinder). |
| $\yy \in \mathbb{R}^m$ | Output vector | Power‑conjugate to $\uu_{\text{ext}}$ (e.g. pressure at the same port). The product $\yy^{\!\top}\uu_{\text{ext}}$ is the power entering the system. |
| $\BB(\xx) \in \mathbb{R}^{n\times m}$ | Input matrix | Maps external inputs onto the state derivatives; determines where power enters. |
| $\DD(\xx) \in \mathbb{R}^{m\times m}$ | Feed‑through matrix | Often zero in physical models; allows direct input‑output coupling if needed. |
$\BB(\xx)$ (often constant) selects where external inputs enter the system. It is the Jacobian of the port variables with respect to the inputs, or equivalently the matrix that satisfies $\BB(\xx)\,\uu_{\text{ext}} = \sum_k G_k\,\uu_{\text{ext},k}$, where $G_k$ are the input vector fields.
Examples:
Taking the time derivative of the Hamiltonian along trajectories: $$ \frac{\dif\HH}{\dif t} = (\nabla\HH)^{\!\top}\,\dot{\xx} = (\nabla\HH)^{\!\top}\bigl[(\JJm-\RRm)\nabla\HH + \BB\,\uu_{\text{ext}}\bigr] = -(\nabla\HH)^{\!\top}\RRm\,\nabla\HH \;+\; \yy^{\!\top}\uu_{\text{ext}} \;-\; \uu_{\text{ext}}^{\!\top}\DD\,\uu_{\text{ext}}. $$ Since $\RRm \succeq 0$, the first term is $\le 0$ (dissipation). If $\DD + \DD^{\!\top} \succeq 0$, the third term is also non‑positive. Therefore, $$ \frac{\dif\HH}{\dif t} \le \yy^{\!\top}\uu_{\text{ext}}, $$ which is the passivity inequality: the stored energy can increase only due to external power supplied through the ports. When $\uu_{\text{ext}} = 0$, the energy is non‑increasing – the system is Lyapunov stable.
In physical models derived from balance equations, $\DD$ is usually zero because the output $\yy$ depends only on the state $\xx$, not directly on the input $\uu_{\text{ext}}$. For our acoustics system $\DD = 0$ everywhere. A non‑zero $\DD$ can arise when algebraic constraints are eliminated or when reduced‑order models are used.
PHSPipe1DThe linear wave equation for an acoustic duct of constant cross‑section $A$ is written in PHS form using the state variables $\alpha = \frac{p}{\rho_0 c_0^2}$ (dilatation) and $\mu = \rho_0 u$ (momentum density): $$ \begin{aligned} \frac{\pdif}{\pdif t}\begin{bmatrix} \alpha \\ \mu \end{bmatrix} &= \begin{bmatrix} 0 & -\frac{\pdif}{\pdif \xi} \\[2pt] -\frac{\pdif}{\pdif \xi} & 0 \end{bmatrix} \begin{bmatrix} \frac{\delta\HH}{\delta\alpha} \\[2pt] \frac{\delta\HH}{\delta\mu} \end{bmatrix}, \end{aligned} $$ with Hamiltonian $$ \HH = \frac12 \int_0^L \Bigl( \rho_0 c_0^2\,\alpha^2 + \frac{1}{\rho_0}\,\mu^2 \Bigr) A\,\dif\xi. $$ In terms of the more familiar pressure $p$ and volume velocity $U = A u$: $$ \frac{\pdif p}{\pdif t} = -\frac{\rho_0 c_0^2}{A}\,\frac{\pdif U}{\pdif \xi},\qquad \frac{\pdif U}{\pdif t} = -\frac{A}{\rho_0}\,\frac{\pdif p}{\pdif \xi}. $$ The boundary ports are $(p(0), U(0))$ at the left end and $(p(L), -U(L))$ at the right end (sign convention: positive flow enters the domain).
Divide the pipe into $N$ segments of length $\Delta x = L/N$. Place pressures $p_i$ at integer nodes $i = 0,\dots,N$ and volume velocities $U_{j+1/2}$ at half‑integer midpoints $j = 0,\dots,N-1$. This is a staggered grid, which is essential for preserving the skew‑symmetry of the gradient operator.
The discrete gradient and divergence operators are: $$ (\DDm p)_{j+1/2} = \frac{p_{j+1} - p_j}{\Delta x},\qquad (\DDm\!\cdot\! U)_i = \frac{U_{i+1/2} - U_{i-1/2}}{\Delta x}\quad (i=1,\dots,N-1). $$ These satisfy $\DDm^{\!\top} = -\DDm\!\cdot$, which is the discrete analogue of integration by parts.
The semi‑discrete equations become: $$ \begin{aligned} \frac{\dif U_{j+1/2}}{\dif t} &= -\frac{A}{\rho_0}\,(\DDm p)_{j+1/2},\\[4pt] \frac{\dif p_i}{\dif t} &= -\frac{\rho_0 c_0^2}{A}\,(\DDm\!\cdot\! U)_i \qquad (i=1,\dots,N-1),\\[4pt] \frac{\dif p_0}{\dif t} &= -\frac{\rho_0 c_0^2}{A}\,\bigl(Q_{\text{left}} - U_{1/2}\bigr),\\[4pt] \frac{\dif p_N}{\dif t} &= -\frac{\rho_0 c_0^2}{A}\,\bigl(U_{N-1/2} + Q_{\text{right}}\bigr), \end{aligned} $$ where $Q_{\text{left}}$ and $Q_{\text{right}}$ are the external volume flows entering the pipe at the boundaries (positive = into the pipe). The characteristic impedance is $Z_0 = \dfrac{\rho_0 c_0}{A}$.
At the left end ($\xi=0$): effort $= p_0$, flow $= -Q_{\text{left}}$ (flow leaving the domain). At the right end ($\xi=L$): effort $= p_N$, flow $= Q_{\text{right}}$ (flow leaving the domain). The power flowing out of the pipe through the ports is $p_0(-Q_{\text{left}}) + p_N Q_{\text{right}}$.
Real pipes exhibit frequency‑dependent damping due to viscous drag and thermal conduction at the walls. The classical theory of Zwikker and Kosten (1949) gives the complex wave number: $$ k(\omega) = \frac{\omega}{c_0}\sqrt{\frac{1 + (\gamma-1)\Psi_h}{1 - \Psi_v}}, $$ where $\Psi_v$ and $\Psi_h$ are functions of the shear and thermal boundary‑layer thicknesses. For audio frequencies in pipes of radius $a \gtrsim 1$ cm, the damping is weak and can be approximated by a Kelvin–Voigt model: the wall shear stress acts as a velocity‑dependent body force.
In our discrete model this is implemented by adding a diagonal dissipation matrix $\RRm$ with entries $\varepsilon \approx 0.001\text{–}0.005$ on the velocity nodes. Each velocity is multiplied by $(1-\varepsilon)$ per time step, giving a frequency‑independent loss rate that approximates the low‑frequency behaviour of the full Zwikker–Kosten model. A more accurate filter (e.g. a Padé approximant of $\sqrt{i\omega}$) can be substituted later.
For the explicit leapfrog used inside the global midpoint solver, the time step $\Delta t$ must satisfy $$ \frac{c_0\,\Delta t}{\Delta x} \le 1. $$ With typical choices ($L = 1$ m, $N = 20$, $\Delta t \approx 2.27\times10^{-5}$ s) the Courant number is $\approx 0.155$, well within the stable region.
PHSVolumeAcousticA small cavity behaves like an acoustic capacitor. Linearising the ideal gas law around ambient pressure $p_0$: $$ \frac{\dif p_V}{\dif t} = \frac{\gamma p_0}{V_0}\,Q_{\text{net}}, $$ where $p_V$ is the gauge pressure, $V_0$ the cavity volume, and $Q_{\text{net}}$ the net volume flow entering.
PHS formulation: State $x = p_V$. Hamiltonian $\HH = \dfrac{V_0}{2\gamma p_0}\,p_V^{\,2}$. The port is $(p_V, Q_{\text{net}})$ – effort is pressure, flow is volume velocity. The Dirac structure is trivial ($\JJm = 0$) and the port equation is $\dot{p}_V = \frac{\gamma p_0}{V_0}\,Q_{\text{net}}$.
PHSVolumeThermoFor larger volumes where mass and energy balance matters (manifolds, cylinder):
The volume $V$ may be time‑varying (piston) or constant (manifold). When $V$ is constant, $\dif V/\dif t = 0$ and the $p\,\dif V/\dif t$ term vanishes.
PHSCylinderThe cylinder is the most complex component. It is a thermodynamic volume with a moving wall (piston) and external heat addition (combustion). It exposes four power ports:
| Port | Effort | Flow | Physical meaning |
|---|---|---|---|
| Intake acoustic | $p_{\text{cyl}}$ (pressure) | $Q_{\text{int}}$ (volume flow) | Gas exchange with intake manifold |
| Exhaust acoustic | $p_{\text{cyl}}$ (pressure) | $Q_{\text{exh}}$ (volume flow) | Gas exchange with exhaust manifold |
| Mechanical | $-p_{\text{cyl}}$ (pressure) | $\dot{V}$ (volume rate) | Piston work on gas |
| Thermal | $1$ (temperature) | $\dot{Q}_{\text{comb}}$ (heat flow) | Combustion heat release |
The cylinder volume as a function of crank angle $\theta$ (radians) is: $$ V(\theta) = V_c + \frac{\pi}{4}B^2\,s(\theta), $$ where $V_c$ is the clearance volume, $B$ the bore, and $s(\theta)$ the piston displacement: $$ s(\theta) = \frac{S}{2}\Bigl(1 - \cos\theta\Bigr) + L\Bigl(1 - \sqrt{1 - \bigl(\frac{S}{2L}\sin\theta\bigr)^2}\Bigr), $$ with $S$ the stroke and $L$ the connecting‑rod length. The clearance volume is $V_c = V_d / (r_c - 1)$, where $V_d = (\pi/4)B^2 S$ is the displacement and $r_c$ the compression ratio.
Combustion is modelled as a constant‑volume heat addition near top‑dead‑centre (TDC) of the compression stroke: $$ \dot{Q}_{\text{comb}}(t) = \frac{E_{\text{fuel}}}{\tau_{\text{burn}}}\,\, \text{rect}\Bigl(\frac{\theta - \theta_{\text{spark}}}{\Delta\theta_{\text{burn}}}\Bigr), $$ where $E_{\text{fuel}}$ is the fuel energy per cycle, $\theta_{\text{spark}} \approx \pi$ (TDC), and $\Delta\theta_{\text{burn}} \approx \pi/6$ (30° burn duration). The heat is added to the internal energy $U$ via the thermal port.
The intake and exhaust valves are modelled as variable‑area orifices. The mass flow rate is given by the standard compressible‑flow equation: $$ \dot{m} = C_d\,A_{\text{valve}}\,\frac{p_{\text{up}}}{\sqrt{T_{\text{up}}}}\, \begin{cases} \sqrt{\dfrac{\gamma}{R}}\, \Bigl(\dfrac{2}{\gamma+1}\Bigr)^{\!\frac{\gamma+1}{2(\gamma-1)}}, & \text{if } \dfrac{p_{\text{dn}}}{p_{\text{up}}} \le p_{\text{crit}},\\[10pt] \sqrt{\dfrac{2\gamma}{R(\gamma-1)}}\, \Bigl[\Bigl(\dfrac{p_{\text{dn}}}{p_{\text{up}}}\Bigr)^{\!2/\gamma} - \Bigl(\dfrac{p_{\text{dn}}}{p_{\text{up}}}\Bigr)^{\!(\gamma+1)/\gamma}\Bigr]^{1/2}, & \text{otherwise}, \end{cases} $$ where $p_{\text{crit}} = \bigl(\frac{2}{\gamma+1}\bigr)^{\gamma/(\gamma-1)} \approx 0.528$ for air. The curtain area is $A_{\text{valve}} = \pi D_{\text{valve}}\,L_{\text{lift}}$, with $L_{\text{lift}}(\theta)$ given by a sinusoidal cam profile.
(Adapted from Baldan et al. 2015, §3.3)
A multi‑cylinder engine is assembled from the following components:
| Baldan et al. component | Our PHS class | Connection |
|---|---|---|
| Cylinder (waveguide with modulated feedback) | PHSCylinder | Intake/exhaust ports connect to manifolds via resistive valves |
| Intake collector | PHSVolumeThermo | Acoustic port to intake pipe; mass/enthalpy ports to cylinders |
| Exhaust collector | PHSVolumeThermo | Acoustic port to exhaust pipe; mass/enthalpy ports to cylinders |
| Intake duct | PHSPipe1D | Left end open (radiation), right end to intake manifold |
| Exhaust duct | PHSPipe1D | Left end to exhaust manifold, right end open (radiation) |
| Muffler | Network of PHSPipe1D segments | Series/parallel connections via 0‑junctions |
The shared manifolds act as 0‑junctions (common pressure, sum of flows = 0): all cylinder ports and the connecting pipe see the same manifold pressure, and the net volume flow into the manifold determines its pressure change via the compliance equation.
All component states are concatenated into a single vector: $$ \xx = \bigl[\,\underbrace{p_0^{\text{pipe1}}, U_{1/2}^{\text{pipe1}}, p_1^{\text{pipe1}}, \dots, p_N^{\text{pipe1}}}_{\text{pipe 1}}, \underbrace{p_0^{\text{pipe2}}, \dots}_{\text{pipe 2}}, \underbrace{p_V^{\text{vol1}}, m^{\text{cyl}}, U^{\text{cyl}}, \dots}_{\text{volumes \& cylinder}}\,\bigr]^{\!\top}. $$ The total number of states for a typical engine with 2 pipes of $N=20$ cells each, 2 manifold volumes, and 1 cylinder is $2(2N+1) + 2 + 2 \approx 86$ – trivially small for a modern CPU.
The matrix $A$ has the following block structure:
The bandwidth is dominated by the pipe tridiagonal blocks, so the total bandwidth is $\approx 3$. Storage and solution cost is $O(n)$, i.e. linear in the number of states.
For the pure pipe system (no volumes), $A$ is tridiagonal and can be solved by the Thomas algorithm (tridiagonal matrix algorithm, TDMA) in $O(n)$ time with $O(1)$ extra storage. When volumes are added, the system remains narrowly banded and can be solved by a banded LU factorisation without pivoting (since $A$ is diagonally dominant for small $\Delta t$).
The implicit midpoint rule is A‑stable and symplectic for linear Hamiltonian systems. There is no CFL restriction on $\Delta t$: the scheme is unconditionally stable. In practice we choose $\Delta t = 1/44100$ s to resolve the audio bandwidth, which is far below any stability limit.
At each open pipe end, the volume velocity $U_{\text{open}}$ is read from the state vector.
The radiated pressure at a listener distance $r$ is given by the monopole formula:
$$
p_{\text{rad}}(t) = \frac{\rho_0}{4\pi r}\,\frac{\dif U_{\text{open}}}{\dif t},
$$
which we approximate by the finite difference
$$
p_{\text{rad}}[n] = \frac{\rho_0}{4\pi r}\,\frac{U_{\text{open}}[n] - U_{\text{open}}[n-1]}{\Delta t}.
$$
This is the raw physical pressure in Pascals. It is converted to a digital audio sample by
multiplying by a single global OutputScale:
$$
\text{sample}[n] = \text{OutputScale} \cdot p_{\text{rad}}[n].
$$
With typical engine flows, OutputScale ≈ 5000 gives a comfortable listening level.
A soft clipper ($\tanh$) can be applied as a safety limiter without affecting the physics.
| Property | How the PHS architecture guarantees it |
|---|---|
| Passivity | The midpoint rule preserves the Dirac structure exactly; every component is passive by construction [1,3]. |
| Energy conservation | Without dissipation ($\RRm=0$), the discrete energy $\HH(\xx^n)$ is conserved exactly (up to rounding). With $\RRm\succeq0$, energy decreases monotonically. |
| DC stability | Static pressure offsets are part of the state vector $\xx$ and are evolved by the same integrator as acoustic waves. No separate "DC path" or "leak" term is needed. |
| Thermodynamic positivity | The cylinder's internal energy $U$ is integrated with the midpoint rule, which preserves quadratic invariants. An energy floor ($U \ge 10^{-6}$ J) safeguards against unphysical negative temperatures. |
| CFL‑free | The implicit midpoint rule is unconditionally stable. The only constraint on $\Delta t$ is the desired audio bandwidth. |
| Modularity | New components (turbocharger, Helmholtz resonator, side‑branch muffler) can be added by defining their PHS port formulation and connecting them to existing ports. No change to the solver is required. |
| Phase | Tasks | Validation |
|---|---|---|
| 0 – Verification | Write unit tests for each component in isolation: PHSPipe1D impulse response, PHSVolumeAcoustic compliance, Orifice mass flow, OpenEndRadiation reflection coefficient. |
Each test passes independently before any interconnection. |
| 1 – Core 1D PHS Pipe | Implement PHSPipe1D: staggered grid, tridiagonal solver (Thomas algorithm). Closed‑closed and closed‑open boundary conditions. |
Impulse response matches theoretical delay ($L/c_0$) and reflection pattern. Energy conserved for loss‑free case. |
| 2 – 0D Acoustic Volume | Implement PHSVolumeAcoustic. Add volume–pipe boundary coupling to the global system matrix. |
Helmholtz resonator: resonance frequency matches $f_H = \frac{c_0}{2\pi}\sqrt{A/(V L_{\text{eff}})}$, decay time matches radiation loss. |
| 3 – Thermodynamic Volume + Cylinder | Implement PHSVolumeThermo and PHSCylinder (moving‑wall, combustion, valve ports). |
Single‑cylinder motored: correct $p$–$V$ diagram, exhaust pulse amplitude. Single‑cylinder firing: pressure peak after combustion. |
| 4 – Engine Network | Implement PHSEngineNetwork: multiple cylinders with firing order, shared manifolds, resistive valve couplings. |
4‑cylinder engine: firing frequency $= 2\times\text{RPM}/60$, exhaust manifold pressure oscillations, radiated sound spectrum. |
| 5 – Flow Noise & Dissipation | Add frequency‑dependent damping matrix $\RRm$ (Padé approximant of Zwikker–Kosten model). Add stochastic flow noise at valve orifices proportional to $|\dot{m}|$. | Hiss and rumble components audible; spectrum matches empirical engine noise. |
| 6 – Performance Tuning | Profile the solver; cache LU factorisation between valve events; optimise sparse matrix assembly. | CPU usage < 5% on a typical desktop at 44.1 kHz with a 4‑cylinder engine. |
| Symbol | Description | Units |
|---|---|---|
| $p$ | Gauge pressure | Pa |
| $U$, $Q$ | Volume velocity (flow rate) | m³ s⁻¹ |
| $m$ | Mass of gas in a volume | kg |
| $U$ (internal) | Internal energy | J |
| $V$ | Volume | m³ |
| $T$ | Temperature | K |
| $c_0$ | Speed of sound at ambient conditions | m s⁻¹ |
| $\rho_0$ | Density at ambient conditions | kg m⁻³ |
| $A$ | Pipe cross‑sectional area | m² |
| $L$ | Pipe length | m |
| $r$ | Pipe radius | m |
| $Z_0$ | Characteristic impedance $\rho_0 c_0 / A$ | Pa·s m⁻³ |
| $\gamma$ | Ratio of specific heats ($c_p/c_v$) | – |
| $R$ | Specific gas constant for air | J kg⁻¹ K⁻¹ |
| $c_v$ | Specific heat at constant volume | J kg⁻¹ K⁻¹ |
| $c_p$ | Specific heat at constant pressure | J kg⁻¹ K⁻¹ |
| $B$ | Cylinder bore | m |
| $S$ | Cylinder stroke | m |
| $L$ (rod) | Connecting‑rod length | m |
| $r_c$ | Compression ratio | – |
| $C_d$ | Valve discharge coefficient | – |
| $D_{\text{valve}}$ | Valve head diameter | m |
| $L_{\text{lift}}$ | Valve lift | m |
| Constant | Symbol | Value | Units |
|---|---|---|---|
| Specific gas constant (air) | $R$ | 287.058 | J kg⁻¹ K⁻¹ |
| Ratio of specific heats (air) | $\gamma$ | 1.4 | – |
| Speed of sound at 300 K | $c_0$ | 343 | m s⁻¹ |
| Ambient density | $\rho_0$ | 1.2 | kg m⁻³ |
| Ambient pressure | $p_0$ | 101 325 | Pa |
| Ambient temperature | $T_0$ | 300 | K |
| Kinematic viscosity (air, 300 K) | $\nu$ | $1.5\times10^{-5}$ | m² s⁻¹ |
| Prandtl number (air) | $\Pr$ | 0.71 | – |
| Critical pressure ratio (choked flow) | $p_{\text{crit}}$ | 0.528 | – |
| Sample rate | $f_s$ | 44 100 | Hz |
| Time step | $\Delta t$ | $1/f_s \approx 2.27\times10^{-5}$ | s |
| Listening distance (radiation) | $r$ | 1.0 | m |
| Parameter | Symbol | Small car | Sports car | Units |
|---|---|---|---|---|
| Bore | $B$ | 0.080 | 0.100 | m |
| Stroke | $S$ | 0.080 | 0.090 | m |
| Rod length | $L$ | 0.150 | 0.165 | m |
| Compression ratio | $r_c$ | 10 | 12 | – |
| Displacement (per cyl) | $V_d$ | 0.402 | 0.707 | L |
| Intake valve diameter | $D_{\text{int}}$ | 0.030 | 0.040 | m |
| Exhaust valve diameter | $D_{\text{exh}}$ | 0.028 | 0.036 | m |
| Max valve lift | $L_{\text{lift,max}}$ | 0.008 | 0.012 | m |
| Fuel energy per cycle | $E_{\text{fuel}}$ | 400 | 600 | J |
| Intake pipe length | – | 1.0 | 0.8 | m |
| Exhaust pipe length | – | 1.5 | 1.2 | m |
| Pipe inner radius | $a$ | 0.020 | 0.025 | m |
| Idle RPM | – | 800 | 900 | min⁻¹ |
| Max RPM | – | 6000 | 8000 | min⁻¹ |
Smith (2010) showed that a digital waveguide network (DWN) is exactly equivalent to a staggered‑grid finite‑difference scheme when the Courant number is exactly 1 ($c_0\Delta t / \Delta x = 1$). In that special case the leapfrog update reduces to shifting samples along delay lines – a waveguide.
Our PHS formulation generalises this:
The Baldan et al. (2015) engine model uses DWNs exclusively. Our approach produces the same sound but with stronger mathematical guarantees and the flexibility to add nonlinear components without sacrificing stability.
Scripts/ ├── Physics/ │ ├── Port.cs # Effort/flow port descriptor │ ├── PHSVolume.cs # Base class (acoustic & thermo) │ ├── PHSPipe1D.cs # Staggered‑grid 1D duct │ ├── PHSCylinder.cs # Cylinder (inherits PHSVolume) │ ├── PHSEngineNetwork.cs # Multi‑cylinder assembly │ ├── OpenEndRadiation.cs # Reflection filter + monopole │ ├── Orifice.cs # Compressible orifice mass flow │ ├── FlowNoise.cs # Turbulence noise source │ └── GlobalSolver.cs # Midpoint rule, sparse assembler ├── Tests/ │ ├── TestScenario.cs # Abstract base class │ ├── TestRunner.cs # Sequencer node │ ├── Test1_PipeImpulse.cs # Waveguide validation │ ├── Test2_Helmholtz.cs # Volume + pipe coupling │ ├── Test3_OrificeFlow.cs # Choked/subsonic validation │ ├── Test4_MotoredCylinder.cs # Single cylinder, no combustion │ ├── Test5_FiringCylinder.cs # With combustion │ ├── Test6_MultiCylinder.cs # 4‑cylinder engine │ └── Test7_FlowNoise.cs # Noise injection ├── SoundEngine.cs # AudioStreamPlayer wrapper └── phs_implementation_guide.html # This document
End of document – Version 2.0