started reworking audio around Voice API

pull/50/head
cosmonaut 2023-07-31 01:16:19 -07:00
parent 81cd397013
commit b1c7740b86
10 changed files with 723 additions and 159 deletions

View File

@ -9,24 +9,15 @@ namespace MoonWorks.Audio
{ {
public IntPtr Handle { get; } public IntPtr Handle { get; }
public byte[] Handle3D { get; } public byte[] Handle3D { get; }
public IntPtr MasteringVoice { get; }
public FAudio.FAudioDeviceDetails DeviceDetails { get; } public FAudio.FAudioDeviceDetails DeviceDetails { get; }
private MasteringVoice masteringVoice;
public MasteringVoice MasteringVoice => masteringVoice;
public float CurveDistanceScalar = 1f; public float CurveDistanceScalar = 1f;
public float DopplerScale = 1f; public float DopplerScale = 1f;
public float SpeedOfSound = 343.5f; public float SpeedOfSound = 343.5f;
private float masteringVolume = 1f;
public float MasteringVolume
{
get => masteringVolume;
set
{
masteringVolume = value;
FAudio.FAudioVoice_SetVolume(MasteringVoice, masteringVolume, 0);
}
}
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>(); private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>(); private readonly List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>();
private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>(); private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>();
@ -93,26 +84,18 @@ namespace MoonWorks.Audio
} }
/* Init Mastering Voice */ /* Init Mastering Voice */
IntPtr masteringVoice; var result = MasteringVoice.Create(
this,
if (FAudio.FAudio_CreateMasteringVoice(
Handle,
out masteringVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i, i,
IntPtr.Zero out masteringVoice
) != 0) );
if (!result)
{ {
Logger.LogError("No mastering voice found!"); Logger.LogError("Audio device creation failed!");
FAudio.FAudio_Release(Handle);
Handle = IntPtr.Zero;
return; return;
} }
MasteringVoice = masteringVoice;
/* Init 3D Audio */ /* Init 3D Audio */
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE]; Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
@ -192,7 +175,7 @@ namespace MoonWorks.Audio
{ {
if (soundSequenceReferences[i].TryGetTarget(out var soundSequence)) if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
{ {
soundSequence.Update(); soundSequence.OnUpdate();
} }
else else
{ {
@ -203,13 +186,13 @@ namespace MoonWorks.Audio
AudioTweenManager.Update(elapsedSeconds); AudioTweenManager.Update(elapsedSeconds);
} }
public void SyncPlay() public void TriggerSyncGroup(uint syncGroup)
{ {
FAudio.FAudio_CommitChanges(Handle, 1); FAudio.FAudio_CommitChanges(Handle, syncGroup);
} }
internal void CreateTween( internal void CreateTween(
SoundInstance soundInstance, Voice voice,
AudioTweenProperty property, AudioTweenProperty property,
System.Func<float, float> easingFunction, System.Func<float, float> easingFunction,
float start, float start,
@ -220,7 +203,7 @@ namespace MoonWorks.Audio
lock (StateLock) lock (StateLock)
{ {
AudioTweenManager.CreateTween( AudioTweenManager.CreateTween(
soundInstance, voice,
property, property,
easingFunction, easingFunction,
start, start,
@ -232,12 +215,12 @@ namespace MoonWorks.Audio
} }
internal void ClearTweens( internal void ClearTweens(
SoundInstance soundReference, Voice voice,
AudioTweenProperty property AudioTweenProperty property
) { ) {
lock (StateLock) lock (StateLock)
{ {
AudioTweenManager.ClearTweens(soundReference, property); AudioTweenManager.ClearTweens(voice, property);
} }
} }
@ -286,6 +269,18 @@ namespace MoonWorks.Audio
if (disposing) if (disposing)
{ {
// stop all source voices
foreach (var weakReference in resources)
{
var target = weakReference.Target;
if (target != null && target is SourceVoice voice)
{
voice.Stop();
}
}
// destroy all audio resources
foreach (var weakReference in resources) foreach (var weakReference in resources)
{ {
var target = weakReference.Target; var target = weakReference.Target;
@ -295,10 +290,10 @@ namespace MoonWorks.Audio
(target as IDisposable).Dispose(); (target as IDisposable).Dispose();
} }
} }
resources.Clear(); resources.Clear();
} }
FAudio.FAudioVoice_DestroyVoice(MasteringVoice);
FAudio.FAudio_Release(Handle); FAudio.FAudio_Release(Handle);
IsDisposed = true; IsDisposed = true;

View File

@ -14,7 +14,7 @@ namespace MoonWorks.Audio
internal class AudioTween internal class AudioTween
{ {
public SoundInstance SoundInstance; public Voice Voice;
public AudioTweenProperty Property; public AudioTweenProperty Property;
public EasingFunction EasingFunction; public EasingFunction EasingFunction;
public float Time; public float Time;
@ -51,7 +51,7 @@ namespace MoonWorks.Audio
public void Free(AudioTween tween) public void Free(AudioTween tween)
{ {
tween.SoundInstance = null; tween.Voice = null;
Tweens.Enqueue(tween); Tweens.Enqueue(tween);
} }
} }

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Audio
internal class AudioTweenManager internal class AudioTweenManager
{ {
private AudioTweenPool AudioTweenPool = new AudioTweenPool(); private AudioTweenPool AudioTweenPool = new AudioTweenPool();
private readonly Dictionary<(SoundInstance, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(SoundInstance, AudioTweenProperty), AudioTween>(); private readonly Dictionary<(Voice, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(Voice, AudioTweenProperty), AudioTween>();
private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>(); private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
public void Update(float elapsedSeconds) public void Update(float elapsedSeconds)
@ -14,7 +14,7 @@ namespace MoonWorks.Audio
for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--) for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
{ {
var audioTween = DelayedAudioTweens[i]; var audioTween = DelayedAudioTweens[i];
var soundInstance = audioTween.SoundInstance; var voice = audioTween.Voice;
audioTween.Time += elapsedSeconds; audioTween.Time += elapsedSeconds;
@ -24,23 +24,23 @@ namespace MoonWorks.Audio
switch (audioTween.Property) switch (audioTween.Property)
{ {
case AudioTweenProperty.Pan: case AudioTweenProperty.Pan:
audioTween.StartValue = soundInstance.Pan; audioTween.StartValue = voice.Pan;
break; break;
case AudioTweenProperty.Pitch: case AudioTweenProperty.Pitch:
audioTween.StartValue = soundInstance.Pitch; audioTween.StartValue = voice.Pitch;
break; break;
case AudioTweenProperty.Volume: case AudioTweenProperty.Volume:
audioTween.StartValue = soundInstance.Volume; audioTween.StartValue = voice.Volume;
break; break;
case AudioTweenProperty.FilterFrequency: case AudioTweenProperty.FilterFrequency:
audioTween.StartValue = soundInstance.FilterFrequency; audioTween.StartValue = voice.FilterFrequency;
break; break;
case AudioTweenProperty.Reverb: case AudioTweenProperty.Reverb:
audioTween.StartValue = soundInstance.Reverb; audioTween.StartValue = voice.Reverb;
break; break;
} }
@ -64,7 +64,7 @@ namespace MoonWorks.Audio
} }
public void CreateTween( public void CreateTween(
SoundInstance soundInstance, Voice voice,
AudioTweenProperty property, AudioTweenProperty property,
System.Func<float, float> easingFunction, System.Func<float, float> easingFunction,
float start, float start,
@ -73,7 +73,7 @@ namespace MoonWorks.Audio
float delayTime float delayTime
) { ) {
var tween = AudioTweenPool.Obtain(); var tween = AudioTweenPool.Obtain();
tween.SoundInstance = soundInstance; tween.Voice = voice;
tween.Property = property; tween.Property = property;
tween.EasingFunction = easingFunction; tween.EasingFunction = easingFunction;
tween.StartValue = start; tween.StartValue = start;
@ -92,21 +92,21 @@ namespace MoonWorks.Audio
} }
} }
public void ClearTweens(SoundInstance soundInstance, AudioTweenProperty property) public void ClearTweens(Voice voice, AudioTweenProperty property)
{ {
AudioTweens.Remove((soundInstance, property)); AudioTweens.Remove((voice, property));
} }
private void AddTween( private void AddTween(
AudioTween audioTween AudioTween audioTween
) { ) {
// if a tween with the same sound and property already exists, get rid of it // if a tween with the same sound and property already exists, get rid of it
if (AudioTweens.TryGetValue((audioTween.SoundInstance, audioTween.Property), out var currentTween)) if (AudioTweens.TryGetValue((audioTween.Voice, audioTween.Property), out var currentTween))
{ {
AudioTweenPool.Free(currentTween); AudioTweenPool.Free(currentTween);
} }
AudioTweens[(audioTween.SoundInstance, audioTween.Property)] = audioTween; AudioTweens[(audioTween.Voice, audioTween.Property)] = audioTween;
} }
private static bool UpdateAudioTween(AudioTween audioTween, float delta) private static bool UpdateAudioTween(AudioTween audioTween, float delta)
@ -133,23 +133,23 @@ namespace MoonWorks.Audio
switch (audioTween.Property) switch (audioTween.Property)
{ {
case AudioTweenProperty.Pan: case AudioTweenProperty.Pan:
audioTween.SoundInstance.Pan = value; audioTween.Voice.Pan = value;
break; break;
case AudioTweenProperty.Pitch: case AudioTweenProperty.Pitch:
audioTween.SoundInstance.Pitch = value; audioTween.Voice.Pitch = value;
break; break;
case AudioTweenProperty.Volume: case AudioTweenProperty.Volume:
audioTween.SoundInstance.Volume = value; audioTween.Voice.Volume = value;
break; break;
case AudioTweenProperty.FilterFrequency: case AudioTweenProperty.FilterFrequency:
audioTween.SoundInstance.FilterFrequency = value; audioTween.Voice.FilterFrequency = value;
break; break;
case AudioTweenProperty.Reverb: case AudioTweenProperty.Reverb:
audioTween.SoundInstance.Reverb = value; audioTween.Voice.Reverb = value;
break; break;
} }

View File

@ -0,0 +1,46 @@
using System;
namespace MoonWorks.Audio
{
public class MasteringVoice : Voice
{
// mastering voice can't pan
public override float Pan => 0;
internal static bool Create(
AudioDevice device,
uint deviceIndex,
out MasteringVoice masteringVoice
) {
var result = FAudio.FAudio_CreateMasteringVoice(
device.Handle,
out var handle,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
deviceIndex,
IntPtr.Zero
);
if (result == 0)
{
masteringVoice = new MasteringVoice(device, handle);
}
else
{
Logger.LogError("Failed to create mastering voice!");
masteringVoice = null;
}
return result == 0;
}
internal MasteringVoice(
AudioDevice device,
IntPtr handle
) : base(device)
{
Handle = handle;
}
}
}

View File

@ -4,53 +4,31 @@ using System.Runtime.InteropServices;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
// sound instances can send their audio to this voice to add reverb // sound instances can send their audio to this voice to add reverb
public unsafe class ReverbEffect : AudioResource public unsafe class ReverbEffect : SubmixVoice
{ {
private IntPtr voice; public ReverbEffect(AudioDevice audioDevice) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec)
public IntPtr Voice => voice;
public ReverbEffect(AudioDevice audioDevice) : base(audioDevice)
{ {
/* Init reverb */ /* Init reverb */
IntPtr reverb; IntPtr reverb;
FAudio.FAudioCreateReverb(out reverb, 0); FAudio.FAudioCreateReverb(out reverb, 0);
IntPtr chainPtr; var chain = new FAudio.FAudioEffectChain();
chainPtr = (nint) NativeMemory.Alloc( var descriptor = new FAudio.FAudioEffectDescriptor();
(nuint) Marshal.SizeOf<FAudio.FAudioEffectChain>()
descriptor.InitialState = 1;
descriptor.OutputChannels = Device.DeviceDetails.OutputFormat.Format.nChannels;
descriptor.pEffect = reverb;
chain.EffectCount = 1;
chain.pEffectDescriptors = (nint) (&descriptor);
FAudio.FAudioVoice_SetEffectChain(
Handle,
ref chain
); );
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
reverbChain->EffectCount = 1;
reverbChain->pEffectDescriptors = (nint) NativeMemory.Alloc(
(nuint) Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
);
FAudio.FAudioEffectDescriptor* reverbDescriptor =
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
reverbDescriptor->InitialState = 1;
reverbDescriptor->OutputChannels = (uint) (
(audioDevice.DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
);
reverbDescriptor->pEffect = reverb;
FAudio.FAudio_CreateSubmixVoice(
audioDevice.Handle,
out voice,
1, /* omnidirectional reverb */
audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
IntPtr.Zero,
chainPtr
);
FAudio.FAPOBase_Release(reverb); FAudio.FAPOBase_Release(reverb);
NativeMemory.Free((void*) reverbChain->pEffectDescriptors);
NativeMemory.Free((void*) chainPtr);
/* Init reverb params */ /* Init reverb params */
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC // Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
@ -86,7 +64,7 @@ namespace MoonWorks.Audio
fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams) fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
{ {
FAudio.FAudioVoice_SetEffectParameters( FAudio.FAudioVoice_SetEffectParameters(
voice, Handle,
0, 0,
(nint) reverbParamsPtr, (nint) reverbParamsPtr,
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(), (uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
@ -94,10 +72,5 @@ namespace MoonWorks.Audio
); );
} }
} }
protected override void Destroy()
{
FAudio.FAudioVoice_DestroyVoice(Voice);
}
} }
} }

View File

@ -3,25 +3,25 @@ using System;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
// NOTE: all sounds played with a SoundSequence must have the same audio format! // NOTE: all sounds played with a SoundSequence must have the same audio format!
public class SoundSequence : SoundInstance public class SoundSequence : SourceVoice
{ {
public int NeedSoundThreshold = 0; public int NeedSoundThreshold = 0;
public delegate void OnSoundNeededFunc(); public delegate void OnSoundNeededFunc();
public OnSoundNeededFunc OnSoundNeeded; public OnSoundNeededFunc OnSoundNeeded;
private object StateLock = new object();
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, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
{ {
device.AddSoundSequenceReference(this); 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.FormatTag, templateSound.BitsPerSample, templateSound.BlockAlign, templateSound.Channels, templateSound.SamplesPerSecond)
{ {
device.AddSoundSequenceReference(this); device.AddSoundSequenceReference(this);
OnUpdate += Update;
} }
public void Update() private void Update()
{ {
lock (StateLock) lock (StateLock)
{ {
@ -31,7 +31,7 @@ namespace MoonWorks.Audio
if (NeedSoundThreshold > 0) if (NeedSoundThreshold > 0)
{ {
FAudio.FAudioSourceVoice_GetState( FAudio.FAudioSourceVoice_GetState(
Voice, Handle,
out var state, out var state,
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
); );
@ -65,66 +65,11 @@ namespace MoonWorks.Audio
lock (StateLock) lock (StateLock)
{ {
FAudio.FAudioSourceVoice_SubmitSourceBuffer( FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Voice, Handle,
ref sound.Handle, ref sound.Handle,
IntPtr.Zero IntPtr.Zero
); );
} }
} }
public override void Pause()
{
lock (StateLock)
{
if (State == SoundState.Playing)
{
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
State = SoundState.Paused;
}
}
}
public override void Play()
{
PlayUsingOperationSet(0);
}
public override void QueueSyncPlay()
{
PlayUsingOperationSet(1);
}
private void PlayUsingOperationSet(uint operationSet)
{
lock (StateLock)
{
if (State == SoundState.Playing)
{
return;
}
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
State = SoundState.Playing;
}
}
public override void Stop()
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_ExitLoop(Voice, 0);
State = SoundState.Stopped;
}
}
public override void StopImmediate()
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
State = SoundState.Stopped;
}
}
} }
} }

123
src/Audio/SourceVoice.cs Normal file
View File

@ -0,0 +1,123 @@
using System;
namespace MoonWorks.Audio
{
public class SourceVoice : Voice
{
protected FAudio.FAudioWaveFormatEx Format;
protected object StateLock = new object();
public uint BuffersQueued
{
get
{
FAudio.FAudioSourceVoice_GetState(
Handle,
out var state,
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
);
return state.BuffersQueued;
}
}
private SoundState state;
public SoundState State
{
get
{
if (BuffersQueued == 0)
{
Stop();
}
return state;
}
internal set
{
state = value;
}
}
public delegate void OnUpdateFunc();
public OnUpdateFunc OnUpdate; // called by AudioDevice thread
public SourceVoice(
AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels,
uint samplesPerSecond
) : base(device)
{
Format = new FAudio.FAudioWaveFormatEx
{
wFormatTag = formatTag,
wBitsPerSample = bitsPerSample,
nChannels = channels,
nBlockAlign = blockAlign,
nSamplesPerSec = samplesPerSecond,
nAvgBytesPerSec = blockAlign * samplesPerSecond
};
FAudio.FAudio_CreateSourceVoice(
device.Handle,
out var Handle,
ref Format,
FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero,
IntPtr.Zero, // default sends to mastering voice!
IntPtr.Zero
);
}
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
State = SoundState.Playing;
}
}
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
State = SoundState.Paused;
}
}
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
}
}
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
State = SoundState.Stopped;
}
}
protected override unsafe void Destroy()
{
Stop();
base.Destroy();
}
}
}

