Files
FluidSim/Core/SoundProcessor.cs
2026-05-07 21:48:37 +02:00

144 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using FluidSim.Core;
namespace FluidSim.Core
{
/// <summary>
/// Synthesises farfield 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 (shearlayer / vortex shedding).
/// 3. Jet noise Lighthilltype 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 FineScale Turbulence".
/// </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; // crosssectional 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;
/// <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>
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 lowpass 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
/// exitplane mass flow, density, velocity, and pressure.
/// </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
// ============================================================
// 1. MONOPOLE due to unsteady mass addition (Lighthill 1952)
// Farfield 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²
// Farfield (compact, low M): p'(r,θ,t) ≈ (cosθ / 4πr c0) · dF/dt
// For onaxis 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, bandpass shaped
// rms pressure: p'_jet ~ ρ0 · A / r · U⁴ / c0²
// Model as broadband noise with amplitude ∝ U⁴.
// A simple firstorder lowpass filter shapes the spectrum
// (cutoff ≈ 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 (sampleandhold 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;
// Softclip to ±1
return (float)Math.Tanh(pressure);
}
}
}