using System; using FluidSim.Components; using SFML.Graphics; using SFML.System; namespace FluidSim.Core { public class EngineScenario : Scenario { private Solver solver; private Crankshaft crankshaft; private EngineCylinder engineCyl; private Pipe1D exhaustPipe; private PipeVolumeConnection coupling; private SoundProcessor soundProcessor; private double dt; private double ambientPressure = 101325.0; private double time; private int stepCount = 0; private const int LogInterval = 10000; // Throttle 0..1 → target combustion pressure public double Throttle { get; set; } = 0.05; // tiny throttle to keep idle private const double IdlePeakPressure = 5.0 * 101325.0; // 5 bar private const double MaxPeakPressure = 50.0 * 101325.0; // 50 bar public override void Initialize(int sampleRate) { dt = 1.0 / sampleRate; // Crankshaft (inertia + friction) crankshaft = new Crankshaft(initialRPM: 100.0) // starter speed { Inertia = 0.05, FrictionConstant = 1.0, FrictionViscous = 0.01 }; // Pipe double pipeLength = 0.5; double pipeRadius = 0.1; double pipeArea = Math.PI * pipeRadius * pipeRadius; exhaustPipe = new Pipe1D(pipeLength, pipeArea, sampleRate, forcedCellCount: 60); exhaustPipe.SetUniformState(1.225, 0.0, ambientPressure); exhaustPipe.EnergyRelaxationRate = 0f; exhaustPipe.DampingMultiplier = 0; // Cylinder (coupled to crankshaft) engineCyl = new EngineCylinder(crankshaft, bore: 0.065, stroke: 0.0565, compressionRatio: 10.0, pipeArea: pipeArea, sampleRate: sampleRate); // Coupling (valve → pipe) coupling = new PipeVolumeConnection(engineCyl.Cylinder, exhaustPipe, true, orificeArea: 0.0); // Solver solver = new Solver(); solver.SetTimeStep(dt); solver.AddVolume(engineCyl.Cylinder); solver.AddPipe(exhaustPipe); solver.AddConnection(coupling); solver.SetPipeBoundary(exhaustPipe, false, BoundaryType.OpenEnd, ambientPressure); // Sound (your tuned gains) soundProcessor = new SoundProcessor(sampleRate, pipeRadius * 2, reverbTimeMs: 500.0f); soundProcessor.MasterGain = 0.0f; //0.00001f; soundProcessor.PressureGain = 0.1f; soundProcessor.TurbulenceGain = 0.0f; soundProcessor.Turbulence = 0.001f; soundProcessor.SetAmbientPressure(ambientPressure); Console.WriteLine("=== EngineScenario (torque‑driven RPM, throttle = pressure) ==="); Console.WriteLine($"Crankshaft inertia: {crankshaft.Inertia}, friction: {crankshaft.FrictionConstant} + {crankshaft.FrictionViscous}*ω"); Console.WriteLine($"Throttle range: {IdlePeakPressure/101325:F0} – {MaxPeakPressure/101325:F0} bar"); Console.WriteLine($"Pipe: {pipeLength} m, fundamental: {340/(4*pipeLength):F1} Hz"); } public override float Process() { // 1. Map throttle to target peak pressure double targetPressure = IdlePeakPressure + Throttle * (MaxPeakPressure - IdlePeakPressure); engineCyl.TargetPeakPressure = targetPressure; // 2. Step the cylinder (adds torque to crankshaft, updates valve) engineCyl.Step(dt); // 3. Integrate crankshaft (applies friction, updates RPM) crankshaft.Step(dt); // 4. Set orifice area for coupling coupling.OrificeArea = engineCyl.OrificeArea; // 5. Fluid solver step float massFlow = solver.Step(); float endPressure = (float)exhaustPipe.GetCellPressure(exhaustPipe.GetCellCount() - 1); // 6. Audio float audioSample = soundProcessor.Process(massFlow, endPressure); time += dt; stepCount++; if (stepCount % LogInterval == 0) { Console.WriteLine(audioSample); } if (stepCount % 1000 == 0 && false) { Console.WriteLine($"{time,5:F3} {crankshaft.AngularVelocity*60/(2*Math.PI),5:F0} RPM " + $"Thr:{Throttle:F2} P_target:{targetPressure/101325:F1} bar " + $"mflow:{massFlow,14:E4} Comb#{engineCyl.CombustionCount} Mis#{engineCyl.MisfireCount}"); } return audioSample; } // ---- Drawing (unchanged) ---- public override void Draw(RenderWindow target) { float winW = target.GetView().Size.X; float winH = target.GetView().Size.Y; float centerY = winH / 2f; const float T_ambient = 293.15f; const float T_hot = 1500f; const float T_cold = 0f; const float R = 287.05f; float deltaHot = T_hot - T_ambient; float deltaCold = T_ambient - T_cold; float NormaliseTemperature(double T) { double t; if (T >= T_ambient) t = (T - T_ambient) / deltaHot; else t = (T - T_ambient) / deltaCold; return (float)Math.Clamp(t, -1.0, 1.0); } float cylW = 80f, cylH = 150f; var cylRect = new RectangleShape(new Vector2f(cylW, cylH)); cylRect.Position = new Vector2f(40f, centerY - cylH / 2f); double tempCyl = engineCyl.Cylinder.Temperature; float tnCyl = NormaliseTemperature(tempCyl); byte rC = (byte)(tnCyl > 0 ? 255 * tnCyl : 0); byte bC = (byte)(tnCyl < 0 ? -255 * tnCyl : 0); byte gC = (byte)(255 * (1 - Math.Abs(tnCyl))); cylRect.FillColor = new Color(rC, gC, bC); target.Draw(cylRect); int n = exhaustPipe.GetCellCount(); float pipeStartX = 120f, pipeEndX = winW - 60f; float pipeLen = pipeEndX - pipeStartX; float dx = pipeLen / (n - 1); float baseRadius = 20f; var vertices = new Vertex[n * 2]; float ambPress = 101325f; for (int i = 0; i < n; i++) { float x = pipeStartX + i * dx; double p = exhaustPipe.GetCellPressure(i); double rho = exhaustPipe.GetCellDensity(i); double T = p / (rho * R); float r = baseRadius * 0.3f * (float)(1.0 + (p - ambPress) / ambPress); if (r < 2f) r = 2f; float tn = NormaliseTemperature(T); byte rCol = (byte)(tn > 0 ? 255 * tn : 0); byte bCol = (byte)(tn < 0 ? -255 * tn : 0); byte gCol = (byte)(255 * (1 - Math.Abs(tn))); var col = new Color(rCol, gCol, bCol); 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); } } }