refined
This commit is contained in:
@@ -16,11 +16,11 @@ namespace FluidSim.Core
|
||||
private readonly OrthonormalMixer mixerL, mixerR;
|
||||
private readonly LowPassFilter[] filterL, filterR;
|
||||
|
||||
public float DryMix { get; set; } = 1.0f;
|
||||
public float EarlyMix { get; set; } = 0.5f;
|
||||
public float TailMix { get; set; } = 0.9f;
|
||||
public float Feedback { get; set; } = 0.55f; // safe range 0.7‑0.9
|
||||
public float DampingFreq { get; set; } = 6000f; // Hz
|
||||
public float DryMix { get; set; } = 1.0f; // direct sound unchanged
|
||||
public float EarlyMix { get; set; } = 0.12f; // very little early reflection (ground bounce)
|
||||
public float TailMix { get; set; } = 0.18f; // subtle diffuse tail
|
||||
public float Feedback { get; set; } = 0.35f; // lower feedback – outdoor doesn't ring
|
||||
public float DampingFreq { get; set; } = 2500f; // air absorption – high frequencies die quickly
|
||||
|
||||
public OutdoorExhaustReverb(int sampleRate)
|
||||
{
|
||||
@@ -118,7 +118,7 @@ namespace FluidSim.Core
|
||||
public float Process(float drySample)
|
||||
{
|
||||
var (l, r) = ProcessStereo(drySample);
|
||||
return (l + r) * 0.5f;
|
||||
return MathF.Tanh((l + r) * 0.5f);
|
||||
}
|
||||
|
||||
// ========== Helper classes ==========
|
||||
|
||||
@@ -4,141 +4,73 @@ using FluidSim.Core;
|
||||
namespace FluidSim.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// Synthesises far‑field exhaust sound using the monopole model
|
||||
/// of Jones (1978). The radiated pressure is proportional to the
|
||||
/// time derivative of the mass flow at the pipe exit.
|
||||
///
|
||||
/// 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".
|
||||
/// Reference:
|
||||
/// Jones, A.D. (1978) "Noise characteristics and exhaust process
|
||||
/// gas dynamics of a small 2-stroke engine", PhD thesis, Univ. Adelaide.
|
||||
/// </summary>
|
||||
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²)
|
||||
private readonly double scaleFactor; // 1 / (4π r) (free-field monopole)
|
||||
|
||||
// ---------- monopole state ----------
|
||||
// ---------- Mass‑flow derivative (identical to original) ----------
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="sampleRate">Audio sample rate (Hz).</param>
|
||||
/// <param name="listenerDistanceMeters">Distance from the pipe exit to the listener (m).</param>
|
||||
/// <param name="pipeDiameterMeters">Internal diameter of the pipe (m).</param>
|
||||
/// <param name="listenerDistanceMeters">Listener distance (m).</param>
|
||||
/// <param name="pipeDiameterMeters">Ignored in this model; kept for compatibility.</param>
|
||||
public SoundProcessor(int sampleRate,
|
||||
double listenerDistanceMeters = 1.0,
|
||||
double pipeDiameterMeters = 0.0217) // ~3.7 cm² area
|
||||
double pipeDiameterMeters = 0.0217)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
r = listenerDistanceMeters;
|
||||
pipeArea = Math.PI * 0.25 * pipeDiameterMeters * pipeDiameterMeters;
|
||||
scaleFactor = 1.0 / (4.0 * Math.PI * r); // free‑field monopole
|
||||
|
||||
// Ambient air properties
|
||||
c0 = 340.0;
|
||||
rho0 = 1.225;
|
||||
|
||||
// ---- Monopole smoothing ----
|
||||
double tau = 0.002; // 2 ms
|
||||
// ---- Smoothing time constants (unchanged) ----
|
||||
double tau = 0.02; // 2 ms for derivative
|
||||
alpha = Math.Exp(-dt / tau);
|
||||
|
||||
double tauLP = 0.005; // 5 ms low‑pass on mass flow
|
||||
double tauLP = 0.00001; // 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process one sample. The OpenEndLink provides the instantaneous
|
||||
/// exit‑plane mass flow, density, velocity, and pressure.
|
||||
/// exit‑plane mass flow.
|
||||
/// </summary>
|
||||
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
|
||||
double flowOut = openEnd.LastMassFlowRate; // kg/s, positive = leaving pipe
|
||||
|
||||
// ============================================================
|
||||
// 1. MONOPOLE – due to unsteady mass addition (Lighthill 1952)
|
||||
// Far‑field pressure: p'(r,t) = (1 / 4πr c0) · dṁ/dt
|
||||
// ============================================================
|
||||
// Low‑pass the mass flow signal
|
||||
flowLP = lpAlpha * flowLP + (1.0 - lpAlpha) * flowOut;
|
||||
|
||||
// Derivative of the smoothed mass flow
|
||||
double rawDerivative = (flowLP - prevMassFlowOut) / dt;
|
||||
prevMassFlowOut = flowLP;
|
||||
|
||||
// Smooth the derivative
|
||||
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);
|
||||
// Far‑field monopole pressure (free‑field, Jones eq. 2.15 adapted)
|
||||
double pressure = smoothDMdt * scaleFactor * Gain;
|
||||
|
||||
// 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);
|
||||
// Soft clip to ±1
|
||||
return (float)pressure;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user