mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 17:47:24 +02:00
216 lines
6.4 KiB
C#
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);
|
|
}
|
|
}
|