using System; namespace FluidSim.Core { public class SoundProcessor { // Monopole state private double lastMassFlow = 0.0; private double dt; // Resonant band‑pass filter (second‑order) private double b0, b1, b2, a1, a2; private double x1 = 0, x2 = 0, y1 = 0, y2 = 0; private double pipeLength; // Reverb (outdoor) private float[] delayLine; private int writeIndex; private float feedback = 0.50f; private float lowpassCoeff = 0.70f; private float lastFeedbackSample = 0f; // Turbulence (pink noise scaled by U³) private PinkNoiseGenerator pinkNoise; private float turbulenceGain = 0.05f; private double pipeArea; private double ambientPressure = 101325.0; // Gains private float masterGain = 0.0005f; private float pressureGain = 0.12f; public SoundProcessor(int sampleRate, double pipeLengthMeters, double pipeDiameterMeters = 0.04, float reverbTimeMs = 200.0f) { dt = 1.0 / sampleRate; pipeLength = pipeLengthMeters; pipeArea = Math.PI * Math.Pow(pipeDiameterMeters / 2.0, 2.0); // Design resonant filter at pipe fundamental frequency double c = 340.0; double f0 = c / (4.0 * pipeLength); double Q = 15.0; double omega = 2.0 * Math.PI * f0; double alpha = Math.Sin(omega * dt) / (2.0 * Q); double norm = 1.0 / (1.0 + alpha); b0 = alpha * norm; b1 = 0.0; b2 = -alpha * norm; a1 = -2.0 * Math.Cos(omega * dt) * norm; a2 = (1.0 - alpha) * norm; // Reverb delay line int delaySamples = (int)(sampleRate * reverbTimeMs / 1000.0); delayLine = new float[delaySamples]; writeIndex = 0; pinkNoise = new PinkNoiseGenerator(); } public float MasterGain { get => masterGain; set => masterGain = value; } public float PressureGain { get => pressureGain; set => pressureGain = value; } public float TurbulenceGain { get => turbulenceGain; set => turbulenceGain = value; } public void SetAmbientPressure(double p) => ambientPressure = p; public void SetPipeDiameter(double diameterMeters) => pipeArea = Math.PI * Math.Pow(diameterMeters / 2.0, 2.0); public float Process(float massFlow, float pipeEndPressure) { // 1. Monopole source: d(mdot)/dt double derivative = (massFlow - lastMassFlow) / dt; derivative = Math.Clamp(derivative, -500, 500); lastMassFlow = massFlow; float monopole = (float)(derivative * masterGain); // 2. Pressure difference (low‑frequency component) float pressureDiff = (float)((pipeEndPressure - ambientPressure) / ambientPressure) * pressureGain; float mixed = monopole + pressureDiff; // DO NOT clamp here – let the filter and final clamp handle dynamics // 3. Resonant band‑pass filter double y = b0 * mixed + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; x2 = x1; x1 = mixed; y2 = y1; y1 = y; float resonant = (float)Math.Clamp(y, -1f, 1f); // 4. Turbulence noise: amplitude ∝ U³ (empirical for low speeds) double velocity = massFlow / (pipeArea * 1.225); double Uref = 100.0; double turbulenceAmp = Math.Pow(Math.Abs(velocity) / Uref, 3.0); float pink = pinkNoise.Next() * turbulenceGain * (float)turbulenceAmp; resonant += pink; resonant = Math.Clamp(resonant, -1f, 1f); // 5. Outdoor reverb float delayed = delayLine[writeIndex]; float filteredDelay = delayed * lowpassCoeff + lastFeedbackSample * (1f - lowpassCoeff); lastFeedbackSample = filteredDelay; float wet = delayed + filteredDelay * feedback; delayLine[writeIndex] = resonant + filteredDelay * feedback; writeIndex = (writeIndex + 1) % delayLine.Length; // 6. Dry/wet mix float output = resonant * 0.7f + wet * 0.3f; output = MathF.Tanh(output); return output; } } internal class PinkNoiseGenerator { private readonly Random random = new Random(); private readonly float[] whiteNoise = new float[7]; private int currentIndex = 0; public PinkNoiseGenerator() { for (int i = 0; i < 7; i++) whiteNoise[i] = (float)(random.NextDouble() * 2.0 - 1.0); } public float Next() { whiteNoise[0] = (float)(random.NextDouble() * 2.0 - 1.0); currentIndex = (currentIndex + 1) & 0x7F; int updateMask = 0; int temp = currentIndex; for (int i = 0; i < 7; i++) { if ((temp & 1) == 0) updateMask |= (1 << i); temp >>= 1; } for (int i = 1; i < 7; i++) if ((updateMask & (1 << i)) != 0) whiteNoise[i] = (float)(random.NextDouble() * 2.0 - 1.0); float sum = 0f; for (int i = 0; i < 7; i++) sum += whiteNoise[i]; return sum / 3.5f; } } }