started reworking audio around Voice API
parent
81cd397013
commit
b1c7740b86
|
@ -9,24 +9,15 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
public IntPtr Handle { get; }
|
||||
public byte[] Handle3D { get; }
|
||||
public IntPtr MasteringVoice { get; }
|
||||
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
||||
|
||||
private MasteringVoice masteringVoice;
|
||||
public MasteringVoice MasteringVoice => masteringVoice;
|
||||
|
||||
public float CurveDistanceScalar = 1f;
|
||||
public float DopplerScale = 1f;
|
||||
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 List<StreamingSound> autoUpdateStreamingSoundReferences = new List<StreamingSound>();
|
||||
private readonly List<StaticSoundInstance> autoFreeStaticSoundInstanceReferences = new List<StaticSoundInstance>();
|
||||
|
@ -93,26 +84,18 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
/* Init Mastering Voice */
|
||||
IntPtr masteringVoice;
|
||||
|
||||
if (FAudio.FAudio_CreateMasteringVoice(
|
||||
Handle,
|
||||
out masteringVoice,
|
||||
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||
0,
|
||||
var result = MasteringVoice.Create(
|
||||
this,
|
||||
i,
|
||||
IntPtr.Zero
|
||||
) != 0)
|
||||
out masteringVoice
|
||||
);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
Logger.LogError("No mastering voice found!");
|
||||
FAudio.FAudio_Release(Handle);
|
||||
Handle = IntPtr.Zero;
|
||||
Logger.LogError("Audio device creation failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
MasteringVoice = masteringVoice;
|
||||
|
||||
/* Init 3D Audio */
|
||||
|
||||
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
||||
|
@ -192,7 +175,7 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
|
||||
{
|
||||
soundSequence.Update();
|
||||
soundSequence.OnUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -203,13 +186,13 @@ namespace MoonWorks.Audio
|
|||
AudioTweenManager.Update(elapsedSeconds);
|
||||
}
|
||||
|
||||
public void SyncPlay()
|
||||
public void TriggerSyncGroup(uint syncGroup)
|
||||
{
|
||||
FAudio.FAudio_CommitChanges(Handle, 1);
|
||||
FAudio.FAudio_CommitChanges(Handle, syncGroup);
|
||||
}
|
||||
|
||||
internal void CreateTween(
|
||||
SoundInstance soundInstance,
|
||||
Voice voice,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
|
@ -220,7 +203,7 @@ namespace MoonWorks.Audio
|
|||
lock (StateLock)
|
||||
{
|
||||
AudioTweenManager.CreateTween(
|
||||
soundInstance,
|
||||
voice,
|
||||
property,
|
||||
easingFunction,
|
||||
start,
|
||||
|
@ -232,12 +215,12 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
internal void ClearTweens(
|
||||
SoundInstance soundReference,
|
||||
Voice voice,
|
||||
AudioTweenProperty property
|
||||
) {
|
||||
lock (StateLock)
|
||||
{
|
||||
AudioTweenManager.ClearTweens(soundReference, property);
|
||||
AudioTweenManager.ClearTweens(voice, property);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +269,18 @@ namespace MoonWorks.Audio
|
|||
|
||||
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)
|
||||
{
|
||||
var target = weakReference.Target;
|
||||
|
@ -295,10 +290,10 @@ namespace MoonWorks.Audio
|
|||
(target as IDisposable).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
resources.Clear();
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(MasteringVoice);
|
||||
FAudio.FAudio_Release(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace MoonWorks.Audio
|
|||
|
||||
internal class AudioTween
|
||||
{
|
||||
public SoundInstance SoundInstance;
|
||||
public Voice Voice;
|
||||
public AudioTweenProperty Property;
|
||||
public EasingFunction EasingFunction;
|
||||
public float Time;
|
||||
|
@ -51,7 +51,7 @@ namespace MoonWorks.Audio
|
|||
|
||||
public void Free(AudioTween tween)
|
||||
{
|
||||
tween.SoundInstance = null;
|
||||
tween.Voice = null;
|
||||
Tweens.Enqueue(tween);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Audio
|
|||
internal class AudioTweenManager
|
||||
{
|
||||
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>();
|
||||
|
||||
public void Update(float elapsedSeconds)
|
||||
|
@ -14,7 +14,7 @@ namespace MoonWorks.Audio
|
|||
for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var audioTween = DelayedAudioTweens[i];
|
||||
var soundInstance = audioTween.SoundInstance;
|
||||
var voice = audioTween.Voice;
|
||||
|
||||
audioTween.Time += elapsedSeconds;
|
||||
|
||||
|
@ -24,23 +24,23 @@ namespace MoonWorks.Audio
|
|||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
audioTween.StartValue = soundInstance.Pan;
|
||||
audioTween.StartValue = voice.Pan;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
audioTween.StartValue = soundInstance.Pitch;
|
||||
audioTween.StartValue = voice.Pitch;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
audioTween.StartValue = soundInstance.Volume;
|
||||
audioTween.StartValue = voice.Volume;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
audioTween.StartValue = soundInstance.FilterFrequency;
|
||||
audioTween.StartValue = voice.FilterFrequency;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
audioTween.StartValue = soundInstance.Reverb;
|
||||
audioTween.StartValue = voice.Reverb;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
public void CreateTween(
|
||||
SoundInstance soundInstance,
|
||||
Voice voice,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
|
@ -73,7 +73,7 @@ namespace MoonWorks.Audio
|
|||
float delayTime
|
||||
) {
|
||||
var tween = AudioTweenPool.Obtain();
|
||||
tween.SoundInstance = soundInstance;
|
||||
tween.Voice = voice;
|
||||
tween.Property = property;
|
||||
tween.EasingFunction = easingFunction;
|
||||
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(
|
||||
AudioTween audioTween
|
||||
) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
AudioTweens[(audioTween.SoundInstance, audioTween.Property)] = audioTween;
|
||||
AudioTweens[(audioTween.Voice, audioTween.Property)] = audioTween;
|
||||
}
|
||||
|
||||
private static bool UpdateAudioTween(AudioTween audioTween, float delta)
|
||||
|
@ -133,23 +133,23 @@ namespace MoonWorks.Audio
|
|||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
audioTween.SoundInstance.Pan = value;
|
||||
audioTween.Voice.Pan = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
audioTween.SoundInstance.Pitch = value;
|
||||
audioTween.Voice.Pitch = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
audioTween.SoundInstance.Volume = value;
|
||||
audioTween.Voice.Volume = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
audioTween.SoundInstance.FilterFrequency = value;
|
||||
audioTween.Voice.FilterFrequency = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
audioTween.SoundInstance.Reverb = value;
|
||||
audioTween.Voice.Reverb = value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,53 +4,31 @@ using System.Runtime.InteropServices;
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
// 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 IntPtr Voice => voice;
|
||||
|
||||
public ReverbEffect(AudioDevice audioDevice) : base(audioDevice)
|
||||
public ReverbEffect(AudioDevice audioDevice) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec)
|
||||
{
|
||||
/* Init reverb */
|
||||
|
||||
IntPtr reverb;
|
||||
FAudio.FAudioCreateReverb(out reverb, 0);
|
||||
|
||||
IntPtr chainPtr;
|
||||
chainPtr = (nint) NativeMemory.Alloc(
|
||||
(nuint) Marshal.SizeOf<FAudio.FAudioEffectChain>()
|
||||
var chain = new FAudio.FAudioEffectChain();
|
||||
var descriptor = new FAudio.FAudioEffectDescriptor();
|
||||
|
||||
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);
|
||||
|
||||
NativeMemory.Free((void*) reverbChain->pEffectDescriptors);
|
||||
NativeMemory.Free((void*) chainPtr);
|
||||
|
||||
/* Init reverb params */
|
||||
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
|
||||
|
||||
|
@ -86,7 +64,7 @@ namespace MoonWorks.Audio
|
|||
fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
|
||||
{
|
||||
FAudio.FAudioVoice_SetEffectParameters(
|
||||
voice,
|
||||
Handle,
|
||||
0,
|
||||
(nint) reverbParamsPtr,
|
||||
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
|
||||
|
@ -94,10 +72,5 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
FAudio.FAudioVoice_DestroyVoice(Voice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,25 +3,25 @@ using System;
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
// 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 delegate void OnSoundNeededFunc();
|
||||
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)
|
||||
{
|
||||
device.AddSoundSequenceReference(this);
|
||||
OnUpdate += Update;
|
||||
}
|
||||
|
||||
public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.FormatTag, templateSound.BitsPerSample, templateSound.BlockAlign, templateSound.Channels, templateSound.SamplesPerSecond)
|
||||
{
|
||||
device.AddSoundSequenceReference(this);
|
||||
OnUpdate += Update;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
private void Update()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace MoonWorks.Audio
|
|||
if (NeedSoundThreshold > 0)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_GetState(
|
||||
Voice,
|
||||
Handle,
|
||||
out var state,
|
||||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
||||
);
|
||||
|
@ -65,66 +65,11 @@ namespace MoonWorks.Audio
|
|||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Voice,
|
||||
Handle,
|
||||
ref sound.Handle,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue