using FluidSim.Audio; using FluidSim.Components; using FluidSim.Interfaces; namespace FluidSim.Core { public class Solver { private readonly List _volumes = new(); private readonly List _pipes = new(); private readonly List _connections = new(); public float LastSample { get; private set; } public void AddVolume(Volume0D v) => _volumes.Add(v); public void AddPipe(Pipe1D p) => _pipes.Add(p); public void AddConnection(Connection c) => _connections.Add(c); public void Step() { // 1. Publish volume states to their own ports foreach (var v in _volumes) v.PushStateToPort(); // 2. Handle direct volume‑to‑volume connections foreach (var conn in _connections) { if (IsVolumePort(conn.PortA) && IsVolumePort(conn.PortB)) { Volume0D volA = _volumes.Find(v => v.Port == conn.PortA); Volume0D volB = _volumes.Find(v => v.Port == conn.PortB); if (volA == null || volB == null) continue; double pA = volA.Pressure, rhoA = volA.Density; double pB = volB.Pressure, rhoB = volB.Density; double mdot = OrificeBoundary.MassFlow(pA, rhoA, pB, rhoB, conn); if (mdot > 0) // A → B { volA.Port.MassFlowRate = -mdot; volB.Port.MassFlowRate = mdot; volB.Port.SpecificEnthalpy = volA.SpecificEnthalpy; // fluid from A volA.Port.SpecificEnthalpy = volA.SpecificEnthalpy; // outflow carries its own enthalpy } else // B → A (mdot negative) { volA.Port.MassFlowRate = -mdot; // positive volB.Port.MassFlowRate = mdot; // negative volA.Port.SpecificEnthalpy = volB.SpecificEnthalpy; // fluid from B volB.Port.SpecificEnthalpy = volB.SpecificEnthalpy; // outflow carries its own } } } // 3. Pipe‑volume boundary conditions – unchanged foreach (var conn in _connections) { if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB)) SetVolumeBC(conn.PortA, conn.PortB); else if (IsVolumePort(conn.PortA) && IsPipePort(conn.PortB)) SetVolumeBC(conn.PortB, conn.PortA); } // 4. Run pipe simulations foreach (var p in _pipes) p.Simulate(); // 5. Transfer pipe‑to‑volume flows – unchanged foreach (var conn in _connections) { if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB)) TransferPipeToVolume(conn.PortA, conn.PortB); else if (IsVolumePort(conn.PortA) && IsPipePort(conn.PortB)) TransferPipeToVolume(conn.PortB, conn.PortA); } // 6. Integrate volumes foreach (var v in _volumes) v.Integrate(); // 7. COMPUTE AUDIO SAMPLE from all sound connections double sample = 0f; foreach (var conn in _connections) { if (conn is SoundConnection) { // Both ports have the same absolute mass flow; either works. sample += SoundProcessor.ComputeSample(conn); } } LastSample = (float)Math.Tanh(sample); } bool IsVolumePort(Port p) => _volumes.Exists(v => v.Port == p); bool IsPipePort(Port p) => _pipes.Exists(pp => pp.PortA == p || pp.PortB == p); Pipe1D GetPipe(Port p) => _pipes.Find(pp => pp.PortA == p || pp.PortB == p); void SetVolumeBC(Port pipePort, Port volPort) { Pipe1D pipe = GetPipe(pipePort); if (pipe == null) return; bool isLeft = pipe.PortA == pipePort; if (isLeft) pipe.SetLeftVolumeState(volPort.Density, volPort.Pressure); else pipe.SetRightVolumeState(volPort.Density, volPort.Pressure); } void TransferPipeToVolume(Port pipePort, Port volPort) { double mdot = pipePort.MassFlowRate; volPort.MassFlowRate = -mdot; if (mdot < 0) // pipe → volume { // pipePort.SpecificEnthalpy is already total (h + ½u²) volPort.SpecificEnthalpy = pipePort.SpecificEnthalpy; } // else: volume → pipe, volume’s own static enthalpy is used (already set) } } }