mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Revert audio rework (#4554)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
- type: entity
|
||||
id: Audio
|
||||
name: Audio
|
||||
description: Audio entity used by engine
|
||||
save: false
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
@@ -39,12 +37,7 @@ namespace Robust.Client.Animations
|
||||
|
||||
var keyFrame = KeyFrames[keyFrameIndex];
|
||||
|
||||
var audioParams = keyFrame.AudioParamsFunc.Invoke();
|
||||
var audio = new SoundPathSpecifier(keyFrame.Resource)
|
||||
{
|
||||
Params = audioParams
|
||||
};
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(audio, Filter.Local(), entity, true);
|
||||
SoundSystem.Play(keyFrame.Resource, Filter.Local(), entity, keyFrame.AudioParamsFunc.Invoke());
|
||||
}
|
||||
|
||||
return (keyFrameIndex, playingTime);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
internal partial class AudioManager
|
||||
{
|
||||
// Used to track audio sources that were disposed in the finalizer thread,
|
||||
// so we need to properly send them off in the main thread.
|
||||
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _sourceDisposeQueue = new();
|
||||
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new();
|
||||
private readonly ConcurrentQueue<int> _bufferDisposeQueue = new();
|
||||
|
||||
public void FlushALDisposeQueues()
|
||||
{
|
||||
// Clear out finalized audio sources.
|
||||
while (_sourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
OpenALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
_audioSources.Remove(handles.sourceHandle);
|
||||
}
|
||||
|
||||
// Clear out finalized buffered audio sources.
|
||||
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
OpenALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
_bufferedAudioSources.Remove(handles.sourceHandle);
|
||||
}
|
||||
|
||||
// Clear out finalized audio buffers.
|
||||
while (_bufferDisposeQueue.TryDequeue(out var handle))
|
||||
{
|
||||
AL.DeleteBuffer(handle);
|
||||
_checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
internal void DeleteSourceOnMainThread(int sourceHandle, int filterHandle)
|
||||
{
|
||||
_sourceDisposeQueue.Enqueue((sourceHandle, filterHandle));
|
||||
}
|
||||
|
||||
internal void DeleteBufferedSourceOnMainThread(int bufferedSourceHandle, int filterHandle)
|
||||
{
|
||||
_bufferedSourceDisposeQueue.Enqueue((bufferedSourceHandle, filterHandle));
|
||||
}
|
||||
|
||||
internal void DeleteAudioBufferOnMainThread(int bufferHandle)
|
||||
{
|
||||
_bufferDisposeQueue.Enqueue(bufferHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
internal partial class AudioManager
|
||||
{
|
||||
private float _zOffset;
|
||||
|
||||
public void SetZOffset(float offset)
|
||||
{
|
||||
_zOffset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance)
|
||||
{
|
||||
switch (_attenuation)
|
||||
{
|
||||
case Attenuation.LinearDistance:
|
||||
return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance);
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
distance = MathF.Max(referenceDistance, MathF.Min(distance, maxDistance));
|
||||
return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance);
|
||||
default:
|
||||
// TODO: If you see this you can implement
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializePostWindowing()
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
InitializeAudio();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
DisposeAllAudio();
|
||||
|
||||
if (_openALContext != ALContext.Null)
|
||||
{
|
||||
ALC.MakeContextCurrent(ALContext.Null);
|
||||
|
||||
ALC.DestroyContext(_openALContext);
|
||||
}
|
||||
|
||||
if (_openALDevice != IntPtr.Zero)
|
||||
{
|
||||
ALC.CloseDevice(_openALDevice);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
AL.Listener(ALListener3f.Position, position.X, position.Y, _zOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetRotation(Angle angle)
|
||||
{
|
||||
var vec = angle.ToVec();
|
||||
|
||||
// Default orientation: at: (0, 0, -1) up: (0, 1, 0)
|
||||
var at = new OpenTK.Mathematics.Vector3(0f, 0f, -1f);
|
||||
var up = new OpenTK.Mathematics.Vector3(vec.Y, vec.X, 0f);
|
||||
AL.Listener(ALListenerfv.Orientation, new []{0, 0, -1, vec.X, vec.Y, 0});
|
||||
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
var vorbis = _readOggVorbis(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
ALFormat format;
|
||||
// NVorbis only supports loading into floats.
|
||||
// If this becomes a problem due to missing extension support (doubt it but ok),
|
||||
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
|
||||
if (vorbis.Channels == 1)
|
||||
{
|
||||
format = ALFormat.MonoFloat32Ext;
|
||||
}
|
||||
else if (vorbis.Channels == 2)
|
||||
{
|
||||
format = ALFormat.StereoFloat32Ext;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = vorbis.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
|
||||
(int) vorbis.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
var wav = _readWav(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
ALFormat format;
|
||||
if (wav.BitsPerSample == 16)
|
||||
{
|
||||
if (wav.NumChannels == 1)
|
||||
{
|
||||
format = ALFormat.Mono16;
|
||||
}
|
||||
else if (wav.NumChannels == 2)
|
||||
{
|
||||
format = ALFormat.Stereo16;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
}
|
||||
else if (wav.BitsPerSample == 8)
|
||||
{
|
||||
if (wav.NumChannels == 1)
|
||||
{
|
||||
format = ALFormat.Mono8;
|
||||
}
|
||||
else if (wav.NumChannels == 2)
|
||||
{
|
||||
format = ALFormat.Stereo8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load wav with bits per sample different from 8 or 16");
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = wav.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, wav.Data.Length, wav.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
1 => ALFormat.Mono16,
|
||||
2 => ALFormat.Stereo16,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(channels), "Only stereo and mono is currently supported")
|
||||
};
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
_checkAlError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
}
|
||||
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
{
|
||||
switch (attenuation)
|
||||
{
|
||||
case Attenuation.NoAttenuation:
|
||||
AL.DistanceModel(ALDistanceModel.None);
|
||||
break;
|
||||
case Attenuation.InverseDistance:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistance);
|
||||
break;
|
||||
case Attenuation.InverseDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistanceClamped);
|
||||
break;
|
||||
case Attenuation.LinearDistance:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistance);
|
||||
break;
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
|
||||
break;
|
||||
case Attenuation.ExponentDistance:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistance);
|
||||
break;
|
||||
case Attenuation.ExponentDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!");
|
||||
}
|
||||
|
||||
_attenuation = attenuation;
|
||||
OpenALSawmill.Info($"Set audio attenuation to {attenuation.ToString()}");
|
||||
}
|
||||
|
||||
internal void RemoveAudioSource(int handle)
|
||||
{
|
||||
_audioSources.Remove(handle);
|
||||
}
|
||||
|
||||
internal void RemoveBufferedAudioSource(int handle)
|
||||
{
|
||||
_bufferedAudioSources.Remove(handle);
|
||||
}
|
||||
|
||||
public IAudioSource? CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
// TODO: This really shouldn't be indexing based on the ClydeHandle...
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle);
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
|
||||
_bufferedAudioSources.Add(source, new WeakReference<BufferedAudioSource>(audioSource));
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
foreach (var source in _audioSources.Values)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var source in _bufferedAudioSources.Values)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DisposeAllAudio()
|
||||
{
|
||||
// TODO: Do we even need to stop?
|
||||
foreach (var source in _audioSources.Values)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_audioSources.Clear();
|
||||
|
||||
foreach (var source in _bufferedAudioSources.Values)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_bufferedAudioSources.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
internal sealed partial class AudioManager : SharedAudioManager, IAudioInternal
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ALDevice _openALDevice;
|
||||
private ALContext _openALContext;
|
||||
|
||||
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BufferedAudioSource>> _bufferedAudioSources =
|
||||
new();
|
||||
|
||||
private readonly HashSet<string> _alcDeviceExtensions = new();
|
||||
private readonly HashSet<string> _alContextExtensions = new();
|
||||
private Attenuation _attenuation;
|
||||
|
||||
public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension);
|
||||
public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension);
|
||||
|
||||
internal bool IsEfxSupported;
|
||||
|
||||
internal ISawmill OpenALSawmill = default!;
|
||||
|
||||
private void _audioCreateContext()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_openALContext = ALC.CreateContext(_openALDevice, (int*) 0);
|
||||
}
|
||||
|
||||
ALC.MakeContextCurrent(_openALContext);
|
||||
_checkAlcError(_openALDevice);
|
||||
_checkAlError();
|
||||
|
||||
// Load up AL context extensions.
|
||||
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' '))
|
||||
{
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
OpenALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
OpenALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
OpenALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
}
|
||||
|
||||
private bool _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = _cfg.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
{
|
||||
_openALDevice = ALC.OpenDevice(preferredDevice);
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
OpenALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
preferredDevice, ALC.GetError(ALDevice.Null));
|
||||
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
}
|
||||
|
||||
_checkAlcError(_openALDevice);
|
||||
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
OpenALSawmill.Error("Unable to open OpenAL device! {1}", ALC.GetError(ALDevice.Null));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load up ALC extensions.
|
||||
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' '))
|
||||
{
|
||||
_alcDeviceExtensions.Add(extension);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitializeAudio()
|
||||
{
|
||||
OpenALSawmill = _logMan.GetSawmill("clyde.oal");
|
||||
|
||||
if (!_audioOpenDevice())
|
||||
return;
|
||||
|
||||
// Create OpenAL context.
|
||||
_audioCreateContext();
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
{
|
||||
return Thread.CurrentThread == _gameThread;
|
||||
}
|
||||
|
||||
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
|
||||
{
|
||||
if (handles.filterHandle != 0)
|
||||
EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
private void _checkAlcError(ALDevice device,
|
||||
[CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = ALC.GetError(device);
|
||||
if (error != AlcError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LoadedAudioSample
|
||||
{
|
||||
public readonly int BufferHandle;
|
||||
|
||||
public LoadedAudioSample(int bufferHandle)
|
||||
{
|
||||
BufferHandle = bufferHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Debug overlay for audio.
|
||||
/// </summary>
|
||||
public sealed class AudioOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
private IEntityManager _entManager;
|
||||
private IPlayerManager _playerManager;
|
||||
private AudioSystem _audio;
|
||||
private SharedTransformSystem _transform;
|
||||
|
||||
private Font _font;
|
||||
|
||||
public AudioOverlay(IEntityManager entManager, IPlayerManager playerManager, IClientResourceCache cache, AudioSystem audio, SharedTransformSystem transform)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_playerManager = playerManager;
|
||||
_audio = audio;
|
||||
_transform = transform;
|
||||
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (args.ViewportControl == null || localPlayer == null)
|
||||
return;
|
||||
|
||||
var screenHandle = args.ScreenHandle;
|
||||
var output = new StringBuilder();
|
||||
var listenerPos = _entManager.GetComponent<TransformComponent>(localPlayer.Value).MapPosition;
|
||||
|
||||
if (listenerPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var query = _entManager.AllEntityQueryEnumerator<AudioComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
var mapId = MapId.Nullspace;
|
||||
var audioPos = Vector2.Zero;
|
||||
|
||||
if (_entManager.TryGetComponent<TransformComponent>(uid, out var xform))
|
||||
{
|
||||
mapId = xform.MapID;
|
||||
audioPos = _transform.GetWorldPosition(uid);
|
||||
}
|
||||
|
||||
if (mapId != args.MapId)
|
||||
continue;
|
||||
|
||||
var screenPos = args.ViewportControl.WorldToScreen(audioPos);
|
||||
var distance = audioPos - listenerPos.Position;
|
||||
var posOcclusion = _audio.GetOcclusion(uid, listenerPos, distance, distance.Length());
|
||||
|
||||
output.Clear();
|
||||
output.AppendLine("Audio Source");
|
||||
output.AppendLine("Runtime:");
|
||||
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
|
||||
output.AppendLine("Params:");
|
||||
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
|
||||
output.AppendLine($"- Reference distance: {comp.ReferenceDistance}");
|
||||
output.AppendLine($"- Max distance: {comp.MaxDistance}");
|
||||
var outputText = output.ToString().Trim();
|
||||
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
|
||||
var buffer = new Vector2(3f, 3f);
|
||||
screenHandle.DrawRect(new UIBox2(screenPos - buffer, screenPos + dimensions + buffer), new Color(39, 39, 48));
|
||||
screenHandle.DrawString(_font, screenPos, outputText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
using System;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
|
||||
/// </summary>
|
||||
public sealed class AudioStream
|
||||
{
|
||||
public TimeSpan Length { get; }
|
||||
internal IClydeHandle? ClydeHandle { get; }
|
||||
internal ClydeHandle? ClydeHandle { get; }
|
||||
public string? Name { get; }
|
||||
public string? Title { get; }
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
{
|
||||
ClydeHandle = handle;
|
||||
Length = length;
|
||||
@@ -1,76 +0,0 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
public sealed partial class AudioSystem
|
||||
{
|
||||
protected override void InitializeEffect()
|
||||
{
|
||||
base.InitializeEffect();
|
||||
SubscribeLocalEvent<AudioEffectComponent, ComponentAdd>(OnEffectAdd);
|
||||
SubscribeLocalEvent<AudioEffectComponent, ComponentShutdown>(OnEffectShutdown);
|
||||
|
||||
SubscribeLocalEvent<AudioAuxiliaryComponent, ComponentAdd>(OnAuxiliaryAdd);
|
||||
SubscribeLocalEvent<AudioAuxiliaryComponent, AfterAutoHandleStateEvent>(OnAuxiliaryAuto);
|
||||
}
|
||||
|
||||
private void OnEffectAdd(EntityUid uid, AudioEffectComponent component, ComponentAdd args)
|
||||
{
|
||||
var effect = new AudioEffect(_audio);
|
||||
component.Effect = effect;
|
||||
}
|
||||
|
||||
private void OnEffectShutdown(EntityUid uid, AudioEffectComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.Effect is AudioEffect effect)
|
||||
{
|
||||
effect.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAuxiliaryAdd(EntityUid uid, AudioAuxiliaryComponent component, ComponentAdd args)
|
||||
{
|
||||
component.Auxiliary = new AuxiliaryAudio();
|
||||
}
|
||||
|
||||
private void OnAuxiliaryAuto(EntityUid uid, AudioAuxiliaryComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (TryComp<AudioEffectComponent>(component.Effect, out var effectComp))
|
||||
{
|
||||
component.Auxiliary.SetEffect(effectComp.Effect);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Auxiliary.SetEffect(null);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetAuxiliary(EntityUid uid, AudioComponent audio, EntityUid? auxUid)
|
||||
{
|
||||
base.SetAuxiliary(uid, audio, auxUid);
|
||||
if (TryComp<AudioAuxiliaryComponent>(audio.Auxiliary, out var auxComp))
|
||||
{
|
||||
audio.Source.SetAuxiliary(auxComp.Auxiliary);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Source.SetAuxiliary(null);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetEffect(EntityUid auxUid, AudioAuxiliaryComponent aux, EntityUid? effectUid)
|
||||
{
|
||||
base.SetEffect(auxUid, aux, effectUid);
|
||||
if (TryComp<AudioEffectComponent>(aux.Effect, out var effectComp))
|
||||
{
|
||||
aux.Auxiliary.SetEffect(effectComp.Effect);
|
||||
}
|
||||
else
|
||||
{
|
||||
aux.Auxiliary.SetEffect(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.ResourceManagement.ResourceTypes;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
/*
|
||||
* There's still a lot more OpenAL can do in terms of filters, auxiliary slots, etc.
|
||||
* but exposing the whole thing in an easy way is a lot of effort.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IAudioInternal _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tick cache of relevant streams.
|
||||
/// </summary>
|
||||
private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new();
|
||||
private EntityUid? _listenerGrid;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
// Need to run after Eye updates so we have an accurate listener position.
|
||||
UpdatesAfter.Add(typeof(EyeSystem));
|
||||
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<AudioComponent, ComponentStartup>(OnAudioStartup);
|
||||
SubscribeLocalEvent<AudioComponent, ComponentShutdown>(OnAudioShutdown);
|
||||
SubscribeLocalEvent<AudioComponent, EntityPausedEvent>(OnAudioPaused);
|
||||
SubscribeLocalEvent<AudioComponent, AfterAutoHandleStateEvent>(OnAudioState);
|
||||
|
||||
// Replay stuff
|
||||
SubscribeNetworkEvent<PlayAudioGlobalMessage>(OnGlobalAudio);
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
CfgManager.OnValueChanged(CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Source.Global = component.Global;
|
||||
|
||||
if (TryComp<AudioAuxiliaryComponent>(component.Auxiliary, out var auxComp))
|
||||
{
|
||||
component.Source.SetAuxiliary(auxComp.Auxiliary);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Source.SetAuxiliary(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume for the entire game.
|
||||
/// </summary>
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
_audio.SetMasterVolume(value);
|
||||
}
|
||||
|
||||
protected override void SetZOffset(float value)
|
||||
{
|
||||
base.SetZOffset(value);
|
||||
_audio.SetZOffset(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CfgManager.UnsubValueChanged(CVars.AudioAttenuation, OnAudioAttenuation);
|
||||
CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged);
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args)
|
||||
{
|
||||
component.Pause();
|
||||
}
|
||||
|
||||
protected override void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
base.OnAudioUnpaused(uid, component, ref args);
|
||||
component.StartPlaying();
|
||||
}
|
||||
|
||||
private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!Timing.ApplyingState && !Timing.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetAudio(component.FileName, out var audioResource))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
|
||||
component.Source = new DummyAudioSource();
|
||||
return;
|
||||
}
|
||||
|
||||
var source = _audio.CreateAudioSource(audioResource);
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = new DummyAudioSource();
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
component.Source = source;
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Global = component.Global;
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds;
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
component.PlaybackPosition = (float) offset;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAudioShutdown(EntityUid uid, AudioComponent component, ComponentShutdown args)
|
||||
{
|
||||
// Breaks with prediction?
|
||||
component.Source.Dispose();
|
||||
}
|
||||
|
||||
private void OnAudioAttenuation(int obj)
|
||||
{
|
||||
_audio.SetAttenuation((Attenuation) obj);
|
||||
}
|
||||
|
||||
private void OnRaycastLengthChanged(float value)
|
||||
{
|
||||
_maxRayLength = value;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
_audio.SetRotation(eye.Rotation);
|
||||
_audio.SetPosition(eye.Position.Position);
|
||||
|
||||
var ourPos = eye.Position;
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
|
||||
var query = AllEntityQuery<AudioComponent, TransformComponent>();
|
||||
_streams.Clear();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
_streams.Add((uid, comp, xform));
|
||||
}
|
||||
|
||||
_mapManager.TryFindGridAt(ourPos, out var gridUid, out _);
|
||||
_listenerGrid = gridUid == EntityUid.Invalid ? null : gridUid;
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(_streams, opts, comp => ProcessStream(comp.Entity, comp.Component, comp.Xform, ourPos));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while processing entity streams.");
|
||||
_runtimeLog.LogException(e, $"{nameof(AudioSystem)}.{nameof(FrameUpdate)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessStream(EntityUid entity, AudioComponent component, TransformComponent xform, MapCoordinates listener)
|
||||
{
|
||||
// TODO:
|
||||
// I Originally tried to be fancier here but it caused audio issues so just trying
|
||||
// to replicate the old behaviour for now.
|
||||
if (!component.Started)
|
||||
{
|
||||
component.Started = true;
|
||||
component.StartPlaying();
|
||||
}
|
||||
|
||||
// If it's global but on another map (that isn't nullspace) then stop playing it.
|
||||
if (component.Global)
|
||||
{
|
||||
if (xform.MapID != MapId.Nullspace && listener.MapId != xform.MapID)
|
||||
{
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume playing.
|
||||
component.Volume = component.Params.Volume;
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-global sounds, stop playing if on another map.
|
||||
// Not relevant to us.
|
||||
if (listener.MapId != xform.MapID)
|
||||
{
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 worldPos;
|
||||
var gridUid = xform.ParentUid;
|
||||
|
||||
// Handle grid audio differently by using nearest-edge instead of entity centre.
|
||||
if (_gridQuery.HasComponent(gridUid))
|
||||
{
|
||||
// It's our grid so max volume.
|
||||
if (_listenerGrid == gridUid)
|
||||
{
|
||||
component.Volume = component.Params.Volume;
|
||||
component.Occlusion = 0f;
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Need a grid-optimised version because this is gonna be expensive.
|
||||
// Just to avoid clipping on and off grid or nearestPoint changing we'll
|
||||
// always set the sound to listener's pos, we'll just manually do gain ourselves.
|
||||
if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance))
|
||||
{
|
||||
// Out of range
|
||||
if (gridDistance > component.MaxDistance)
|
||||
{
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsGain = MathF.Pow(10, component.Params.Volume / 10);
|
||||
|
||||
// Thought I'd never have to manually calculate gain again but this is the least
|
||||
// unpleasant audio I could get at the moment.
|
||||
component.Gain = paramsGain * _audio.GetAttenuationGain(
|
||||
gridDistance,
|
||||
component.Params.RolloffFactor,
|
||||
component.Params.ReferenceDistance,
|
||||
component.Params.MaxDistance);
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't get nearest point so don't play anymore.
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
worldPos = _xformSys.GetWorldPosition(entity);
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Max distance check
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > component.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(entity, listener, delta, distance);
|
||||
component.Occlusion = occlusion;
|
||||
|
||||
// Update audio positions.
|
||||
component.Position = worldPos;
|
||||
|
||||
// Make race cars go NYYEEOOOOOMMMMM
|
||||
if (_physicsQuery.TryGetComponent(entity, out var physicsComp))
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform, _xformQuery, _physicsQuery);
|
||||
component.Velocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
internal float GetOcclusion(EntityUid entity, MapCoordinates listener, Vector2 delta, float distance)
|
||||
{
|
||||
float occlusion = 0;
|
||||
|
||||
if (distance > 0.1)
|
||||
{
|
||||
var rayLength = MathF.Min(distance, _maxRayLength);
|
||||
var ray = new CollisionRay(listener.Position, delta / distance, OcclusionCollisionMask);
|
||||
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, entity);
|
||||
}
|
||||
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
|
||||
return true;
|
||||
|
||||
Log.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IAudioSource? source)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
{
|
||||
source = null;
|
||||
Log.Error($"Tried to create audio source outside of prediction!");
|
||||
DebugTools.Assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
source = _audio.CreateAudioSource(stream);
|
||||
return source != null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
|
||||
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream globally, without position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
{
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
|
||||
component.Global = true;
|
||||
component.Source.Global = true;
|
||||
Dirty(entity, component);
|
||||
return (entity, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
NetEntity = GetNetEntity(entity),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream following an entity.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream at a static position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var comp = SetupAudio(entity, stream.Name!, audioP);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var source = comp.Source;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioP.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
ApplyAudioParams(audioP, comp);
|
||||
comp.Params = audioP;
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the audioparams to the underlying audio source.
|
||||
/// </summary>
|
||||
private void ApplyAudioParams(AudioParams audioParams, IAudioSource source)
|
||||
{
|
||||
source.Pitch = audioParams.Pitch;
|
||||
source.Volume = audioParams.Volume;
|
||||
source.RolloffFactor = audioParams.RolloffFactor;
|
||||
source.MaxDistance = audioParams.MaxDistance;
|
||||
source.ReferenceDistance = audioParams.ReferenceDistance;
|
||||
source.Looping = audioParams.Loop;
|
||||
}
|
||||
|
||||
private void OnEntityCoordinates(PlayAudioPositionalMessage ev)
|
||||
{
|
||||
PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnEntityAudio(PlayAudioEntityMessage ev)
|
||||
{
|
||||
PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnGlobalAudio(PlayAudioGlobalMessage ev)
|
||||
{
|
||||
PlayGlobal(ev.FileName, ev.AudioParams, false);
|
||||
}
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
using System;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Effects;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class AudioEffect : IAudioEffect
|
||||
{
|
||||
internal int Handle;
|
||||
|
||||
private readonly IAudioInternal _master;
|
||||
|
||||
public AudioEffect(IAudioInternal manager)
|
||||
{
|
||||
Handle = EFX.GenEffect();
|
||||
_master = manager;
|
||||
EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
EFX.DeleteEffect(Handle);
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void _checkDisposed()
|
||||
{
|
||||
if (Handle == -1)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(AudioEffect));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Density
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Diffusion
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Gain
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GainHF
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GainLF
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayTime
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayHFRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayLFRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ReflectionsGain
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ReflectionsDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector3 ReflectionsPan
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LateReverbGain
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LateReverbDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector3 LateReverbPan
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float EchoTime
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float EchoDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ModulationTime
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ModulationDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float AirAbsorptionGainHF
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float HFReference
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LFReference
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float RoomRolloffFactor
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int DecayHFLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
|
||||
namespace Robust.Client.Audio.Effects;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class AuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
internal int Handle = EFX.GenAuxiliaryEffectSlot();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != -1)
|
||||
{
|
||||
EFX.DeleteAuxiliaryEffectSlot(Handle);
|
||||
Handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetEffect(IAudioEffect? effect)
|
||||
{
|
||||
if (effect is AudioEffect audEffect)
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -21,7 +20,7 @@ public interface IMidiRenderer : IDisposable
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
internal IBufferedAudioSource Source { get; }
|
||||
internal IClydeBufferedAudioSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer has been disposed or not.
|
||||
|
||||
@@ -10,7 +10,6 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -42,10 +41,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
[ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero;
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IResourceCacheInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
[Dependency] private readonly IAudioInternal _audio = default!;
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly ILogManager _logger = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
@@ -274,7 +273,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
|
||||
|
||||
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
@@ -352,7 +351,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Volume = _volume;
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -375,7 +374,6 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
// Update positions of streams every frame.
|
||||
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
|
||||
// so TRUE
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -417,13 +415,11 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
}
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.Global = true;
|
||||
renderer.Source.SetGlobal();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,19 +434,14 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
var position = renderer.TrackingCoordinates.Value;
|
||||
|
||||
if (position.MapId == MapId.Nullspace)
|
||||
if (!renderer.Source.SetPosition(renderer.TrackingCoordinates.Value.Position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.Source.Position = position.Position;
|
||||
|
||||
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
|
||||
xformQuery: transQuery, physicsQuery: physicsQuery);
|
||||
|
||||
renderer.Source.Velocity = vel;
|
||||
renderer.Source.SetVelocity(vel);
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
|
||||
@@ -474,11 +465,11 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.Occlusion = float.MaxValue;
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ using JetBrains.Annotations;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
@@ -54,8 +52,8 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
|
||||
private IMidiRenderer? _master;
|
||||
public MidiRendererState RendererState => _rendererState;
|
||||
public IBufferedAudioSource Source { get; set; }
|
||||
IBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Disposed { get; private set; } = false;
|
||||
@@ -249,7 +247,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono,
|
||||
IMidiManager midiManager, IAudioInternal clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
|
||||
IMidiManager midiManager, IClydeAudio clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
|
||||
{
|
||||
_midiManager = midiManager;
|
||||
_taskManager = taskManager;
|
||||
@@ -490,7 +488,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
Source.StartPlaying();
|
||||
if (!Source.IsPlaying) Source.StartPlaying();
|
||||
}
|
||||
|
||||
public void ApplyState(MidiRendererState state, bool filterChannels = false)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Shows a debug overlay for audio sources.
|
||||
/// </summary>
|
||||
public sealed class ShowAudioCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _client = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMgr = default!;
|
||||
public override string Command => "showaudio";
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (_overlayManager.HasOverlay<AudioOverlay>())
|
||||
_overlayManager.RemoveOverlay<AudioOverlay>();
|
||||
else
|
||||
_overlayManager.AddOverlay(new AudioOverlay(
|
||||
_entManager,
|
||||
_playerMgr,
|
||||
_client,
|
||||
_entManager.System<AudioSystem>(),
|
||||
_entManager.System<SharedTransformSystem>()));
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal sealed class AudioSource : BaseAudioSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Underlying stream to the audio.
|
||||
/// </summary>
|
||||
private readonly AudioStream _sourceStream;
|
||||
|
||||
#if DEBUG
|
||||
private bool _didPositionWarning;
|
||||
#endif
|
||||
|
||||
public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle)
|
||||
{
|
||||
_sourceStream = sourceStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override 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;
|
||||
}
|
||||
#if DEBUG
|
||||
// OpenAL doesn't seem to want to play stereo positionally.
|
||||
// Log a warning if people try to.
|
||||
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
{
|
||||
_didPositionWarning = true;
|
||||
Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
|
||||
_sourceStream.Name);
|
||||
// warning isn't enough, people just ignore it :(
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
}
|
||||
#endif
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
~AudioSource()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
// We can't run this code inside the finalizer thread so tell Clyde to clear it up later.
|
||||
Master.DeleteSourceOnMainThread(SourceHandle, FilterHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle);
|
||||
Master.RemoveAudioSource(SourceHandle);
|
||||
Master._checkAlError();
|
||||
}
|
||||
|
||||
FilterHandle = 0;
|
||||
SourceHandle = -1;
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal 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;
|
||||
|
||||
protected readonly AudioManager Master;
|
||||
|
||||
/// <summary>
|
||||
/// Prior gain that was set.
|
||||
/// </summary>
|
||||
private float _gain;
|
||||
|
||||
private bool IsEfxSupported => Master.IsEfxSupported;
|
||||
|
||||
protected BaseAudioSource(AudioManager master, int sourceHandle)
|
||||
{
|
||||
Master = master;
|
||||
SourceHandle = sourceHandle;
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
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 virtual bool Playing
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
Master._checkAlError();
|
||||
return state == 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; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
var gain = Gain;
|
||||
var volume = 10f * MathF.Log10(gain);
|
||||
return volume;
|
||||
}
|
||||
set => Gain = MathF.Pow(10, value / 10);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Gain
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var gain);
|
||||
Master._checkAlError();
|
||||
return gain;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = value;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Occlusion
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
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);
|
||||
}
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float PlaybackPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.SecOffset, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
||||
}
|
||||
|
||||
Master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
{
|
||||
if (FilterHandle == 0)
|
||||
{
|
||||
FilterHandle = EFX.GenFilter();
|
||||
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
}
|
||||
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSource
|
||||
{
|
||||
private int? SourceHandle = null;
|
||||
private int[] BufferHandles;
|
||||
private Dictionary<int, int> BufferMap = new();
|
||||
private readonly AudioManager _master;
|
||||
private bool _mono = true;
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
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;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Playing
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle!.Value);
|
||||
_master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_checkDisposed();
|
||||
// IDK why this stackallocs but gonna leave it for now.
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
_master._checkAlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isDisposed())
|
||||
return;
|
||||
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~BufferedAudioSource()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (SourceHandle == null)
|
||||
return;
|
||||
|
||||
if (!_master.IsMainThread())
|
||||
{
|
||||
// We can't run this code inside another thread so tell Clyde to clear it up later.
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
|
||||
|
||||
foreach (var handle in BufferHandles)
|
||||
{
|
||||
_master.DeleteAudioBufferOnMainThread(handle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master.RemoveBufferedAudioSource(SourceHandle.Value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
FilterHandle = 0;
|
||||
SourceHandle = null;
|
||||
}
|
||||
|
||||
public int GetNumberOfBuffersProcessed()
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.GetSource(SourceHandle!.Value, 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!.Value, 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!.Value, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Configuration;
|
||||
using Robust.Client.Console;
|
||||
@@ -7,6 +6,7 @@ using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Audio;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
@@ -29,7 +29,6 @@ using Robust.Client.UserInterface.Themes;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -73,11 +72,10 @@ namespace Robust.Client
|
||||
deps.Register<GameController, GameController>();
|
||||
deps.Register<IGameController, GameController>();
|
||||
deps.Register<IGameControllerInternal, GameController>();
|
||||
deps.Register<IResourceManager, ResourceManager>();
|
||||
deps.Register<IResourceManagerInternal, ResourceManager>();
|
||||
deps.Register<IClientResourceCache, ResourceCache>();
|
||||
deps.Register<IClientResourceCacheInternal, ResourceCache>();
|
||||
deps.Register<IResourceManager, ResourceCache>();
|
||||
deps.Register<IResourceManagerInternal, ResourceCache>();
|
||||
deps.Register<IResourceCache, ResourceCache>();
|
||||
deps.Register<IResourceCacheInternal, ResourceCache>();
|
||||
deps.Register<IClientNetManager, NetManager>();
|
||||
deps.Register<EntityManager, ClientEntityManager>();
|
||||
deps.Register<ClientEntityManager>();
|
||||
@@ -109,8 +107,8 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, ClydeHeadless>();
|
||||
deps.Register<IClipboardManager, ClydeHeadless>();
|
||||
deps.Register<IClydeInternal, ClydeHeadless>();
|
||||
deps.Register<IAudioInternal, HeadlessAudioManager>();
|
||||
deps.Register<SharedAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IClydeAudio, ClydeAudioHeadless>();
|
||||
deps.Register<IClydeAudioInternal, ClydeAudioHeadless>();
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpenerDummy>();
|
||||
@@ -119,8 +117,8 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, Clyde>();
|
||||
deps.Register<IClipboardManager, Clyde>();
|
||||
deps.Register<IClydeInternal, Clyde>();
|
||||
deps.Register<IAudioInternal, AudioManager>();
|
||||
deps.Register<SharedAudioManager, AudioManager>();
|
||||
deps.Register<IClydeAudio, FallbackProxyClydeAudio>();
|
||||
deps.Register<IClydeAudioInternal, FallbackProxyClydeAudio>();
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpener>();
|
||||
|
||||
@@ -15,7 +15,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -355,7 +354,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class LoadResource : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _res = default!;
|
||||
[Dependency] private readonly IResourceCache _res = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
public override string Command => "ldrsc";
|
||||
@@ -392,7 +391,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class ReloadResource : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _res = default!;
|
||||
[Dependency] private readonly IResourceCache _res = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
public override string Command => "rldrsc";
|
||||
@@ -459,13 +458,13 @@ namespace Robust.Client.Console.Commands
|
||||
internal sealed class GuiDumpCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
[Dependency] private readonly IResourceCache _res = default!;
|
||||
|
||||
public override string Command => "guidump";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
using var writer = _resManager.UserData.OpenWriteText(new ResPath("/guidump.txt"));
|
||||
using var writer = _res.UserData.OpenWriteText(new ResPath("/guidump.txt"));
|
||||
|
||||
foreach (var root in _ui.AllRoots)
|
||||
{
|
||||
@@ -645,8 +644,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class ReloadShadersCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
|
||||
[Dependency] private readonly IResourceCacheInternal _res = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
|
||||
public override string Command => "rldshader";
|
||||
@@ -657,7 +655,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var resC = _resManager;
|
||||
var resC = _res;
|
||||
if (args.Length == 1)
|
||||
{
|
||||
if (args[0] == "+watch")
|
||||
@@ -681,9 +679,9 @@ namespace Robust.Client.Console.Commands
|
||||
var shaderCount = 0;
|
||||
var created = 0;
|
||||
var dirs = new ConcurrentDictionary<string, SortedSet<string>>(stringComparer);
|
||||
foreach (var (path, src) in _cache.GetAllResources<ShaderSourceResource>())
|
||||
foreach (var (path, src) in resC.GetAllResources<ShaderSourceResource>())
|
||||
{
|
||||
if (!_resManager.TryGetDiskFilePath(path, out var fullPath))
|
||||
if (!resC.TryGetDiskFilePath(path, out var fullPath))
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -732,7 +730,7 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
try
|
||||
{
|
||||
_cache.ReloadResource<ShaderSourceResource>(resPath);
|
||||
resC.ReloadResource<ShaderSourceResource>(resPath);
|
||||
shell.WriteLine($"Reloaded shader: {resPath}");
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -793,11 +791,11 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
shell.WriteLine("Reloading content shader resources...");
|
||||
|
||||
foreach (var (path, _) in _cache.GetAllResources<ShaderSourceResource>())
|
||||
foreach (var (path, _) in resC.GetAllResources<ShaderSourceResource>())
|
||||
{
|
||||
try
|
||||
{
|
||||
_cache.ReloadResource<ShaderSourceResource>(path);
|
||||
resC.ReloadResource<ShaderSourceResource>(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Robust.Client.Debugging
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
IoCManager.Resolve<IPlayerManager>(),
|
||||
IoCManager.Resolve<IClientResourceCache>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
this,
|
||||
Get<EntityLookupSystem>(),
|
||||
Get<SharedPhysicsSystem>()));
|
||||
@@ -208,7 +208,7 @@ namespace Robust.Client.Debugging
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IClientResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -25,7 +24,6 @@ using Robust.Client.WebViewHook;
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -50,8 +48,7 @@ namespace Robust.Client
|
||||
internal sealed partial class GameController : IGameControllerInternal
|
||||
{
|
||||
[Dependency] private readonly INetConfigurationManagerInternal _configurationManager = default!;
|
||||
[Dependency] private readonly IClientResourceCacheInternal _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
|
||||
[Dependency] private readonly IResourceCacheInternal _resourceCache = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
@@ -71,7 +68,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IClientViewVariablesManagerInternal _viewVariablesManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IAudioInternal _audio = default!;
|
||||
[Dependency] private readonly IClydeAudioInternal _clydeAudio = default!;
|
||||
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IScriptClient _scriptClient = default!;
|
||||
@@ -114,7 +111,7 @@ namespace Robust.Client
|
||||
DebugTools.AssertNotNull(_resourceManifest);
|
||||
|
||||
_clyde.InitializePostWindowing();
|
||||
_audio.InitializePostWindowing();
|
||||
_clydeAudio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
|
||||
@@ -151,7 +148,7 @@ namespace Robust.Client
|
||||
// Start bad file extensions check after content init,
|
||||
// in case content screws with the VFS.
|
||||
var checkBadExtensions = ProgramShared.CheckBadFileExtensions(
|
||||
_resManager,
|
||||
_resourceCache,
|
||||
_configurationManager,
|
||||
_logManager.GetSawmill("res"));
|
||||
|
||||
@@ -363,13 +360,13 @@ namespace Robust.Client
|
||||
_parallelMgr.Initialize();
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
: Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resManager, mountOptions, Options.ContentBuildDirectory,
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
|
||||
Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
@@ -379,16 +376,16 @@ namespace Robust.Client
|
||||
{
|
||||
foreach (var (api, prefix) in mounts)
|
||||
{
|
||||
_resourceCache.MountLoaderApi(_resManager, api, "", new(prefix));
|
||||
_resourceCache.MountLoaderApi(api, "", new(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
_stringSerializer.EnableCaching = false;
|
||||
_resourceCache.MountLoaderApi(_resManager, _loaderArgs.FileApi, "Resources/");
|
||||
_resourceCache.MountLoaderApi(_loaderArgs.FileApi, "Resources/");
|
||||
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
|
||||
}
|
||||
|
||||
_resourceManifest = ResourceManifestData.LoadResourceManifest(_resManager);
|
||||
_resourceManifest = ResourceManifestData.LoadResourceManifest(_resourceCache);
|
||||
|
||||
{
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
@@ -570,6 +567,11 @@ namespace Robust.Client
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("ClydeAudio"))
|
||||
{
|
||||
_clydeAudio.FrameProcess(frameEventArgs);
|
||||
}
|
||||
|
||||
using (_prof.Group("Clyde"))
|
||||
{
|
||||
_clyde.FrameProcess(frameEventArgs);
|
||||
@@ -708,7 +710,7 @@ namespace Robust.Client
|
||||
internal void CleanupWindowThread()
|
||||
{
|
||||
_clyde.Shutdown();
|
||||
_audio.Shutdown();
|
||||
_clydeAudio.Shutdown();
|
||||
}
|
||||
|
||||
public event Action<FrameEventArgs>? TickUpdateOverride;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.GameObjects
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IEntityManager entities = default!;
|
||||
[Dependency] private readonly IReflectionManager reflection = default!;
|
||||
@@ -1379,7 +1379,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
internal static RSI.State GetFallbackState(IClientResourceCache cache)
|
||||
internal static RSI.State GetFallbackState(IResourceCache cache)
|
||||
{
|
||||
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
|
||||
return rsi["error"];
|
||||
@@ -2101,12 +2101,12 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IClientResourceCache resourceCache)
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
return GetPrototypeTextures(prototype, resourceCache, out var _);
|
||||
}
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IClientResourceCache resourceCache, out bool noRot)
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
||||
{
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
@@ -2161,7 +2161,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IClientResourceCache resourceCache)
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
|
||||
626
Robust.Client/GameObjects/EntitySystems/AudioSystem.cs
Normal file
626
Robust.Client/GameObjects/EntitySystems/AudioSystem.cs
Normal file
@@ -0,0 +1,626 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private float _maxRayLength;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(PlayAudioEntityHandler);
|
||||
SubscribeNetworkEvent<PlayAudioGlobalMessage>(PlayAudioGlobalHandler);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
_sawmill = _logManager.GetSawmill("audio");
|
||||
|
||||
CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged);
|
||||
foreach (var stream in _playingClydeStreams)
|
||||
{
|
||||
stream.Source.Dispose();
|
||||
}
|
||||
_playingClydeStreams.Clear();
|
||||
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnRaycastLengthChanged(float value)
|
||||
{
|
||||
_maxRayLength = value;
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
|
||||
{
|
||||
var uid = GetEntity(ev.NetEntity);
|
||||
var coords = GetCoordinates(ev.Coordinates);
|
||||
var fallback = GetCoordinates(ev.FallbackCoordinates);
|
||||
|
||||
var stream = EntityManager.EntityExists(uid)
|
||||
? (PlayingStream?) Play(ev.FileName, uid, fallback, ev.AudioParams, false)
|
||||
: (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
|
||||
|
||||
if (stream != null)
|
||||
stream.NetIdentifier = ev.Identifier;
|
||||
}
|
||||
|
||||
private void PlayAudioGlobalHandler(PlayAudioGlobalMessage ev)
|
||||
{
|
||||
var stream = (PlayingStream?) Play(ev.FileName, ev.AudioParams, false);
|
||||
if (stream != null)
|
||||
stream.NetIdentifier = ev.Identifier;
|
||||
}
|
||||
|
||||
private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev)
|
||||
{
|
||||
var coords = GetCoordinates(ev.Coordinates);
|
||||
var fallback = GetCoordinates(ev.FallbackCoordinates);
|
||||
|
||||
var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
|
||||
if (stream != null)
|
||||
stream.NetIdentifier = ev.Identifier;
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
{
|
||||
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
|
||||
if (stream == null)
|
||||
return;
|
||||
|
||||
stream.Done = true;
|
||||
stream.Source.Dispose();
|
||||
_playingClydeStreams.Remove(stream);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var xforms = GetEntityQuery<TransformComponent>();
|
||||
var physics = GetEntityQuery<PhysicsComponent>();
|
||||
var ourPos = _eyeManager.CurrentEye.Position;
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(_playingClydeStreams, opts, (stream) => ProcessStream(stream, ourPos, xforms, physics));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while processing entity streams.");
|
||||
_runtimeLog.LogException(e, $"{nameof(AudioSystem)}.{nameof(FrameUpdate)}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
for (var i = _playingClydeStreams.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var stream = _playingClydeStreams[i];
|
||||
if (stream.Done)
|
||||
{
|
||||
stream.Source.Dispose();
|
||||
_playingClydeStreams.RemoveSwap(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessStream(PlayingStream stream,
|
||||
MapCoordinates listener,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
EntityQuery<PhysicsComponent> physics)
|
||||
{
|
||||
if (!stream.Source.IsPlaying)
|
||||
{
|
||||
stream.Done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream.Source.IsGlobal)
|
||||
{
|
||||
DebugTools.Assert(stream.TrackingCoordinates == null
|
||||
&& stream.TrackingEntity == null
|
||||
&& stream.TrackingFallbackCoordinates == null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(stream.TrackingCoordinates != null
|
||||
|| stream.TrackingEntity != null
|
||||
|| stream.TrackingFallbackCoordinates != null);
|
||||
|
||||
// Get audio Position
|
||||
if (!TryGetStreamPosition(stream, xforms, out var mapPos)
|
||||
|| mapPos == MapCoordinates.Nullspace
|
||||
|| mapPos.Value.MapId != listener.MapId)
|
||||
{
|
||||
stream.Done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Max distance check
|
||||
var delta = mapPos.Value.Position - listener.Position;
|
||||
var distance = delta.Length();
|
||||
if (distance > stream.MaxDistance)
|
||||
{
|
||||
stream.Source.SetVolumeDirect(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
float occlusion = 0;
|
||||
if (distance > 0.1)
|
||||
{
|
||||
var rayLength = MathF.Min(distance, _maxRayLength);
|
||||
var ray = new CollisionRay(listener.Position, delta/distance, OcclusionCollisionMask);
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(listener.MapId, ray, rayLength, stream.TrackingEntity);
|
||||
}
|
||||
stream.Source.SetOcclusion(occlusion);
|
||||
|
||||
// Update attenuation dependent volume.
|
||||
UpdatePositionalVolume(stream, distance);
|
||||
|
||||
// Update audio positions.
|
||||
var audioPos = stream.Attenuation != Attenuation.NoAttenuation ? mapPos.Value : listener;
|
||||
if (!stream.Source.SetPosition(audioPos.Position))
|
||||
{
|
||||
_sawmill.Warning("Interrupting positional audio, can't set position.");
|
||||
stream.Source.StopPlaying();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make race cars go NYYEEOOOOOMMMMM
|
||||
if (stream.TrackingEntity != null && physics.TryGetComponent(stream.TrackingEntity, out var physicsComp))
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(stream.TrackingEntity.Value, physicsComp, null, xforms, physics);
|
||||
stream.Source.SetVelocity(velocity);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionalVolume(PlayingStream stream, float distance)
|
||||
{
|
||||
// OpenAL also limits the distance to <= AL_MAX_DISTANCE, but since we cull
|
||||
// sources that are further away than stream.MaxDistance, we don't do that.
|
||||
distance = MathF.Max(stream.ReferenceDistance, distance);
|
||||
float gain;
|
||||
|
||||
// Technically these are formulas for gain not decibels but EHHHHHHHH.
|
||||
switch (stream.Attenuation)
|
||||
{
|
||||
case Attenuation.Default:
|
||||
gain = 1f;
|
||||
break;
|
||||
// You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting
|
||||
// I didn't even wanna implement this much for linear but figured it'd be cleaner.
|
||||
case Attenuation.InverseDistanceClamped:
|
||||
case Attenuation.InverseDistance:
|
||||
gain = stream.ReferenceDistance
|
||||
/ (stream.ReferenceDistance
|
||||
+ stream.RolloffFactor * (distance - stream.ReferenceDistance));
|
||||
|
||||
break;
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
case Attenuation.LinearDistance:
|
||||
gain = 1f
|
||||
- stream.RolloffFactor
|
||||
* (distance - stream.ReferenceDistance)
|
||||
/ (stream.MaxDistance - stream.ReferenceDistance);
|
||||
|
||||
break;
|
||||
case Attenuation.ExponentDistanceClamped:
|
||||
case Attenuation.ExponentDistance:
|
||||
gain = MathF.Pow(distance / stream.ReferenceDistance, -stream.RolloffFactor);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(
|
||||
$"No implemented attenuation for {stream.Attenuation}");
|
||||
}
|
||||
|
||||
var volume = MathF.Pow(10, stream.Volume / 10);
|
||||
var actualGain = MathF.Max(0f, volume * gain);
|
||||
stream.Source.SetVolumeDirect(actualGain);
|
||||
}
|
||||
|
||||
private bool TryGetStreamPosition(PlayingStream stream, EntityQuery<TransformComponent> xformQuery, [NotNullWhen(true)] out MapCoordinates? mapPos)
|
||||
{
|
||||
if (stream.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = stream.TrackingCoordinates.Value.ToMap(EntityManager);
|
||||
if (mapPos != MapCoordinates.Nullspace)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (xformQuery.TryGetComponent(stream.TrackingEntity, out var xform)
|
||||
&& xform.MapID != MapId.Nullspace)
|
||||
{
|
||||
mapPos = new MapCoordinates(_xformSys.GetWorldPosition(xform, xformQuery), xform.MapID);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stream.TrackingFallbackCoordinates != null)
|
||||
{
|
||||
mapPos = stream.TrackingFallbackCoordinates.Value.ToMap(EntityManager);
|
||||
return mapPos != MapCoordinates.Nullspace;
|
||||
}
|
||||
|
||||
mapPos = MapCoordinates.Nullspace;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Play AudioStream
|
||||
private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<AudioResource>(new ResPath(filename), out audio))
|
||||
return true;
|
||||
|
||||
_sawmill.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IClydeAudioSource? source)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
{
|
||||
source = null;
|
||||
_sawmill.Error($"Tried to create audio source outside of prediction!");
|
||||
DebugTools.Assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
source = _clyde.CreateAudioSource(stream);
|
||||
return source != null;
|
||||
}
|
||||
|
||||
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
ApplyAudioParams(audioParams, source, stream);
|
||||
source.StartPlaying();
|
||||
var playing = new PlayingStream
|
||||
{
|
||||
Source = source,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
RolloffFactor = audioParams?.RolloffFactor ?? 1f,
|
||||
Volume = audioParams?.Volume ?? 0
|
||||
};
|
||||
_playingClydeStreams.Add(playing);
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? Play(audio, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream globally, without position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!TryCreateAudioSource(stream, out var source))
|
||||
{
|
||||
_sawmill.Error($"Error setting up global audio for {stream.Name}: {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
source.SetGlobal();
|
||||
|
||||
return CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, EntityUid entity, EntityCoordinates? fallbackCoordinates,
|
||||
AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
NetEntity = GetNetEntity(entity),
|
||||
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default,
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? Play(audio, entity, fallbackCoordinates, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream following an entity.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, EntityUid entity, EntityCoordinates? fallbackCoordinates = null,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (!TryCreateAudioSource(stream, out var source))
|
||||
{
|
||||
_sawmill.Error($"Error setting up entity audio for {stream.Name} / {ToPrettyString(entity)}: {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
var query = GetEntityQuery<TransformComponent>();
|
||||
var xform = query.GetComponent(entity);
|
||||
var worldPos = _xformSys.GetWorldPosition(xform, query);
|
||||
fallbackCoordinates ??= GetFallbackCoordinates(new MapCoordinates(worldPos, xform.MapID));
|
||||
|
||||
if (!source.SetPosition(worldPos))
|
||||
return Play(stream, fallbackCoordinates.Value, fallbackCoordinates.Value, audioParams);
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingEntity = entity;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates,
|
||||
EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? Play(audio, coordinates, fallbackCoordinates, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream at a static position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
|
||||
EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!TryCreateAudioSource(stream, out var source))
|
||||
{
|
||||
_sawmill.Error($"Error setting up coordinates audio for {stream.Name} / {coordinates}: {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!source.SetPosition(fallbackCoordinates.Position))
|
||||
{
|
||||
source.Dispose();
|
||||
_sawmill.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingCoordinates = coordinates;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_timing.IsFirstTimePredicted || sound == null)
|
||||
return Play(sound, Filter.Local(), source, false, audioParams);
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_timing.IsFirstTimePredicted || sound == null)
|
||||
return Play(sound, Filter.Local(), coordinates, false, audioParams);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source, AudioStream audio)
|
||||
{
|
||||
if (!audioParams.HasValue)
|
||||
return;
|
||||
|
||||
if (audioParams.Value.Variation.HasValue)
|
||||
source.SetPitch(audioParams.Value.PitchScale
|
||||
* (float) RandMan.NextGaussian(1, audioParams.Value.Variation.Value));
|
||||
else
|
||||
source.SetPitch(audioParams.Value.PitchScale);
|
||||
|
||||
source.SetVolume(audioParams.Value.Volume);
|
||||
source.SetRolloffFactor(audioParams.Value.RolloffFactor);
|
||||
source.SetMaxDistance(audioParams.Value.MaxDistance);
|
||||
source.SetReferenceDistance(audioParams.Value.ReferenceDistance);
|
||||
source.IsLooping = audioParams.Value.Loop;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioParams.Value.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) audio.Length.TotalSeconds);
|
||||
source.SetPlaybackPosition(offset);
|
||||
}
|
||||
|
||||
public sealed class PlayingStream : IPlayingAudioStream
|
||||
{
|
||||
public uint? NetIdentifier;
|
||||
public IClydeAudioSource Source = default!;
|
||||
public EntityUid? TrackingEntity;
|
||||
public EntityCoordinates? TrackingCoordinates;
|
||||
public EntityCoordinates? TrackingFallbackCoordinates;
|
||||
public bool Done;
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
Source.SetVolume(value);
|
||||
}
|
||||
}
|
||||
|
||||
private float _volume;
|
||||
|
||||
public float MaxDistance;
|
||||
public float ReferenceDistance;
|
||||
public float RolloffFactor;
|
||||
|
||||
public Attenuation Attenuation
|
||||
{
|
||||
get => _attenuation;
|
||||
set
|
||||
{
|
||||
if (value == _attenuation) return;
|
||||
_attenuation = value;
|
||||
if (_attenuation != Attenuation.Default)
|
||||
{
|
||||
// Need to disable default attenuation when using a custom one
|
||||
// Damn Sloth wanting linear ambience sounds so they smoothly cut-off and are short-range
|
||||
Source.SetRolloffFactor(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Attenuation _attenuation = Attenuation.Default;
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Source.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, entity, null, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, uid, null, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, uid, null, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resource = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class PointLightSystem : SharedPointLightSystem
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly LightTreeSystem _lightTree = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.GameStates
|
||||
public NetEntityOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IClientResourceCache>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
|
||||
public NetGraphOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IClientResourceCache>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
|
||||
59
Robust.Client/Graphics/Audio/ClydeAudio.ALDisposeQueues.cs
Normal file
59
Robust.Client/Graphics/Audio/ClydeAudio.ALDisposeQueues.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Concurrent;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal partial class ClydeAudio
|
||||
{
|
||||
// Used to track audio sources that were disposed in the finalizer thread,
|
||||
// so we need to properly send them off in the main thread.
|
||||
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _sourceDisposeQueue = new();
|
||||
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new();
|
||||
private readonly ConcurrentQueue<int> _bufferDisposeQueue = new();
|
||||
|
||||
private void _flushALDisposeQueues()
|
||||
{
|
||||
// Clear out finalized audio sources.
|
||||
while (_sourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
OpenALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
_audioSources.Remove(handles.sourceHandle);
|
||||
}
|
||||
|
||||
// Clear out finalized buffered audio sources.
|
||||
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
OpenALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
_bufferedAudioSources.Remove(handles.sourceHandle);
|
||||
}
|
||||
|
||||
// Clear out finalized audio buffers.
|
||||
while (_bufferDisposeQueue.TryDequeue(out var handle))
|
||||
{
|
||||
AL.DeleteBuffer(handle);
|
||||
_checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteSourceOnMainThread(int sourceHandle, int filterHandle)
|
||||
{
|
||||
_sourceDisposeQueue.Enqueue((sourceHandle, filterHandle));
|
||||
}
|
||||
|
||||
private void DeleteBufferedSourceOnMainThread(int bufferedSourceHandle, int filterHandle)
|
||||
{
|
||||
_bufferedSourceDisposeQueue.Enqueue((bufferedSourceHandle, filterHandle));
|
||||
}
|
||||
|
||||
private void DeleteAudioBufferOnMainThread(int bufferHandle)
|
||||
{
|
||||
_bufferDisposeQueue.Enqueue(bufferHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
680
Robust.Client/Graphics/Audio/ClydeAudio.AudioSources.cs
Normal file
680
Robust.Client/Graphics/Audio/ClydeAudio.AudioSources.cs
Normal file
@@ -0,0 +1,680 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Mathematics;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal partial class ClydeAudio
|
||||
{
|
||||
private sealed class AudioSource : IClydeAudioSource
|
||||
{
|
||||
private int SourceHandle;
|
||||
private readonly ClydeAudio _master;
|
||||
private readonly AudioStream _sourceStream;
|
||||
private int FilterHandle;
|
||||
#if DEBUG
|
||||
private bool _didPositionWarning;
|
||||
#endif
|
||||
|
||||
private float _gain;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
|
||||
public AudioSource(ClydeAudio master, int sourceHandle, AudioStream sourceStream)
|
||||
{
|
||||
_master = master;
|
||||
SourceHandle = sourceHandle;
|
||||
_sourceStream = sourceStream;
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
public void StartPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourcePlay(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
if (_isDisposed()) return;
|
||||
AL.SourceStop(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
return state == ALSourceState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLooping
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
|
||||
_master._checkAlError();
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.Looping, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsGlobal
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourceb.SourceRelative, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGlobal()
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float gain)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float distance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.MaxDistance, distance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.RolloffFactor, rolloffFactor);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, refDistance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
_checkDisposed();
|
||||
var cutoff = MathF.Exp(-blocks * 1);
|
||||
var gain = MathF.Pow(cutoff, 0.1f);
|
||||
if (IsEfxSupported)
|
||||
{
|
||||
SetOcclusionEfx(gain, cutoff);
|
||||
}
|
||||
else
|
||||
{
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
{
|
||||
if (FilterHandle == 0)
|
||||
{
|
||||
FilterHandle = EFX.GenFilter();
|
||||
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
}
|
||||
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
|
||||
}
|
||||
|
||||
public void SetPlaybackPosition(float seconds)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
var (x, y) = position;
|
||||
|
||||
if (!AreFinite(x, y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#if DEBUG
|
||||
// OpenAL doesn't seem to want to play stereo positionally.
|
||||
// Log a warning if people try to.
|
||||
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
{
|
||||
_didPositionWarning = true;
|
||||
_master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
|
||||
_sourceStream.Name);
|
||||
// warning isn't enough, people just ignore it :(
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
}
|
||||
#endif
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AreFinite(float x, float y)
|
||||
{
|
||||
if (float.IsFinite(x) && float.IsFinite(y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
var (x, y) = velocity;
|
||||
|
||||
if (!AreFinite(x, y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~AudioSource()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
// We can't run this code inside the finalizer thread so tell Clyde to clear it up later.
|
||||
_master.DeleteSourceOnMainThread(SourceHandle, FilterHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
|
||||
AL.DeleteSource(SourceHandle);
|
||||
_master._audioSources.Remove(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = -1;
|
||||
}
|
||||
|
||||
private bool _isDisposed()
|
||||
{
|
||||
return SourceHandle == -1;
|
||||
}
|
||||
|
||||
private void _checkDisposed()
|
||||
{
|
||||
if (SourceHandle == -1)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(AudioSource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BufferedAudioSource : IClydeBufferedAudioSource
|
||||
{
|
||||
private int? SourceHandle = null;
|
||||
private int[] BufferHandles;
|
||||
private Dictionary<int, int> BufferMap = new();
|
||||
private readonly ClydeAudio _master;
|
||||
private bool _mono = true;
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
|
||||
public BufferedAudioSource(ClydeAudio master, int sourceHandle, int[] bufferHandles, bool floatAudio = false)
|
||||
{
|
||||
_master = master;
|
||||
SourceHandle = sourceHandle;
|
||||
BufferHandles = bufferHandles;
|
||||
for (int i = 0; i < BufferHandles.Length; i++)
|
||||
{
|
||||
var bufferHandle = BufferHandles[i];
|
||||
BufferMap[bufferHandle] = i;
|
||||
}
|
||||
_float = floatAudio;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
public void StartPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
if (_isDisposed()) return;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
var state = AL.GetSourceState(SourceHandle!.Value);
|
||||
return state == ALSourceState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLooping
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetGlobal()
|
||||
{
|
||||
_checkDisposed();
|
||||
_mono = false;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetLooping()
|
||||
{
|
||||
// TODO?waaaaddDDDDD
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float gain)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = gain;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float distance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.MaxDistance, distance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.RolloffFactor, rolloffFactor);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.ReferenceDistance, refDistance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
_checkDisposed();
|
||||
var cutoff = MathF.Exp(-blocks * 1.5f);
|
||||
var gain = MathF.Pow(cutoff, 0.1f);
|
||||
if (IsEfxSupported)
|
||||
{
|
||||
SetOcclusionEfx(gain, cutoff);
|
||||
}
|
||||
else
|
||||
{
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
|
||||
}
|
||||
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
{
|
||||
if (FilterHandle == 0)
|
||||
{
|
||||
FilterHandle = EFX.GenFilter();
|
||||
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
}
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
AL.Source(SourceHandle!.Value, ALSourcei.EfxDirectFilter, FilterHandle);
|
||||
}
|
||||
|
||||
public void SetPlaybackPosition(float seconds)
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsGlobal
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle!.Value, ALSourceb.SourceRelative, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
var (x, y) = position;
|
||||
|
||||
if (!AreFinite(x, y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_mono = true;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AreFinite(float x, float y)
|
||||
{
|
||||
if (float.IsFinite(x) && float.IsFinite(y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
var (x, y) = velocity;
|
||||
|
||||
if (!AreFinite(x, y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~BufferedAudioSource()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (SourceHandle == null) return;
|
||||
|
||||
if (!_master.IsMainThread())
|
||||
{
|
||||
// We can't run this code inside another thread so tell Clyde to clear it up later.
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
|
||||
for (var i = 0; i < BufferHandles.Length; i++)
|
||||
_master.DeleteAudioBufferOnMainThread(BufferHandles[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master._bufferedAudioSources.Remove(SourceHandle.Value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = null;
|
||||
}
|
||||
|
||||
private bool _isDisposed()
|
||||
{
|
||||
return SourceHandle == null;
|
||||
}
|
||||
|
||||
private void _checkDisposed()
|
||||
{
|
||||
if (SourceHandle == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(AudioSource));
|
||||
}
|
||||
}
|
||||
|
||||
public int GetNumberOfBuffersProcessed()
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.GetSource(SourceHandle!.Value, 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)
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceUnqueueBuffers(SourceHandle!.Value, 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!.Value, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Robust.Client/Graphics/Audio/ClydeAudio.Formats.OggVorbis.cs
Normal file
54
Robust.Client/Graphics/Audio/ClydeAudio.Formats.OggVorbis.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal partial class ClydeAudio
|
||||
{
|
||||
private OggVorbisData _readOggVorbis(Stream stream)
|
||||
{
|
||||
using (var vorbis = new NVorbis.VorbisReader(stream, false))
|
||||
{
|
||||
var sampleRate = vorbis.SampleRate;
|
||||
var channels = vorbis.Channels;
|
||||
var totalSamples = vorbis.TotalSamples;
|
||||
|
||||
var readSamples = 0;
|
||||
var buffer = new float[totalSamples * channels];
|
||||
|
||||
while (readSamples < totalSamples)
|
||||
{
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples);
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
readSamples += read;
|
||||
}
|
||||
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct OggVorbisData
|
||||
{
|
||||
public readonly long TotalSamples;
|
||||
public readonly long SampleRate;
|
||||
public readonly long Channels;
|
||||
public readonly ReadOnlyMemory<float> Data;
|
||||
public readonly string Title;
|
||||
public readonly string Artist;
|
||||
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data, string title, string artist)
|
||||
{
|
||||
TotalSamples = totalSamples;
|
||||
SampleRate = sampleRate;
|
||||
Channels = channels;
|
||||
Data = data;
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
Robust.Client/Graphics/Audio/ClydeAudio.Formats.Wav.cs
Normal file
144
Robust.Client/Graphics/Audio/ClydeAudio.Formats.Wav.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal partial class ClydeAudio
|
||||
{
|
||||
/// <summary>
|
||||
/// Load up a WAVE file.
|
||||
/// </summary>
|
||||
private static WavData _readWav(Stream stream)
|
||||
{
|
||||
var reader = new BinaryReader(stream, EncodingHelpers.UTF8, true);
|
||||
|
||||
void SkipChunk()
|
||||
{
|
||||
var length = reader.ReadUInt32();
|
||||
stream.Position += length;
|
||||
}
|
||||
|
||||
// Read outer most chunks.
|
||||
Span<byte> fourCc = stackalloc byte[4];
|
||||
while (true)
|
||||
{
|
||||
_readFourCC(reader, fourCc);
|
||||
|
||||
if (!fourCc.SequenceEqual("RIFF"u8))
|
||||
{
|
||||
SkipChunk();
|
||||
continue;
|
||||
}
|
||||
|
||||
return _readRiffChunk(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private static void _skipChunk(BinaryReader reader)
|
||||
{
|
||||
var length = reader.ReadUInt32();
|
||||
reader.BaseStream.Position += length;
|
||||
}
|
||||
|
||||
private static void _readFourCC(BinaryReader reader, Span<byte> fourCc)
|
||||
{
|
||||
fourCc[0] = reader.ReadByte();
|
||||
fourCc[1] = reader.ReadByte();
|
||||
fourCc[2] = reader.ReadByte();
|
||||
fourCc[3] = reader.ReadByte();
|
||||
}
|
||||
|
||||
private static WavData _readRiffChunk(BinaryReader reader)
|
||||
{
|
||||
Span<byte> format = stackalloc byte[4];
|
||||
reader.ReadUInt32();
|
||||
_readFourCC(reader, format);
|
||||
if (!format.SequenceEqual("WAVE"u8))
|
||||
{
|
||||
throw new InvalidDataException("File is not a WAVE file.");
|
||||
}
|
||||
|
||||
_readFourCC(reader, format);
|
||||
if (!format.SequenceEqual("fmt "u8))
|
||||
{
|
||||
throw new InvalidDataException("Expected fmt chunk.");
|
||||
}
|
||||
|
||||
// Read fmt chunk.
|
||||
|
||||
var size = reader.ReadInt32();
|
||||
var afterFmtPos = reader.BaseStream.Position + size;
|
||||
|
||||
var audioType = (WavAudioFormatType) reader.ReadInt16();
|
||||
var channels = reader.ReadInt16();
|
||||
var sampleRate = reader.ReadInt32();
|
||||
var byteRate = reader.ReadInt32();
|
||||
var blockAlign = reader.ReadInt16();
|
||||
var bitsPerSample = reader.ReadInt16();
|
||||
|
||||
if (audioType != WavAudioFormatType.PCM)
|
||||
{
|
||||
throw new NotImplementedException("Unable to support audio types other than PCM.");
|
||||
}
|
||||
|
||||
DebugTools.Assert(byteRate == sampleRate * channels * bitsPerSample / 8);
|
||||
|
||||
// Fmt is not of guaranteed size, so use the size header to skip to the end.
|
||||
reader.BaseStream.Position = afterFmtPos;
|
||||
|
||||
while (true)
|
||||
{
|
||||
_readFourCC(reader, format);
|
||||
if (!format.SequenceEqual("data"u8))
|
||||
{
|
||||
_skipChunk(reader);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in the data chunk.
|
||||
size = reader.ReadInt32();
|
||||
var data = reader.ReadBytes(size);
|
||||
|
||||
return new WavData(audioType, channels, sampleRate, byteRate, blockAlign, bitsPerSample, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See http://soundfile.sapp.org/doc/WaveFormat/ for reference.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
private readonly struct WavData
|
||||
{
|
||||
public readonly WavAudioFormatType AudioType;
|
||||
public readonly short NumChannels;
|
||||
public readonly int SampleRate;
|
||||
public readonly int ByteRate;
|
||||
public readonly short BlockAlign;
|
||||
public readonly short BitsPerSample;
|
||||
public readonly ReadOnlyMemory<byte> Data;
|
||||
|
||||
public WavData(WavAudioFormatType audioType, short numChannels, int sampleRate, int byteRate,
|
||||
short blockAlign, short bitsPerSample, ReadOnlyMemory<byte> data)
|
||||
{
|
||||
AudioType = audioType;
|
||||
NumChannels = numChannels;
|
||||
SampleRate = sampleRate;
|
||||
ByteRate = byteRate;
|
||||
BlockAlign = blockAlign;
|
||||
BitsPerSample = bitsPerSample;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private enum WavAudioFormatType : short
|
||||
{
|
||||
Unknown = 0,
|
||||
PCM = 1,
|
||||
// There's a bunch of other types, those are all unsupported.
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Robust.Client/Graphics/Audio/ClydeAudio.IoC.cs
Normal file
51
Robust.Client/Graphics/Audio/ClydeAudio.IoC.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Mathematics;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal partial class ClydeAudio
|
||||
{
|
||||
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
return _initializeAudio();
|
||||
}
|
||||
|
||||
public void FrameProcess(FrameEventArgs eventArgs)
|
||||
{
|
||||
_updateAudio();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_shutdownAudio();
|
||||
}
|
||||
|
||||
private bool IsMainThread()
|
||||
{
|
||||
return Thread.CurrentThread == _gameThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
432
Robust.Client/Graphics/Audio/ClydeAudio.cs
Normal file
432
Robust.Client/Graphics/Audio/ClydeAudio.cs
Normal file
@@ -0,0 +1,432 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Mathematics;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
internal sealed partial class ClydeAudio : IClydeAudio, IClydeAudioInternal
|
||||
{
|
||||
private ALDevice _openALDevice;
|
||||
private ALContext _openALContext;
|
||||
|
||||
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<AudioSource>> _audioSources =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BufferedAudioSource>> _bufferedAudioSources =
|
||||
new();
|
||||
|
||||
private readonly HashSet<string> _alcDeviceExtensions = new();
|
||||
private readonly HashSet<string> _alContextExtensions = new();
|
||||
|
||||
// The base gain value for a listener, used to boost the default volume.
|
||||
private const float _baseGain = 2f;
|
||||
|
||||
public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension);
|
||||
public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension);
|
||||
|
||||
internal bool IsEfxSupported;
|
||||
|
||||
internal ISawmill OpenALSawmill = default!;
|
||||
|
||||
private bool _initializeAudio()
|
||||
{
|
||||
OpenALSawmill = _logMan.GetSawmill("clyde.oal");
|
||||
|
||||
if (!_audioOpenDevice())
|
||||
return false;
|
||||
|
||||
// Create OpenAL context.
|
||||
_audioCreateContext();
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioAttenuation, SetAudioAttenuation, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_openALContext = ALC.CreateContext(_openALDevice, (int*) 0);
|
||||
}
|
||||
|
||||
ALC.MakeContextCurrent(_openALContext);
|
||||
_checkAlcError(_openALDevice);
|
||||
_checkAlError();
|
||||
|
||||
// Load up AL context extensions.
|
||||
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' '))
|
||||
{
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
OpenALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
OpenALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
OpenALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
}
|
||||
|
||||
private bool _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = _cfg.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
{
|
||||
_openALDevice = ALC.OpenDevice(preferredDevice);
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
OpenALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
preferredDevice, ALC.GetError(ALDevice.Null));
|
||||
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
}
|
||||
|
||||
_checkAlcError(_openALDevice);
|
||||
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
OpenALSawmill.Error("Unable to open OpenAL device! {1}", ALC.GetError(ALDevice.Null));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load up ALC extensions.
|
||||
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' '))
|
||||
{
|
||||
_alcDeviceExtensions.Add(extension);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void StopAllAudio()
|
||||
{
|
||||
foreach (var (key, source) in _audioSources)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, source) in _bufferedAudioSources)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.StopPlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DisposeAllAudio()
|
||||
{
|
||||
foreach (var (key, source) in _audioSources)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
_audioSources.Clear();
|
||||
|
||||
foreach (var (key, source) in _bufferedAudioSources)
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.StopPlaying();
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
_bufferedAudioSources.Clear();
|
||||
}
|
||||
|
||||
private void _shutdownAudio()
|
||||
{
|
||||
DisposeAllAudio();
|
||||
|
||||
if (_openALContext != ALContext.Null)
|
||||
{
|
||||
ALC.MakeContextCurrent(ALContext.Null);
|
||||
|
||||
ALC.DestroyContext(_openALContext);
|
||||
}
|
||||
|
||||
if (_openALDevice != IntPtr.Zero)
|
||||
{
|
||||
ALC.CloseDevice(_openALDevice);
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateAudio()
|
||||
{
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
var vec = eye.Position.Position;
|
||||
AL.Listener(ALListener3f.Position, vec.X, vec.Y, -5);
|
||||
var rot2d = eye.Rotation.ToVec();
|
||||
AL.Listener(ALListenerfv.Orientation, new []{0, 0, -1, rot2d.X, rot2d.Y, 0});
|
||||
|
||||
// Default orientation: at: (0, 0, -1) up: (0, 1, 0)
|
||||
var rot = eye.Rotation.ToVec();
|
||||
var at = new Vector3(0f, 0f, -1f);
|
||||
var up = new Vector3(rot.Y, rot.X, 0f);
|
||||
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
|
||||
|
||||
_flushALDisposeQueues();
|
||||
}
|
||||
|
||||
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
|
||||
{
|
||||
if (handles.filterHandle != 0) EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
|
||||
}
|
||||
|
||||
public void SetAudioAttenuation(int value)
|
||||
{
|
||||
var attenuation = (Attenuation) value;
|
||||
|
||||
switch (attenuation)
|
||||
{
|
||||
case Attenuation.NoAttenuation:
|
||||
AL.DistanceModel(ALDistanceModel.None);
|
||||
break;
|
||||
case Attenuation.InverseDistance:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistance);
|
||||
break;
|
||||
case Attenuation.Default:
|
||||
case Attenuation.InverseDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistanceClamped);
|
||||
break;
|
||||
case Attenuation.LinearDistance:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistance);
|
||||
break;
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
|
||||
break;
|
||||
case Attenuation.ExponentDistance:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistance);
|
||||
break;
|
||||
case Attenuation.ExponentDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!");
|
||||
}
|
||||
|
||||
var attToString = attenuation == Attenuation.Default ? Attenuation.InverseDistanceClamped : attenuation;
|
||||
|
||||
OpenALSawmill.Info($"Set audio attenuation to {attToString.ToString()}");
|
||||
}
|
||||
|
||||
public IClydeAudioSource? CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
// TODO: This really shouldn't be indexing based on the ClydeHandle...
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value.Value].BufferHandle);
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<AudioSource>(audioSource));
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
throw new Exception("Failed to generate source. Too many simultaneous audio streams?");
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
|
||||
_bufferedAudioSources.Add(source, new WeakReference<BufferedAudioSource>(audioSource));
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private void _checkAlcError(ALDevice device,
|
||||
[CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = ALC.GetError(device);
|
||||
if (error != AlcError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
private void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
var vorbis = _readOggVorbis(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
ALFormat format;
|
||||
// NVorbis only supports loading into floats.
|
||||
// If this becomes a problem due to missing extension support (doubt it but ok),
|
||||
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
|
||||
if (vorbis.Channels == 1)
|
||||
{
|
||||
format = ALFormat.MonoFloat32Ext;
|
||||
}
|
||||
else if (vorbis.Channels == 2)
|
||||
{
|
||||
format = ALFormat.StereoFloat32Ext;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = vorbis.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
|
||||
(int) vorbis.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
var wav = _readWav(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
ALFormat format;
|
||||
if (wav.BitsPerSample == 16)
|
||||
{
|
||||
if (wav.NumChannels == 1)
|
||||
{
|
||||
format = ALFormat.Mono16;
|
||||
}
|
||||
else if (wav.NumChannels == 2)
|
||||
{
|
||||
format = ALFormat.Stereo16;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
}
|
||||
else if (wav.BitsPerSample == 8)
|
||||
{
|
||||
if (wav.NumChannels == 1)
|
||||
{
|
||||
format = ALFormat.Mono8;
|
||||
}
|
||||
else if (wav.NumChannels == 2)
|
||||
{
|
||||
format = ALFormat.Stereo8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load wav with bits per sample different from 8 or 16");
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = wav.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, wav.Data.Length, wav.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
1 => ALFormat.Mono16,
|
||||
2 => ALFormat.Stereo16,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(channels), "Only stereo and mono is currently supported")
|
||||
};
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
_checkAlError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
private sealed class LoadedAudioSample
|
||||
{
|
||||
public readonly int BufferHandle;
|
||||
|
||||
public LoadedAudioSample(int bufferHandle)
|
||||
{
|
||||
BufferHandle = bufferHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Robust.Client/Graphics/Audio/ClydeAudioHeadless.cs
Normal file
80
Robust.Client/Graphics/Audio/ClydeAudioHeadless.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Hey look, it's ClydeAudio's evil twin brother!
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class ClydeAudioHeadless : IClydeAudio, IClydeAudioInternal
|
||||
{
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FrameProcess(FrameEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
// TODO: Might wanna actually load this so the length gets reported correctly.
|
||||
return new(default, default, 1, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
// TODO: Might wanna actually load this so the length gets reported correctly.
|
||||
return new(default, default, 1, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
// TODO: Might wanna actually load this so the length gets reported correctly.
|
||||
return new(default, default, channels, name);
|
||||
}
|
||||
|
||||
public IClydeAudioSource CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
return DummyAudioSource.Instance;
|
||||
}
|
||||
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void DisposeAllAudio()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void StopAllAudio()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
}
|
||||
102
Robust.Client/Graphics/Audio/DummyAudioSource.cs
Normal file
102
Robust.Client/Graphics/Audio/DummyAudioSource.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Hey look, it's ClydeAudio.AudioSource's evil twin brother!
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
internal class DummyAudioSource : IClydeAudioSource
|
||||
{
|
||||
public static DummyAudioSource Instance { get; } = new();
|
||||
|
||||
public bool IsPlaying => default;
|
||||
public bool IsLooping { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void StartPlaying()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public bool IsGlobal { get; }
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetGlobal()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float gain)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float maxDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetPlaybackPosition(float seconds)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Shared.Audio.Sources
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Hey look, it's Audio.BufferedAudioSource's evil twin brother!
|
||||
/// Hey look, it's ClydeAudio.BufferedAudioSource's evil twin brother!
|
||||
/// </summary>
|
||||
internal sealed class DummyBufferedAudioSource : DummyAudioSource, IBufferedAudioSource
|
||||
internal sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
|
||||
{
|
||||
public new static DummyBufferedAudioSource Instance { get; } = new();
|
||||
public int SampleRate { get; set; } = 0;
|
||||
36
Robust.Client/Graphics/Audio/FallbackProxyClydeAudio.cs
Normal file
36
Robust.Client/Graphics/Audio/FallbackProxyClydeAudio.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// For "start ss14 with no audio devices" Smugleaf
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class FallbackProxyClydeAudio : ProxyClydeAudio
|
||||
{
|
||||
[Dependency] private readonly IDependencyCollection _deps = default!;
|
||||
|
||||
public override bool InitializePostWindowing()
|
||||
{
|
||||
// Deliberate lack of base call here (see base implementation for comments as to why there even is a base)
|
||||
|
||||
ActualImplementation = new ClydeAudio();
|
||||
_deps.InjectDependencies(ActualImplementation, true);
|
||||
if (ActualImplementation.InitializePostWindowing())
|
||||
return true;
|
||||
|
||||
// If we get here, that failed, so use the fallback
|
||||
ActualImplementation = new ClydeAudioHeadless();
|
||||
_deps.InjectDependencies(ActualImplementation, true);
|
||||
return ActualImplementation.InitializePostWindowing();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Robust.Client/Graphics/Audio/ProxyClydeAudio.cs
Normal file
82
Robust.Client/Graphics/Audio/ProxyClydeAudio.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// For "start ss14 with no audio devices" Smugleaf
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal abstract class ProxyClydeAudio : IClydeAudio, IClydeAudioInternal
|
||||
{
|
||||
protected IClydeAudioInternal ActualImplementation = default!;
|
||||
|
||||
public virtual bool InitializePostWindowing()
|
||||
{
|
||||
// This particular implementation exists to be overridden because removing this method causes C# to complain
|
||||
return ActualImplementation.InitializePostWindowing();
|
||||
}
|
||||
|
||||
public void FrameProcess(FrameEventArgs eventArgs)
|
||||
{
|
||||
ActualImplementation.FrameProcess(eventArgs);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
ActualImplementation.Shutdown();
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
return ActualImplementation.LoadAudioOggVorbis(stream, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
return ActualImplementation.LoadAudioWav(stream, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
return ActualImplementation.LoadAudioRaw(samples, channels, sampleRate, name);
|
||||
}
|
||||
|
||||
public IClydeAudioSource? CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
return ActualImplementation.CreateAudioSource(stream);
|
||||
}
|
||||
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return ActualImplementation.CreateBufferedAudioSource(buffers, floatAudio);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
ActualImplementation.SetMasterVolume(newVolume);
|
||||
}
|
||||
|
||||
public void DisposeAllAudio()
|
||||
{
|
||||
ActualImplementation.DisposeAllAudio();
|
||||
}
|
||||
|
||||
public void StopAllAudio()
|
||||
{
|
||||
ActualImplementation.StopAllAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,14 +260,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var file in _resManager.ContentFindFiles(_windowIconPath))
|
||||
foreach (var file in _resourceCache.ContentFindFiles(_windowIconPath))
|
||||
{
|
||||
if (file.Extension != "png")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using var stream = _resManager.ContentFileRead(file);
|
||||
using var stream = _resourceCache.ContentFileRead(file);
|
||||
yield return Image.Load<Rgba32>(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -36,8 +35,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
@@ -292,6 +292,123 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
private class DummyAudioSource : IClydeAudioSource
|
||||
{
|
||||
public static DummyAudioSource Instance { get; } = new();
|
||||
|
||||
public bool IsPlaying => default;
|
||||
public bool IsLooping { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void StartPlaying()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public bool IsGlobal { get; }
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetGlobal()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float gain)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float maxDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetPlaybackPosition(float seconds)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
|
||||
{
|
||||
public new static DummyBufferedAudioSource Instance { get; } = new();
|
||||
public int SampleRate { get; set; } = 0;
|
||||
|
||||
public void WriteBuffer(int handle, ReadOnlySpan<ushort> data)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void WriteBuffer(int handle, ReadOnlySpan<float> data)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void QueueBuffers(ReadOnlySpan<int> handles)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void EmptyBuffers()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void GetBuffersProcessed(Span<int> handles)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public int GetNumberOfBuffersProcessed()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyTexture : OwnedTexture
|
||||
{
|
||||
public DummyTexture(Vector2i size) : base(size)
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
using System;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
internal readonly struct ClydeHandle : IEquatable<ClydeHandle>, IClydeHandle
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public ClydeHandle(long value)
|
||||
internal struct ClydeHandle : IEquatable<ClydeHandle>
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public ClydeHandle(long value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public long Value { get; }
|
||||
public readonly long Value;
|
||||
|
||||
public static explicit operator ClydeHandle(long x)
|
||||
{
|
||||
return new(x);
|
||||
}
|
||||
public static explicit operator ClydeHandle(long x)
|
||||
{
|
||||
return new(x);
|
||||
}
|
||||
|
||||
public static explicit operator long(ClydeHandle h)
|
||||
{
|
||||
return h.Value;
|
||||
}
|
||||
public static explicit operator long(ClydeHandle h)
|
||||
{
|
||||
return h.Value;
|
||||
}
|
||||
|
||||
public bool Equals(ClydeHandle other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
public bool Equals(ClydeHandle other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ClydeHandle other && Equals(other);
|
||||
}
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ClydeHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(ClydeHandle left, ClydeHandle right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
public static bool operator ==(ClydeHandle left, ClydeHandle right)
|
||||
{
|
||||
return left.Value == right.Value;
|
||||
}
|
||||
|
||||
public static bool operator !=(ClydeHandle left, ClydeHandle right)
|
||||
{
|
||||
return left.Value != right.Value;
|
||||
}
|
||||
public static bool operator !=(ClydeHandle left, ClydeHandle right)
|
||||
{
|
||||
return left.Value != right.Value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ClydeHandle {Value}";
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ClydeHandle {Value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +114,9 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
if (rune == new Rune('\n'))
|
||||
{
|
||||
baseLine.X = 0f;
|
||||
baseLine.Y += lineHeight;
|
||||
advanceTotal.Y += lineHeight;
|
||||
advanceTotal.X = Math.Max(advanceTotal.X, baseLine.X);
|
||||
baseLine.X = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -127,6 +126,7 @@ namespace Robust.Client.Graphics
|
||||
continue;
|
||||
|
||||
var advance = metrics.Value.Advance;
|
||||
advanceTotal.X += advance;
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
|
||||
|
||||
23
Robust.Client/Graphics/IClydeAudio.cs
Normal file
23
Robust.Client/Graphics/IClydeAudio.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public interface IClydeAudio
|
||||
{
|
||||
// AUDIO SYSTEM DOWN BELOW.
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
|
||||
|
||||
void SetMasterVolume(float newVolume);
|
||||
|
||||
void DisposeAllAudio();
|
||||
|
||||
void StopAllAudio();
|
||||
|
||||
IClydeAudioSource? CreateAudioSource(AudioStream stream);
|
||||
IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
}
|
||||
}
|
||||
16
Robust.Client/Graphics/IClydeAudioInternal.cs
Normal file
16
Robust.Client/Graphics/IClydeAudioInternal.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
internal interface IClydeAudioInternal : IClydeAudio
|
||||
{
|
||||
bool InitializePostWindowing();
|
||||
void FrameProcess(FrameEventArgs eventArgs);
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
||||
31
Robust.Client/Graphics/IClydeAudioSource.cs
Normal file
31
Robust.Client/Graphics/IClydeAudioSource.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public interface IClydeAudioSource : IDisposable
|
||||
{
|
||||
void StartPlaying();
|
||||
void StopPlaying();
|
||||
|
||||
bool IsPlaying { get; }
|
||||
|
||||
bool IsLooping { get; set; }
|
||||
bool IsGlobal { get; }
|
||||
|
||||
[MustUseReturnValue]
|
||||
bool SetPosition(Vector2 position);
|
||||
void SetPitch(float pitch);
|
||||
void SetGlobal();
|
||||
void SetVolume(float decibels);
|
||||
void SetVolumeDirect(float gain);
|
||||
void SetMaxDistance(float maxDistance);
|
||||
void SetRolloffFactor(float rolloffFactor);
|
||||
void SetReferenceDistance(float refDistance);
|
||||
void SetOcclusion(float blocks);
|
||||
void SetPlaybackPosition(float seconds);
|
||||
void SetVelocity(Vector2 velocity);
|
||||
}
|
||||
}
|
||||
15
Robust.Client/Graphics/IClydeBufferedAudioSource.cs
Normal file
15
Robust.Client/Graphics/IClydeBufferedAudioSource.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public interface IClydeBufferedAudioSource : IClydeAudioSource
|
||||
{
|
||||
int SampleRate { get; set; }
|
||||
int GetNumberOfBuffersProcessed();
|
||||
void GetBuffersProcessed(Span<int> handles);
|
||||
void WriteBuffer(int handle, ReadOnlySpan<ushort> data);
|
||||
void WriteBuffer(int handle, ReadOnlySpan<float> data);
|
||||
void QueueBuffers(ReadOnlySpan<int> handles);
|
||||
void EmptyBuffers();
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,32 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
[PublicAPI]
|
||||
public interface IOverlayManager
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
bool AddOverlay(Overlay overlay);
|
||||
|
||||
bool RemoveOverlay(Overlay overlay);
|
||||
bool RemoveOverlay(Type overlayClass);
|
||||
bool RemoveOverlay<T>() where T : Overlay;
|
||||
bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay);
|
||||
bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay;
|
||||
[PublicAPI]
|
||||
public interface IOverlayManager
|
||||
{
|
||||
bool AddOverlay(Overlay overlay);
|
||||
|
||||
Overlay GetOverlay(Type overlayClass);
|
||||
T GetOverlay<T>() where T : Overlay;
|
||||
bool RemoveOverlay(Overlay overlay);
|
||||
bool RemoveOverlay(Type overlayClass);
|
||||
bool RemoveOverlay<T>() where T : Overlay;
|
||||
|
||||
bool HasOverlay(Type overlayClass);
|
||||
bool HasOverlay<T>() where T : Overlay;
|
||||
bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay);
|
||||
bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay;
|
||||
|
||||
IEnumerable<Overlay> AllOverlays { get; }
|
||||
}
|
||||
|
||||
internal interface IOverlayManagerInternal : IOverlayManager
|
||||
{
|
||||
void FrameUpdate(FrameEventArgs args);
|
||||
Overlay GetOverlay(Type overlayClass);
|
||||
T GetOverlay<T>() where T : Overlay;
|
||||
|
||||
bool HasOverlay(Type overlayClass);
|
||||
bool HasOverlay<T>() where T : Overlay;
|
||||
|
||||
IEnumerable<Overlay> AllOverlays { get; }
|
||||
}
|
||||
|
||||
internal interface IOverlayManagerInternal : IOverlayManager
|
||||
{
|
||||
void FrameUpdate(FrameEventArgs args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,106 +6,107 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
|
||||
|
||||
public void FrameUpdate(FrameEventArgs args)
|
||||
internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
{
|
||||
foreach (var overlay in _overlays.Values)
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
|
||||
|
||||
public void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
overlay.FrameUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddOverlay(Overlay overlay)
|
||||
{
|
||||
if (_overlays.ContainsKey(overlay.GetType()))
|
||||
return false;
|
||||
_overlays.Add(overlay.GetType(), overlay);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveOverlay(Type overlayClass)
|
||||
{
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
{
|
||||
_logger.Error($"RemoveOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
return false;
|
||||
foreach (var overlay in _overlays.Values)
|
||||
{
|
||||
overlay.FrameUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
return _overlays.Remove(overlayClass);
|
||||
}
|
||||
|
||||
public bool RemoveOverlay<T>() where T : Overlay
|
||||
{
|
||||
return RemoveOverlay(typeof(T));
|
||||
}
|
||||
|
||||
public bool RemoveOverlay(Overlay overlay)
|
||||
{
|
||||
return _overlays.Remove(overlay.GetType());
|
||||
}
|
||||
|
||||
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
|
||||
{
|
||||
overlay = null;
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
public bool AddOverlay(Overlay overlay)
|
||||
{
|
||||
_logger.Error($"TryGetOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _overlays.TryGetValue(overlayClass, out overlay);
|
||||
}
|
||||
|
||||
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay
|
||||
{
|
||||
overlay = null;
|
||||
if (_overlays.TryGetValue(typeof(T), out Overlay? toReturn))
|
||||
{
|
||||
overlay = (T)toReturn;
|
||||
if (_overlays.ContainsKey(overlay.GetType()))
|
||||
return false;
|
||||
_overlays.Add(overlay.GetType(), overlay);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Overlay GetOverlay(Type overlayClass)
|
||||
{
|
||||
return _overlays[overlayClass];
|
||||
}
|
||||
|
||||
public T GetOverlay<T>() where T : Overlay
|
||||
{
|
||||
return (T)_overlays[typeof(T)];
|
||||
}
|
||||
|
||||
public bool HasOverlay(Type overlayClass)
|
||||
{
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
public bool RemoveOverlay(Type overlayClass)
|
||||
{
|
||||
_logger.Error($"HasOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
{
|
||||
_logger.Error($"RemoveOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _overlays.Remove(overlayClass);
|
||||
}
|
||||
|
||||
return _overlays.ContainsKey(overlayClass);
|
||||
}
|
||||
public bool RemoveOverlay<T>() where T : Overlay
|
||||
{
|
||||
return RemoveOverlay(typeof(T));
|
||||
}
|
||||
|
||||
public bool HasOverlay<T>() where T : Overlay
|
||||
{
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
public bool RemoveOverlay(Overlay overlay)
|
||||
{
|
||||
return _overlays.Remove(overlay.GetType());
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_logger = _logMan.GetSawmill("overlay");
|
||||
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
|
||||
{
|
||||
overlay = null;
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
{
|
||||
_logger.Error($"TryGetOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _overlays.TryGetValue(overlayClass, out overlay);
|
||||
}
|
||||
|
||||
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay
|
||||
{
|
||||
overlay = null;
|
||||
if (_overlays.TryGetValue(typeof(T), out Overlay? toReturn))
|
||||
{
|
||||
overlay = (T)toReturn;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Overlay GetOverlay(Type overlayClass)
|
||||
{
|
||||
return _overlays[overlayClass];
|
||||
}
|
||||
|
||||
public T GetOverlay<T>() where T : Overlay
|
||||
{
|
||||
return (T)_overlays[typeof(T)];
|
||||
}
|
||||
|
||||
public bool HasOverlay(Type overlayClass)
|
||||
{
|
||||
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
|
||||
{
|
||||
_logger.Error($"HasOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!");
|
||||
}
|
||||
|
||||
return _overlays.ContainsKey(overlayClass);
|
||||
}
|
||||
|
||||
public bool HasOverlay<T>() where T : Overlay
|
||||
{
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_logger = _logMan.GetSawmill("overlay");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Robust.Client.Graphics
|
||||
if (_path == null)
|
||||
throw new InvalidOperationException("Source shaders must specify a source file.");
|
||||
|
||||
_source = IoCManager.Resolve<IClientResourceCache>().GetResource<ShaderSourceResource>(_path.Value);
|
||||
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(_path.Value);
|
||||
|
||||
if (_paramMapping != null)
|
||||
{
|
||||
@@ -142,7 +142,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
case "canvas":
|
||||
Kind = ShaderKind.Canvas;
|
||||
_source = IoCManager.Resolve<IClientResourceCache>().GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl");
|
||||
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl");
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -23,7 +22,7 @@ namespace Robust.Client.Map
|
||||
{
|
||||
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _manager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private Texture? _tileTextureAtlas;
|
||||
|
||||
@@ -87,7 +86,7 @@ namespace Robust.Client.Map
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _manager.ContentFileRead("/Textures/noTile.png"))
|
||||
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
@@ -111,7 +110,7 @@ namespace Robust.Client.Map
|
||||
// Already know it's not null above
|
||||
var path = def.Sprite!.Value;
|
||||
|
||||
using (var stream = _manager.ContentFileRead(path))
|
||||
using (var stream = _resourceCache.ContentFileRead(path))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] internal readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] internal readonly IClientResourceCache ResourceCache = default!;
|
||||
[Dependency] internal readonly IResourceCache ResourceCache = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] internal readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.Profiling;
|
||||
public sealed class LiveProfileViewControl : Control
|
||||
{
|
||||
[Dependency] private readonly ProfManager _profManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public int MaxDepth { get; set; } = 2;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Configuration;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -11,7 +10,6 @@ using Robust.Client.Player;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.Upload;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -29,7 +27,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IMidiManager _midi = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IAudioInternal _clydeAudio = default!;
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientNetManager _netMan = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
|
||||
36
Robust.Client/ResourceManagement/BaseResource.cs
Normal file
36
Robust.Client/ResourceManagement/BaseResource.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Base resource for the cache.
|
||||
/// </summary>
|
||||
public abstract class BaseResource : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Fallback resource path if this one does not exist.
|
||||
/// </summary>
|
||||
public virtual ResPath? Fallback => null;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this resource.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the resource from the VFS.
|
||||
/// </summary>
|
||||
/// <param name="cache">ResourceCache this resource is being loaded into.</param>
|
||||
/// <param name="path">Path of the resource requested on the VFS.</param>
|
||||
public abstract void Load(IResourceCache cache, ResPath path);
|
||||
|
||||
public virtual void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.ResourceManagement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public interface IClientResourceCache : IResourceCache
|
||||
{
|
||||
// Resource load callbacks so content can hook stuff like click maps.
|
||||
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
|
||||
event Action<RsiLoadedEventArgs> OnRsiLoaded;
|
||||
|
||||
IClyde Clyde { get; }
|
||||
IFontManager FontManager { get; }
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal interface IClientResourceCacheInternal : IClientResourceCache
|
||||
{
|
||||
void TextureLoaded(TextureLoadedEventArgs eventArgs);
|
||||
void RsiLoaded(RsiLoadedEventArgs eventArgs);
|
||||
void PreloadTextures();
|
||||
|
||||
void MountLoaderApi(IResourceManager manager, IFileApi api, string apiPrefix, ResPath? prefix = null);
|
||||
}
|
||||
49
Robust.Client/ResourceManagement/IResourceCache.cs
Normal file
49
Robust.Client/ResourceManagement/IResourceCache.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
public interface IResourceCache : IResourceManager
|
||||
{
|
||||
T GetResource<T>(string path, bool useFallback = true)
|
||||
where T : BaseResource, new();
|
||||
|
||||
T GetResource<T>(ResPath path, bool useFallback = true)
|
||||
where T : BaseResource, new();
|
||||
|
||||
bool TryGetResource<T>(string path, [NotNullWhen(true)] out T? resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void ReloadResource<T>(string path)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void ReloadResource<T>(ResPath path)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void CacheResource<T>(string path, T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void CacheResource<T>(ResPath path, T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
T GetFallback<T>()
|
||||
where T : BaseResource, new();
|
||||
|
||||
IEnumerable<KeyValuePair<ResPath, T>> GetAllResources<T>() where T : BaseResource, new();
|
||||
|
||||
// Resource load callbacks so content can hook stuff like click maps.
|
||||
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
|
||||
event Action<RsiLoadedEventArgs> OnRsiLoaded;
|
||||
|
||||
IClyde Clyde { get; }
|
||||
IClydeAudio ClydeAudio { get; }
|
||||
IFontManager FontManager { get; }
|
||||
}
|
||||
}
|
||||
15
Robust.Client/ResourceManagement/IResourceCacheInternal.cs
Normal file
15
Robust.Client/ResourceManagement/IResourceCacheInternal.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
internal interface IResourceCacheInternal : IResourceCache, IResourceManagerInternal
|
||||
{
|
||||
void TextureLoaded(TextureLoadedEventArgs eventArgs);
|
||||
void RsiLoaded(RsiLoadedEventArgs eventArgs);
|
||||
|
||||
void MountLoaderApi(IFileApi api, string apiPrefix, ResPath? prefix=null);
|
||||
void PreloadTextures();
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,6 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
internal partial class ResourceCache
|
||||
{
|
||||
public void MountLoaderApi(IResourceManager manager, IFileApi api, string apiPrefix, ResPath? prefix = null)
|
||||
{
|
||||
prefix ??= ResPath.Root;
|
||||
var root = new LoaderApiLoader(api, apiPrefix);
|
||||
manager.AddRoot(prefix.Value, root);
|
||||
}
|
||||
|
||||
private sealed class LoaderApiLoader : IContentRoot
|
||||
{
|
||||
private readonly IFileApi _api;
|
||||
|
||||
@@ -3,13 +3,10 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -22,8 +19,7 @@ namespace Robust.Client.ResourceManagement
|
||||
internal partial class ResourceCache
|
||||
{
|
||||
[field: Dependency] public IClyde Clyde { get; } = default!;
|
||||
[field: Dependency] public IAudioInternal ClydeAudio { get; } = default!;
|
||||
[Dependency] private readonly IResourceManager _manager = default!;
|
||||
[field: Dependency] public IClydeAudio ClydeAudio { get; } = default!;
|
||||
[field: Dependency] public IFontManager FontManager { get; } = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
@@ -48,7 +44,7 @@ namespace Robust.Client.ResourceManagement
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resList = GetTypeDict<TextureResource>();
|
||||
|
||||
var texList = _manager.ContentFindFiles("/Textures/")
|
||||
var texList = ContentFindFiles("/Textures/")
|
||||
// Skip PNG files inside RSIs.
|
||||
.Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p))
|
||||
.Select(p => new TextureResource.LoadStepData {Path = p})
|
||||
@@ -58,7 +54,7 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
try
|
||||
{
|
||||
TextureResource.LoadPreTexture(_manager, data);
|
||||
TextureResource.LoadPreTexture(this, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -120,7 +116,7 @@ namespace Robust.Client.ResourceManagement
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resList = GetTypeDict<RSIResource>();
|
||||
|
||||
var rsiList = _manager.ContentFindFiles("/Textures/")
|
||||
var rsiList = ContentFindFiles("/Textures/")
|
||||
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
|
||||
.Select(c => c.Directory)
|
||||
.Where(p => !resList.ContainsKey(p))
|
||||
@@ -131,7 +127,7 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
try
|
||||
{
|
||||
RSIResource.LoadPreTexture(_manager, data);
|
||||
RSIResource.LoadPreTexture(this, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,221 @@
|
||||
using System;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.LoaderApi;
|
||||
|
||||
namespace Robust.Client.ResourceManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Handles caching of <see cref="BaseResource"/>
|
||||
/// </summary>
|
||||
internal sealed partial class ResourceCache : SharedResourceCache, IClientResourceCacheInternal, IDisposable
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
public event Action<TextureLoadedEventArgs>? OnRawTextureLoaded;
|
||||
public event Action<RsiLoadedEventArgs>? OnRsiLoaded;
|
||||
|
||||
public void TextureLoaded(TextureLoadedEventArgs eventArgs)
|
||||
internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
|
||||
{
|
||||
OnRawTextureLoaded?.Invoke(eventArgs);
|
||||
}
|
||||
private readonly Dictionary<Type, Dictionary<ResPath, BaseResource>> CachedResources =
|
||||
new();
|
||||
|
||||
public void RsiLoaded(RsiLoadedEventArgs eventArgs)
|
||||
{
|
||||
OnRsiLoaded?.Invoke(eventArgs);
|
||||
private readonly Dictionary<Type, BaseResource> _fallbacks = new();
|
||||
|
||||
public T GetResource<T>(string path, bool useFallback = true) where T : BaseResource, new()
|
||||
{
|
||||
return GetResource<T>(new ResPath(path), useFallback);
|
||||
}
|
||||
|
||||
public T GetResource<T>(ResPath path, bool useFallback = true) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
if (cache.TryGetValue(path, out var cached))
|
||||
{
|
||||
return (T) cached;
|
||||
}
|
||||
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
_resource.Load(this, path);
|
||||
cache[path] = _resource;
|
||||
return _resource;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (useFallback && _resource.Fallback != null)
|
||||
{
|
||||
Logger.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}");
|
||||
return GetResource<T>(_resource.Fallback.Value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(string path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new()
|
||||
{
|
||||
return TryGetResource(new ResPath(path), out resource);
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
if (cache.TryGetValue(path, out var cached))
|
||||
{
|
||||
resource = (T) cached;
|
||||
return true;
|
||||
}
|
||||
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
_resource.Load(this, path);
|
||||
resource = _resource;
|
||||
cache[path] = resource;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadResource<T>(string path) where T : BaseResource, new()
|
||||
{
|
||||
ReloadResource<T>(new ResPath(path));
|
||||
}
|
||||
|
||||
public void ReloadResource<T>(ResPath path) where T : BaseResource, new()
|
||||
{
|
||||
var cache = GetTypeDict<T>();
|
||||
|
||||
if (!cache.TryGetValue(path, out var res))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
res.Reload(this, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasResource<T>(string path) where T : BaseResource, new()
|
||||
{
|
||||
return HasResource<T>(new ResPath(path));
|
||||
}
|
||||
|
||||
public bool HasResource<T>(ResPath path) where T : BaseResource, new()
|
||||
{
|
||||
return TryGetResource<T>(path, out var _);
|
||||
}
|
||||
|
||||
public void CacheResource<T>(string path, T resource) where T : BaseResource, new()
|
||||
{
|
||||
CacheResource(new ResPath(path), resource);
|
||||
}
|
||||
|
||||
public void CacheResource<T>(ResPath path, T resource) where T : BaseResource, new()
|
||||
{
|
||||
GetTypeDict<T>()[path] = resource;
|
||||
}
|
||||
|
||||
public T GetFallback<T>() where T : BaseResource, new()
|
||||
{
|
||||
if (_fallbacks.TryGetValue(typeof(T), out var fallback))
|
||||
{
|
||||
return (T) fallback;
|
||||
}
|
||||
|
||||
var res = new T();
|
||||
if (res.Fallback == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Resource of type '{typeof(T)}' has no fallback.");
|
||||
}
|
||||
|
||||
fallback = GetResource<T>(res.Fallback.Value, useFallback: false);
|
||||
_fallbacks.Add(typeof(T), fallback);
|
||||
return (T) fallback;
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<ResPath, T>> GetAllResources<T>() where T : BaseResource, new()
|
||||
{
|
||||
return GetTypeDict<T>().Select(p => new KeyValuePair<ResPath, T>(p.Key, (T) p.Value));
|
||||
}
|
||||
|
||||
public event Action<TextureLoadedEventArgs>? OnRawTextureLoaded;
|
||||
public event Action<RsiLoadedEventArgs>? OnRsiLoaded;
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
foreach (var res in CachedResources.Values.SelectMany(dict => dict.Values))
|
||||
{
|
||||
res.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
~ResourceCache()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion IDisposable Members
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Dictionary<ResPath, BaseResource> GetTypeDict<T>()
|
||||
{
|
||||
if (!CachedResources.TryGetValue(typeof(T), out var ret))
|
||||
{
|
||||
ret = new Dictionary<ResPath, BaseResource>();
|
||||
CachedResources.Add(typeof(T), ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void TextureLoaded(TextureLoadedEventArgs eventArgs)
|
||||
{
|
||||
OnRawTextureLoaded?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
public void RsiLoaded(RsiLoadedEventArgs eventArgs)
|
||||
{
|
||||
OnRsiLoaded?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
public void MountLoaderApi(IFileApi api, string apiPrefix, ResPath? prefix=null)
|
||||
{
|
||||
prefix ??= ResPath.Root;
|
||||
var root = new LoaderApiLoader(api, apiPrefix);
|
||||
AddRoot(prefix.Value, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
using System.IO;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
public sealed class AudioResource : BaseResource
|
||||
{
|
||||
public AudioStream AudioStream { get; private set; } = default!;
|
||||
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
if (!cache.ContentFileExists(path))
|
||||
{
|
||||
throw new FileNotFoundException("Content file does not exist for audio sample.");
|
||||
}
|
||||
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
{
|
||||
if (path.Extension == "ogg")
|
||||
{
|
||||
AudioStream = cache.ClydeAudio.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
}
|
||||
else if (path.Extension == "wav")
|
||||
{
|
||||
AudioStream = cache.ClydeAudio.LoadAudioWav(fileStream, path.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator AudioStream(AudioResource res)
|
||||
{
|
||||
return res.AudioStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.IO;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement
|
||||
@@ -11,17 +9,16 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
internal IFontFaceHandle FontFaceHandle { get; private set; } = default!;
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
|
||||
if (!dependencies.Resolve<IResourceManager>().TryContentFileRead(path, out var stream))
|
||||
if (!cache.TryContentFileRead(path, out var stream))
|
||||
{
|
||||
throw new FileNotFoundException("Content file does not exist for font");
|
||||
}
|
||||
|
||||
using (stream)
|
||||
{
|
||||
FontFaceHandle = dependencies.Resolve<IFontManagerInternal>().Load(stream);
|
||||
FontFaceHandle = ((IFontManagerInternal)cache.FontManager).Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.Resources;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -36,27 +34,26 @@ namespace Robust.Client.ResourceManagement
|
||||
/// </summary>
|
||||
public const uint MAXIMUM_RSI_VERSION = RsiLoading.MAXIMUM_RSI_VERSION;
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var loadStepData = new LoadStepData {Path = path};
|
||||
var manager = dependencies.Resolve<IResourceManager>();
|
||||
LoadPreTexture(manager, loadStepData);
|
||||
LoadPreTexture(cache, loadStepData);
|
||||
|
||||
loadStepData.AtlasTexture = dependencies.Resolve<IClyde>().LoadTextureFromImage(
|
||||
loadStepData.AtlasTexture = cache.Clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString());
|
||||
|
||||
LoadPostTexture(loadStepData);
|
||||
LoadFinish(dependencies.Resolve<IClientResourceCacheInternal>(), loadStepData);
|
||||
LoadFinish(cache, loadStepData);
|
||||
|
||||
loadStepData.AtlasSheet.Dispose();
|
||||
}
|
||||
|
||||
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
|
||||
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
|
||||
{
|
||||
var manifestPath = data.Path / "meta.json";
|
||||
RsiLoading.RsiMetadata metadata;
|
||||
using (var manifestFile = manager.ContentFileRead(manifestPath))
|
||||
using (var manifestFile = cache.ContentFileRead(manifestPath))
|
||||
{
|
||||
metadata = RsiLoading.LoadRsiMetadata(manifestFile);
|
||||
}
|
||||
@@ -89,7 +86,7 @@ namespace Robust.Client.ResourceManagement
|
||||
var stateObject = metadata.States[index];
|
||||
// Load image from disk.
|
||||
var texPath = data.Path / (stateObject.StateId + ".png");
|
||||
using (var stream = manager.ContentFileRead(texPath))
|
||||
using (var stream = cache.ContentFileRead(texPath))
|
||||
{
|
||||
reg.Src = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
@@ -215,10 +212,14 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadFinish(IClientResourceCacheInternal cache, LoadStepData data)
|
||||
internal void LoadFinish(IResourceCache cache, LoadStepData data)
|
||||
{
|
||||
RSI = data.Rsi;
|
||||
cache.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
|
||||
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
{
|
||||
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -21,31 +20,28 @@ namespace Robust.Client.ResourceManagement
|
||||
[ViewVariables]
|
||||
internal ParsedShader ParsedShader { get; private set; } = default!;
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var manager = dependencies.Resolve<IResourceManager>();
|
||||
|
||||
using (var stream = manager.ContentFileRead(path))
|
||||
using (var stream = cache.ContentFileRead(path))
|
||||
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
ParsedShader = ShaderParser.Parse(reader, manager);
|
||||
ParsedShader = ShaderParser.Parse(reader, cache);
|
||||
}
|
||||
|
||||
ClydeHandle = dependencies.Resolve<IClydeInternal>().LoadShader(ParsedShader, path.ToString());
|
||||
ClydeHandle = ((IClydeInternal)cache.Clyde).LoadShader(ParsedShader, path.ToString());
|
||||
}
|
||||
|
||||
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
|
||||
public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
|
||||
{
|
||||
var manager = dependencies.Resolve<IResourceManager>();
|
||||
ct = ct != default ? ct : new CancellationTokenSource(30000).Token;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = manager.ContentFileRead(path);
|
||||
using var stream = cache.ContentFileRead(path);
|
||||
using var reader = new StreamReader(stream, EncodingHelpers.UTF8);
|
||||
ParsedShader = ShaderParser.Parse(reader, manager);
|
||||
ParsedShader = ShaderParser.Parse(reader, cache);
|
||||
break;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
@@ -61,7 +57,7 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.Resolve<IClydeInternal>().ReloadShader(ClydeHandle, ParsedShader);
|
||||
((IClydeInternal)cache.Clyde).ReloadShader(ClydeHandle, ParsedShader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -21,7 +19,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public Texture Texture => _texture;
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
if (path.Directory.Filename.EndsWith(".rsi"))
|
||||
{
|
||||
@@ -33,12 +31,12 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
var data = new LoadStepData {Path = path};
|
||||
|
||||
LoadPreTexture(dependencies.Resolve<IResourceManager>(), data);
|
||||
LoadTexture(dependencies.Resolve<IClyde>(), data);
|
||||
LoadFinish(dependencies.Resolve<IClientResourceCache>(), data);
|
||||
LoadPreTexture(cache, data);
|
||||
LoadTexture(cache.Clyde, data);
|
||||
LoadFinish(cache, data);
|
||||
}
|
||||
|
||||
internal static void LoadPreTexture(IResourceManager cache, LoadStepData data)
|
||||
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
|
||||
{
|
||||
using (var stream = cache.ContentFileRead(data.Path))
|
||||
{
|
||||
@@ -53,11 +51,11 @@ namespace Robust.Client.ResourceManagement
|
||||
data.Texture = clyde.LoadTextureFromImage(data.Image, data.Path.ToString(), data.LoadParameters);
|
||||
}
|
||||
|
||||
internal void LoadFinish(IClientResourceCache cache, LoadStepData data)
|
||||
internal void LoadFinish(IResourceCache cache, LoadStepData data)
|
||||
{
|
||||
_texture = data.Texture;
|
||||
|
||||
if (cache is IClientResourceCacheInternal cacheInternal)
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
{
|
||||
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(data.Path, data.Image, this));
|
||||
}
|
||||
@@ -65,7 +63,7 @@ namespace Robust.Client.ResourceManagement
|
||||
data.Image.Dispose();
|
||||
}
|
||||
|
||||
private static TextureLoadParameters? TryLoadTextureParameters(IResourceManager cache, ResPath path)
|
||||
private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var metaPath = path.WithName(path.Filename + ".yml");
|
||||
if (cache.TryContentFileRead(metaPath, out var stream))
|
||||
@@ -92,11 +90,12 @@ namespace Robust.Client.ResourceManagement
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
|
||||
public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
|
||||
{
|
||||
var data = new LoadStepData {Path = path};
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
LoadPreTexture(dependencies.Resolve<IResourceManager>(), data);
|
||||
var data = new LoadStepData {Path = path};
|
||||
LoadPreTexture(cache, data);
|
||||
|
||||
if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height)
|
||||
{
|
||||
@@ -107,7 +106,7 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
// Dimensions do not match, make new texture.
|
||||
_texture.Dispose();
|
||||
LoadTexture(dependencies.Resolve<IClyde>(), data);
|
||||
LoadTexture(clyde, data);
|
||||
_texture = data.Texture;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class ClientSpriteSpecifierSerializer : SpriteSpecifierSerializer
|
||||
return new ErrorNode(node, "Sprite specifier has missing/invalid state node");
|
||||
}
|
||||
|
||||
var res = dependencies.Resolve<IClientResourceCache>();
|
||||
var res = dependencies.Resolve<IResourceCache>();
|
||||
var rsiPath = TextureRoot / valuePathNode.Value;
|
||||
if (!res.TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class EntitySpawningUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resources = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
|
||||
private EntitySpawnWindow? _window;
|
||||
private readonly List<EntityPrototype> _shownEntities = new();
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.UserInterface.Controllers.Implementations;
|
||||
public sealed class TileSpawningUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resources = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tiles = default!;
|
||||
|
||||
private TileSpawnWindow? _window;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
set
|
||||
{
|
||||
Texture = IoCManager.Resolve<IClientResourceCache>().GetResource<TextureResource>(value);
|
||||
Texture = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(value);
|
||||
_texturePath = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Client.UserInterface
|
||||
TabContainer.SetTabTitle(Perf, "Profiling");
|
||||
|
||||
Stylesheet =
|
||||
new DefaultStylesheet(IoCManager.Resolve<IClientResourceCache>(), IoCManager.Resolve<IUserInterfaceManager>()).Stylesheet;
|
||||
new DefaultStylesheet(IoCManager.Resolve<IResourceCache>(), IoCManager.Resolve<IUserInterfaceManager>()).Stylesheet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public sealed class BoldItalicTag : IMarkupTag
|
||||
{
|
||||
public const string BoldItalicFont = "DefaultBoldItalic";
|
||||
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "bolditalic";
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class BoldTag : IMarkupTag
|
||||
{
|
||||
public const string BoldFont = "DefaultBold";
|
||||
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "bold";
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class FontTag : IMarkupTag
|
||||
public const string DefaultFont = "Default";
|
||||
public const int DefaultSize = 12;
|
||||
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "font";
|
||||
@@ -43,7 +43,7 @@ public sealed class FontTag : IMarkupTag
|
||||
public static Font CreateFont(
|
||||
Stack<Font> contextFontStack,
|
||||
MarkupNode node,
|
||||
IClientResourceCache cache,
|
||||
IResourceCache cache,
|
||||
IPrototypeManager prototypeManager,
|
||||
string fontId)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class HeadingTag : IMarkupTag
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "head";
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class ItalicTag : IMarkupTag
|
||||
{
|
||||
public const string ItalicFont = "DefaultItalic";
|
||||
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "italic";
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class DefaultStylesheet
|
||||
{
|
||||
public Stylesheet Stylesheet { get; private set; } = default!;
|
||||
|
||||
public DefaultStylesheet(IClientResourceCache res, IUserInterfaceManager userInterfaceManager)
|
||||
public DefaultStylesheet(IResourceCache res, IUserInterfaceManager userInterfaceManager)
|
||||
{
|
||||
var notoSansFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf");
|
||||
var notoSansFont12 = new VectorFont(notoSansFont, 12);
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -19,7 +18,7 @@ namespace Robust.Client.UserInterface.Themes;
|
||||
[Prototype("uiTheme")]
|
||||
public sealed class UITheme : IPrototype
|
||||
{
|
||||
private IClientResourceCache? _cache;
|
||||
private IResourceCache? _cache;
|
||||
private IUserInterfaceManager? _uiMan;
|
||||
|
||||
//this is used for ease of access
|
||||
@@ -38,11 +37,10 @@ public sealed class UITheme : IPrototype
|
||||
public Dictionary<string, Color>? Colors { get; }
|
||||
public ResPath Path => _path == default ? new ResPath(DefaultPath+"/"+ID) : _path;
|
||||
|
||||
private void ValidateFilePath(IResourceManager manager)
|
||||
private void ValidateFilePath(IResourceCache resourceCache)
|
||||
{
|
||||
var foundFolders = manager.ContentFindFiles(Path.ToRootedPath());
|
||||
if (!foundFolders.Any())
|
||||
throw new Exception("UITheme: "+ID+" not found in resources!");
|
||||
var foundFolders = resourceCache.ContentFindFiles(Path.ToRootedPath());
|
||||
if (!foundFolders.Any()) throw new Exception("UITheme: "+ID+" not found in resources!");
|
||||
}
|
||||
|
||||
public Texture ResolveTexture(string texturePath)
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Client.UserInterface
|
||||
[Dependency] private readonly IFontManager _fontManager = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Client.Utility
|
||||
/// </summary>
|
||||
public static class SpriteSpecifierExt
|
||||
{
|
||||
public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IClientResourceCache cache)
|
||||
public static Texture GetTexture(this SpriteSpecifier.Texture texSpecifier, IResourceCache cache)
|
||||
{
|
||||
return cache
|
||||
.GetResource<TextureResource>(SpriteSpecifierSerializer.TextureRoot / texSpecifier.TexturePath)
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client.Utility
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IClientResourceCache cache)
|
||||
public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache)
|
||||
{
|
||||
if (!cache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / rsiSpecifier.RsiPath, out var theRsi))
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace Robust.Client.Utility
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier)
|
||||
{
|
||||
var resC = IoCManager.Resolve<IClientResourceCache>();
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
switch (specifier)
|
||||
{
|
||||
case SpriteSpecifier.Texture tex:
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Audio;
|
||||
|
||||
public sealed partial class AudioSystem
|
||||
{
|
||||
protected override void InitializeEffect()
|
||||
{
|
||||
base.InitializeEffect();
|
||||
SubscribeLocalEvent<AudioEffectComponent, ComponentAdd>(OnEffectAdd);
|
||||
SubscribeLocalEvent<AudioAuxiliaryComponent, ComponentAdd>(OnAuxiliaryAdd);
|
||||
}
|
||||
|
||||
private void ShutdownEffect()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads all <see cref="AudioPresetPrototype"/> entities.
|
||||
/// </summary>
|
||||
public void ReloadPresets()
|
||||
{
|
||||
var query = AllEntityQuery<AudioPresetComponent>();
|
||||
var toDelete = new ValueList<EntityUid>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
toDelete.Add(uid);
|
||||
}
|
||||
|
||||
foreach (var ent in toDelete)
|
||||
{
|
||||
Del(ent);
|
||||
}
|
||||
|
||||
foreach (var proto in ProtoMan.EnumeratePrototypes<AudioPresetPrototype>())
|
||||
{
|
||||
if (!proto.CreateAuxiliary)
|
||||
continue;
|
||||
|
||||
var effect = CreateEffect();
|
||||
var aux = CreateAuxiliary();
|
||||
SetEffectPreset(effect.Entity, effect.Component, proto);
|
||||
SetEffect(aux.Entity, aux.Component, effect.Entity);
|
||||
var preset = AddComp<AudioPresetComponent>(aux.Entity);
|
||||
_auxiliaries.Remove(preset.Preset);
|
||||
preset.Preset = proto.ID;
|
||||
_auxiliaries[preset.Preset] = aux.Entity;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEffectAdd(EntityUid uid, AudioEffectComponent component, ComponentAdd args)
|
||||
{
|
||||
component.Effect = new DummyAudioEffect();
|
||||
}
|
||||
|
||||
private void OnAuxiliaryAdd(EntityUid uid, AudioAuxiliaryComponent component, ComponentAdd args)
|
||||
{
|
||||
component.Auxiliary = new DummyAuxiliaryAudio();
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioAuxiliaryComponent Component) CreateAuxiliary()
|
||||
{
|
||||
var (ent, comp) = base.CreateAuxiliary();
|
||||
_pvs.AddGlobalOverride(GetNetEntity(ent));
|
||||
return (ent, comp);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioEffectComponent Component) CreateEffect()
|
||||
{
|
||||
var (ent, comp) = base.CreateEffect();
|
||||
_pvs.AddGlobalOverride(GetNetEntity(ent));
|
||||
return (ent, comp);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Audio;
|
||||
|
||||
public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AudioComponent, ComponentStartup>(OnAudioStartup);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ShutdownEffect();
|
||||
}
|
||||
|
||||
private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentStartup args)
|
||||
{
|
||||
component.Source = new DummyAudioSource();
|
||||
}
|
||||
|
||||
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
|
||||
{
|
||||
var count = filter.Count;
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var nent = GetNetEntity(uid);
|
||||
_pvs.AddSessionOverrides(nent, filter);
|
||||
|
||||
var ents = new HashSet<EntityUid>(count);
|
||||
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
var ent = session.AttachedEntity;
|
||||
|
||||
if (ent == null)
|
||||
continue;
|
||||
|
||||
ents.Add(ent.Value);
|
||||
}
|
||||
|
||||
DebugTools.Assert(component.IncludedEntities == null);
|
||||
component.IncludedEntities = ents;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
var entity = Spawn("Audio", MapCoordinates.Nullspace);
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
AddAudioFilter(entity, audio, playerFilter);
|
||||
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
return null;
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
AddAudioFilter(entity, audio, playerFilter);
|
||||
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
return null;
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
var entity = Spawn("Audio", coordinates);
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
AddAudioFilter(entity, audio, playerFilter);
|
||||
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
var entity = Spawn("Audio", coordinates);
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
|
||||
return (entity, audio);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var audio = PlayPvs(GetSound(sound), source, audioParams ?? sound.Params);
|
||||
|
||||
if (audio == null)
|
||||
return null;
|
||||
|
||||
audio.Value.Component.ExcludedEntity = user;
|
||||
return audio;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var audio = PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
|
||||
if (audio == null)
|
||||
return null;
|
||||
|
||||
audio.Value.Component.ExcludedEntity = user;
|
||||
return audio;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayGlobal(filename, actor.PlayerSession, audioParams);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
176
Robust.Server/GameObjects/EntitySystems/AudioSystem.cs
Normal file
176
Robust.Server/GameObjects/EntitySystems/AudioSystem.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
[UsedImplicitly]
|
||||
public sealed class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
|
||||
private uint _streamIndex;
|
||||
|
||||
private sealed class AudioSourceServer : IPlayingAudioStream
|
||||
{
|
||||
private readonly uint _id;
|
||||
private readonly AudioSystem _audioSystem;
|
||||
private readonly IEnumerable<ICommonSession>? _sessions;
|
||||
|
||||
internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable<ICommonSession>? sessions = null)
|
||||
{
|
||||
_audioSystem = parent;
|
||||
_id = identifier;
|
||||
_sessions = sessions;
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
_audioSystem.InternalStop(_id, _sessions);
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalStop(uint id, IEnumerable<ICommonSession>? sessions = null)
|
||||
{
|
||||
var msg = new StopAudioMessageClient
|
||||
{
|
||||
Identifier = id
|
||||
};
|
||||
|
||||
if (sessions == null)
|
||||
RaiseNetworkEvent(msg);
|
||||
else
|
||||
{
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
RaiseNetworkEvent(msg, session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private uint CacheIdentifier()
|
||||
{
|
||||
return unchecked(_streamIndex++);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
var id = CacheIdentifier();
|
||||
var msg = new PlayAudioGlobalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
AudioParams = audioParams ?? AudioParams.Default,
|
||||
Identifier = id
|
||||
};
|
||||
|
||||
RaiseNetworkEvent(msg, playerFilter, recordReplay);
|
||||
|
||||
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if(!EntityManager.TryGetComponent<TransformComponent>(uid, out var transform))
|
||||
return null;
|
||||
|
||||
var id = CacheIdentifier();
|
||||
|
||||
var fallbackCoordinates = GetFallbackCoordinates(transform.MapPosition);
|
||||
|
||||
var msg = new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = GetNetCoordinates(transform.Coordinates),
|
||||
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
|
||||
NetEntity = GetNetEntity(uid),
|
||||
AudioParams = audioParams ?? AudioParams.Default,
|
||||
Identifier = id,
|
||||
};
|
||||
|
||||
RaiseNetworkEvent(msg, playerFilter, recordReplay);
|
||||
|
||||
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
var id = CacheIdentifier();
|
||||
|
||||
var fallbackCoordinates = GetFallbackCoordinates(coordinates.ToMap(EntityManager, _transform));
|
||||
|
||||
var msg = new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
|
||||
AudioParams = audioParams ?? AudioParams.Default,
|
||||
Identifier = id
|
||||
};
|
||||
|
||||
RaiseNetworkEvent(msg, playerFilter, recordReplay);
|
||||
|
||||
return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var filter = Filter.Pvs(source, entityManager: EntityManager, playerManager: PlayerManager, cfgManager: CfgManager).RemoveWhereAttachedEntity(e => e == user);
|
||||
return Play(sound, filter, source, true, audioParams);
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var filter = Filter.Pvs(coordinates, entityMan: EntityManager, playerMan: PlayerManager).RemoveWhereAttachedEntity(e => e == user);
|
||||
return Play(sound, filter, coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayGlobal(filename, actor.PlayerSession, audioParams);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
|
||||
}
|
||||
|
||||
public override IPlayingAudioStream? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -16,38 +16,30 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
|
||||
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
|
||||
public void AddGlobalOverride(NetEntity entity, bool removeExistingOverride = true, bool recursive = false)
|
||||
public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false)
|
||||
{
|
||||
_pvs.EntityPVSCollection.AddGlobalOverride(entity, removeExistingOverride, recursive);
|
||||
_pvs.EntityPVSCollection.AddGlobalOverride(GetNetEntity(uid), removeExistingOverride, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
|
||||
/// client-specific overrides.
|
||||
/// Used to ensure that an entity is always sent to a specific client. By default this overrides any global or pre-existing
|
||||
/// client-specific overrides. Unlike global overrides, this is always recursive.
|
||||
/// </summary>
|
||||
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
|
||||
public void AddSessionOverride(NetEntity entity, ICommonSession session, bool removeExistingOverride = true)
|
||||
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session, bool removeExistingOverride = true)
|
||||
{
|
||||
_pvs.EntityPVSCollection.AddSessionOverride(entity, session, removeExistingOverride);
|
||||
}
|
||||
|
||||
// 'placeholder'
|
||||
public void AddSessionOverrides(NetEntity entity, Filter filter, bool removeExistingOverride = true)
|
||||
{
|
||||
foreach (var player in filter.Recipients)
|
||||
{
|
||||
AddSessionOverride(entity, player, removeExistingOverride);
|
||||
}
|
||||
_pvs.EntityPVSCollection.AddSessionOverride(GetNetEntity(uid), session, removeExistingOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes any global or client-specific overrides.
|
||||
/// </summary>
|
||||
public void ClearOverride(NetEntity entity, TransformComponent? xform = null)
|
||||
public void ClearOverride(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (!TryGetEntity(entity, out var uid) || !Resolve(uid.Value, ref xform))
|
||||
if (!Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
_pvs.EntityPVSCollection.UpdateIndex(entity, xform.Coordinates, true);
|
||||
_pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), xform.Coordinates, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Robust.Server.Graphics;
|
||||
|
||||
public struct ClydeHandle : IClydeHandle
|
||||
{
|
||||
public long Value => -1;
|
||||
}
|
||||
@@ -15,7 +15,6 @@ using Robust.Server.ServerStatus;
|
||||
using Robust.Server.Upload;
|
||||
using Robust.Server.ViewVariables;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -27,7 +26,6 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.ResourceManagement;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Upload;
|
||||
@@ -66,7 +64,6 @@ namespace Robust.Server
|
||||
deps.Register<IPrototypeManagerInternal, ServerPrototypeManager>();
|
||||
deps.Register<IResourceManager, ResourceManager>();
|
||||
deps.Register<IResourceManagerInternal, ResourceManager>();
|
||||
deps.Register<IResourceCache, SharedResourceCache>();
|
||||
deps.Register<EntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
|
||||
@@ -95,7 +92,6 @@ namespace Robust.Server
|
||||
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IHttpClientHolder, HttpClientHolder>();
|
||||
deps.Register<SharedAudioManager, HeadlessAudioManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,6 @@ namespace Robust.Shared.Maths
|
||||
get => (TopRight - BottomLeft) * 0.5f;
|
||||
}
|
||||
|
||||
public static Box2 Empty = new Box2();
|
||||
|
||||
/// <summary>
|
||||
/// A 1x1 unit box with the origin centered.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -12,7 +11,10 @@ namespace Robust.Shared.Audio
|
||||
{
|
||||
// https://hackage.haskell.org/package/OpenAL-1.7.0.5/docs/Sound-OpenAL-AL-Attenuation.html
|
||||
|
||||
Invalid = 0,
|
||||
/// <summary>
|
||||
/// Default to the overall attenuation. If set project-wide will use InverseDistanceClamped. This is what you typically want for an audio source.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
NoAttenuation = 1 << 0,
|
||||
InverseDistance = 1 << 1,
|
||||
InverseDistanceClamped = 1 << 2,
|
||||
@@ -32,56 +34,54 @@ namespace Robust.Shared.Audio
|
||||
/// <summary>
|
||||
/// The DistanceModel to use for this specific source.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Attenuation Attenuation { get; set; } = Attenuation.LinearDistanceClamped;
|
||||
[DataField("attenuation")]
|
||||
public Attenuation Attenuation { get; set; } = Default.Attenuation;
|
||||
|
||||
/// <summary>
|
||||
/// Base volume to play the audio at, in dB.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("volume")]
|
||||
public float Volume { get; set; } = Default.Volume;
|
||||
|
||||
/// <summary>
|
||||
/// Scale for the audio pitch.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Pitch { get; set; } = Default.Pitch;
|
||||
[DataField("pitchscale")]
|
||||
public float PitchScale { get; set; } = Default.PitchScale;
|
||||
|
||||
/// <summary>
|
||||
/// Audio bus to play on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("busname")]
|
||||
public string BusName { get; set; } = Default.BusName;
|
||||
|
||||
/// <summary>
|
||||
/// Only applies to positional audio.
|
||||
/// The maximum distance from which the audio is hearable.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("maxdistance")]
|
||||
public float MaxDistance { get; set; } = Default.MaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Used for distance attenuation calculations. Set to 0f to make a sound exempt from distance attenuation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("rolloffFactor")]
|
||||
public float RolloffFactor { get; set; } = Default.RolloffFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent of the minimum distance to use for an audio source.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("referenceDistance")]
|
||||
public float ReferenceDistance { get; set; } = Default.ReferenceDistance;
|
||||
|
||||
[DataField]
|
||||
public bool Loop { get; set; } = Default.Loop;
|
||||
[DataField("loop")] public bool Loop { get; set; } = Default.Loop;
|
||||
|
||||
[DataField]
|
||||
public float PlayOffsetSeconds { get; set; } = Default.PlayOffsetSeconds;
|
||||
[DataField("playoffset")] public float PlayOffsetSeconds { get; set; } = Default.PlayOffsetSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this will randomly modify the pitch scale by adding a number drawn from a normal distribution with this deviation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField("variation")]
|
||||
public float? Variation { get; set; } = null;
|
||||
|
||||
// For the max distance value: it's 2000 in Godot, but I assume that's PIXELS due to the 2D positioning,
|
||||
@@ -96,21 +96,21 @@ namespace Robust.Shared.Audio
|
||||
|
||||
public AudioParams(
|
||||
float volume,
|
||||
float pitch,
|
||||
float pitchScale,
|
||||
string busName,
|
||||
float maxDistance,
|
||||
float refDistance,
|
||||
bool loop,
|
||||
float playOffsetSeconds,
|
||||
float? variation = null)
|
||||
: this(volume, pitch, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
|
||||
: this(volume, pitchScale, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
|
||||
{
|
||||
}
|
||||
|
||||
public AudioParams(float volume, float pitch, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
|
||||
public AudioParams(float volume, float pitchScale, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
|
||||
{
|
||||
Volume = volume;
|
||||
Pitch = pitch;
|
||||
PitchScale = pitchScale;
|
||||
BusName = busName;
|
||||
MaxDistance = maxDistance;
|
||||
RolloffFactor = rolloffFactor;
|
||||
@@ -163,7 +163,7 @@ namespace Robust.Shared.Audio
|
||||
public readonly AudioParams WithPitchScale(float pitch)
|
||||
{
|
||||
var me = this;
|
||||
me.Pitch = pitch;
|
||||
me.PitchScale = pitch;
|
||||
return me;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Contains audio defaults to set for sounds.
|
||||
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
|
||||
/// </summary>
|
||||
[Prototype("audioPreset")]
|
||||
public sealed class AudioPresetPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Should the engine automatically create an auxiliary audio effect slot for this.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CreateAuxiliary;
|
||||
|
||||
[DataField]
|
||||
public float Density;
|
||||
|
||||
[DataField]
|
||||
public float Diffusion;
|
||||
|
||||
[DataField]
|
||||
public float Gain;
|
||||
|
||||
[DataField("gainHf")]
|
||||
public float GainHF;
|
||||
|
||||
[DataField("gainLf")]
|
||||
public float GainLF;
|
||||
|
||||
[DataField]
|
||||
public float DecayTime;
|
||||
|
||||
[DataField("decayHfRatio")]
|
||||
public float DecayHFRatio;
|
||||
|
||||
[DataField("decayLfRatio")]
|
||||
public float DecayLFRatio;
|
||||
|
||||
[DataField]
|
||||
public float ReflectionsGain;
|
||||
|
||||
[DataField]
|
||||
public float ReflectionsDelay;
|
||||
|
||||
[DataField]
|
||||
public Vector3 ReflectionsPan;
|
||||
|
||||
[DataField]
|
||||
public float LateReverbGain;
|
||||
|
||||
[DataField]
|
||||
public float LateReverbDelay;
|
||||
|
||||
[DataField]
|
||||
public Vector3 LateReverbPan;
|
||||
|
||||
[DataField]
|
||||
public float EchoTime;
|
||||
|
||||
[DataField]
|
||||
public float EchoDepth;
|
||||
|
||||
[DataField]
|
||||
public float ModulationTime;
|
||||
|
||||
[DataField]
|
||||
public float ModulationDepth;
|
||||
|
||||
[DataField("airAbsorptionGainHf")]
|
||||
public float AirAbsorptionGainHF;
|
||||
|
||||
[DataField("hfReference")]
|
||||
public float HFReference;
|
||||
|
||||
[DataField("lfReference")]
|
||||
public float LFReference;
|
||||
|
||||
[DataField]
|
||||
public float RoomRolloffFactor;
|
||||
|
||||
[DataField("decayHfLimit")]
|
||||
public int DecayHFLimit;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Audio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Can have Audio passed to it to apply effects or filters.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedAudioSystem))]
|
||||
public sealed partial class AudioAuxiliaryComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio effect to attach to this auxiliary audio slot.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Effect;
|
||||
|
||||
[ViewVariables]
|
||||
internal IAuxiliaryAudio Auxiliary = new DummyAuxiliaryAudio();
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Audio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the audio data for an audio entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))]
|
||||
public sealed partial class AudioComponent : Component, IAudioSource
|
||||
{
|
||||
#region Filter
|
||||
|
||||
public override bool SessionSpecific => true;
|
||||
|
||||
/// <summary>
|
||||
/// Used for synchronising audio on client that comes into PVS range.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField]
|
||||
public TimeSpan AudioStart;
|
||||
|
||||
#region Filters
|
||||
|
||||
// Don't need to network these as client doesn't care.
|
||||
|
||||
/// <summary>
|
||||
/// If this sound was predicted do we exclude it from a specific entity.
|
||||
/// Useful for predicted audio.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? ExcludedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// If the sound was filtered what entities were included.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<EntityUid>? IncludedEntities;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
// We can't just start playing on audio creation as we don't have the correct position yet.
|
||||
// As such we'll wait for FrameUpdate before we start playing to avoid the position being cooked.
|
||||
public bool Started = false;
|
||||
|
||||
[AutoNetworkedField]
|
||||
[DataField(required: true)]
|
||||
public string FileName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Audio params. Set this if you want to adjust default volume, max distance, etc.
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField]
|
||||
public AudioParams Params = AudioParams.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Audio source that interacts with OpenAL.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
internal IAudioSource Source = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Auxiliary entity to pass audio to.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Auxiliary;
|
||||
|
||||
/*
|
||||
* Values for IAudioSource stored on the component and sent to IAudioSource as applicable.
|
||||
* Most of these aren't networked as they double AudioParams data and these just interact with IAudioSource.
|
||||
*/
|
||||
|
||||
#region Source
|
||||
|
||||
public void Pause() => Source.Pause();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StartPlaying() => Source.StartPlaying();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopPlaying() => Source.StopPlaying();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Playing"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Playing
|
||||
{
|
||||
get => Source.Playing;
|
||||
set => Source.Playing = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Looping"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Looping
|
||||
{
|
||||
get => Source.Looping;
|
||||
set => Source.Looping = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Global"/>
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
public bool Global { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Pitch"/>
|
||||
/// </summary>
|
||||
public float Pitch
|
||||
{
|
||||
get => Source.Pitch;
|
||||
set => Source.Pitch = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.MaxDistance"/>
|
||||
/// </summary>
|
||||
public float MaxDistance
|
||||
{
|
||||
get => Source.MaxDistance;
|
||||
set => Source.MaxDistance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.RolloffFactor"/>
|
||||
/// </summary>
|
||||
public float RolloffFactor
|
||||
{
|
||||
get => Source.RolloffFactor;
|
||||
set => Source.RolloffFactor = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.ReferenceDistance"/>
|
||||
/// </summary>
|
||||
public float ReferenceDistance
|
||||
{
|
||||
get => Source.ReferenceDistance;
|
||||
set => Source.ReferenceDistance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Position"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not replicated as audio always tracks the entity's position.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public Vector2 Position
|
||||
{
|
||||
get => Source.Position;
|
||||
set => Source.Position = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Volume"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public float Volume
|
||||
{
|
||||
get => Source.Volume;
|
||||
set => Source.Volume = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Gain"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public float Gain
|
||||
{
|
||||
get => Source.Gain;
|
||||
set => Source.Gain = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Occlusion"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public float Occlusion
|
||||
{
|
||||
get => Source.Occlusion;
|
||||
set => Source.Occlusion = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.PlaybackPosition"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float PlaybackPosition
|
||||
{
|
||||
get => Source.PlaybackPosition;
|
||||
set => Source.PlaybackPosition = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Velocity"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not replicated.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public Vector2 Velocity
|
||||
{
|
||||
get => Source.Velocity;
|
||||
set => Source.Velocity = value;
|
||||
}
|
||||
|
||||
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
{
|
||||
Source.SetAuxiliary(audio);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Audio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores OpenAL audio effect data that can be bound to an <see cref="AudioAuxiliaryComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedAudioSystem)), AutoGenerateComponentState]
|
||||
public sealed partial class AudioEffectComponent : Component, IAudioEffect
|
||||
{
|
||||
[ViewVariables]
|
||||
internal IAudioEffect Effect = new DummyAudioEffect();
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Density
|
||||
{
|
||||
get => Effect.Density;
|
||||
set => Effect.Density = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Diffusion
|
||||
{
|
||||
get => Effect.Diffusion;
|
||||
set => Effect.Diffusion = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Gain
|
||||
{
|
||||
get => Effect.Gain;
|
||||
set => Effect.Gain = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float GainHF
|
||||
{
|
||||
get => Effect.GainHF;
|
||||
set => Effect.GainHF = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float GainLF
|
||||
{
|
||||
get => Effect.GainLF;
|
||||
set => Effect.GainLF = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DecayTime
|
||||
{
|
||||
get => Effect.DecayTime;
|
||||
set => Effect.DecayTime = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DecayHFRatio
|
||||
{
|
||||
get => Effect.DecayHFRatio;
|
||||
set => Effect.DecayHFRatio = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DecayLFRatio
|
||||
{
|
||||
get => Effect.DecayLFRatio;
|
||||
set => Effect.DecayLFRatio = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ReflectionsGain
|
||||
{
|
||||
get => Effect.ReflectionsGain;
|
||||
set => Effect.ReflectionsGain = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ReflectionsDelay
|
||||
{
|
||||
get => Effect.ReflectionsDelay;
|
||||
set => Effect.ReflectionsDelay = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public Vector3 ReflectionsPan
|
||||
{
|
||||
get => Effect.ReflectionsPan;
|
||||
set => Effect.ReflectionsPan = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float LateReverbGain
|
||||
{
|
||||
get => Effect.LateReverbGain;
|
||||
set => Effect.LateReverbGain = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float LateReverbDelay
|
||||
{
|
||||
get => Effect.LateReverbDelay;
|
||||
set => Effect.LateReverbDelay = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public Vector3 LateReverbPan
|
||||
{
|
||||
get => Effect.LateReverbPan;
|
||||
set => Effect.LateReverbPan = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float EchoTime
|
||||
{
|
||||
get => Effect.EchoTime;
|
||||
set => Effect.EchoTime = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float EchoDepth
|
||||
{
|
||||
get => Effect.EchoDepth;
|
||||
set => Effect.EchoDepth = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ModulationTime
|
||||
{
|
||||
get => Effect.ModulationTime;
|
||||
set => Effect.ModulationTime = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ModulationDepth
|
||||
{
|
||||
get => Effect.ModulationDepth;
|
||||
set => Effect.ModulationDepth = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float AirAbsorptionGainHF
|
||||
{
|
||||
get => Effect.AirAbsorptionGainHF;
|
||||
set => Effect.AirAbsorptionGainHF = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float HFReference
|
||||
{
|
||||
get => Effect.HFReference;
|
||||
set => Effect.HFReference = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float LFReference
|
||||
{
|
||||
get => Effect.LFReference;
|
||||
set => Effect.LFReference = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public float RoomRolloffFactor
|
||||
{
|
||||
get => Effect.RoomRolloffFactor;
|
||||
set => Effect.RoomRolloffFactor = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField, AutoNetworkedField]
|
||||
public int DecayHFLimit
|
||||
{
|
||||
get => Effect.DecayHFLimit;
|
||||
set => Effect.DecayHFLimit = value;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Shared.Audio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Marks this entity as being spawned for audio presets in case we need to reload.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))]
|
||||
public sealed partial class AudioPresetComponent : Component
|
||||
{
|
||||
[AutoNetworkedField]
|
||||
public string Preset = string.Empty;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Audio.Effects;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class DummyAudioEffect : IAudioEffect
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Density { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Diffusion { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Gain { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GainHF { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GainLF { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayTime { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayHFRatio { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float DecayLFRatio { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ReflectionsGain { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ReflectionsDelay { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector3 ReflectionsPan { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LateReverbGain { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LateReverbDelay { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector3 LateReverbPan { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float EchoTime { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float EchoDepth { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ModulationTime { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ModulationDepth { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float AirAbsorptionGainHF { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float HFReference { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float LFReference { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float RoomRolloffFactor { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int DecayHFLimit { get; set; }
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Robust.Shared.Audio.Effects;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class DummyAuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetEffect(IAudioEffect? effect)
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user