96 lines
2.9 KiB
C#
96 lines
2.9 KiB
C#
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Appends a chunk, dropping the oldest samples if the buffer is full.</summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>Reads up to <paramref name="count"/> samples, removing them;
|
|
/// returns the number actually read (fewer on underrun).</summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|