restructure and implement Voice hierarchy
parent
b1c7740b86
commit
e2c85ec728
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
|
@ -25,6 +24,8 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
private AudioTweenManager AudioTweenManager;
|
private AudioTweenManager AudioTweenManager;
|
||||||
|
|
||||||
|
private SourceVoicePool VoicePool;
|
||||||
|
|
||||||
private const int Step = 200;
|
private const int Step = 200;
|
||||||
private TimeSpan UpdateInterval;
|
private TimeSpan UpdateInterval;
|
||||||
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
|
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
|
||||||
|
@ -106,6 +107,7 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
|
|
||||||
AudioTweenManager = new AudioTweenManager();
|
AudioTweenManager = new AudioTweenManager();
|
||||||
|
VoicePool = new SourceVoicePool(this);
|
||||||
|
|
||||||
Logger.LogInfo("Setting up audio thread...");
|
Logger.LogInfo("Setting up audio thread...");
|
||||||
WakeSignal = new AutoResetEvent(true);
|
WakeSignal = new AutoResetEvent(true);
|
||||||
|
@ -260,6 +262,16 @@ namespace MoonWorks.Audio
|
||||||
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)
|
||||||
|
|
|
@ -24,7 +24,10 @@ namespace MoonWorks.Audio
|
||||||
switch (audioTween.Property)
|
switch (audioTween.Property)
|
||||||
{
|
{
|
||||||
case AudioTweenProperty.Pan:
|
case AudioTweenProperty.Pan:
|
||||||
audioTween.StartValue = voice.Pan;
|
if (voice is SendableVoice pannableVoice)
|
||||||
|
{
|
||||||
|
audioTween.StartValue = pannableVoice.Pan;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Pitch:
|
case AudioTweenProperty.Pitch:
|
||||||
|
@ -40,7 +43,10 @@ namespace MoonWorks.Audio
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Reverb:
|
case AudioTweenProperty.Reverb:
|
||||||
audioTween.StartValue = voice.Reverb;
|
if (voice is SendableVoice reverbableVoice)
|
||||||
|
{
|
||||||
|
audioTween.StartValue = reverbableVoice.Reverb;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +139,10 @@ namespace MoonWorks.Audio
|
||||||
switch (audioTween.Property)
|
switch (audioTween.Property)
|
||||||
{
|
{
|
||||||
case AudioTweenProperty.Pan:
|
case AudioTweenProperty.Pan:
|
||||||
audioTween.Voice.Pan = value;
|
if (audioTween.Voice is SendableVoice pannableVoice)
|
||||||
|
{
|
||||||
|
pannableVoice.Pan = value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Pitch:
|
case AudioTweenProperty.Pitch:
|
||||||
|
@ -149,7 +158,10 @@ namespace MoonWorks.Audio
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioTweenProperty.Reverb:
|
case AudioTweenProperty.Reverb:
|
||||||
audioTween.Voice.Reverb = value;
|
if (audioTween.Voice is SendableVoice reverbableVoice)
|
||||||
|
{
|
||||||
|
reverbableVoice.Reverb = value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,39 +4,30 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public static class AudioUtils
|
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);
|
var fileInfo = new FileInfo(filePath);
|
||||||
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||||
using BinaryReader br = new BinaryReader(fs);
|
using BinaryReader br = new BinaryReader(fs);
|
||||||
|
|
||||||
headerData.FileLength = (int)fileInfo.Length - 8;
|
|
||||||
fs.Position = 20;
|
fs.Position = 20;
|
||||||
headerData.FormatTag = br.ReadInt16();
|
var formatTag = br.ReadInt16();
|
||||||
fs.Position = 22;
|
fs.Position = 22;
|
||||||
headerData.Channels = br.ReadInt16();
|
var channels = br.ReadInt16();
|
||||||
fs.Position = 24;
|
fs.Position = 24;
|
||||||
headerData.SampleRate = br.ReadInt32();
|
var sampleRate = br.ReadInt32();
|
||||||
fs.Position = 32;
|
|
||||||
headerData.BlockAlign = br.ReadInt16();
|
|
||||||
fs.Position = 34;
|
fs.Position = 34;
|
||||||
headerData.BitsPerSample = br.ReadInt16();
|
var bitsPerSample = br.ReadInt16();
|
||||||
fs.Position = 40;
|
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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public interface IReceivableVoice
|
||||||
|
{
|
||||||
|
public System.IntPtr Handle { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,8 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
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(
|
internal static bool Create(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
uint deviceIndex,
|
uint deviceIndex,
|
||||||
|
@ -38,9 +35,9 @@ namespace MoonWorks.Audio
|
||||||
internal MasteringVoice(
|
internal MasteringVoice(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr handle
|
IntPtr handle
|
||||||
) : base(device)
|
) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, 0)
|
||||||
{
|
{
|
||||||
Handle = handle;
|
this.handle = handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,13 @@ namespace MoonWorks.Audio
|
||||||
public delegate void OnSoundNeededFunc();
|
public delegate void OnSoundNeededFunc();
|
||||||
public OnSoundNeededFunc OnSoundNeeded;
|
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);
|
device.AddSoundSequenceReference(this);
|
||||||
OnUpdate += Update;
|
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);
|
device.AddSoundSequenceReference(this);
|
||||||
OnUpdate += Update;
|
OnUpdate += Update;
|
||||||
|
@ -51,12 +51,7 @@ namespace MoonWorks.Audio
|
||||||
public void EnqueueSound(StaticSound sound)
|
public void EnqueueSound(StaticSound sound)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (
|
if (!(sound.Format == Format))
|
||||||
sound.FormatTag != Format.wFormatTag ||
|
|
||||||
sound.BitsPerSample != Format.wBitsPerSample ||
|
|
||||||
sound.Channels != Format.nChannels ||
|
|
||||||
sound.SamplesPerSecond != Format.nSamplesPerSec
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Logger.LogWarn("Playlist audio format mismatch!");
|
Logger.LogWarn("Playlist audio format mismatch!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,10 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
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();
|
protected object StateLock = new object();
|
||||||
|
|
||||||
|
@ -46,27 +47,16 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public SourceVoice(
|
public SourceVoice(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
ushort formatTag,
|
Format format
|
||||||
ushort bitsPerSample,
|
) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
|
||||||
ushort blockAlign,
|
|
||||||
ushort channels,
|
|
||||||
uint samplesPerSecond
|
|
||||||
) : base(device)
|
|
||||||
{
|
{
|
||||||
Format = new FAudio.FAudioWaveFormatEx
|
this.format = format;
|
||||||
{
|
var fAudioFormat = format.ToFAudioFormat();
|
||||||
wFormatTag = formatTag,
|
|
||||||
wBitsPerSample = bitsPerSample,
|
|
||||||
nChannels = channels,
|
|
||||||
nBlockAlign = blockAlign,
|
|
||||||
nSamplesPerSec = samplesPerSecond,
|
|
||||||
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
|
||||||
};
|
|
||||||
|
|
||||||
FAudio.FAudio_CreateSourceVoice(
|
FAudio.FAudio_CreateSourceVoice(
|
||||||
device.Handle,
|
device.Handle,
|
||||||
out var Handle,
|
out var Handle,
|
||||||
ref Format,
|
ref fAudioFormat,
|
||||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||||
IntPtr.Zero,
|
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()
|
protected override unsafe void Destroy()
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,9 @@ namespace MoonWorks.Audio
|
||||||
public class StaticSound : AudioResource
|
public class StaticSound : AudioResource
|
||||||
{
|
{
|
||||||
internal FAudio.FAudioBuffer Handle;
|
internal FAudio.FAudioBuffer Handle;
|
||||||
public ushort FormatTag { get; }
|
|
||||||
public ushort BitsPerSample { get; }
|
private Format format;
|
||||||
public ushort Channels { get; }
|
public Format Format => format;
|
||||||
public uint SamplesPerSecond { get; }
|
|
||||||
public ushort BlockAlign { get; }
|
|
||||||
|
|
||||||
public uint LoopStart { get; set; } = 0;
|
public uint LoopStart { get; set; } = 0;
|
||||||
public uint LoopLength { get; set; } = 0;
|
public uint LoopLength { get; set; } = 0;
|
||||||
|
@ -45,13 +43,17 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
FAudio.stb_vorbis_close(filePointer);
|
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(
|
return new StaticSound(
|
||||||
device,
|
device,
|
||||||
3,
|
format,
|
||||||
32,
|
|
||||||
(ushort) (4 * info.channels),
|
|
||||||
(ushort) info.channels,
|
|
||||||
info.sample_rate,
|
|
||||||
(nint) buffer,
|
(nint) buffer,
|
||||||
(uint) lengthInBytes,
|
(uint) lengthInBytes,
|
||||||
true);
|
true);
|
||||||
|
@ -183,13 +185,17 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
// End scan
|
// End scan
|
||||||
|
|
||||||
|
var format = new Format
|
||||||
|
{
|
||||||
|
Tag = (FormatTag) wFormatTag,
|
||||||
|
BitsPerSample = wBitsPerSample,
|
||||||
|
Channels = nChannels,
|
||||||
|
SampleRate = nSamplesPerSec
|
||||||
|
};
|
||||||
|
|
||||||
var sound = new StaticSound(
|
var sound = new StaticSound(
|
||||||
device,
|
device,
|
||||||
wFormatTag,
|
format,
|
||||||
wBitsPerSample,
|
|
||||||
nBlockAlign,
|
|
||||||
nChannels,
|
|
||||||
nSamplesPerSec,
|
|
||||||
(nint) waveDataBuffer,
|
(nint) waveDataBuffer,
|
||||||
(uint) waveDataLength,
|
(uint) waveDataLength,
|
||||||
true
|
true
|
||||||
|
@ -223,13 +229,17 @@ namespace MoonWorks.Audio
|
||||||
FAudio.qoa_close(qoaHandle);
|
FAudio.qoa_close(qoaHandle);
|
||||||
NativeMemory.Free(fileDataPtr);
|
NativeMemory.Free(fileDataPtr);
|
||||||
|
|
||||||
|
var format = new Format
|
||||||
|
{
|
||||||
|
Tag = FormatTag.PCM,
|
||||||
|
BitsPerSample = 16,
|
||||||
|
Channels = (ushort) channels,
|
||||||
|
SampleRate = samplerate
|
||||||
|
};
|
||||||
|
|
||||||
return new StaticSound(
|
return new StaticSound(
|
||||||
device,
|
device,
|
||||||
1,
|
format,
|
||||||
16,
|
|
||||||
(ushort) (channels * 2),
|
|
||||||
(ushort) channels,
|
|
||||||
samplerate,
|
|
||||||
(nint) buffer,
|
(nint) buffer,
|
||||||
bufferLengthInBytes,
|
bufferLengthInBytes,
|
||||||
true
|
true
|
||||||
|
@ -238,20 +248,13 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public StaticSound(
|
public StaticSound(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
ushort formatTag,
|
Format format,
|
||||||
ushort bitsPerSample,
|
|
||||||
ushort blockAlign,
|
|
||||||
ushort channels,
|
|
||||||
uint samplesPerSecond,
|
|
||||||
IntPtr bufferPtr,
|
IntPtr bufferPtr,
|
||||||
uint bufferLengthInBytes,
|
uint bufferLengthInBytes,
|
||||||
bool ownsBuffer) : base(device)
|
bool ownsBuffer) : base(device)
|
||||||
{
|
{
|
||||||
FormatTag = formatTag;
|
// TODO: should we wrap the format struct to make it nicer?
|
||||||
BitsPerSample = bitsPerSample;
|
this.format = format;
|
||||||
BlockAlign = blockAlign;
|
|
||||||
Channels = channels;
|
|
||||||
SamplesPerSecond = samplesPerSecond;
|
|
||||||
|
|
||||||
Handle = new FAudio.FAudioBuffer
|
Handle = new FAudio.FAudioBuffer
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,22 +2,22 @@ using System;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class SubmixVoice : Voice
|
public class SubmixVoice : SendableVoice, IReceivableVoice
|
||||||
{
|
{
|
||||||
public SubmixVoice(
|
public SubmixVoice(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
uint inputChannels,
|
uint sourceChannelCount,
|
||||||
uint sampleRate
|
uint sampleRate
|
||||||
) : base(device)
|
) : base(device, sourceChannelCount, device.DeviceDetails.OutputFormat.Format.nChannels)
|
||||||
{
|
{
|
||||||
FAudio.FAudio_CreateSubmixVoice(
|
FAudio.FAudio_CreateSubmixVoice(
|
||||||
device.Handle,
|
device.Handle,
|
||||||
out Handle,
|
out handle,
|
||||||
inputChannels,
|
sourceChannelCount,
|
||||||
sampleRate,
|
sampleRate,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
IntPtr.Zero,
|
IntPtr.Zero, // default sends to mastering voice
|
||||||
IntPtr.Zero
|
IntPtr.Zero
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,28 @@
|
||||||
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 class Voice : AudioResource
|
||||||
{
|
{
|
||||||
protected IntPtr Handle;
|
protected IntPtr handle;
|
||||||
|
public IntPtr Handle => handle;
|
||||||
|
|
||||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
public uint SourceChannelCount { get; }
|
||||||
|
public uint DestinationChannelCount { get; }
|
||||||
private ReverbEffect ReverbEffect;
|
|
||||||
|
|
||||||
public bool Is3D { get; protected set; }
|
public bool Is3D { get; protected set; }
|
||||||
|
|
||||||
private float pan = 0;
|
private float dopplerFactor;
|
||||||
public virtual float Pan
|
public float DopplerFactor
|
||||||
{
|
{
|
||||||
get => pan;
|
get => dopplerFactor;
|
||||||
internal set
|
set
|
||||||
{
|
{
|
||||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
if (dopplerFactor != value)
|
||||||
if (pan != value)
|
|
||||||
{
|
{
|
||||||
pan = value;
|
dopplerFactor = value;
|
||||||
|
UpdatePitch();
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,62 +150,10 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float reverb;
|
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
|
||||||
public unsafe float Reverb
|
|
||||||
{
|
{
|
||||||
get => reverb;
|
SourceChannelCount = sourceChannelCount;
|
||||||
internal set
|
DestinationChannelCount = destinationChannelCount;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPitch(float targetValue)
|
public void SetPitch(float targetValue)
|
||||||
|
@ -283,85 +209,6 @@ namespace MoonWorks.Audio
|
||||||
FilterOneOverQ = targetValue;
|
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()
|
private void UpdatePitch()
|
||||||
{
|
{
|
||||||
float doppler;
|
float doppler;
|
||||||
|
@ -372,7 +219,7 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
doppler = dspSettings.DopplerFactor * dopplerScale;
|
doppler = DopplerFactor * dopplerScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
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()
|
protected unsafe override void Destroy()
|
||||||
{
|
{
|
||||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||||
|
|
||||||
NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue