Files
FluidSim/Core/SoundProcessor.cs
2026-05-05 10:32:30 +02:00

155 lines
5.4 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;
namespace FluidSim.Core
{
public class SoundProcessor
{
// Monopole state
private double lastMassFlow = 0.0;
private double dt;
// Resonant bandpass filter (secondorder)
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;
lastMassFlow = massFlow;
float monopole = (float)(derivative * masterGain);
// 2. Pressure difference (lowfrequency 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 bandpass 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 = Math.Clamp(output, -1f, 1f);
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;
}
}
}