restructure and implement Voice hierarchy
parent
b1c7740b86
commit
e2c85ec728
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
|
@ -25,6 +24,8 @@ namespace MoonWorks.Audio
|
|||
|
||||
private AudioTweenManager AudioTweenManager;
|
||||
|
||||
private SourceVoicePool VoicePool;
|
||||
|
||||
private const int Step = 200;
|
||||
private TimeSpan UpdateInterval;
|
||||
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
|
||||
|
@ -106,6 +107,7 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
|
||||
AudioTweenManager = new AudioTweenManager();
|
||||
VoicePool = new SourceVoicePool(this);
|
||||
|
||||
Logger.LogInfo("Setting up audio thread...");
|
||||
WakeSignal = new AutoResetEvent(true);
|
||||
|
@ -260,6 +262,16 @@ namespace MoonWorks.Audio
|
|||
soundSequenceReferences.Add(new WeakReference<SoundSequence>(sequence));
|
||||
}
|
||||
|
||||
internal SourceVoice ObtainSourceVoice(Format format)
|
||||
{
|
||||
return VoicePool.Obtain(format);
|
||||
}
|
||||
|
||||
internal void ReturnSourceVoice(SourceVoice voice)
|
||||
{
|
||||
VoicePool.Return(voice);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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!");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,50 +1,28 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using EasingFunction = System.Func<float, float>;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class Voice : AudioResource
|
||||
{
|
||||
protected IntPtr Handle;
|
||||
protected IntPtr handle;
|
||||
public IntPtr Handle => handle;
|
||||
|
||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
|
||||
private ReverbEffect ReverbEffect;
|
||||
public uint SourceChannelCount { get; }
|
||||
public uint DestinationChannelCount { get; }
|
||||
|
||||
public bool Is3D { get; protected set; }
|
||||
|
||||
private float pan = 0;
|
||||
public virtual float Pan
|
||||
private float dopplerFactor;
|
||||
public float DopplerFactor
|
||||
{
|
||||
get => pan;
|
||||
internal set
|
||||
get => dopplerFactor;
|
||||
set
|
||||
{
|
||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
if (pan != value)
|
||||
if (dopplerFactor != value)
|
||||
{
|
||||
pan = value;
|
||||
|
||||
if (pan < -1f)
|
||||
{
|
||||
pan = -1f;
|
||||
}
|
||||
if (pan > 1f)
|
||||
{
|
||||
pan = 1f;
|
||||
}
|
||||
|
||||
if (Is3D) { return; }
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
Device.MasteringVoice.Handle,
|
||||
dspSettings.SrcChannelCount,
|
||||
dspSettings.DstChannelCount,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
dopplerFactor = value;
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,62 +150,10 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
private float reverb;
|
||||
public unsafe float Reverb
|
||||
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
|
||||
{
|
||||
get => reverb;
|
||||
internal set
|
||||
{
|
||||
if (ReverbEffect != null)
|
||||
{
|
||||
value = MathF.Max(0, value);
|
||||
if (reverb != value)
|
||||
{
|
||||
reverb = value;
|
||||
|
||||
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
||||
outputMatrix[0] = reverb;
|
||||
if (dspSettings.SrcChannelCount == 2)
|
||||
{
|
||||
outputMatrix[1] = reverb;
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
ReverbEffect.Handle,
|
||||
dspSettings.SrcChannelCount,
|
||||
1,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (ReverbEffect == null)
|
||||
{
|
||||
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Voice(AudioDevice device) : base(device) { }
|
||||
|
||||
public void SetPan(float targetValue)
|
||||
{
|
||||
Pan = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Pan);
|
||||
}
|
||||
|
||||
public void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
||||
SourceChannelCount = sourceChannelCount;
|
||||
DestinationChannelCount = destinationChannelCount;
|
||||
}
|
||||
|
||||
public void SetPitch(float targetValue)
|
||||
|
@ -283,85 +209,6 @@ namespace MoonWorks.Audio
|
|||
FilterOneOverQ = targetValue;
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue)
|
||||
{
|
||||
Reverb = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Reverb);
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public unsafe void SetSends(Voice send)
|
||||
{
|
||||
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
|
||||
sendDesc[0].Flags = 0;
|
||||
sendDesc[0].pOutputVoice = send.Handle;
|
||||
|
||||
var sends = new FAudio.FAudioVoiceSends();
|
||||
sends.SendCount = 1;
|
||||
sends.pSends = (nint) sendDesc;
|
||||
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
Handle,
|
||||
ref sends
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void SetSends(Voice sendOne, Voice sendTwo)
|
||||
{
|
||||
var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
|
||||
sendDesc[0].Flags = 0;
|
||||
sendDesc[0].pOutputVoice = sendOne.Handle;
|
||||
sendDesc[1].Flags = 0;
|
||||
sendDesc[1].pOutputVoice = sendTwo.Handle;
|
||||
|
||||
var sends = new FAudio.FAudioVoiceSends();
|
||||
sends.SendCount = 2;
|
||||
sends.pSends = (nint) sendDesc;
|
||||
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
Handle,
|
||||
ref sends
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
|
||||
{
|
||||
SetSends(Device.MasteringVoice, reverbEffect);
|
||||
ReverbEffect = reverbEffect;
|
||||
}
|
||||
|
||||
private unsafe void InitDSPSettings(uint srcChannels)
|
||||
{
|
||||
dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
|
||||
dspSettings.DopplerFactor = 1f;
|
||||
dspSettings.SrcChannelCount = srcChannels;
|
||||
dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
|
||||
|
||||
nuint memsize = (
|
||||
4 *
|
||||
dspSettings.SrcChannelCount *
|
||||
dspSettings.DstChannelCount
|
||||
);
|
||||
|
||||
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
|
||||
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
||||
for (uint i = 0; i < memsize; i += 1)
|
||||
{
|
||||
memPtr[i] = 0;
|
||||
}
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
}
|
||||
|
||||
private void UpdatePitch()
|
||||
{
|
||||
float doppler;
|
||||
|
@ -372,7 +219,7 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
else
|
||||
{
|
||||
doppler = dspSettings.DopplerFactor * dopplerScale;
|
||||
doppler = DopplerFactor * dopplerScale;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||
|
@ -382,65 +229,9 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
|
||||
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
||||
private unsafe void SetPanMatrixCoefficients()
|
||||
{
|
||||
/* Two major things to notice:
|
||||
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
|
||||
* 2. Stereo panning is WAY more complicated than you think.
|
||||
* The main thing is that hard panning does NOT eliminate an
|
||||
* entire channel; the two channels are blended on each side.
|
||||
* -flibit
|
||||
*/
|
||||
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
||||
if (dspSettings.SrcChannelCount == 1)
|
||||
{
|
||||
if (dspSettings.DstChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
|
||||
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dspSettings.DstChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
outputMatrix[1] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pan <= 0.0f)
|
||||
{
|
||||
// Left speaker blends left/right channels
|
||||
outputMatrix[0] = 0.5f * pan + 1.0f;
|
||||
outputMatrix[1] = 0.5f * -pan;
|
||||
// Right speaker gets less of the right channel
|
||||
outputMatrix[2] = 0.0f;
|
||||
outputMatrix[3] = pan + 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left speaker gets less of the left channel
|
||||
outputMatrix[0] = -pan + 1.0f;
|
||||
outputMatrix[1] = 0.0f;
|
||||
// Right speaker blends right/left channels
|
||||
outputMatrix[2] = 0.5f * pan;
|
||||
outputMatrix[3] = 0.5f * -pan + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected unsafe override void Destroy()
|
||||
{
|
||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||
|
||||
NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue