forked from MoonsideGames/MoonWorks
initial 3D audio implementation
parent
7cc14ea819
commit
5eb5027bf1
|
@ -17,6 +17,8 @@ namespace MoonWorks.Audio
|
|||
public float SpeedOfSound = 343.5f;
|
||||
|
||||
internal FAudio.FAudioVoiceSends ReverbSends;
|
||||
|
||||
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
|
||||
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
||||
|
||||
private bool IsDisposed;
|
||||
|
@ -216,6 +218,22 @@ namespace MoonWorks.Audio
|
|||
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
||||
}
|
||||
|
||||
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class AudioEmitter : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_EMITTER emitterData;
|
||||
|
||||
public float DopplerScale
|
||||
{
|
||||
get
|
||||
{
|
||||
return emitterData.DopplerScaler;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value < 0.0f)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("AudioEmitter.DopplerScale must be greater than or equal to 0.0f");
|
||||
}
|
||||
emitterData.DopplerScaler = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Forward
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
emitterData.OrientFront.x,
|
||||
emitterData.OrientFront.y,
|
||||
-emitterData.OrientFront.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
emitterData.OrientFront.x = value.X;
|
||||
emitterData.OrientFront.y = value.Y;
|
||||
emitterData.OrientFront.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
emitterData.Position.x,
|
||||
emitterData.Position.y,
|
||||
-emitterData.Position.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
emitterData.Position.x = value.X;
|
||||
emitterData.Position.y = value.Y;
|
||||
emitterData.Position.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Vector3 Up
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
emitterData.OrientTop.x,
|
||||
emitterData.OrientTop.y,
|
||||
-emitterData.OrientTop.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
emitterData.OrientTop.x = value.X;
|
||||
emitterData.OrientTop.y = value.Y;
|
||||
emitterData.OrientTop.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
emitterData.Velocity.x,
|
||||
emitterData.Velocity.y,
|
||||
-emitterData.Velocity.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
emitterData.Velocity.x = value.X;
|
||||
emitterData.Velocity.y = value.Y;
|
||||
emitterData.Velocity.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] stereoAzimuth = new float[]
|
||||
{
|
||||
0.0f, 0.0f
|
||||
};
|
||||
|
||||
private static readonly GCHandle stereoAzimuthHandle = GCHandle.Alloc(
|
||||
stereoAzimuth,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
public AudioEmitter(AudioDevice device) : base(device)
|
||||
{
|
||||
emitterData = new FAudio.F3DAUDIO_EMITTER();
|
||||
|
||||
DopplerScale = 1f;
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
emitterData.pCone = IntPtr.Zero;
|
||||
emitterData.ChannelCount = 1;
|
||||
emitterData.ChannelRadius = 1.0f;
|
||||
emitterData.pChannelAzimuths = stereoAzimuthHandle.AddrOfPinnedObject();
|
||||
emitterData.pVolumeCurve = IntPtr.Zero;
|
||||
emitterData.pLFECurve = IntPtr.Zero;
|
||||
emitterData.pLPFDirectCurve = IntPtr.Zero;
|
||||
emitterData.pLPFReverbCurve = IntPtr.Zero;
|
||||
emitterData.pReverbCurve = IntPtr.Zero;
|
||||
emitterData.CurveDistanceScaler = 1.0f;
|
||||
}
|
||||
|
||||
protected override void Destroy() { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using MoonWorks.Math;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class AudioListener : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_LISTENER listenerData;
|
||||
|
||||
public Vector3 Forward
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
listenerData.OrientFront.x,
|
||||
listenerData.OrientFront.y,
|
||||
-listenerData.OrientFront.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
listenerData.OrientFront.x = value.X;
|
||||
listenerData.OrientFront.y = value.Y;
|
||||
listenerData.OrientFront.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
listenerData.Position.x,
|
||||
listenerData.Position.y,
|
||||
-listenerData.Position.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
listenerData.Position.x = value.X;
|
||||
listenerData.Position.y = value.Y;
|
||||
listenerData.Position.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Vector3 Up
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
listenerData.OrientTop.x,
|
||||
listenerData.OrientTop.y,
|
||||
-listenerData.OrientTop.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
listenerData.OrientTop.x = value.X;
|
||||
listenerData.OrientTop.y = value.Y;
|
||||
listenerData.OrientTop.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
listenerData.Velocity.x,
|
||||
listenerData.Velocity.y,
|
||||
-listenerData.Velocity.z
|
||||
);
|
||||
}
|
||||
set
|
||||
{
|
||||
listenerData.Velocity.x = value.X;
|
||||
listenerData.Velocity.y = value.Y;
|
||||
listenerData.Velocity.z = -value.Z;
|
||||
}
|
||||
}
|
||||
|
||||
public AudioListener(AudioDevice device) : base(device)
|
||||
{
|
||||
listenerData = new FAudio.F3DAUDIO_LISTENER();
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
listenerData.pCone = IntPtr.Zero;
|
||||
}
|
||||
|
||||
protected override void Destroy() { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class AudioResource : IDisposable
|
||||
{
|
||||
public AudioDevice Device { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private WeakReference<AudioResource> selfReference;
|
||||
|
||||
public AudioResource(AudioDevice device)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
selfReference = new WeakReference<AudioResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
}
|
||||
|
||||
protected abstract void Destroy();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
if (selfReference != null)
|
||||
{
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~AudioResource()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class SoundInstance : IDisposable
|
||||
public abstract class SoundInstance : AudioResource
|
||||
{
|
||||
protected AudioDevice Device { get; }
|
||||
internal IntPtr Handle { get; }
|
||||
internal FAudio.FAudioWaveFormatEx Format { get; }
|
||||
public bool Loop { get; }
|
||||
|
@ -13,7 +13,6 @@ namespace MoonWorks.Audio
|
|||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
|
||||
protected bool is3D;
|
||||
private bool IsDisposed;
|
||||
|
||||
public abstract SoundState State { get; protected set; }
|
||||
|
||||
|
@ -54,22 +53,8 @@ namespace MoonWorks.Audio
|
|||
get => _pitch;
|
||||
set
|
||||
{
|
||||
float doppler;
|
||||
if (!is3D || Device.DopplerScale == 0f)
|
||||
{
|
||||
doppler = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doppler = dspSettings.DopplerFactor * Device.DopplerScale;
|
||||
}
|
||||
|
||||
_pitch = value;
|
||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||
Handle,
|
||||
(float) System.Math.Pow(2.0, _pitch) * doppler,
|
||||
0
|
||||
);
|
||||
_pitch = MathHelper.Clamp(value, -1f, 1f);
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,9 +167,8 @@ namespace MoonWorks.Audio
|
|||
uint samplesPerSecond,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) {
|
||||
Device = device;
|
||||
|
||||
) : base(device)
|
||||
{
|
||||
var blockAlign = (ushort)(4 * channels);
|
||||
var format = new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
|
@ -228,6 +212,32 @@ namespace MoonWorks.Audio
|
|||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public void Apply3D(AudioListener listener, AudioEmitter emitter)
|
||||
{
|
||||
is3D = true;
|
||||
|
||||
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
||||
emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
|
||||
|
||||
FAudio.F3DAudioCalculate(
|
||||
Device.Handle3D,
|
||||
ref listener.listenerData,
|
||||
ref emitter.emitterData,
|
||||
FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
|
||||
ref dspSettings
|
||||
);
|
||||
|
||||
UpdatePitch();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
Device.MasteringVoice,
|
||||
dspSettings.SrcChannelCount,
|
||||
dspSettings.DstChannelCount,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public abstract void Play();
|
||||
public abstract void Pause();
|
||||
public abstract void Stop(bool immediate);
|
||||
|
@ -257,6 +267,26 @@ namespace MoonWorks.Audio
|
|||
SetPanMatrixCoefficients();
|
||||
}
|
||||
|
||||
private void UpdatePitch()
|
||||
{
|
||||
float doppler;
|
||||
float dopplerScale = Device.DopplerScale;
|
||||
if (!is3D || dopplerScale == 0.0f)
|
||||
{
|
||||
doppler = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doppler = dspSettings.DopplerFactor * dopplerScale;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||
Handle,
|
||||
(float) System.Math.Pow(2.0, _pitch) * doppler,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
||||
private unsafe void SetPanMatrixCoefficients()
|
||||
{
|
||||
|
@ -311,33 +341,12 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
Stop(true);
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~SoundInstance()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class StaticSound : IDisposable
|
||||
public class StaticSound : AudioResource
|
||||
{
|
||||
internal AudioDevice Device { get; }
|
||||
internal FAudio.FAudioBuffer Handle;
|
||||
public ushort Channels { get; }
|
||||
public uint SamplesPerSecond { get; }
|
||||
|
@ -14,8 +12,6 @@ namespace MoonWorks.Audio
|
|||
public uint LoopStart { get; set; } = 0;
|
||||
public uint LoopLength { get; set; } = 0;
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public static StaticSound LoadOgg(AudioDevice device, string filePath)
|
||||
{
|
||||
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
|
@ -54,9 +50,8 @@ namespace MoonWorks.Audio
|
|||
float[] buffer,
|
||||
uint bufferOffset, /* in floats */
|
||||
uint bufferLength /* in floats */
|
||||
) {
|
||||
Device = device;
|
||||
|
||||
) : base(device)
|
||||
{
|
||||
Channels = channels;
|
||||
SamplesPerSecond = samplesPerSecond;
|
||||
|
||||
|
@ -79,32 +74,9 @@ namespace MoonWorks.Audio
|
|||
return new StaticSoundInstance(Device, this, false, loop);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(Handle.pAudioData);
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
~StaticSound()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ namespace MoonWorks.Audio
|
|||
|
||||
public int PendingBufferCount => queuedBuffers.Count;
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public StreamingSound(
|
||||
AudioDevice device,
|
||||
ushort channels,
|
||||
|
@ -172,21 +170,9 @@ namespace MoonWorks.Audio
|
|||
|
||||
protected abstract void SeekStart();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
Stop(true);
|
||||
}
|
||||
|
||||
// dispose unmanaged state
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ namespace MoonWorks.Audio
|
|||
|
||||
public override SoundState State { get; protected set; }
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public static StreamingSoundOgg Load(
|
||||
AudioDevice device,
|
||||
string filePath,
|
||||
|
@ -83,22 +81,9 @@ namespace MoonWorks.Audio
|
|||
FAudio.stb_vorbis_seek_start(FileHandle);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// dispose unmanaged state
|
||||
FAudio.stb_vorbis_close(FileHandle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue