restructure and implement Voice hierarchy

pull/50/head
cosmonaut 2023-08-01 11:22:49 -07:00
parent b1c7740b86
commit e2c85ec728
14 changed files with 452 additions and 330 deletions

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace MoonWorks.Audio
@ -25,6 +24,8 @@ namespace MoonWorks.Audio
private AudioTweenManager AudioTweenManager;
private SourceVoicePool VoicePool;
private const int Step = 200;
private TimeSpan UpdateInterval;
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
@ -106,6 +107,7 @@ namespace MoonWorks.Audio
);
AudioTweenManager = new AudioTweenManager();
VoicePool = new SourceVoicePool(this);
Logger.LogInfo("Setting up audio thread...");
WakeSignal = new AutoResetEvent(true);
@ -260,6 +262,16 @@ namespace MoonWorks.Audio
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)
{
if (!IsDisposed)

View File

@ -24,7 +24,10 @@ namespace MoonWorks.Audio
switch (audioTween.Property)
{
case AudioTweenProperty.Pan:
audioTween.StartValue = voice.Pan;
if (voice is SendableVoice pannableVoice)
{
audioTween.StartValue = pannableVoice.Pan;
}
break;
case AudioTweenProperty.Pitch:
@ -40,7 +43,10 @@ namespace MoonWorks.Audio
break;
case AudioTweenProperty.Reverb:
audioTween.StartValue = voice.Reverb;
if (voice is SendableVoice reverbableVoice)
{
audioTween.StartValue = reverbableVoice.Reverb;
}
break;
}
@ -133,7 +139,10 @@ namespace MoonWorks.Audio
switch (audioTween.Property)
{
case AudioTweenProperty.Pan:
audioTween.Voice.Pan = value;
if (audioTween.Voice is SendableVoice pannableVoice)
{
pannableVoice.Pan = value;
}
break;
case AudioTweenProperty.Pitch:
@ -149,7 +158,10 @@ namespace MoonWorks.Audio
break;
case AudioTweenProperty.Reverb:
audioTween.Voice.Reverb = value;
if (audioTween.Voice is SendableVoice reverbableVoice)
{
reverbableVoice.Reverb = value;
}
break;
}

View File

@ -4,39 +4,30 @@ namespace MoonWorks.Audio
{
public static class AudioUtils
{
public struct WaveHeaderData
public static Format ReadWaveFormat(string filePath, out int dataLength)
{
public int FileLength;
public short FormatTag;
public short Channels;
public int SampleRate;
public short BitsPerSample;
public short BlockAlign;
public int DataLength;
}
public static WaveHeaderData ReadWaveHeaderData(string filePath)
{
WaveHeaderData headerData;
var fileInfo = new FileInfo(filePath);
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using BinaryReader br = new BinaryReader(fs);
headerData.FileLength = (int)fileInfo.Length - 8;
fs.Position = 20;
headerData.FormatTag = br.ReadInt16();
var formatTag = br.ReadInt16();
fs.Position = 22;
headerData.Channels = br.ReadInt16();
var channels = br.ReadInt16();
fs.Position = 24;
headerData.SampleRate = br.ReadInt32();
fs.Position = 32;
headerData.BlockAlign = br.ReadInt16();
var sampleRate = br.ReadInt32();
fs.Position = 34;
headerData.BitsPerSample = br.ReadInt16();
var bitsPerSample = br.ReadInt16();
fs.Position = 40;
headerData.DataLength = br.ReadInt32();
dataLength = br.ReadInt32();
return headerData;
return new Format
{
Tag = (FormatTag) formatTag,
Channels = (ushort) channels,
SampleRate = (uint) sampleRate,
BitsPerSample = (ushort) bitsPerSample
};
}
}
}

33
src/Audio/Format.cs Normal file
View File

@ -0,0 +1,33 @@
namespace MoonWorks.Audio
{
public enum FormatTag : ushort
{
Unknown = 0,
PCM = 1,
MSADPCM = 2,
IEEE_FLOAT = 3
}
public record struct Format
{
public FormatTag Tag;
public ushort Channels;
public uint SampleRate;
public ushort BitsPerSample;
internal FAudio.FAudioWaveFormatEx ToFAudioFormat()
{
var blockAlign = (ushort) ((BitsPerSample / 8) * Channels);
return new FAudio.FAudioWaveFormatEx
{
wFormatTag = (ushort) Tag,
nChannels = Channels,
nSamplesPerSec = SampleRate,
wBitsPerSample = BitsPerSample,
nBlockAlign = blockAlign,
nAvgBytesPerSec = blockAlign * SampleRate
};
}
}
}

View File

@ -0,0 +1,7 @@
namespace MoonWorks.Audio
{
public interface IReceivableVoice
{
public System.IntPtr Handle { get; }
}
}

View File

@ -2,11 +2,8 @@ using System;
namespace MoonWorks.Audio
{
public class MasteringVoice : Voice
public class MasteringVoice : Voice, IReceivableVoice
{
// mastering voice can't pan
public override float Pan => 0;
internal static bool Create(
AudioDevice device,
uint deviceIndex,
@ -38,9 +35,9 @@ namespace MoonWorks.Audio
internal MasteringVoice(
AudioDevice device,
IntPtr handle
) : base(device)
) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, 0)
{
Handle = handle;
this.handle = handle;
}
}
}

