From e2c85ec72890ad5e3b73dd5330001da373cc9221 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 1 Aug 2023 11:22:49 -0700 Subject: [PATCH] restructure and implement Voice hierarchy --- src/Audio/AudioDevice.cs | 14 +- src/Audio/AudioTweenManager.cs | 20 ++- src/Audio/AudioUtils.cs | 35 ++--- src/Audio/Format.cs | 33 +++++ src/Audio/IReceivableVoice.cs | 7 + src/Audio/MasteringVoice.cs | 9 +- src/Audio/SendableVoice.cs | 227 +++++++++++++++++++++++++++++++ src/Audio/SoundSequence.cs | 11 +- src/Audio/SourceVoice.cs | 62 ++++++--- src/Audio/SourceVoicePool.cs | 39 ++++++ src/Audio/StaticSound.cs | 63 ++++----- src/Audio/StaticSourceVoice.cs | 11 -- src/Audio/SubmixVoice.cs | 12 +- src/Audio/Voice.cs | 239 +++------------------------------ 14 files changed, 452 insertions(+), 330 deletions(-) create mode 100644 src/Audio/Format.cs create mode 100644 src/Audio/IReceivableVoice.cs create mode 100644 src/Audio/SendableVoice.cs create mode 100644 src/Audio/SourceVoicePool.cs delete mode 100644 src/Audio/StaticSourceVoice.cs diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index 9ffbdf9..18bb9ea 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -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(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) diff --git a/src/Audio/AudioTweenManager.cs b/src/Audio/AudioTweenManager.cs index d5b05ee..88d8203 100644 --- a/src/Audio/AudioTweenManager.cs +++ b/src/Audio/AudioTweenManager.cs @@ -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; } diff --git a/src/Audio/AudioUtils.cs b/src/Audio/AudioUtils.cs index 143eb8f..a9caa2d 100644 --- a/src/Audio/AudioUtils.cs +++ b/src/Audio/AudioUtils.cs @@ -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 + }; } } } diff --git a/src/Audio/Format.cs b/src/Audio/Format.cs new file mode 100644 index 0000000..40d736e --- /dev/null +++ b/src/Audio/Format.cs @@ -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 + }; + } + } +} diff --git a/src/Audio/IReceivableVoice.cs b/src/Audio/IReceivableVoice.cs new file mode 100644 index 0000000..a40abeb --- /dev/null +++ b/src/Audio/IReceivableVoice.cs @@ -0,0 +1,7 @@ +namespace MoonWorks.Audio +{ + public interface IReceivableVoice + { + public System.IntPtr Handle { get; } + } +} diff --git a/src/Audio/MasteringVoice.cs b/src/Audio/MasteringVoice.cs index 78fe624..e5c23ed 100644 --- a/src/Audio/MasteringVoice.cs +++ b/src/Audio/MasteringVoice.cs @@ -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; } } } diff --git a/src/Audio/SendableVoice.cs b/src/Audio/SendableVoice.cs new file mode 100644 index 0000000..c467584 --- /dev/null +++ b/src/Audio/SendableVoice.cs @@ -0,0 +1,227 @@ +using System; +using System.Runtime.InteropServices; +using EasingFunction = System.Func; + +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(); + } + } +} diff --git a/src/Audio/SoundSequence.cs b/src/Audio/SoundSequence.cs index 6b8c554..fdb4155 100644 --- a/src/Audio/SoundSequence.cs +++ b/src/Audio/SoundSequence.cs @@ -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!"); } diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs index 55c1918..15462a2 100644 --- a/src/Audio/SourceVoice.cs +++ b/src/Audio/SourceVoice.cs @@ -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(); diff --git a/src/Audio/SourceVoicePool.cs b/src/Audio/SourceVoicePool.cs new file mode 100644 index 0000000..3299a1f --- /dev/null +++ b/src/Audio/SourceVoicePool.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace MoonWorks.Audio +{ + internal class SourceVoicePool + { + private AudioDevice Device; + + Dictionary> VoiceLists = new Dictionary>(); + + public SourceVoicePool(AudioDevice device) + { + Device = device; + } + + public SourceVoice Obtain(Format format) + { + if (!VoiceLists.ContainsKey(format)) + { + VoiceLists.Add(format, new Queue()); + } + + 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); + } + } +} diff --git a/src/Audio/StaticSound.cs b/src/Audio/StaticSound.cs index b5def25..3c92377 100644 --- a/src/Audio/StaticSound.cs +++ b/src/Audio/StaticSound.cs @@ -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 { diff --git a/src/Audio/StaticSourceVoice.cs b/src/Audio/StaticSourceVoice.cs deleted file mode 100644 index f4f6a7b..0000000 --- a/src/Audio/StaticSourceVoice.cs +++ /dev/null @@ -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) - { - } - - - } -} diff --git a/src/Audio/SubmixVoice.cs b/src/Audio/SubmixVoice.cs index 02ad4ad..338e08e 100644 --- a/src/Audio/SubmixVoice.cs +++ b/src/Audio/SubmixVoice.cs @@ -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 ); } diff --git a/src/Audio/Voice.cs b/src/Audio/Voice.cs index 14ee3a2..d098186 100644 --- a/src/Audio/Voice.cs +++ b/src/Audio/Voice.cs @@ -1,50 +1,28 @@ using System; -using System.Runtime.InteropServices; using EasingFunction = System.Func; 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); } } }