using System; using FluidSim.Core; namespace FluidSim.Core { /// /// Synthesises far‑field sound at a listener position from an open pipe end. /// Three source mechanisms are combined: /// 1. Monopole – time derivative of mass flow (dominant at low speed / high pulsation). /// 2. Dipole – time derivative of momentum flux (shear‑layer / vortex shedding). /// 3. Jet noise – Lighthill‑type turbulence mixing noise (scales with U^8). /// /// References: /// • Lighthill, M.J. (1952) "On Sound Generated Aerodynamically". /// • Dowling, A.P. & Williams, J.E.F. (1983) "Sound and Sources of Sound". /// • Munjal, M.L. (2014) "Acoustics of Ducts and Mufflers", 2nd ed. /// • Tam, C.K.W. & Auriault, L. (1999) "Jet Mixing Noise from Fine‑Scale Turbulence". /// public class SoundProcessor { private readonly double dt; private readonly double c0; // ambient speed of sound (m/s) private readonly double rho0; // ambient density (kg/m³) private readonly double r; // listener distance (m) private readonly double pipeArea; // cross‑sectional area of the pipe end (m²) // ---------- monopole state ---------- private double flowLP; private readonly double lpAlpha; private double prevMassFlowOut; private double smoothDMdt; private readonly double alpha; // ---------- dipole state ---------- private double prevMomentumFlux; private double smoothDMomDt; private readonly double dipAlpha; // ---------- jet noise state ---------- private double jetNoiseSample; // previous random sample (for simple shaping) private readonly double jetTau; // correlation time ≈ D / U_mean public float Gain { get; set; } = 1.0f; /// /// /// Audio sample rate (Hz). /// Distance from the pipe exit to the listener (m). /// Internal diameter of the pipe (m). public SoundProcessor(int sampleRate, double listenerDistanceMeters = 1.0, double pipeDiameterMeters = 0.0217) // ~3.7 cm² area { dt = 1.0 / sampleRate; r = listenerDistanceMeters; pipeArea = Math.PI * 0.25 * pipeDiameterMeters * pipeDiameterMeters; // Ambient air properties c0 = 340.0; rho0 = 1.225; // ---- Monopole smoothing ---- double tau = 0.002; // 2 ms alpha = Math.Exp(-dt / tau); double tauLP = 0.005; // 5 ms low‑pass on mass flow lpAlpha = Math.Exp(-dt / tauLP); // ---- Dipole smoothing ---- double tauDip = 0.003; // 3 ms dipAlpha = Math.Exp(-dt / tauDip); // ---- Jet noise correlation time ---- jetTau = Math.Max(0.0005, pipeDiameterMeters / 50.0); // D / U_ref, floor at 0.5 ms } /// /// Process one sample. The OpenEndLink provides the instantaneous /// exit‑plane mass flow, density, velocity, and pressure. /// public float Process(OpenEndLink openEnd) { double flowOut = openEnd.LastMassFlowRate; // kg/s, positive = leaving pipe double rhoExit = openEnd.LastFaceDensity; // kg/m³ at exit double uExit = openEnd.LastFaceVelocity; // m/s (axial, positive = leaving) double pExit = openEnd.LastFacePressure; // Pa // ============================================================ // 1. MONOPOLE – due to unsteady mass addition (Lighthill 1952) // Far‑field pressure: p'(r,t) = (1 / 4πr c0) · dṁ/dt // ============================================================ flowLP = lpAlpha * flowLP + (1.0 - lpAlpha) * flowOut; double rawDerivative = (flowLP - prevMassFlowOut) / dt; prevMassFlowOut = flowLP; smoothDMdt = alpha * smoothDMdt + (1.0 - alpha) * rawDerivative; double pMono = smoothDMdt / (4.0 * Math.PI * r * c0); // ============================================================ // 2. DIPOLE – due to unsteady momentum flux at the exit plane // Momentum flux: F(t) = ṁ(t) · u(t) = ρ·A·u² // Far‑field (compact, low M): p'(r,θ,t) ≈ (cosθ / 4πr c0) · dF/dt // For on‑axis listener (θ = 0): p'(r,t) ≈ (1 / 4πr c0) · dF/dt // We also include a U⁶ scaling factor relative to a reference velocity. // ============================================================ double momentumFlux = Math.Abs(flowOut) * Math.Abs(uExit); // N double rawMomDeriv = (momentumFlux - prevMomentumFlux) / dt; prevMomentumFlux = momentumFlux; smoothDMomDt = dipAlpha * smoothDMomDt + (1.0 - dipAlpha) * rawMomDeriv; double pDipole = smoothDMomDt / (4.0 * Math.PI * r * c0); // Dipole efficiency factor: ∝ (U / c0)³ (since Idipole ∝ U⁶, pdipole ∝ U³) double Mach = Math.Abs(uExit) / c0; double dipoleEfficiency = Math.Pow(Mach, 3.0); pDipole *= dipoleEfficiency; // ============================================================ // 3. JET NOISE – Lighthill U⁸ mixing noise, band‑pass shaped // rms pressure: p'_jet ~ ρ0 · A / r · U⁴ / c0² // Model as broadband noise with amplitude ∝ U⁴. // A simple first‑order low‑pass filter shapes the spectrum // (cut‑off ≈ Strouhal frequency f ≈ 0.2 · U / D). // ============================================================ double Uref = Math.Max(1.0, Math.Abs(uExit)); // avoid division by zero double jetAmplitude = rho0 * pipeArea / r * Math.Pow(Uref / c0, 4.0); // Correlation time (sample‑and‑hold style random walk) double alphaJet = Math.Exp(-dt / jetTau); // Generate a new random target each step, filter with alphaJet double randomTarget = (new Random().NextDouble() * 2.0 - 1.0); jetNoiseSample = alphaJet * jetNoiseSample + (1.0 - alphaJet) * randomTarget; double pJet = jetAmplitude * jetNoiseSample; // ============================================================ // Combine contributions (monopole is primary; dipole & jet are // weighted down for realistic mix). Weights can be tuned per engine. // ============================================================ double pressure = (3000.0 * pMono) + (0.01 * pDipole) + (0 * pJet); pressure *= Gain; // Soft‑clip to ±1 return (float)Math.Tanh(pressure); } } }