227
src/Audio/SendableVoice.cs Normal file
View File

@ -0,0 +1,227 @@
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();
}
}
}

View File

@ -9,13 +9,13 @@ namespace MoonWorks.Audio
public delegate void OnSoundNeededFunc();
public OnSoundNeededFunc OnSoundNeeded;
public SoundSequence(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
public SoundSequence(AudioDevice device, Format format) : base(device, format)
{
device.AddSoundSequenceReference(this);
OnUpdate += Update;
}
public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.FormatTag, templateSound.BitsPerSample, templateSound.BlockAlign, templateSound.Channels, templateSound.SamplesPerSecond)
public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.Format)
{
device.AddSoundSequenceReference(this);
OnUpdate += Update;
@ -51,12 +51,7 @@ namespace MoonWorks.Audio
public void EnqueueSound(StaticSound sound)
{
#if DEBUG
if (
sound.FormatTag != Format.wFormatTag ||
sound.BitsPerSample != Format.wBitsPerSample ||
sound.Channels != Format.nChannels ||
sound.SamplesPerSecond != Format.nSamplesPerSec
)
if (!(sound.Format == Format))
{
Logger.LogWarn("Playlist audio format mismatch!");
}

View File

@ -2,9 +2,10 @@ using System;
namespace MoonWorks.Audio
{
public class SourceVoice : Voice
public class SourceVoice : SendableVoice
{
protected FAudio.FAudioWaveFormatEx Format;
private Format format;
public Format Format => format;
protected object StateLock = new object();
@ -46,27 +47,16 @@ namespace MoonWorks.Audio
public SourceVoice(
AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond
) : base(device)
Format format
) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
{
Format = new FAudio.FAudioWaveFormatEx
{
wFormatTag = formatTag,
wBitsPerSample = bitsPerSample,
nChannels = channels,
nBlockAlign = blockAlign,
nSamplesPerSec = samplesPerSecond,
nAvgBytesPerSec = blockAlign * samplesPerSecond
};
this.format = format;
var fAudioFormat = format.ToFAudioFormat();
FAudio.FAudio_CreateSourceVoice(
device.Handle,
out var Handle,
ref Format,
ref fAudioFormat,
FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero,
@ -114,6 +104,42 @@ namespace MoonWorks.Audio
}
}
public void SubmitBuffer(FAudio.FAudioBuffer buffer)
{
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Handle,
ref buffer,
IntPtr.Zero
);
}
// FIXME: maybe this is bad
// 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
public static SourceVoice PlayStaticSound(AudioDevice device, StaticSound sound, SubmixVoice sendVoice = null)
{
var voice = ObtainSourceVoice(device, sound.Format);
if (sendVoice == null)
{
voice.SetOutputVoice(device.MasteringVoice);
}
else
{
voice.SetOutputVoice(sendVoice);
}
voice.SubmitBuffer(sound.Handle);
voice.Play();
return voice;
}
protected override unsafe void Destroy()
{
Stop();

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace MoonWorks.Audio
{
internal class SourceVoicePool
{
private AudioDevice Device;
Dictionary<Format, Queue<SourceVoice>> VoiceLists = new Dictionary<Format, Queue<SourceVoice>>();
public SourceVoicePool(AudioDevice device)
{
Device = device;
}
public SourceVoice Obtain(Format format)
{
if (!VoiceLists.ContainsKey(format))
{
VoiceLists.Add(format, new Queue<SourceVoice>());
}
var list = VoiceLists[format];
if (list.Count == 0)
{
list.Enqueue(new SourceVoice(Device, format));
}
return list.Dequeue();
}
public void Return(SourceVoice voice)
{
var list = VoiceLists[voice.Format];
list.Enqueue(voice);
}
}
}

View File

@ -8,11 +8,9 @@ namespace MoonWorks.Audio
public class StaticSound : AudioResource
{
internal FAudio.FAudioBuffer Handle;
public ushort FormatTag { get; }
public ushort BitsPerSample { get; }
public ushort Channels { get; }
public uint SamplesPerSecond { get; }
public ushort BlockAlign { get; }
private Format format;
public Format Format => format;
public uint LoopStart { get; set; } = 0;
public uint LoopLength { get; set; } = 0;
@ -45,13 +43,17 @@ namespace MoonWorks.Audio
FAudio.stb_vorbis_close(filePointer);
var format = new Format
{
Tag = FormatTag.IEEE_FLOAT,
BitsPerSample = 32,
Channels = (ushort) info.channels,
SampleRate = info.sample_rate
};
return new StaticSound(
device,
3,
32,
(ushort) (4 * info.channels),
(ushort) info.channels,
info.sample_rate,
format,
(nint) buffer,
(uint) lengthInBytes,
true);
@ -183,13 +185,17 @@ namespace MoonWorks.Audio
}
// End scan
var format = new Format
{
Tag = (FormatTag) wFormatTag,
BitsPerSample = wBitsPerSample,
Channels = nChannels,
SampleRate = nSamplesPerSec
};
var sound = new StaticSound(
device,
wFormatTag,
wBitsPerSample,
nBlockAlign,
nChannels,
nSamplesPerSec,
format,
(nint) waveDataBuffer,
(uint) waveDataLength,
true
@ -223,13 +229,17 @@ namespace MoonWorks.Audio
FAudio.qoa_close(qoaHandle);
NativeMemory.Free(fileDataPtr);
var format = new Format
{
Tag = FormatTag.PCM,
BitsPerSample = 16,
Channels = (ushort) channels,
SampleRate = samplerate
};
return new StaticSound(
device,
1,
16,
(ushort) (channels * 2),
(ushort) channels,
samplerate,
format,
(nint) buffer,
bufferLengthInBytes,
true
@ -238,20 +248,13 @@ namespace MoonWorks.Audio
public StaticSound(
AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond,
Format format,
IntPtr bufferPtr,
uint bufferLengthInBytes,
bool ownsBuffer) : base(device)
{
FormatTag = formatTag;
BitsPerSample = bitsPerSample;
BlockAlign = blockAlign;
Channels = channels;
SamplesPerSecond = samplesPerSecond;
// TODO: should we wrap the format struct to make it nicer?
this.format = format;
Handle = new FAudio.FAudioBuffer
{

View File

@ -1,11 +0,0 @@
namespace MoonWorks.Audio
{
public class StaticSourceVoice : SourceVoice
{
public StaticSourceVoice(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
{
}
}
}

View File

@ -2,22 +2,22 @@ using System;
namespace MoonWorks.Audio
{
public class SubmixVoice : Voice
public class SubmixVoice : SendableVoice, IReceivableVoice
{
public SubmixVoice(
AudioDevice device,
uint inputChannels,
uint sourceChannelCount,
uint sampleRate
) : base(device)
) : base(device, sourceChannelCount, device.DeviceDetails.OutputFormat.Format.nChannels)
{
FAudio.FAudio_CreateSubmixVoice(
device.Handle,
out Handle,
inputChannels,
out handle,
sourceChannelCount,
sampleRate,
0,
0,
IntPtr.Zero,
IntPtr.Zero, // default sends to mastering voice
IntPtr.Zero
);
}

View File

@ -1,50 +1,28 @@
using System;
using System.Runtime.InteropServices;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio
{
public abstract class Voice : AudioResource
{
protected IntPtr Handle;
protected IntPtr handle;
public IntPtr Handle => handle;
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
private ReverbEffect ReverbEffect;
public uint SourceChannelCount { get; }
public uint DestinationChannelCount { get; }
public bool Is3D { get; protected set; }
private float pan = 0;
public virtual float Pan
private float dopplerFactor;
public float DopplerFactor
{
get => pan;
internal set
get => dopplerFactor;
set
{
value = Math.MathHelper.Clamp(value, -1f, 1f);
if (pan != value)
if (dopplerFactor != value)
{
pan = value;
if (pan < -1f)
{
pan = -1f;
}
if (pan > 1f)
{
pan = 1f;
}
if (Is3D) { return; }
SetPanMatrixCoefficients();
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
Device.MasteringVoice.Handle,
dspSettings.SrcChannelCount,
dspSettings.DstChannelCount,
dspSettings.pMatrixCoefficients,
0
);
dopplerFactor = value;
UpdatePitch();
}
}
}
@ -172,62 +150,10 @@ namespace MoonWorks.Audio
}
}
private float reverb;
public unsafe float Reverb
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
{
get => reverb;
internal set
{
if (ReverbEffect != null)
{
value = MathF.Max(0, value);
if (reverb != value)
{
reverb = value;
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
outputMatrix[0] = reverb;
if (dspSettings.SrcChannelCount == 2)
{
outputMatrix[1] = reverb;
}
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
ReverbEffect.Handle,
dspSettings.SrcChannelCount,
1,
dspSettings.pMatrixCoefficients,
0
);
}
}
#if DEBUG
if (ReverbEffect == null)
{
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
}
#endif
}
}
public Voice(AudioDevice device) : base(device) { }
public void SetPan(float targetValue)
{
Pan = targetValue;
Device.ClearTweens(this, AudioTweenProperty.Pan);
}
public void SetPan(float targetValue, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
}
public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
SourceChannelCount = sourceChannelCount;
DestinationChannelCount = destinationChannelCount;
}
public void SetPitch(float targetValue)
@ -283,85 +209,6 @@ namespace MoonWorks.Audio
FilterOneOverQ = targetValue;
}
public void SetReverb(float targetValue)
{
Reverb = targetValue;
Device.ClearTweens(this, AudioTweenProperty.Reverb);
}
public void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
}
public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
}
public unsafe void SetSends(Voice 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
);
}
public unsafe void SetSends(Voice sendOne, Voice sendTwo)
{
var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = sendOne.Handle;
sendDesc[1].Flags = 0;
sendDesc[1].pOutputVoice = sendTwo.Handle;
var sends = new FAudio.FAudioVoiceSends();
sends.SendCount = 2;
sends.pSends = (nint) sendDesc;
FAudio.FAudioVoice_SetOutputVoices(
Handle,
ref sends
);
}
public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
{
SetSends(Device.MasteringVoice, reverbEffect);
ReverbEffect = reverbEffect;
}
private unsafe void InitDSPSettings(uint srcChannels)
{
dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
dspSettings.DopplerFactor = 1f;
dspSettings.SrcChannelCount = srcChannels;
dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
nuint memsize = (
4 *
dspSettings.SrcChannelCount *
dspSettings.DstChannelCount
);
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
for (uint i = 0; i < memsize; i += 1)
{
memPtr[i] = 0;
}
SetPanMatrixCoefficients();
}
private void UpdatePitch()
{
float doppler;
@ -372,7 +219,7 @@ namespace MoonWorks.Audio
}
else
{
doppler = dspSettings.DopplerFactor * dopplerScale;
doppler = DopplerFactor * dopplerScale;
}
FAudio.FAudioSourceVoice_SetFrequencyRatio(
@ -382,65 +229,9 @@ namespace MoonWorks.Audio
);
}
// 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*) dspSettings.pMatrixCoefficients;
if (dspSettings.SrcChannelCount == 1)
{
if (dspSettings.DstChannelCount == 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 (dspSettings.DstChannelCount == 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 unsafe override void Destroy()
{
FAudio.FAudioVoice_DestroyVoice(Handle);
NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
}
}
}