namespace ytLive.Services.Audio; /// /// A thread-safe float ring buffer for the live audio path (TASK 9): NAudio /// raises capture chunks on its own threads, the mixer's live loop drains on /// its own. Writes overwrite the OLDEST samples when full so the buffer never /// grows unbounded — the stream can't stall on capture jitter, it just skips /// the stale tail. /// public sealed class AudioRingBuffer { private readonly object _sync = new(); private readonly float[] _buffer; private int _head; private int _count; public AudioRingBuffer(int capacity) { if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); _buffer = new float[capacity]; } public int Count { get { lock (_sync) { return _count; } } } /// Appends a chunk, dropping the oldest samples if the buffer is full. public void Write(float[] samples) { if (samples.Length == 0) return; lock (_sync) { if (samples.Length >= _buffer.Length) { Array.Copy(samples, samples.Length - _buffer.Length, _buffer, 0, _buffer.Length); _head = 0; _count = _buffer.Length; return; } if (_count + samples.Length > _buffer.Length) { // Evict the oldest samples to make room; the incoming write then // overwrites them as it wraps. Head stays put — only writing // advances it. var drop = _count + samples.Length - _buffer.Length; _count -= drop; } var writeAt = _head; var first = Math.Min(samples.Length, _buffer.Length - writeAt); Array.Copy(samples, 0, _buffer, writeAt, first); if (first < samples.Length) Array.Copy(samples, first, _buffer, 0, samples.Length - first); _head = (writeAt + samples.Length) % _buffer.Length; _count += samples.Length; } } /// Reads up to samples, removing them; /// returns the number actually read (fewer on underrun). public int Read(float[] destination, int count) { lock (_sync) { var n = Math.Min(count, _count); var tail = (_head - _count + _buffer.Length) % _buffer.Length; var first = Math.Min(n, _buffer.Length - tail); Array.Copy(_buffer, tail, destination, 0, first); if (first < n) Array.Copy(_buffer, 0, destination, first, n - first); _count -= n; return n; } } public void Clear() { lock (_sync) { _head = 0; _count = 0; } } }