using System;
namespace Nerfed.Runtime.Audio;
///
/// Emits audio from submitted audio buffers.
///
public abstract class SourceVoice : Voice
{
private Format format;
public Format Format => format;
protected bool PlaybackInitiated;
///
/// The number of buffers queued in the voice.
/// This includes the currently playing voice!
///
public uint BuffersQueued
{
get
{
FAudio.FAudioSourceVoice_GetState(
Handle,
out FAudio.FAudioVoiceState state,
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
);
return state.BuffersQueued;
}
}
private SoundState state = SoundState.Stopped;
public SoundState State
{
get
{
if (BuffersQueued == 0)
{
Stop();
}
return state;
}
internal set
{
state = value;
}
}
protected object StateLock = new object();
public SourceVoice(
AudioDevice device,
Format format
) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
{
this.format = format;
FAudio.FAudioWaveFormatEx fAudioFormat = format.ToFAudioFormat();
FAudio.FAudio_CreateSourceVoice(
device.Handle,
out handle,
ref fAudioFormat,
FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero,
IntPtr.Zero, // default sends to mastering voice!
IntPtr.Zero
);
SetOutputVoice(device.MasteringVoice);
}
///
/// Starts consumption and processing of audio by the voice.
/// Delivers the result to any connected submix or mastering voice.
///
/// Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
State = SoundState.Playing;
}
}
///
/// Pauses playback.
/// All source buffers that are queued on the voice and the current cursor position are preserved.
///
/// Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
State = SoundState.Paused;
}
}
///
/// Stops looping the voice when it reaches the end of the current loop region.
/// If the cursor for the voice is not in a loop region, ExitLoop does nothing.
///
/// Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
}
}
///
/// Stops playback and removes all pending audio buffers from the voice queue.
///
/// Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
State = SoundState.Stopped;
}
}
///
/// Adds an AudioBuffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
///
/// The buffer to submit to the voice.
public void Submit(AudioBuffer buffer)
{
Submit(buffer.ToFAudioBuffer());
}
///
/// Calculates positional sound. This must be called continuously to update positional sound.
///
///
///
public unsafe void Apply3D(AudioListener listener, AudioEmitter emitter)
{
Is3D = true;
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
emitter.emitterData.ChannelCount = SourceChannelCount;
FAudio.F3DAUDIO_DSP_SETTINGS dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS
{
DopplerFactor = DopplerFactor,
SrcChannelCount = SourceChannelCount,
DstChannelCount = DestinationChannelCount,
pMatrixCoefficients = (nint) pMatrixCoefficients
};
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,
OutputVoice.Handle,
SourceChannelCount,
DestinationChannelCount,
(nint) pMatrixCoefficients,
0
);
}
///
/// Specifies that this source voice can be returned to the voice pool.
/// Holding on to the reference after calling this will cause problems!
///
public void Return()
{
Stop();
Device.Return(this);
}
///
/// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
///
/// The buffer to submit to the voice.
protected void Submit(FAudio.FAudioBuffer buffer)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Handle,
ref buffer,
IntPtr.Zero
);
}
}
public override void Reset()
{
Stop();
PlaybackInitiated = false;
base.Reset();
}
}