2023-08-03 19:54:02 +00:00
|
|
|
using System;
|
|
|
|
|
|
|
|
namespace MoonWorks.Audio
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Emits audio from submitted audio buffers.
|
|
|
|
/// </summary>
|
|
|
|
public abstract class SourceVoice : Voice
|
|
|
|
{
|
|
|
|
private Format format;
|
|
|
|
public Format Format => format;
|
|
|
|
|
|
|
|
protected bool PlaybackInitiated;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The number of buffers queued in the voice.
|
|
|
|
/// This includes the currently playing voice!
|
|
|
|
/// </summary>
|
|
|
|
public uint BuffersQueued
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
FAudio.FAudioSourceVoice_GetState(
|
|
|
|
Handle,
|
|
|
|
out var state,
|
|
|
|
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
|
|
|
);
|
|
|
|
|
|
|
|
return state.BuffersQueued;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-09 23:10:00 +00:00
|
|
|
private SoundState state = SoundState.Stopped;
|
2023-08-03 19:54:02 +00:00
|
|
|
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;
|
|
|
|
var 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
|
|
|
|
);
|
2023-08-10 17:46:19 +00:00
|
|
|
|
|
|
|
SetOutputVoice(device.MasteringVoice);
|
2023-08-03 19:54:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Starts consumption and processing of audio by the voice.
|
|
|
|
/// Delivers the result to any connected submix or mastering voice.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
|
|
|
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
|
|
|
{
|
|
|
|
lock (StateLock)
|
|
|
|
{
|
|
|
|
FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
|
|
|
|
|
|
|
|
State = SoundState.Playing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pauses playback.
|
|
|
|
/// All source buffers that are queued on the voice and the current cursor position are preserved.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
|
|
|
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
|
|
|
{
|
|
|
|
lock (StateLock)
|
|
|
|
{
|
|
|
|
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
|
|
|
|
|
|
|
|
State = SoundState.Paused;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
|
|
|
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
|
|
|
{
|
|
|
|
lock (StateLock)
|
|
|
|
{
|
|
|
|
FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Stops playback and removes all pending audio buffers from the voice queue.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
|
|
|
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
|
|
|
{
|
|
|
|
lock (StateLock)
|
|
|
|
{
|
|
|
|
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
|
|
|
|
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
|
|
|
|
|
|
|
State = SoundState.Stopped;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="buffer">The buffer to submit to the voice.</param>
|
|
|
|
public void Submit(AudioBuffer buffer)
|
|
|
|
{
|
|
|
|
Submit(buffer.ToFAudioBuffer());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Calculates positional sound. This must be called continuously to update positional sound.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="listener"></param>
|
|
|
|
/// <param name="emitter"></param>
|
|
|
|
public unsafe void Apply3D(AudioListener listener, AudioEmitter emitter)
|
|
|
|
{
|
|
|
|
Is3D = true;
|
|
|
|
|
|
|
|
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
|
|
|
emitter.emitterData.ChannelCount = SourceChannelCount;
|
|
|
|
|
|
|
|
var 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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Specifies that this source voice can be returned to the voice pool.
|
|
|
|
/// Holding on to the reference after calling this will cause problems!
|
|
|
|
/// </summary>
|
|
|
|
public void Return()
|
|
|
|
{
|
|
|
|
Stop();
|
|
|
|
Device.Return(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="buffer">The buffer to submit to the voice.</param>
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|