using Amphora;
using System;
using System.Runtime.InteropServices;
namespace Susurrus.XAudio2
{
///
/// An XAudio2 engine, which makes the voices and runs the thread that mixes them. Destroy every voice before
/// disposing the engine.
///
public class XAudio2Engine : IDisposable
{
// AudioCategory_GameEffects, the category XAudio2 uses when a caller doesn't choose one.
private const int GameEffectsCategory = 6;
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate uint ReleaseCall(IntPtr self);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int CreateSourceVoiceCall(
IntPtr self, out IntPtr voice, ref WaveFormatEx format, uint flags, float maxFrequencyRatio,
IntPtr callback, IntPtr sendList, IntPtr effectChain);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int CreateMasteringVoiceCall(
IntPtr self, out IntPtr voice, uint inputChannels, uint inputSampleRate, uint flags, IntPtr deviceId,
IntPtr effectChain, int streamCategory);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void GetPerformanceDataCall(IntPtr self, out XAudio2PerformanceData data);
[DllImport("XAudio2_9.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int XAudio2Create(out IntPtr engine, uint flags, uint processor);
private readonly ReleaseCall release;
private readonly CreateSourceVoiceCall createSourceVoice;
private readonly CreateMasteringVoiceCall createMasteringVoice;
private readonly GetPerformanceDataCall getPerformanceData;
private IntPtr handle;
private XAudio2Engine(IntPtr handle)
{
this.release = Vtable.Bind(handle, EngineSlots.Release);
this.createSourceVoice = Vtable.Bind(handle, EngineSlots.CreateSourceVoice);
this.createMasteringVoice = Vtable.Bind(handle, EngineSlots.CreateMasteringVoice);
this.getPerformanceData = Vtable.Bind(handle, EngineSlots.GetPerformanceData);
this.handle = handle;
}
private void ThrowIfDisposed()
{
if (handle == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(XAudio2Engine));
}
}
///
/// Makes an engine, which has already started its mixing thread. Fails when this machine has no XAudio2 to
/// load.
///
public static StatusOr Create()
{
int hresult;
IntPtr native;
try
{
hresult = XAudio2Create(out native, flags: 0, processor: 0);
}
catch (Exception exception) when (exception is DllNotFoundException ||
exception is EntryPointNotFoundException || exception is BadImageFormatException)
{
return XAudio2Failures.Unavailable.Fail(exception.Message);
}
if (hresult < 0)
{
return XAudio2Failures.CallFailed.Fail("XAudio2Create", hresult);
}
return new XAudio2Engine(native);
}
///
/// Makes the voice that plays on the default audio device, in the device's own format. Fails when the machine
/// has no device to play on.
///
public StatusOr CreateMasteringVoice()
{
ThrowIfDisposed();
int hresult = createMasteringVoice(handle, out IntPtr voice, inputChannels: 0, inputSampleRate: 0,
flags: 0, deviceId: IntPtr.Zero, effectChain: IntPtr.Zero, streamCategory: GameEffectsCategory);
if (hresult < 0)
{
return XAudio2Failures.CallFailed.Fail("IXAudio2::CreateMasteringVoice", hresult);
}
return new MasteringVoice(voice);
}
///
/// Makes a voice that plays samples in . It is sized for sample rate changes of up to
/// times the format's rate.
///
public StatusOr CreateSourceVoice(in WaveFormatEx format, float maxFrequencyRatio)
{
ThrowIfDisposed();
WaveFormatEx copy = format;
int hresult = createSourceVoice(handle, out IntPtr voice, ref copy, flags: 0, maxFrequencyRatio,
callback: IntPtr.Zero, sendList: IntPtr.Zero, effectChain: IntPtr.Zero);
if (hresult < 0)
{
return XAudio2Failures.CallFailed.Fail("IXAudio2::CreateSourceVoice", hresult);
}
return new SourceVoice(voice);
}
public XAudio2PerformanceData GetPerformanceData()
{
ThrowIfDisposed();
getPerformanceData(handle, out XAudio2PerformanceData data);
return data;
}
///
/// Releases the engine. Does nothing if it is already disposed.
///
public void Dispose()
{
if (handle == IntPtr.Zero)
{
return;
}
IntPtr native = handle;
handle = IntPtr.Zero;
release(native);
}
}
}