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)); /// Number of samples currently available for reading (integer count). 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; } } }