228 lines
5.5 KiB
C#
228 lines
5.5 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|