engine almost working, backup before adding gas types.

This commit is contained in:
max
2026-05-07 20:07:15 +02:00
parent 14f5ba925f
commit 92d84eacfe
18 changed files with 1236 additions and 587 deletions

View File

@@ -0,0 +1,53 @@
using SFML.Audio;
using SFML.System;
namespace FluidSim.Audio
{
public class AudioOutputStream : SoundStream
{
private readonly SimulationRingBuffer _sourceBuffer;
private double _speed = 1.0; // nonvolatile, accessed with Volatile.Read/Write
public AudioOutputStream(SimulationRingBuffer sourceBuffer)
{
_sourceBuffer = sourceBuffer;
// 2 channels, 44.1 kHz, stereo
Initialize(2, 44100, new[] { SoundChannel.FrontLeft, SoundChannel.FrontRight });
}
/// <summary>Playback speed (0.01 … 1.0 or higher for catchup).</summary>
public double Speed
{
get => Volatile.Read(ref _speed);
set => Volatile.Write(ref _speed, value);
}
protected override bool OnGetData(out short[] samples)
{
const int monoBlockSize = 512;
float[] temp = new float[monoBlockSize];
int read = _sourceBuffer.ReadInterpolated(temp, monoBlockSize, Speed);
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
}
}
// Fill rest with silence
for (int i = read * 2; i < samples.Length; i++)
samples[i] = 0;
return true;
}
protected override void OnSeek(Time timeOffset) =>
throw new NotSupportedException();
}
}

View File

@@ -0,0 +1,98 @@
namespace FluidSim.Audio
{
public class SimulationRingBuffer
{
private readonly float[] _buffer;
private readonly int _capacity;
private int _writeHead; // monotonic, producer only
private int _readHead; // monotonic, consumer advances after consumption
// Consumer interpolation state
private double _readPosFrac;
private bool _consumerInit;
// Events for signalling
private readonly AutoResetEvent _spaceAvailable = new AutoResetEvent(false);
private readonly AutoResetEvent _dataAvailable = new AutoResetEvent(false);
public SimulationRingBuffer(int capacity)
{
if ((capacity & (capacity - 1)) != 0)
throw new ArgumentException("Capacity must be a power of two.");
_capacity = capacity;
_buffer = new float[capacity];
}
// ---------- Producer ----------
public int FreeSpace => _capacity - (_writeHead - Volatile.Read(ref _readHead));
/// <summary>Number of samples currently available for reading (integer count).</summary>
public int AvailableSamples => Volatile.Read(ref _writeHead) - Volatile.Read(ref _readHead);
public void Write(float sample)
{
while (FreeSpace == 0)
_spaceAvailable.WaitOne();
int w = _writeHead;
int mask = _capacity - 1;
_buffer[w & mask] = sample;
Volatile.Write(ref _writeHead, w + 1);
_dataAvailable.Set();
}
public int Write(float[] data, int count)
{
int free = FreeSpace;
int toWrite = Math.Min(count, free);
int w = _writeHead;
int mask = _capacity - 1;
for (int i = 0; i < toWrite; i++)
_buffer[(w + i) & mask] = data[i];
Volatile.Write(ref _writeHead, w + toWrite);
if (toWrite > 0)
_dataAvailable.Set();
return toWrite;
}
// ---------- Consumer ----------
public void ResetConsumer() => _consumerInit = false;
public int ReadInterpolated(float[] dest, int destCount, double speed)
{
if (!_consumerInit)
{
_readPosFrac = Volatile.Read(ref _readHead);
_consumerInit = true;
}
int mask = _capacity - 1;
int writeHead = Volatile.Read(ref _writeHead);
int produced = 0;
for (int i = 0; i < destCount; i++)
{
int idxFloor = (int)_readPosFrac;
int idxCeil = idxFloor + 1;
if (idxCeil >= writeHead)
break;
float y0 = _buffer[idxFloor & mask];
float y1 = _buffer[idxCeil & mask];
double frac = _readPosFrac - idxFloor;
dest[i] = (float)(y0 + (y1 - y0) * frac);
_readPosFrac += speed;
produced++;
}
int newReadHead = (int)_readPosFrac;
if (newReadHead > Volatile.Read(ref _readHead))
{
Volatile.Write(ref _readHead, newReadHead);
_spaceAvailable.Set();
}
return produced;
}
}
}

45
Audio/SoundEngine.cs Normal file
View File

@@ -0,0 +1,45 @@
namespace FluidSim.Audio
{
public class SoundEngine : IDisposable
{
private readonly AudioOutputStream _stream;
private bool _isPlaying;
public SoundEngine(SimulationRingBuffer sourceBuffer, int bufferCapacity = 16384)
{
_stream = new AudioOutputStream(sourceBuffer);
}
public void Start()
{
if (_isPlaying) return;
_stream.Play();
_isPlaying = true;
}
public void Stop()
{
if (!_isPlaying) return;
_stream.Stop();
_isPlaying = false;
}
public double Speed
{
get => _stream.Speed;
set => _stream.Speed = value;
}
public float Volume
{
get => _stream.Volume;
set => _stream.Volume = value;
}
public void Dispose()
{
Stop();
_stream.Dispose();
}
}
}