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); } } }