using System; namespace FluidSim.Core { public class SoundProcessor { private double dt; private double pipeArea; private double ambientPressure = 101325.0; // Monopole source state private double lastMassFlow = 0.0; // Gains private float masterGain = 0.0005f; private float pressureGain = 0.12f; private float turbulenceGain = 0.05f; private float turbulence = 0.05f; private PinkNoiseGenerator pinkNoise; // Reverb (outdoor) private float[] delayLine; private int writeIndex; private float feedback = 0.50f; private float lowpassCoeff = 0.70f; private float lastFeedbackSample = 0f; public SoundProcessor(int sampleRate, double pipeDiameterMeters, float reverbTimeMs = 200.0f) { dt = 1.0 / sampleRate; pipeArea = Math.PI * Math.Pow(pipeDiameterMeters / 2.0, 2.0); 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 float Turbulence { get => turbulence; set => turbulence = 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: d(mdot)/dt double derivative = (massFlow - lastMassFlow) / dt; lastMassFlow = massFlow; float monopole = (float)(derivative * masterGain); // 2. Pressure component float pressureDiff = (float)((pipeEndPressure - ambientPressure) / ambientPressure) * pressureGain; float mixed = monopole + pressureDiff; // 3. Turbulence: amplitude ∝ U^8 double velocity = massFlow / (pipeArea * 1.225); double turbulenceAmp = Math.Pow(Math.Abs(velocity) * turbulence, 3.0); float pink = pinkNoise.Next() * turbulenceGain * (float)turbulenceAmp; float combined = mixed + pink; // 4. Outdoor reverb float delayed = delayLine[writeIndex]; float filteredDelay = delayed * lowpassCoeff + lastFeedbackSample * (1f - lowpassCoeff); lastFeedbackSample = filteredDelay; float wet = delayed + filteredDelay * feedback; delayLine[writeIndex] = combined + filteredDelay * feedback; writeIndex = (writeIndex + 1) % delayLine.Length; // 5. Dry/wet mix float output = combined * 0.7f + wet * 0.3f; return MathF.Tanh(output); } } // PinkNoiseGenerator unchanged, same as before 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; } } }