faux mastering voice + submitting static sounds
parent
e2c85ec728
commit
c2cb83f93f
|
@ -10,8 +10,12 @@ namespace MoonWorks.Audio
|
||||||
public byte[] Handle3D { get; }
|
public byte[] Handle3D { get; }
|
||||||
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
||||||
|
|
||||||
private MasteringVoice masteringVoice;
|
private IntPtr trueMasteringVoice;
|
||||||
public MasteringVoice MasteringVoice => masteringVoice;
|
|
||||||
|
// this is a fun little trick where we use a submix voice as a "faux" mastering voice
|
||||||
|
// this lets us maintain API consistency for effects like panning and reverb
|
||||||
|
private SubmixVoice fauxMasteringVoice;
|
||||||
|
public SubmixVoice MasteringVoice => fauxMasteringVoice;
|
||||||
|
|
||||||
public float CurveDistanceScalar = 1f;
|
public float CurveDistanceScalar = 1f;
|
||||||
public float DopplerScale = 1f;
|
public float DopplerScale = 1f;
|
||||||
|
@ -19,8 +23,8 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
||||||
private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>();
|
private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>();
|
||||||
private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>();
|
|
||||||
private readonly List<WeakReference<SoundSequence>> soundSequenceReferences = new List<WeakReference<SoundSequence>>();
|
private readonly List<WeakReference<SoundSequence>> soundSequenceReferences = new List<WeakReference<SoundSequence>>();
|
||||||
|
private readonly List<SourceVoice> autoFreeSourceVoices = new List<SourceVoice>();
|
||||||
|
|
||||||
private AudioTweenManager AudioTweenManager;
|
private AudioTweenManager AudioTweenManager;
|
||||||
|
|
||||||
|
@ -85,18 +89,25 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init Mastering Voice */
|
/* Init Mastering Voice */
|
||||||
var result = MasteringVoice.Create(
|
var result = FAudio.FAudio_CreateMasteringVoice(
|
||||||
this,
|
Handle,
|
||||||
|
out trueMasteringVoice,
|
||||||
|
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||||
|
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||||
|
0,
|
||||||
i,
|
i,
|
||||||
out masteringVoice
|
IntPtr.Zero
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
|
Logger.LogError("Failed to create a mastering voice!");
|
||||||
Logger.LogError("Audio device creation failed!");
|
Logger.LogError("Audio device creation failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fauxMasteringVoice = new SubmixVoice(this, FAudio.FAUDIO_DEFAULT_CHANNELS, FAudio.FAUDIO_DEFAULT_SAMPLERATE);
|
||||||
|
|
||||||
/* Init 3D Audio */
|
/* Init 3D Audio */
|
||||||
|
|
||||||
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
||||||
|
@ -162,17 +173,6 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = autoFreeStaticSoundInstanceReferences.Count - 1; i >= 0; i -= 1)
|
|
||||||
{
|
|
||||||
var staticSoundInstance = autoFreeStaticSoundInstanceReferences[i];
|
|
||||||
|
|
||||||
if (staticSoundInstance.State == SoundState.Stopped)
|
|
||||||
{
|
|
||||||
staticSoundInstance.Free();
|
|
||||||
autoFreeStaticSoundInstanceReferences.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = soundSequenceReferences.Count - 1; i >= 0; i -= 1)
|
for (var i = soundSequenceReferences.Count - 1; i >= 0; i -= 1)
|
||||||
{
|
{
|
||||||
if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
|
if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
|
||||||
|
@ -185,14 +185,61 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var i = autoFreeSourceVoices.Count - 1; i >= 0; i -= 1)
|
||||||
|
{
|
||||||
|
var voice = autoFreeSourceVoices[i];
|
||||||
|
if (voice.BuffersQueued == 0)
|
||||||
|
{
|
||||||
|
Return(voice);
|
||||||
|
autoFreeSourceVoices.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AudioTweenManager.Update(elapsedSeconds);
|
AudioTweenManager.Update(elapsedSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers all pending operations with the given syncGroup value.
|
||||||
|
/// </summary>
|
||||||
public void TriggerSyncGroup(uint syncGroup)
|
public void TriggerSyncGroup(uint syncGroup)
|
||||||
{
|
{
|
||||||
FAudio.FAudio_CommitChanges(Handle, syncGroup);
|
FAudio.FAudio_CommitChanges(Handle, syncGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtains an appropriate source voice from the voice pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format that the voice must match.</param>
|
||||||
|
/// <returns>A source voice with the given format.</returns>
|
||||||
|
public SourceVoice Obtain(Format format)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
return VoicePool.Obtain(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ReturnWhenIdle(SourceVoice voice)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
autoFreeSourceVoices.Add(voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the source voice to the voice pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="voice"></param>
|
||||||
|
internal void Return(SourceVoice voice)
|
||||||
|
{
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
voice.Reset();
|
||||||
|
VoicePool.Return(voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void CreateTween(
|
internal void CreateTween(
|
||||||
Voice voice,
|
Voice voice,
|
||||||
AudioTweenProperty property,
|
AudioTweenProperty property,
|
||||||
|
@ -252,26 +299,11 @@ namespace MoonWorks.Audio
|
||||||
autoUpdateStreamingSoundReferences.Add(instance);
|
autoUpdateStreamingSoundReferences.Add(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddAutoFreeStaticSoundInstance(StaticSoundInstance instance)
|
|
||||||
{
|
|
||||||
autoFreeStaticSoundInstanceReferences.Add(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void AddSoundSequenceReference(SoundSequence sequence)
|
internal void AddSoundSequenceReference(SoundSequence sequence)
|
||||||
{
|
{
|
||||||
soundSequenceReferences.Add(new WeakReference<SoundSequence>(sequence));
|
soundSequenceReferences.Add(new WeakReference<SoundSequence>(sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal SourceVoice ObtainSourceVoice(Format format)
|
|
||||||
{
|
|
||||||
return VoicePool.Obtain(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ReturnSourceVoice(SourceVoice voice)
|
|
||||||
{
|
|
||||||
VoicePool.Return(voice);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
|
@ -306,6 +338,7 @@ namespace MoonWorks.Audio
|
||||||
resources.Clear();
|
resources.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
|
||||||
FAudio.FAudio_Release(Handle);
|
FAudio.FAudio_Release(Handle);
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
|
@ -24,10 +24,7 @@ namespace MoonWorks.Audio
|
||||||
switch (audioTween.Property)
|
switch (audioTween.Property)
|
||||||
{
|
{
|
||||||
case AudioTweenProperty.Pan:
|
case AudioTweenProperty.Pan:
|
||||||
if (voice is SendableVoice pannableVoice)
|
audioTween.StartValue = voice.Pan;
|
||||||
{
|
|
||||||
audioTween.StartValue = pannableVoice.Pan;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Pitch:
|
case AudioTweenProperty.Pitch:
|
||||||
|
@ -43,10 +40,7 @@ namespace MoonWorks.Audio
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Reverb:
|
case AudioTweenProperty.Reverb:
|
||||||
if (voice is SendableVoice reverbableVoice)
|
audioTween.StartValue = voice.Reverb;
|
||||||
{
|
|
||||||
audioTween.StartValue = reverbableVoice.Reverb;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,10 +133,7 @@ namespace MoonWorks.Audio
|
||||||
switch (audioTween.Property)
|
switch (audioTween.Property)
|
||||||
{
|
{
|
||||||
case AudioTweenProperty.Pan:
|
case AudioTweenProperty.Pan:
|
||||||
if (audioTween.Voice is SendableVoice pannableVoice)
|
audioTween.Voice.Pan = value;
|
||||||
{
|
|
||||||
pannableVoice.Pan = value;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Pitch:
|
case AudioTweenProperty.Pitch:
|
||||||
|
@ -158,10 +149,7 @@ namespace MoonWorks.Audio
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Reverb:
|
case AudioTweenProperty.Reverb:
|
||||||
if (audioTween.Voice is SendableVoice reverbableVoice)
|
audioTween.Voice.Reverb = value;
|
||||||
{
|
|
||||||
reverbableVoice.Reverb = value;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public interface IReceivableVoice
|
|
||||||
{
|
|
||||||
public System.IntPtr Handle { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public class MasteringVoice : Voice, IReceivableVoice
|
|
||||||
{
|
|
||||||
internal static bool Create(
|
|
||||||
AudioDevice device,
|
|
||||||
uint deviceIndex,
|
|
||||||
out MasteringVoice masteringVoice
|
|
||||||
) {
|
|
||||||
var result = FAudio.FAudio_CreateMasteringVoice(
|
|
||||||
device.Handle,
|
|
||||||
out var handle,
|
|
||||||
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
|
||||||
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
|
||||||
0,
|
|
||||||
deviceIndex,
|
|
||||||
IntPtr.Zero
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == 0)
|
|
||||||
{
|
|
||||||
masteringVoice = new MasteringVoice(device, handle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogError("Failed to create mastering voice!");
|
|
||||||
masteringVoice = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal MasteringVoice(
|
|
||||||
AudioDevice device,
|
|
||||||
IntPtr handle
|
|
||||||
) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, 0)
|
|
||||||
{
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,227 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using EasingFunction = System.Func<float, float>;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public unsafe class SendableVoice : Voice
|
|
||||||
{
|
|
||||||
private IReceivableVoice OutputVoice;
|
|
||||||
private ReverbEffect ReverbEffect;
|
|
||||||
|
|
||||||
byte* pMatrixCoefficients;
|
|
||||||
|
|
||||||
protected float pan = 0;
|
|
||||||
public float Pan
|
|
||||||
{
|
|
||||||
get => pan;
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
|
||||||
if (pan != value)
|
|
||||||
{
|
|
||||||
pan = value;
|
|
||||||
|
|
||||||
if (pan < -1f)
|
|
||||||
{
|
|
||||||
pan = -1f;
|
|
||||||
}
|
|
||||||
if (pan > 1f)
|
|
||||||
{
|
|
||||||
pan = 1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Is3D) { return; }
|
|
||||||
|
|
||||||
SetPanMatrixCoefficients();
|
|
||||||
FAudio.FAudioVoice_SetOutputMatrix(
|
|
||||||
Handle,
|
|
||||||
OutputVoice.Handle,
|
|
||||||
SourceChannelCount,
|
|
||||||
DestinationChannelCount,
|
|
||||||
(nint) pMatrixCoefficients,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float reverb;
|
|
||||||
public unsafe float Reverb
|
|
||||||
{
|
|
||||||
get => reverb;
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
if (ReverbEffect != null)
|
|
||||||
{
|
|
||||||
value = MathF.Max(0, value);
|
|
||||||
if (reverb != value)
|
|
||||||
{
|
|
||||||
reverb = value;
|
|
||||||
|
|
||||||
float* outputMatrix = (float*) pMatrixCoefficients;
|
|
||||||
outputMatrix[0] = reverb;
|
|
||||||
if (SourceChannelCount == 2)
|
|
||||||
{
|
|
||||||
outputMatrix[1] = reverb;
|
|
||||||
}
|
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetOutputMatrix(
|
|
||||||
Handle,
|
|
||||||
ReverbEffect.Handle,
|
|
||||||
SourceChannelCount,
|
|
||||||
1,
|
|
||||||
(nint) pMatrixCoefficients,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if (ReverbEffect == null)
|
|
||||||
{
|
|
||||||
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendableVoice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device, sourceChannelCount, destinationChannelCount)
|
|
||||||
{
|
|
||||||
OutputVoice = device.MasteringVoice;
|
|
||||||
nuint memsize = (nuint) (4 * sourceChannelCount * destinationChannelCount);
|
|
||||||
pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize);
|
|
||||||
SetPanMatrixCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetPan(float targetValue)
|
|
||||||
{
|
|
||||||
Pan = targetValue;
|
|
||||||
Device.ClearTweens(this, AudioTweenProperty.Pan);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
|
||||||
{
|
|
||||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
|
||||||
{
|
|
||||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetReverb(float targetValue)
|
|
||||||
{
|
|
||||||
Reverb = targetValue;
|
|
||||||
Device.ClearTweens(this, AudioTweenProperty.Reverb);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
|
||||||
{
|
|
||||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
|
||||||
{
|
|
||||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void SetOutputVoice(IReceivableVoice send)
|
|
||||||
{
|
|
||||||
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
|
|
||||||
sendDesc[0].Flags = 0;
|
|
||||||
sendDesc[0].pOutputVoice = send.Handle;
|
|
||||||
|
|
||||||
var sends = new FAudio.FAudioVoiceSends();
|
|
||||||
sends.SendCount = 1;
|
|
||||||
sends.pSends = (nint) sendDesc;
|
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetOutputVoices(
|
|
||||||
Handle,
|
|
||||||
ref sends
|
|
||||||
);
|
|
||||||
|
|
||||||
OutputVoice = send;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
|
|
||||||
{
|
|
||||||
var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
|
|
||||||
sendDesc[0].Flags = 0;
|
|
||||||
sendDesc[0].pOutputVoice = OutputVoice.Handle;
|
|
||||||
sendDesc[1].Flags = 0;
|
|
||||||
sendDesc[1].pOutputVoice = reverbEffect.Handle;
|
|
||||||
|
|
||||||
var sends = new FAudio.FAudioVoiceSends();
|
|
||||||
sends.SendCount = 2;
|
|
||||||
sends.pSends = (nint) sendDesc;
|
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetOutputVoices(
|
|
||||||
Handle,
|
|
||||||
ref sends
|
|
||||||
);
|
|
||||||
|
|
||||||
ReverbEffect = reverbEffect;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
|
||||||
private unsafe void SetPanMatrixCoefficients()
|
|
||||||
{
|
|
||||||
/* Two major things to notice:
|
|
||||||
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
|
|
||||||
* 2. Stereo panning is WAY more complicated than you think.
|
|
||||||
* The main thing is that hard panning does NOT eliminate an
|
|
||||||
* entire channel; the two channels are blended on each side.
|
|
||||||
* -flibit
|
|
||||||
*/
|
|
||||||
float* outputMatrix = (float*) pMatrixCoefficients;
|
|
||||||
if (SourceChannelCount == 1)
|
|
||||||
{
|
|
||||||
if (DestinationChannelCount == 1)
|
|
||||||
{
|
|
||||||
outputMatrix[0] = 1.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
|
|
||||||
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (DestinationChannelCount == 1)
|
|
||||||
{
|
|
||||||
outputMatrix[0] = 1.0f;
|
|
||||||
outputMatrix[1] = 1.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (pan <= 0.0f)
|
|
||||||
{
|
|
||||||
// Left speaker blends left/right channels
|
|
||||||
outputMatrix[0] = 0.5f * pan + 1.0f;
|
|
||||||
outputMatrix[1] = 0.5f * -pan;
|
|
||||||
// Right speaker gets less of the right channel
|
|
||||||
outputMatrix[2] = 0.0f;
|
|
||||||
outputMatrix[3] = pan + 1.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Left speaker gets less of the left channel
|
|
||||||
outputMatrix[0] = -pan + 1.0f;
|
|
||||||
outputMatrix[1] = 0.0f;
|
|
||||||
// Right speaker blends right/left channels
|
|
||||||
outputMatrix[2] = 0.5f * pan;
|
|
||||||
outputMatrix[3] = 0.5f * -pan + 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override unsafe void Destroy()
|
|
||||||
{
|
|
||||||
NativeMemory.Free(pMatrixCoefficients);
|
|
||||||
base.Destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -59,11 +59,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
Submit(sound);
|
||||||
Handle,
|
|
||||||
ref sound.Handle,
|
|
||||||
IntPtr.Zero
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class SourceVoice : SendableVoice
|
/// <summary>
|
||||||
|
/// Emits audio from submitted audio buffers.
|
||||||
|
/// </summary>
|
||||||
|
public class SourceVoice : Voice
|
||||||
{
|
{
|
||||||
private Format format;
|
private Format format;
|
||||||
public Format Format => format;
|
public Format Format => format;
|
||||||
|
@ -55,7 +58,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
FAudio.FAudio_CreateSourceVoice(
|
FAudio.FAudio_CreateSourceVoice(
|
||||||
device.Handle,
|
device.Handle,
|
||||||
out var Handle,
|
out handle,
|
||||||
ref fAudioFormat,
|
ref fAudioFormat,
|
||||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||||
|
@ -65,6 +68,11 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
|
@ -75,6 +83,11 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
|
@ -85,6 +98,11 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
|
@ -93,6 +111,10 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
|
@ -104,7 +126,32 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubmitBuffer(FAudio.FAudioBuffer buffer)
|
/// <summary>
|
||||||
|
/// Adds a static sound 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="sound">The sound to submit to the voice.</param>
|
||||||
|
/// <param name="loop">Designates that the voice will loop the submitted buffer.</param>
|
||||||
|
public void Submit(StaticSound sound, bool loop = false)
|
||||||
|
{
|
||||||
|
if (loop)
|
||||||
|
{
|
||||||
|
sound.Buffer.LoopCount = FAudio.FAUDIO_LOOP_INFINITE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sound.Buffer.LoopCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Submit(sound.Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public void Submit(FAudio.FAudioBuffer buffer)
|
||||||
{
|
{
|
||||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||||
Handle,
|
Handle,
|
||||||
|
@ -113,31 +160,27 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: maybe this is bad
|
public void Submit(StreamingSound streamingSound)
|
||||||
// NOTE: SourceVoices obtained this way will be returned to the voice pool when stopped!
|
|
||||||
public static SourceVoice ObtainSourceVoice(AudioDevice device, Format format)
|
|
||||||
{
|
{
|
||||||
return device.ObtainSourceVoice(format);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// intended for short-lived sound effects
|
/// <summary>
|
||||||
public static SourceVoice PlayStaticSound(AudioDevice device, StaticSound sound, SubmixVoice sendVoice = null)
|
/// Designates that this source voice will return to the voice pool once all its buffers are exhausted.
|
||||||
|
/// </summary>
|
||||||
|
public void ReturnWhenIdle()
|
||||||
{
|
{
|
||||||
var voice = ObtainSourceVoice(device, sound.Format);
|
Device.ReturnWhenIdle(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (sendVoice == null)
|
/// <summary>
|
||||||
{
|
/// Returns this source voice to the voice pool.
|
||||||
voice.SetOutputVoice(device.MasteringVoice);
|
/// </summary>
|
||||||
}
|
public void Return()
|
||||||
else
|
{
|
||||||
{
|
Stop();
|
||||||
voice.SetOutputVoice(sendVoice);
|
Reset();
|
||||||
}
|
Device.Return(this);
|
||||||
|
|
||||||
voice.SubmitBuffer(sound.Handle);
|
|
||||||
voice.Play();
|
|
||||||
|
|
||||||
return voice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void Destroy()
|
protected override unsafe void Destroy()
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
@ -7,17 +6,11 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class StaticSound : AudioResource
|
public class StaticSound : AudioResource
|
||||||
{
|
{
|
||||||
internal FAudio.FAudioBuffer Handle;
|
internal FAudio.FAudioBuffer Buffer;
|
||||||
|
|
||||||
private Format format;
|
private Format format;
|
||||||
public Format Format => format;
|
public Format Format => format;
|
||||||
|
|
||||||
public uint LoopStart { get; set; } = 0;
|
|
||||||
public uint LoopLength { get; set; } = 0;
|
|
||||||
|
|
||||||
private Stack<StaticSoundInstance> AvailableInstances = new Stack<StaticSoundInstance>();
|
|
||||||
private HashSet<StaticSoundInstance> UsedInstances = new HashSet<StaticSoundInstance>();
|
|
||||||
|
|
||||||
private bool OwnsBuffer;
|
private bool OwnsBuffer;
|
||||||
|
|
||||||
public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath)
|
public static unsafe StaticSound LoadOgg(AudioDevice device, string filePath)
|
||||||
|
@ -253,10 +246,9 @@ namespace MoonWorks.Audio
|
||||||
uint bufferLengthInBytes,
|
uint bufferLengthInBytes,
|
||||||
bool ownsBuffer) : base(device)
|
bool ownsBuffer) : base(device)
|
||||||
{
|
{
|
||||||
// TODO: should we wrap the format struct to make it nicer?
|
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
|
||||||
Handle = new FAudio.FAudioBuffer
|
Buffer = new FAudio.FAudioBuffer
|
||||||
{
|
{
|
||||||
Flags = FAudio.FAUDIO_END_OF_STREAM,
|
Flags = FAudio.FAUDIO_END_OF_STREAM,
|
||||||
pContext = IntPtr.Zero,
|
pContext = IntPtr.Zero,
|
||||||
|
@ -269,66 +261,11 @@ namespace MoonWorks.Audio
|
||||||
OwnsBuffer = ownsBuffer;
|
OwnsBuffer = ownsBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a sound instance from the pool.
|
|
||||||
/// NOTE: If AutoFree is false, you will have to call StaticSoundInstance.Free() yourself or leak the instance!
|
|
||||||
/// </summary>
|
|
||||||
public StaticSoundInstance GetInstance(bool autoFree = true)
|
|
||||||
{
|
|
||||||
StaticSoundInstance instance;
|
|
||||||
|
|
||||||
lock (AvailableInstances)
|
|
||||||
{
|
|
||||||
if (AvailableInstances.Count == 0)
|
|
||||||
{
|
|
||||||
AvailableInstances.Push(new StaticSoundInstance(Device, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
instance = AvailableInstances.Pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.AutoFree = autoFree;
|
|
||||||
|
|
||||||
lock (UsedInstances)
|
|
||||||
{
|
|
||||||
UsedInstances.Add(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void FreeInstance(StaticSoundInstance instance)
|
|
||||||
{
|
|
||||||
instance.Reset();
|
|
||||||
|
|
||||||
lock (UsedInstances)
|
|
||||||
{
|
|
||||||
UsedInstances.Remove(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (AvailableInstances)
|
|
||||||
{
|
|
||||||
AvailableInstances.Push(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override unsafe void Destroy()
|
protected override unsafe void Destroy()
|
||||||
{
|
{
|
||||||
foreach (var instance in UsedInstances)
|
|
||||||
{
|
|
||||||
instance.Free();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var instance in AvailableInstances)
|
|
||||||
{
|
|
||||||
instance.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
AvailableInstances.Clear();
|
|
||||||
|
|
||||||
if (OwnsBuffer)
|
if (OwnsBuffer)
|
||||||
{
|
{
|
||||||
NativeMemory.Free((void*) Handle.pAudioData);
|
NativeMemory.Free((void*) Buffer.pAudioData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
public class StaticSoundInstance : SoundInstance
|
|
||||||
{
|
|
||||||
public StaticSound Parent { get; }
|
|
||||||
|
|
||||||
public bool Loop { get; set; }
|
|
||||||
|
|
||||||
private SoundState _state = SoundState.Stopped;
|
|
||||||
public override SoundState State
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
FAudio.FAudioSourceVoice_GetState(
|
|
||||||
Voice,
|
|
||||||
out var state,
|
|
||||||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
|
||||||
);
|
|
||||||
if (state.BuffersQueued == 0)
|
|
||||||
{
|
|
||||||
StopImmediate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _state;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
_state = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutoFree { get; internal set; }
|
|
||||||
|
|
||||||
internal StaticSoundInstance(
|
|
||||||
AudioDevice device,
|
|
||||||
StaticSound parent
|
|
||||||
) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
|
|
||||||
{
|
|
||||||
Parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Play()
|
|
||||||
{
|
|
||||||
PlayUsingOperationSet(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void QueueSyncPlay()
|
|
||||||
{
|
|
||||||
PlayUsingOperationSet(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayUsingOperationSet(uint operationSet)
|
|
||||||
{
|
|
||||||
if (State == SoundState.Playing)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Loop)
|
|
||||||
{
|
|
||||||
Parent.Handle.LoopCount = 255;
|
|
||||||
Parent.Handle.LoopBegin = Parent.LoopStart;
|
|
||||||
Parent.Handle.LoopLength = Parent.LoopLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Parent.Handle.LoopCount = 0;
|
|
||||||
Parent.Handle.LoopBegin = 0;
|
|
||||||
Parent.Handle.LoopLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
|
||||||
Voice,
|
|
||||||
ref Parent.Handle,
|
|
||||||
IntPtr.Zero
|
|
||||||
);
|
|
||||||
|
|
||||||
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
|
||||||
State = SoundState.Playing;
|
|
||||||
|
|
||||||
if (AutoFree)
|
|
||||||
{
|
|
||||||
Device.AddAutoFreeStaticSoundInstance(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Pause()
|
|
||||||
{
|
|
||||||
if (State == SoundState.Playing)
|
|
||||||
{
|
|
||||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
|
||||||
State = SoundState.Paused;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Stop()
|
|
||||||
{
|
|
||||||
FAudio.FAudioSourceVoice_ExitLoop(Voice, 0);
|
|
||||||
State = SoundState.Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void StopImmediate()
|
|
||||||
{
|
|
||||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
|
||||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
|
|
||||||
State = SoundState.Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Seek(uint sampleFrame)
|
|
||||||
{
|
|
||||||
if (State == SoundState.Playing)
|
|
||||||
{
|
|
||||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
|
||||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
|
|
||||||
}
|
|
||||||
|
|
||||||
Parent.Handle.PlayBegin = sampleFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when you no longer need the sound instance.
|
|
||||||
// If AutoFree is set, this will automatically be called when the sound instance stops playing.
|
|
||||||
// If the sound isn't stopped when you call this, things might get weird!
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
Parent.FreeInstance(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Reset()
|
|
||||||
{
|
|
||||||
Pan = 0;
|
|
||||||
Pitch = 0;
|
|
||||||
Volume = 1;
|
|
||||||
Loop = false;
|
|
||||||
Is3D = false;
|
|
||||||
FilterType = FilterType.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class SubmixVoice : SendableVoice, IReceivableVoice
|
public class SubmixVoice : Voice
|
||||||
{
|
{
|
||||||
public SubmixVoice(
|
public SubmixVoice(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using EasingFunction = System.Func<float, float>;
|
using EasingFunction = System.Func<float, float>;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public abstract class Voice : AudioResource
|
public abstract unsafe class Voice : AudioResource
|
||||||
{
|
{
|
||||||
protected IntPtr handle;
|
protected IntPtr handle;
|
||||||
public IntPtr Handle => handle;
|
public IntPtr Handle => handle;
|
||||||
|
@ -11,6 +12,11 @@ namespace MoonWorks.Audio
|
||||||
public uint SourceChannelCount { get; }
|
public uint SourceChannelCount { get; }
|
||||||
public uint DestinationChannelCount { get; }
|
public uint DestinationChannelCount { get; }
|
||||||
|
|
||||||
|
private SubmixVoice OutputVoice;
|
||||||
|
private ReverbEffect ReverbEffect;
|
||||||
|
|
||||||
|
byte* pMatrixCoefficients;
|
||||||
|
|
||||||
public bool Is3D { get; protected set; }
|
public bool Is3D { get; protected set; }
|
||||||
|
|
||||||
private float dopplerFactor;
|
private float dopplerFactor;
|
||||||
|
@ -27,21 +33,6 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float pitch = 0;
|
|
||||||
public float Pitch
|
|
||||||
{
|
|
||||||
get => pitch;
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
|
||||||
if (pitch != value)
|
|
||||||
{
|
|
||||||
pitch = value;
|
|
||||||
UpdatePitch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float volume = 1;
|
private float volume = 1;
|
||||||
public float Volume
|
public float Volume
|
||||||
{
|
{
|
||||||
|
@ -57,6 +48,21 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float pitch = 0;
|
||||||
|
public float Pitch
|
||||||
|
{
|
||||||
|
get => pitch;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||||
|
if (pitch != value)
|
||||||
|
{
|
||||||
|
pitch = value;
|
||||||
|
UpdatePitch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const float MAX_FILTER_FREQUENCY = 1f;
|
private const float MAX_FILTER_FREQUENCY = 1f;
|
||||||
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
||||||
|
|
||||||
|
@ -150,10 +156,89 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected float pan = 0;
|
||||||
|
public float Pan
|
||||||
|
{
|
||||||
|
get => pan;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||||
|
if (pan != value)
|
||||||
|
{
|
||||||
|
pan = value;
|
||||||
|
|
||||||
|
if (pan < -1f)
|
||||||
|
{
|
||||||
|
pan = -1f;
|
||||||
|
}
|
||||||
|
if (pan > 1f)
|
||||||
|
{
|
||||||
|
pan = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Is3D) { return; }
|
||||||
|
|
||||||
|
SetPanMatrixCoefficients();
|
||||||
|
FAudio.FAudioVoice_SetOutputMatrix(
|
||||||
|
Handle,
|
||||||
|
OutputVoice.Handle,
|
||||||
|
SourceChannelCount,
|
||||||
|
DestinationChannelCount,
|
||||||
|
(nint) pMatrixCoefficients,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float reverb;
|
||||||
|
public unsafe float Reverb
|
||||||
|
{
|
||||||
|
get => reverb;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (ReverbEffect != null)
|
||||||
|
{
|
||||||
|
value = MathF.Max(0, value);
|
||||||
|
if (reverb != value)
|
||||||
|
{
|
||||||
|
reverb = value;
|
||||||
|
|
||||||
|
float* outputMatrix = (float*) pMatrixCoefficients;
|
||||||
|
outputMatrix[0] = reverb;
|
||||||
|
if (SourceChannelCount == 2)
|
||||||
|
{
|
||||||
|
outputMatrix[1] = reverb;
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_SetOutputMatrix(
|
||||||
|
Handle,
|
||||||
|
ReverbEffect.Handle,
|
||||||
|
SourceChannelCount,
|
||||||
|
1,
|
||||||
|
(nint) pMatrixCoefficients,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (ReverbEffect == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
|
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
|
||||||
{
|
{
|
||||||
SourceChannelCount = sourceChannelCount;
|
SourceChannelCount = sourceChannelCount;
|
||||||
DestinationChannelCount = destinationChannelCount;
|
DestinationChannelCount = destinationChannelCount;
|
||||||
|
OutputVoice = device.MasteringVoice;
|
||||||
|
nuint memsize = 4 * sourceChannelCount * destinationChannelCount;
|
||||||
|
pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize);
|
||||||
|
SetPanMatrixCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPitch(float targetValue)
|
public void SetPitch(float targetValue)
|
||||||
|
@ -209,6 +294,159 @@ namespace MoonWorks.Audio
|
||||||
FilterOneOverQ = targetValue;
|
FilterOneOverQ = targetValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void SetPan(float targetValue)
|
||||||
|
{
|
||||||
|
Pan = targetValue;
|
||||||
|
Device.ClearTweens(this, AudioTweenProperty.Pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetReverb(float targetValue)
|
||||||
|
{
|
||||||
|
Reverb = targetValue;
|
||||||
|
Device.ClearTweens(this, AudioTweenProperty.Reverb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||||
|
{
|
||||||
|
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void SetOutputVoice(SubmixVoice send)
|
||||||
|
{
|
||||||
|
OutputVoice = send;
|
||||||
|
|
||||||
|
if (ReverbEffect != null)
|
||||||
|
{
|
||||||
|
SetReverbEffectChain(ReverbEffect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
|
||||||
|
sendDesc[0].Flags = 0;
|
||||||
|
sendDesc[0].pOutputVoice = send.Handle;
|
||||||
|
|
||||||
|
var sends = new FAudio.FAudioVoiceSends();
|
||||||
|
sends.SendCount = 1;
|
||||||
|
sends.pSends = (nint) sendDesc;
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_SetOutputVoices(
|
||||||
|
Handle,
|
||||||
|
ref sends
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
|
||||||
|
{
|
||||||
|
var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
|
||||||
|
sendDesc[0].Flags = 0;
|
||||||
|
sendDesc[0].pOutputVoice = OutputVoice.Handle;
|
||||||
|
sendDesc[1].Flags = 0;
|
||||||
|
sendDesc[1].pOutputVoice = reverbEffect.Handle;
|
||||||
|
|
||||||
|
var sends = new FAudio.FAudioVoiceSends();
|
||||||
|
sends.SendCount = 2;
|
||||||
|
sends.pSends = (nint) sendDesc;
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_SetOutputVoices(
|
||||||
|
Handle,
|
||||||
|
ref sends
|
||||||
|
);
|
||||||
|
|
||||||
|
ReverbEffect = reverbEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveReverbEffectChain()
|
||||||
|
{
|
||||||
|
if (ReverbEffect != null)
|
||||||
|
{
|
||||||
|
ReverbEffect = null;
|
||||||
|
reverb = 0;
|
||||||
|
SetOutputVoice(OutputVoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
||||||
|
private unsafe void SetPanMatrixCoefficients()
|
||||||
|
{
|
||||||
|
/* Two major things to notice:
|
||||||
|
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
|
||||||
|
* 2. Stereo panning is WAY more complicated than you think.
|
||||||
|
* The main thing is that hard panning does NOT eliminate an
|
||||||
|
* entire channel; the two channels are blended on each side.
|
||||||
|
* -flibit
|
||||||
|
*/
|
||||||
|
float* outputMatrix = (float*) pMatrixCoefficients;
|
||||||
|
if (SourceChannelCount == 1)
|
||||||
|
{
|
||||||
|
if (DestinationChannelCount == 1)
|
||||||
|
{
|
||||||
|
outputMatrix[0] = 1.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
|
||||||
|
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (DestinationChannelCount == 1)
|
||||||
|
{
|
||||||
|
outputMatrix[0] = 1.0f;
|
||||||
|
outputMatrix[1] = 1.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pan <= 0.0f)
|
||||||
|
{
|
||||||
|
// Left speaker blends left/right channels
|
||||||
|
outputMatrix[0] = 0.5f * pan + 1.0f;
|
||||||
|
outputMatrix[1] = 0.5f * -pan;
|
||||||
|
// Right speaker gets less of the right channel
|
||||||
|
outputMatrix[2] = 0.0f;
|
||||||
|
outputMatrix[3] = pan + 1.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Left speaker gets less of the left channel
|
||||||
|
outputMatrix[0] = -pan + 1.0f;
|
||||||
|
outputMatrix[1] = 0.0f;
|
||||||
|
// Right speaker blends right/left channels
|
||||||
|
outputMatrix[2] = 0.5f * pan;
|
||||||
|
outputMatrix[3] = 0.5f * -pan + 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Reset()
|
||||||
|
{
|
||||||
|
RemoveReverbEffectChain();
|
||||||
|
Volume = 1;
|
||||||
|
Pan = 0;
|
||||||
|
Pitch = 0;
|
||||||
|
FilterType = FilterType.None;
|
||||||
|
FilterFrequency = 1;
|
||||||
|
FilterOneOverQ = 1;
|
||||||
|
SetOutputVoice(Device.MasteringVoice);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdatePitch()
|
private void UpdatePitch()
|
||||||
{
|
{
|
||||||
float doppler;
|
float doppler;
|
||||||
|
@ -231,6 +469,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
protected unsafe override void Destroy()
|
protected unsafe override void Destroy()
|
||||||
{
|
{
|
||||||
|
NativeMemory.Free(pMatrixCoefficients);
|
||||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue