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