View File

@ -0,0 +1,11 @@
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)
{
}
}
}

25
src/Audio/SubmixVoice.cs Normal file
View File

@ -0,0 +1,25 @@
using System;
namespace MoonWorks.Audio
{
public class SubmixVoice : Voice
{
public SubmixVoice(
AudioDevice device,
uint inputChannels,
uint sampleRate
) : base(device)
{
FAudio.FAudio_CreateSubmixVoice(
device.Handle,
out Handle,
inputChannels,
sampleRate,
0,
0,
IntPtr.Zero,
IntPtr.Zero
);
}
}
}

446
src/Audio/Voice.cs Normal file
View File

@ -0,0 +1,446 @@
using System;
using System.Runtime.InteropServices;
using EasingFunction = System.Func<float, float>;
namespace MoonWorks.Audio
{
public abstract class Voice : AudioResource
{
protected IntPtr Handle;
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
private ReverbEffect ReverbEffect;
public bool Is3D { get; protected set; }
private float pan = 0;
public virtual 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,
Device.MasteringVoice.Handle,
dspSettings.SrcChannelCount,
dspSettings.DstChannelCount,
dspSettings.pMatrixCoefficients,
0
);
}
}
}
private float pitch = 0;
public float Pitch
{
get => pitch;
internal set
{
value = Math.MathHelper.Clamp(value, -1f, 1f);
if (pitch != value)
{
pitch = value;
UpdatePitch();
}
}
}
private float volume = 1;
public float Volume
{
get => volume;
internal set
{
value = Math.MathHelper.Max(0, value);
if (volume != value)
{
volume = value;
FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
}
}
}
private const float MAX_FILTER_FREQUENCY = 1f;
private const float MAX_FILTER_ONEOVERQ = 1.5f;
private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
Frequency = 1f,
OneOverQ = 1f
};
public float FilterFrequency
{
get => filterParameters.Frequency;
internal set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
if (filterParameters.Frequency != value)
{
filterParameters.Frequency = value;
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
0
);
}
}
}
public float FilterOneOverQ
{
get => filterParameters.OneOverQ;
internal set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
if (filterParameters.OneOverQ != value)
{
filterParameters.OneOverQ = value;
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
0
);
}
}
}
private FilterType filterType;
public FilterType FilterType
{
get => filterType;
set
{
if (filterType != value)
{
filterType = value;
switch (filterType)
{
case FilterType.None:
filterParameters = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
Frequency = 1f,
OneOverQ = 1f
};
break;
case FilterType.LowPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
filterParameters.Frequency = 1f;
break;
case FilterType.BandPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
break;
case FilterType.HighPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
filterParameters.Frequency = 0f;
break;
}
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
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*) 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)
{
Pitch = targetValue;
Device.ClearTweens(this, AudioTweenProperty.Pitch);
}
public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, 0);
}
public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, delayTime);
}
public void SetVolume(float targetValue)
{
Volume = targetValue;
Device.ClearTweens(this, AudioTweenProperty.Volume);
}
public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
}
public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
}
public void SetFilterFrequency(float targetValue)
{
FilterFrequency = targetValue;
Device.ClearTweens(this, AudioTweenProperty.FilterFrequency);
}
public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
}
public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
{
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
}
public void SetFilterOneOverQ(float 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()
{
float doppler;
float dopplerScale = Device.DopplerScale;
if (!Is3D || dopplerScale == 0.0f)
{
doppler = 1.0f;
}
else
{
doppler = dspSettings.DopplerFactor * dopplerScale;
}
FAudio.FAudioSourceVoice_SetFrequencyRatio(
Handle,
(float) System.Math.Pow(2.0, pitch) * doppler,
0
);
}
// 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);
}
}
}