Files
RobustToolbox/Robust.Client/Audio/Sources/BufferedAudioSource.cs

216 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using OpenTK.Audio.OpenAL;
using Robust.Shared.Audio.Sources;
namespace Robust.Client.Audio.Sources;
internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSource
{
private int[] BufferHandles;
private Dictionary<int, int> BufferMap = new();
private readonly AudioManager _master;
private bool _mono = true;
private bool _float = false;
public int SampleRate { get; set; } = 44100;
private bool IsEfxSupported => _master.IsEfxSupported;
public BufferedAudioSource(AudioManager master, int sourceHandle, int[] bufferHandles, bool floatAudio = false) : base(master, sourceHandle)
{
_master = master;
SourceHandle = sourceHandle;
BufferHandles = bufferHandles;
for (int i = 0; i < BufferHandles.Length; i++)
{
var bufferHandle = BufferHandles[i];
BufferMap[bufferHandle] = i;
}
_float = floatAudio;
}
/// <inheritdoc />
public override bool Playing
{
get
{
_checkDisposed();
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
_master._checkAlError();
return state == (int)ALSourceState.Playing;
}
set
{
if (value)
{
_checkDisposed();
// IDK why this stackallocs but gonna leave it for now.
AL.SourcePlay(stackalloc int[] {SourceHandle});
_master._checkAlError();
}
else
{
if (_isDisposed())
return;
AL.SourceStop(SourceHandle);
_master._checkAlError();
}
}
}
~BufferedAudioSource()
{
Dispose(false);
}
protected override void Dispose(bool disposing)
{
if (SourceHandle == -1)
return;
if (!_master.IsMainThread())
{
// We can't run this code inside another thread so tell Clyde to clear it up later.
_master.DeleteBufferedSourceOnMainThread(SourceHandle, FilterHandle);
foreach (var handle in BufferHandles)
{
_master.DeleteAudioBufferOnMainThread(handle);
}
}
else
{
if (FilterHandle != 0)
ALC.EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
AL.DeleteBuffers(BufferHandles);
_master.RemoveBufferedAudioSource(SourceHandle);
_master._checkAlError();
}
FilterHandle = 0;
SourceHandle = -1;
}
public int GetNumberOfBuffersProcessed()
{
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.GetSource(SourceHandle, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
return buffersProcessed;
}
public unsafe void GetBuffersProcessed(Span<int> handles)
{
_checkDisposed();
var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed());
fixed (int* ptr = handles)
{
AL.SourceUnqueueBuffers(SourceHandle, entries, ptr);
}
for (var i = 0; i < entries; i++)
{
handles[i] = BufferMap[handles[i]];
}
}
public unsafe void WriteBuffer(int handle, ReadOnlySpan<ushort> data)
{
_checkDisposed();
if(_float)
throw new InvalidOperationException("Can't write ushort numbers to buffers when buffer type is float!");
if (handle >= BufferHandles.Length)
{
throw new ArgumentOutOfRangeException(nameof(handle),
$"Got {handle}. Expected less than {BufferHandles.Length}");
}
fixed (ushort* ptr = data)
{
AL.BufferData(BufferHandles[handle], _mono ? ALFormat.Mono16 : ALFormat.Stereo16, (IntPtr) ptr,
_mono ? data.Length / 2 * sizeof(ushort) : data.Length * sizeof(ushort), SampleRate);
}
}
public unsafe void WriteBuffer(int handle, ReadOnlySpan<float> data)
{
_checkDisposed();
if(!_float)
throw new InvalidOperationException("Can't write float numbers to buffers when buffer type is ushort!");
if (handle >= BufferHandles.Length)
{
throw new ArgumentOutOfRangeException(nameof(handle),
$"Got {handle}. Expected less than {BufferHandles.Length}");
}
fixed (float* ptr = data)
{
AL.BufferData(BufferHandles[handle], _mono ? ALFormat.MonoFloat32Ext : ALFormat.StereoFloat32Ext, (IntPtr) ptr,
_mono ? data.Length / 2 * sizeof(float) : data.Length * sizeof(float), SampleRate);
}
}
public unsafe void QueueBuffers(ReadOnlySpan<int> handles)
{
_checkDisposed();
Span<int> realHandles = stackalloc int[handles.Length];
handles.CopyTo(realHandles);
for (var i = 0; i < realHandles.Length; i++)
{
var handle = realHandles[i];
if (handle >= BufferHandles.Length)
throw new ArgumentOutOfRangeException(nameof(handles), $"Invalid handle with index {i}!");
realHandles[i] = BufferHandles[handle];
}
fixed (int* ptr = realHandles)
// ReSharper disable once PossibleInvalidOperationException
{
AL.SourceQueueBuffers(SourceHandle, handles.Length, ptr);
}
}
public unsafe void EmptyBuffers()
{
_checkDisposed();
var length = SampleRate / BufferHandles.Length * (_mono ? 1 : 2);
Span<int> handles = stackalloc int[BufferHandles.Length];
if (_float)
{
var empty = new float[length];
var span = (Span<float>) empty;
for (var i = 0; i < BufferHandles.Length; i++)
{
WriteBuffer(BufferMap[BufferHandles[i]], span);
handles[i] = BufferMap[BufferHandles[i]];
}
}
else
{
var empty = new ushort[length];
var span = (Span<ushort>) empty;
for (var i = 0; i < BufferHandles.Length; i++)
{
WriteBuffer(BufferMap[BufferHandles[i]], span);
handles[i] = BufferMap[BufferHandles[i]];
}
}
QueueBuffers(handles);
}
}