using Amphora;
using System;
using System.Runtime.InteropServices;
namespace Susurrus.XAudio2
{
///
/// A voice that plays queued buffers of samples in one fixed format.
///
public class SourceVoice : XAudio2Voice
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int FlaggedCall(IntPtr self, uint flags, uint operationSet);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int SubmitCall(IntPtr self, ref XAudio2Buffer buffer, IntPtr bufferWma);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int NoArgumentCall(IntPtr self);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void GetStateCall(IntPtr self, out XAudio2VoiceState state, uint flags);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int SetVolumeCall(IntPtr self, float volume, uint operationSet);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int SetSampleRateCall(IntPtr self, uint sampleRate);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int SetFrequencyRatioCall(IntPtr self, float ratio, uint operationSet);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void GetFrequencyRatioCall(IntPtr self, out float ratio);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int SetOutputMatrixCall(
IntPtr self, IntPtr destination, uint sourceChannels, uint destinationChannels, float[] levels,
uint operationSet);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void GetOutputMatrixCall(
IntPtr self, IntPtr destination, uint sourceChannels, uint destinationChannels, float[] levels);
private readonly FlaggedCall start;
private readonly FlaggedCall stop;
private readonly SubmitCall submit;
private readonly NoArgumentCall flush;
private readonly GetStateCall getState;
private readonly SetVolumeCall setVolume;
private readonly SetSampleRateCall setSampleRate;
private readonly SetFrequencyRatioCall setFrequencyRatio;
private readonly GetFrequencyRatioCall getFrequencyRatio;
private readonly SetOutputMatrixCall setOutputMatrix;
private readonly GetOutputMatrixCall getOutputMatrix;
public SourceVoice(IntPtr handle) : base(handle)
{
this.setFrequencyRatio = Vtable.Bind(handle, VoiceSlots.SetFrequencyRatio);
this.getFrequencyRatio = Vtable.Bind(handle, VoiceSlots.GetFrequencyRatio);
this.setOutputMatrix = Vtable.Bind(handle, VoiceSlots.SetOutputMatrix);
this.getOutputMatrix = Vtable.Bind(handle, VoiceSlots.GetOutputMatrix);
this.start = Vtable.Bind(handle, VoiceSlots.Start);
this.stop = Vtable.Bind(handle, VoiceSlots.Stop);
this.submit = Vtable.Bind(handle, VoiceSlots.SubmitSourceBuffer);
this.flush = Vtable.Bind(handle, VoiceSlots.FlushSourceBuffers);
this.getState = Vtable.Bind(handle, VoiceSlots.GetState);
this.setVolume = Vtable.Bind(handle, VoiceSlots.SetVolume);
this.setSampleRate = Vtable.Bind(handle, VoiceSlots.SetSourceSampleRate);
}
private static Status Check(int hresult, string call)
{
if (hresult < 0)
{
return XAudio2Failures.CallFailed.Fail(call, hresult);
}
return Status.Ok;
}
public Status Start()
{
ThrowIfDestroyed();
return Check(start(Handle, flags: 0, operationSet: 0), "IXAudio2SourceVoice::Start");
}
public Status Stop()
{
ThrowIfDestroyed();
return Check(stop(Handle, flags: 0, operationSet: 0), "IXAudio2SourceVoice::Stop");
}
///
/// Queues to play after the buffers already queued. Its samples have to stay pinned
/// until the voice has played it or been flushed.
///
public Status Submit(in XAudio2Buffer buffer)
{
ThrowIfDestroyed();
XAudio2Buffer copy = buffer;
return Check(submit(Handle, ref copy, bufferWma: IntPtr.Zero), "IXAudio2SourceVoice::SubmitSourceBuffer");
}
///
/// Drops the queued buffers. The mixing thread does it, so reaches zero a
/// moment later.
///
public Status Flush()
{
ThrowIfDestroyed();
return Check(flush(Handle), "IXAudio2SourceVoice::FlushSourceBuffers");
}
public XAudio2VoiceState GetState()
{
ThrowIfDestroyed();
getState(Handle, out XAudio2VoiceState state, flags: 0);
return state;
}
///
/// Returns how many buffers are queued, including the one playing. Cheaper than ,
/// which also counts the samples played.
///
public int GetBuffersQueued()
{
ThrowIfDestroyed();
getState(Handle, out XAudio2VoiceState state, XAudio2VoiceState.NoSamplesPlayed);
return (int)state.BuffersQueued;
}
///
/// Sets the linear gain, where one leaves the samples as they are.
///
public Status SetVolume(float volume)
{
ThrowIfDestroyed();
Volume.Check(volume, nameof(volume));
return Check(setVolume(Handle, volume, operationSet: 0), "IXAudio2Voice::SetVolume");
}
///
/// Changes the rate the voice reads its samples at, which pitches the sound without changing the format.
///
public Status SetSampleRate(int sampleRate)
{
ThrowIfDestroyed();
if (sampleRate <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(sampleRate), sampleRate, "A sample rate must be positive.");
}
return Check(setSampleRate(Handle, (uint)sampleRate), "IXAudio2SourceVoice::SetSourceSampleRate");
}
///
/// Sets the ratio the voice reads its samples at, on top of its sample rate. A ratio of one leaves the pitch
/// alone. The voice was made with a highest ratio it can take.
///
public Status SetFrequencyRatio(float ratio)
{
ThrowIfDestroyed();
Pitch.Check(ratio, nameof(ratio));
return Check(setFrequencyRatio(Handle, ratio, operationSet: 0), "IXAudio2SourceVoice::SetFrequencyRatio");
}
public float GetFrequencyRatio()
{
ThrowIfDestroyed();
getFrequencyRatio(Handle, out float ratio);
return ratio;
}
///
/// Sets how much of each channel of the voice goes to each channel of the mastering voice, as a row of levels
/// for every destination channel and a column for every source channel.
///
public Status SetOutputMatrix(int sourceChannels, int destinationChannels, float[] levels)
{
ThrowIfDestroyed();
CheckMatrix(sourceChannels, destinationChannels, levels);
return Check(setOutputMatrix(Handle, IntPtr.Zero, (uint)sourceChannels, (uint)destinationChannels, levels,
operationSet: 0), "IXAudio2Voice::SetOutputMatrix");
}
///
/// Reads back the levels the voice sends to the mastering voice, into the start of .
///
public void GetOutputMatrix(int sourceChannels, int destinationChannels, float[] levels)
{
ThrowIfDestroyed();
CheckMatrix(sourceChannels, destinationChannels, levels);
getOutputMatrix(Handle, IntPtr.Zero, (uint)sourceChannels, (uint)destinationChannels, levels);
}
private static void CheckMatrix(int sourceChannels, int destinationChannels, float[] levels)
{
if (levels == null)
{
throw new ArgumentNullException(nameof(levels));
}
if (sourceChannels < 1 || destinationChannels < 1 || levels.Length < sourceChannels * destinationChannels)
{
throw new ArgumentException(
"The levels need a place for every source channel to every destination channel.", nameof(levels));
}
}
}
}