133 lines
4.8 KiB
C#
133 lines
4.8 KiB
C#
using System;
|
||
using FluidSim.Components;
|
||
using FluidSim.Interfaces;
|
||
using FluidSim.Utils;
|
||
using SFML.Graphics;
|
||
using SFML.System;
|
||
|
||
namespace FluidSim.Core
|
||
{
|
||
public class HelmholtzResonatorScenario : Scenario
|
||
{
|
||
private Solver solver;
|
||
private Volume0D cavity;
|
||
private Pipe1D neck;
|
||
private Connection coupling;
|
||
private int stepCount;
|
||
private double time;
|
||
private double dt;
|
||
private double ambientPressure = 1.0 * Units.atm;
|
||
|
||
public override void Initialize(int sampleRate)
|
||
{
|
||
dt = 1.0 / sampleRate;
|
||
|
||
// 1‑litre cavity, 10% over‑pressure
|
||
double cavityVolume = 1e-3;
|
||
double initialCavityPressure = 1.1 * ambientPressure;
|
||
cavity = new Volume0D(cavityVolume, initialCavityPressure, 300.0, sampleRate)
|
||
{
|
||
Gamma = 1.4,
|
||
GasConstant = 287.0
|
||
};
|
||
|
||
// Neck: length 10 cm, radius 1 cm
|
||
double neckLength = 0.1;
|
||
double neckRadius = 0.01;
|
||
double neckArea = Math.PI * neckRadius * neckRadius;
|
||
neck = new Pipe1D(neckLength, neckArea, sampleRate, forcedCellCount: 40);
|
||
neck.SetUniformState(1.225, 0.0, ambientPressure);
|
||
|
||
coupling = new Connection(neck.PortA, cavity.Port)
|
||
{
|
||
Area = neckArea,
|
||
DischargeCoefficient = 0.62,
|
||
Gamma = 1.4
|
||
};
|
||
|
||
solver = new Solver();
|
||
solver.SetTimeStep(dt);
|
||
solver.AddVolume(cavity);
|
||
solver.AddPipe(neck);
|
||
solver.AddConnection(coupling);
|
||
|
||
// Port A (left) = volume coupling, Port B (right) = open end
|
||
solver.SetPipeBoundary(neck, isA: true, BoundaryType.VolumeCoupling);
|
||
solver.SetPipeBoundary(neck, isA: false, BoundaryType.OpenEnd, ambientPressure);
|
||
}
|
||
|
||
public override float Process()
|
||
{
|
||
float sample = solver.Step();
|
||
time += dt;
|
||
stepCount++;
|
||
|
||
double pOpen = neck.GetCellPressure(neck.GetCellCount() - 1);
|
||
float audio = (float)((pOpen - ambientPressure) / ambientPressure);
|
||
|
||
if (stepCount % 20 == 0)
|
||
{
|
||
double pCav = cavity.Pressure;
|
||
double mdotA = neck.PortA.MassFlowRate; // positive = into pipe (leaving cavity)
|
||
Console.WriteLine(
|
||
$"t={time * 1e3:F2} ms step={stepCount} " +
|
||
$"P_cav={pCav:F1} Pa, P_open={pOpen:F1} Pa, " +
|
||
$"mdot_A={mdotA * 1e3:F4} g/s, audio={audio:F4}");
|
||
}
|
||
|
||
return audio;
|
||
}
|
||
|
||
public override void Draw(RenderWindow target)
|
||
{
|
||
float winW = target.GetView().Size.X;
|
||
float winH = target.GetView().Size.Y;
|
||
float centerY = winH / 2f;
|
||
|
||
// Cavity rectangle
|
||
float cavityWidth = 120f;
|
||
float cavityHeight = 180f;
|
||
var cavityRect = new RectangleShape(new Vector2f(cavityWidth, cavityHeight));
|
||
cavityRect.Position = new Vector2f(40f, centerY - cavityHeight / 2f);
|
||
cavityRect.FillColor = PressureColor(cavity.Pressure);
|
||
target.Draw(cavityRect);
|
||
|
||
// Neck drawn as tapered pipe
|
||
int n = neck.GetCellCount();
|
||
float neckStartX = 40f + cavityWidth + 10f;
|
||
float neckEndX = winW - 60f;
|
||
float neckLenPx = neckEndX - neckStartX;
|
||
float dx = neckLenPx / (n - 1);
|
||
float baseRadius = 20f;
|
||
|
||
Vertex[] vertices = new Vertex[n * 2];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
float x = neckStartX + i * dx;
|
||
double p = neck.GetCellPressure(i);
|
||
float r = baseRadius * (float)(0.5 + 0.5 * Math.Tanh((p - ambientPressure) / (ambientPressure * 0.2)));
|
||
if (r < 4f) r = 4f;
|
||
Color col = PressureColor(p);
|
||
vertices[i * 2] = new Vertex(new Vector2f(x, centerY - r), col);
|
||
vertices[i * 2 + 1] = new Vertex(new Vector2f(x, centerY + r), col);
|
||
}
|
||
target.Draw(vertices, PrimitiveType.TriangleStrip);
|
||
|
||
// Open end indicator
|
||
var arrow = new CircleShape(8f);
|
||
arrow.Position = new Vector2f(neckEndX - 4f, centerY - 4f);
|
||
arrow.FillColor = Color.White;
|
||
target.Draw(arrow);
|
||
}
|
||
|
||
private Color PressureColor(double pressure)
|
||
{
|
||
double range = ambientPressure * 0.1;
|
||
double t = Math.Clamp((pressure - ambientPressure) / range, -1.0, 1.0);
|
||
byte r = (byte)(t > 0 ? 255 * t : 0);
|
||
byte b = (byte)(t < 0 ? -255 * t : 0);
|
||
byte g = (byte)(255 * (1 - Math.Abs(t)));
|
||
return new Color(r, g, b);
|
||
}
|
||
}
|
||
} |