MoonWorks/src/Audio/AudioDevice.cs

275 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio
{
public class AudioDevice : IDisposable
{
public IntPtr Handle { get; }
public byte[] Handle3D { get; }
public IntPtr MasteringVoice { get; }
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
public IntPtr ReverbVoice { get; }
public float CurveDistanceScalar = 1f;
public float DopplerScale = 1f;
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;
public unsafe AudioDevice()
{
FAudio.FAudioCreate(out var handle, 0, 0);
Handle = handle;
/* Find a suitable device */
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
if (devices == 0)
{
Logger.LogError("No audio devices found!");
Handle = IntPtr.Zero;
FAudio.FAudio_Release(Handle);
return;
}
FAudio.FAudioDeviceDetails deviceDetails;
uint i = 0;
for (i = 0; i < devices; i++)
{
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
{
DeviceDetails = deviceDetails;
break;
}
}
if (i == devices)
{
i = 0; /* whatever we'll just use the first one I guess */
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
DeviceDetails = deviceDetails;
}
/* Init Mastering Voice */
IntPtr masteringVoice;
if (FAudio.FAudio_CreateMasteringVoice(
Handle,
out masteringVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
IntPtr.Zero
) != 0)
{
Logger.LogError("No mastering voice found!");
Handle = IntPtr.Zero;
FAudio.FAudio_Release(Handle);
return;
}
MasteringVoice = masteringVoice;
/* Init 3D Audio */
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
FAudio.F3DAudioInitialize(
DeviceDetails.OutputFormat.dwChannelMask,
SpeedOfSound,
Handle3D
);
/* Init reverb */
IntPtr reverbVoice;
IntPtr reverb;
FAudio.FAudioCreateReverb(out reverb, 0);
IntPtr chainPtr;
chainPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectChain>()
);
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
reverbChain->EffectCount = 1;
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
);
FAudio.FAudioEffectDescriptor* reverbDescriptor =
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
reverbDescriptor->InitialState = 1;
reverbDescriptor->OutputChannels = (uint) (
(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
);
reverbDescriptor->pEffect = reverb;
FAudio.FAudio_CreateSubmixVoice(
Handle,
out reverbVoice,
1, /* omnidirectional reverb */
DeviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
IntPtr.Zero,
chainPtr
);
FAudio.FAPOBase_Release(reverb);
Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
Marshal.FreeHGlobal(chainPtr);
ReverbVoice = reverbVoice;
/* Init reverb params */
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
);
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
reverbParams->WetDryMix = 100.0f;
reverbParams->ReflectionsDelay = 7;
reverbParams->ReverbDelay = 11;
reverbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
reverbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams->EarlyDiffusion = 15;
reverbParams->LateDiffusion = 15;
reverbParams->LowEQGain = 8;
reverbParams->LowEQCutoff = 4;
reverbParams->HighEQGain = 8;
reverbParams->HighEQCutoff = 6;
reverbParams->RoomFilterFreq = 5000f;
reverbParams->RoomFilterMain = -10f;
reverbParams->RoomFilterHF = -1f;
reverbParams->ReflectionsGain = -26.0200005f;
reverbParams->ReverbGain = 10.0f;
reverbParams->DecayTime = 1.49000001f;
reverbParams->Density = 100.0f;
reverbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
FAudio.FAudioVoice_SetEffectParameters(
ReverbVoice,
0,
reverbParamsPtr,
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
0
);
Marshal.FreeHGlobal(reverbParamsPtr);
/* Init reverb sends */
ReverbSends = new FAudio.FAudioVoiceSends
{
SendCount = 2,
pSends = Marshal.AllocHGlobal(
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
)
};
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = MasteringVoice;
sendDesc[1].Flags = 0;
sendDesc[1].pOutputVoice = ReverbVoice;
}
public void Update()
{
for (var i = streamingSounds.Count - 1; i >= 0; i--)
{
var weakReference = streamingSounds[i];
if (weakReference.TryGetTarget(out var streamingSound))
{
streamingSound.Update();
}
else
{
streamingSounds.RemoveAt(i);
}
}
}
internal void AddDynamicSoundInstance(StreamingSound instance)
{
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)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
foreach (var weakReference in streamingSounds)
{
if (weakReference.TryGetTarget(out var streamingSound))
{
streamingSound.Dispose();
}
}
streamingSounds.Clear();
}
FAudio.FAudio_Release(Handle);
IsDisposed = true;
}
}
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
~AudioDevice()
{
// 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);
}
}
}