mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 18:17:09 +02:00
423 lines
9.9 KiB
C#
423 lines
9.9 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using OpenTK.Audio.OpenAL;
|
|
using Robust.Client.Audio.Effects;
|
|
using Robust.Shared.Audio.Effects;
|
|
using Robust.Shared.Audio.Sources;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Maths;
|
|
|
|
namespace Robust.Client.Audio.Sources;
|
|
|
|
public abstract class BaseAudioSource : IAudioSource
|
|
{
|
|
/*
|
|
* This may look weird having all these methods here however
|
|
* we need to handle disposing plus checking for errors hence we get this.
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Handle to the AL source.
|
|
/// </summary>
|
|
protected int SourceHandle;
|
|
|
|
/// <summary>
|
|
/// Source to the EFX filter if applicable.
|
|
/// </summary>
|
|
protected int FilterHandle;
|
|
|
|
internal readonly AudioManager Master;
|
|
|
|
/// <summary>
|
|
/// Prior gain that was set.
|
|
/// </summary>
|
|
private float _gain;
|
|
|
|
private float _occlusion;
|
|
|
|
private bool IsEfxSupported => Master.IsEfxSupported;
|
|
|
|
internal BaseAudioSource(AudioManager master, int sourceHandle)
|
|
{
|
|
Master = master;
|
|
SourceHandle = sourceHandle;
|
|
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Pause()
|
|
{
|
|
AL.SourcePause(SourceHandle);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void StartPlaying()
|
|
{
|
|
if (Playing)
|
|
return;
|
|
|
|
Playing = true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void StopPlaying()
|
|
{
|
|
if (!Playing)
|
|
return;
|
|
|
|
Playing = false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Restart()
|
|
{
|
|
AL.SourceRewind(SourceHandle);
|
|
StartPlaying();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual bool Playing
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
|
|
Master._checkAlError();
|
|
return state == (int)ALSourceState.Playing;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
|
|
if (value)
|
|
{
|
|
AL.SourcePlay(SourceHandle);
|
|
}
|
|
else
|
|
{
|
|
AL.SourceStop(SourceHandle);
|
|
}
|
|
|
|
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Looping
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
|
|
Master._checkAlError();
|
|
return ret;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourceb.Looping, value);
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Global
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourceb.SourceRelative, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourceb.SourceRelative, value);
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual Vector2 Position
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSource3f.Position, out var x, out var y, out _);
|
|
Master._checkAlError();
|
|
return new Vector2(x, y);
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
|
|
var (x, y) = value;
|
|
|
|
if (!AreFinite(x, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float Pitch
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourcef.Pitch, value);
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float Volume
|
|
{
|
|
get
|
|
{
|
|
var gain = Gain;
|
|
var volume = SharedAudioSystem.GainToVolume(gain);
|
|
return volume;
|
|
}
|
|
set => Gain = SharedAudioSystem.VolumeToGain(value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float Gain
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.Gain, out var gain);
|
|
Master._checkAlError();
|
|
return gain;
|
|
}
|
|
set
|
|
{
|
|
if (float.IsNaN(value))
|
|
{
|
|
Master.LogError($"Tried to set NaN gain, setting audio source to 0f: {Environment.StackTrace}");
|
|
value = 0f;
|
|
}
|
|
|
|
_checkDisposed();
|
|
var priorOcclusion = 1f;
|
|
if (!IsEfxSupported)
|
|
{
|
|
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
|
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
|
|
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
|
|
}
|
|
|
|
_gain = value;
|
|
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
|
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float MaxDistance
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
|
|
Master.LogALError($"MaxDistance is {value:0.00}");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float RolloffFactor
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.RolloffFactor, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
|
|
Master.LogALError($"RolloffFactor is {value:0.00}");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float ReferenceDistance
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.ReferenceDistance, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
|
|
Master.LogALError($"ReferenceDistance is {value:0.00}");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float Occlusion
|
|
{
|
|
get => _occlusion;
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
var cutoff = MathF.Exp(-value * 1);
|
|
var gain = MathF.Pow(cutoff, 0.1f);
|
|
if (IsEfxSupported)
|
|
{
|
|
SetOcclusionEfx(gain, cutoff);
|
|
}
|
|
else
|
|
{
|
|
gain *= gain * gain;
|
|
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
|
}
|
|
|
|
_occlusion = value;
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float PlaybackPosition
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
AL.GetSource(SourceHandle, ALSourcef.SecOffset, out var value);
|
|
Master._checkAlError();
|
|
return value;
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
|
|
value = MathF.Max(value, 0f);
|
|
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
|
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Vector2 Velocity
|
|
{
|
|
get
|
|
{
|
|
_checkDisposed();
|
|
|
|
AL.GetSource(SourceHandle, ALSource3f.Velocity, out var x, out var y, out _);
|
|
Master._checkAlError();
|
|
return new Vector2(x, y);
|
|
}
|
|
set
|
|
{
|
|
_checkDisposed();
|
|
|
|
var (x, y) = value;
|
|
|
|
if (!AreFinite(x, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
|
|
Master._checkAlError();
|
|
}
|
|
}
|
|
|
|
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
|
|
{
|
|
_checkDisposed();
|
|
if (!IsEfxSupported)
|
|
return;
|
|
|
|
if (audio is AuxiliaryAudio impAudio)
|
|
{
|
|
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
|
}
|
|
|
|
Master._checkAlError();
|
|
}
|
|
|
|
private void SetOcclusionEfx(float gain, float cutoff)
|
|
{
|
|
if (FilterHandle == 0)
|
|
{
|
|
FilterHandle = ALC.EFX.GenFilter();
|
|
ALC.EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
|
}
|
|
|
|
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
|
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
|
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
|
|
}
|
|
|
|
protected static bool AreFinite(float x, float y)
|
|
{
|
|
if (float.IsFinite(x) && float.IsFinite(y))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
~BaseAudioSource()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected abstract void Dispose(bool disposing);
|
|
|
|
protected bool _isDisposed()
|
|
{
|
|
return SourceHandle == -1;
|
|
}
|
|
|
|
protected void _checkDisposed()
|
|
{
|
|
if (SourceHandle == -1)
|
|
{
|
|
throw new ObjectDisposedException(nameof(BaseAudioSource));
|
|
}
|
|
}
|
|
}
|