Attempting to generate sound, and set cells automatically
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Components;
|
||||
|
||||
namespace FluidSim.Core
|
||||
{
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Utils;
|
||||
|
||||
namespace FluidSim.Core
|
||||
{
|
||||
public static class Simulation
|
||||
{
|
||||
private static Solver solver;
|
||||
private static Volume0D volA, volB;
|
||||
private static Pipe1D pipe;
|
||||
private static Connection connA, connB;
|
||||
private static int stepCount;
|
||||
private static double time;
|
||||
private static double dt;
|
||||
|
||||
public static void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
double V = 5.0 * Units.L;
|
||||
volA = new Volume0D(V, 1.1 * Units.atm, Units.Celsius(20), sampleRate);
|
||||
volB = new Volume0D(V, 1.0 * Units.atm, Units.Celsius(20), sampleRate);
|
||||
|
||||
double length = 150 * Units.mm;
|
||||
double diameter = 25 * Units.mm;
|
||||
double area = Units.AreaFromDiameter(25, Units.mm);
|
||||
int nCells = 10;
|
||||
|
||||
pipe = new Pipe1D(length, area, nCells, sampleRate);
|
||||
pipe.SetUniformState(volA.Density, 0.0, volA.Pressure);
|
||||
pipe.FrictionFactor = 0.02;
|
||||
|
||||
// Connections with orifice area equal to pipe area (flange joint)
|
||||
connA = new Connection(volA.Port, pipe.PortA) { Area = area, DischargeCoefficient = 1.0, Gamma = 1.4 };
|
||||
connB = new Connection(pipe.PortB, volB.Port) { Area = area, DischargeCoefficient = 1.0, Gamma = 1.4 };
|
||||
|
||||
solver = new Solver();
|
||||
solver.AddVolume(volA);
|
||||
solver.AddVolume(volB);
|
||||
solver.AddPipe(pipe);
|
||||
solver.AddConnection(connA);
|
||||
solver.AddConnection(connB);
|
||||
}
|
||||
|
||||
public static float Process()
|
||||
{
|
||||
solver.Step();
|
||||
time += dt;
|
||||
stepCount++;
|
||||
Log();
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public static void Log()
|
||||
{
|
||||
if (stepCount <= 50 || stepCount % 200 == 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"t = {time * 1e3:F3} ms Step {stepCount:D4}: " +
|
||||
$"PA = {volA.Pressure / 1e5:F6} bar, " +
|
||||
$"PB = {volB.Pressure / 1e5:F6} bar, " +
|
||||
$"FlowA = {pipe.PortA.MassFlowRate * 1e3:F2} g/s");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using FluidSim.Audio;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Interfaces;
|
||||
|
||||
@@ -10,17 +10,51 @@ namespace FluidSim.Core
|
||||
private readonly List<Pipe1D> _pipes = new();
|
||||
private readonly List<Connection> _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. Volumes publish state to their ports
|
||||
// 1. Publish volume states to their own ports
|
||||
foreach (var v in _volumes)
|
||||
v.PushStateToPort();
|
||||
|
||||
// 2. Set volume states as boundary conditions on pipes
|
||||
// 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))
|
||||
@@ -29,11 +63,11 @@ namespace FluidSim.Core
|
||||
SetVolumeBC(conn.PortB, conn.PortA);
|
||||
}
|
||||
|
||||
// 3. Run pipe simulations
|
||||
// 4. Run pipe simulations
|
||||
foreach (var p in _pipes)
|
||||
p.Simulate();
|
||||
|
||||
// 4. Transfer pipe‑port flows to volume ports
|
||||
// 5. Transfer pipe‑to‑volume flows – unchanged
|
||||
foreach (var conn in _connections)
|
||||
{
|
||||
if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB))
|
||||
@@ -42,9 +76,21 @@ namespace FluidSim.Core
|
||||
TransferPipeToVolume(conn.PortB, conn.PortA);
|
||||
}
|
||||
|
||||
// 5. Integrate volumes
|
||||
// 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);
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
using SFML.Audio;
|
||||
using SFML.System;
|
||||
|
||||
namespace FluidSim;
|
||||
|
||||
#region Lock‑free ring buffer (unchanged)
|
||||
internal class RingBuffer
|
||||
{
|
||||
private readonly float[] buffer;
|
||||
private volatile int readPos;
|
||||
private volatile int writePos;
|
||||
|
||||
public RingBuffer(int capacity)
|
||||
{
|
||||
if ((capacity & (capacity - 1)) != 0)
|
||||
throw new ArgumentException("Capacity must be a power of two.");
|
||||
buffer = new float[capacity];
|
||||
}
|
||||
|
||||
public int Count => (writePos - readPos) & (buffer.Length - 1);
|
||||
public int Space => (readPos - writePos - 1) & (buffer.Length - 1);
|
||||
|
||||
public int Write(float[] data, int count)
|
||||
{
|
||||
int space = Space;
|
||||
int toWrite = Math.Min(count, space);
|
||||
int mask = buffer.Length - 1;
|
||||
for (int i = 0; i < toWrite; i++)
|
||||
buffer[(writePos + i) & mask] = data[i];
|
||||
writePos = (writePos + toWrite) & mask;
|
||||
return toWrite;
|
||||
}
|
||||
|
||||
public int Read(float[] destination, int count)
|
||||
{
|
||||
int available = Count;
|
||||
int toRead = Math.Min(count, available);
|
||||
int mask = buffer.Length - 1;
|
||||
for (int i = 0; i < toRead; i++)
|
||||
destination[i] = buffer[(readPos + i) & mask];
|
||||
readPos = (readPos + toRead) & mask;
|
||||
return toRead;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Stereo stream that consumes the ring buffer
|
||||
internal class RingBufferStream : SoundStream
|
||||
{
|
||||
private readonly RingBuffer ringBuffer;
|
||||
|
||||
public RingBufferStream(RingBuffer buffer)
|
||||
{
|
||||
ringBuffer = buffer;
|
||||
// 2 channels, 44.1 kHz, standard stereo mapping
|
||||
Initialize(2, 44100, new[] { SoundChannel.FrontLeft, SoundChannel.FrontRight });
|
||||
}
|
||||
|
||||
protected override bool OnGetData(out short[] samples)
|
||||
{
|
||||
const int monoBlockSize = 512; // number of mono samples we'll read
|
||||
float[] temp = new float[monoBlockSize];
|
||||
int read = ringBuffer.Read(temp, monoBlockSize);
|
||||
samples = new short[monoBlockSize * 2];
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
for (int i = 0; i < read; i++)
|
||||
{
|
||||
float clamped = Math.Clamp(temp[i], -1f, 1f);
|
||||
short final = (short)(clamped * short.MaxValue);
|
||||
samples[i * 2] = final; // left
|
||||
samples[i * 2 + 1] = final; // right
|
||||
}
|
||||
}
|
||||
for (int i = read * 2; i < samples.Length; i++)
|
||||
samples[i] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnSeek(Time timeOffset) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public sound engine API (unchanged)
|
||||
public class SoundEngine : IDisposable
|
||||
{
|
||||
private readonly RingBuffer ringBuffer;
|
||||
private readonly RingBufferStream stream;
|
||||
private bool isPlaying;
|
||||
|
||||
public SoundEngine(int bufferCapacity = 16384)
|
||||
{
|
||||
ringBuffer = new RingBuffer(bufferCapacity);
|
||||
stream = new RingBufferStream(ringBuffer);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (isPlaying) return;
|
||||
stream.Play();
|
||||
isPlaying = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!isPlaying) return;
|
||||
stream.Stop();
|
||||
isPlaying = false;
|
||||
float[] drain = new float[ringBuffer.Count];
|
||||
ringBuffer.Read(drain, drain.Length);
|
||||
}
|
||||
|
||||
public int WriteSamples(float[] data, int count) =>
|
||||
ringBuffer.Write(data, count);
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => stream.Volume;
|
||||
set => stream.Volume = value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
Reference in New Issue
Block a user