2022-08-30 05:45:28 +00:00
|
|
|
using System;
|
2021-01-20 02:06:10 +00:00
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
namespace MoonWorks.Audio
|
|
|
|
{
|
2022-02-23 05:14:32 +00:00
|
|
|
public abstract class SoundInstance : AudioResource
|
|
|
|
{
|
2023-03-02 01:47:09 +00:00
|
|
|
internal IntPtr Voice;
|
2022-04-05 06:33:36 +00:00
|
|
|
internal FAudio.FAudioWaveFormatEx Format;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
|
|
|
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
private ReverbEffect ReverbEffect;
|
|
|
|
private FAudio.FAudioVoiceSends ReverbSends;
|
|
|
|
|
2022-04-07 21:19:43 +00:00
|
|
|
public bool Is3D { get; protected set; }
|
2022-02-23 05:14:32 +00:00
|
|
|
|
2022-08-02 21:04:12 +00:00
|
|
|
public virtual SoundState State { get; protected set; }
|
2022-02-23 05:14:32 +00:00
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
private float pan = 0;
|
2022-02-23 05:14:32 +00:00
|
|
|
public float Pan
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => pan;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
pan = value;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
if (pan < -1f)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
pan = -1f;
|
2022-02-23 05:14:32 +00:00
|
|
|
}
|
2022-08-30 05:45:28 +00:00
|
|
|
if (pan > 1f)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
pan = 1f;
|
2022-02-23 05:14:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 21:19:43 +00:00
|
|
|
if (Is3D) { return; }
|
2022-02-23 05:14:32 +00:00
|
|
|
|
|
|
|
SetPanMatrixCoefficients();
|
|
|
|
FAudio.FAudioVoice_SetOutputMatrix(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-02-23 05:14:32 +00:00
|
|
|
Device.MasteringVoice,
|
|
|
|
dspSettings.SrcChannelCount,
|
|
|
|
dspSettings.DstChannelCount,
|
|
|
|
dspSettings.pMatrixCoefficients,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
private float pitch = 0;
|
2022-02-23 05:14:32 +00:00
|
|
|
public float Pitch
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => pitch;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
pitch = Math.MathHelper.Clamp(value, -1f, 1f);
|
2022-02-23 05:14:32 +00:00
|
|
|
UpdatePitch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
private float volume = 1;
|
2022-02-23 05:14:32 +00:00
|
|
|
public float Volume
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => volume;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
volume = value;
|
2023-03-02 01:47:09 +00:00
|
|
|
FAudio.FAudioVoice_SetVolume(Voice, volume, 0);
|
2022-02-23 05:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
private float FilterFrequency
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => filterParameters.Frequency;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
|
|
|
|
filterParameters.Frequency = value;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
|
|
|
FAudio.FAudioVoice_SetFilterParameters(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-08-30 05:45:28 +00:00
|
|
|
ref filterParameters,
|
2022-02-23 05:14:32 +00:00
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
private float FilterOneOverQ
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => filterParameters.OneOverQ;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
|
|
|
|
filterParameters.OneOverQ = value;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
|
|
|
FAudio.FAudioVoice_SetFilterParameters(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-08-30 05:45:28 +00:00
|
|
|
ref filterParameters,
|
2022-02-23 05:14:32 +00:00
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
private FilterType filterType;
|
|
|
|
public FilterType FilterType
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
get => filterType;
|
2022-02-23 05:14:32 +00:00
|
|
|
set
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
filterType = value;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
2022-08-30 05:45:28 +00:00
|
|
|
switch (filterType)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
case FilterType.None:
|
|
|
|
filterParameters = new FAudio.FAudioFilterParameters
|
|
|
|
{
|
|
|
|
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
|
|
|
|
Frequency = 1f,
|
|
|
|
OneOverQ = 1f
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FilterType.LowPass:
|
|
|
|
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FilterType.BandPass:
|
|
|
|
filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FilterType.HighPass:
|
|
|
|
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-02-23 05:14:32 +00:00
|
|
|
FAudio.FAudioVoice_SetFilterParameters(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-08-30 05:45:28 +00:00
|
|
|
ref filterParameters,
|
2022-02-23 05:14:32 +00:00
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
private float reverb;
|
|
|
|
public unsafe float Reverb
|
|
|
|
{
|
|
|
|
get => reverb;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (ReverbEffect != null)
|
|
|
|
{
|
|
|
|
reverb = value;
|
|
|
|
|
|
|
|
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
|
|
|
outputMatrix[0] = reverb;
|
|
|
|
if (dspSettings.SrcChannelCount == 2)
|
|
|
|
{
|
|
|
|
outputMatrix[1] = reverb;
|
|
|
|
}
|
|
|
|
|
|
|
|
FAudio.FAudioVoice_SetOutputMatrix(
|
|
|
|
Voice,
|
|
|
|
ReverbEffect.Voice,
|
|
|
|
dspSettings.SrcChannelCount,
|
|
|
|
1,
|
|
|
|
dspSettings.pMatrixCoefficients,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
if (ReverbEffect == null)
|
|
|
|
{
|
|
|
|
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-23 05:14:32 +00:00
|
|
|
public SoundInstance(
|
|
|
|
AudioDevice device,
|
2022-04-05 06:33:36 +00:00
|
|
|
ushort formatTag,
|
|
|
|
ushort bitsPerSample,
|
|
|
|
ushort blockAlign,
|
2022-02-23 05:14:32 +00:00
|
|
|
ushort channels,
|
2022-04-07 21:19:43 +00:00
|
|
|
uint samplesPerSecond
|
2022-02-23 05:14:32 +00:00
|
|
|
) : base(device)
|
|
|
|
{
|
|
|
|
var format = new FAudio.FAudioWaveFormatEx
|
|
|
|
{
|
2022-04-05 06:33:36 +00:00
|
|
|
wFormatTag = formatTag,
|
|
|
|
wBitsPerSample = bitsPerSample,
|
2022-02-23 05:14:32 +00:00
|
|
|
nChannels = channels,
|
|
|
|
nBlockAlign = blockAlign,
|
|
|
|
nSamplesPerSec = samplesPerSecond,
|
|
|
|
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
|
|
|
};
|
|
|
|
|
|
|
|
Format = format;
|
|
|
|
|
|
|
|
FAudio.FAudio_CreateSourceVoice(
|
|
|
|
Device.Handle,
|
2023-03-02 01:47:09 +00:00
|
|
|
out Voice,
|
2022-04-05 06:33:36 +00:00
|
|
|
ref Format,
|
2022-02-23 05:14:32 +00:00
|
|
|
FAudio.FAUDIO_VOICE_USEFILTER,
|
|
|
|
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
|
|
|
IntPtr.Zero,
|
|
|
|
IntPtr.Zero,
|
|
|
|
IntPtr.Zero
|
|
|
|
);
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
if (Voice == IntPtr.Zero)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
|
|
|
Logger.LogError("SoundInstance failed to initialize!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
InitDSPSettings(Format.nChannels);
|
|
|
|
|
|
|
|
State = SoundState.Stopped;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Apply3D(AudioListener listener, AudioEmitter emitter)
|
|
|
|
{
|
2022-04-07 21:19:43 +00:00
|
|
|
Is3D = true;
|
2022-02-23 05:14:32 +00:00
|
|
|
|
|
|
|
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
|
|
|
emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
|
|
|
|
|
|
|
|
FAudio.F3DAudioCalculate(
|
|
|
|
Device.Handle3D,
|
|
|
|
ref listener.listenerData,
|
|
|
|
ref emitter.emitterData,
|
|
|
|
FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
|
|
|
|
ref dspSettings
|
|
|
|
);
|
|
|
|
|
|
|
|
UpdatePitch();
|
|
|
|
FAudio.FAudioVoice_SetOutputMatrix(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-02-23 05:14:32 +00:00
|
|
|
Device.MasteringVoice,
|
|
|
|
dspSettings.SrcChannelCount,
|
|
|
|
dspSettings.DstChannelCount,
|
|
|
|
dspSettings.pMatrixCoefficients,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
public unsafe void ApplyReverb(ReverbEffect reverbEffect)
|
|
|
|
{
|
|
|
|
ReverbSends = new FAudio.FAudioVoiceSends();
|
|
|
|
ReverbSends.SendCount = 2;
|
|
|
|
ReverbSends.pSends = (nint) NativeMemory.Alloc((nuint) (2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()));
|
|
|
|
|
|
|
|
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
|
|
|
|
sendDesc[0].Flags = 0;
|
|
|
|
sendDesc[0].pOutputVoice = Device.MasteringVoice;
|
|
|
|
sendDesc[1].Flags = 0;
|
|
|
|
sendDesc[1].pOutputVoice = reverbEffect.Voice;
|
|
|
|
|
|
|
|
FAudio.FAudioVoice_SetOutputVoices(
|
|
|
|
Voice,
|
|
|
|
ref ReverbSends
|
|
|
|
);
|
|
|
|
|
|
|
|
ReverbEffect = reverbEffect;
|
|
|
|
}
|
|
|
|
|
2022-08-02 21:04:12 +00:00
|
|
|
public abstract void Play();
|
2023-02-16 23:12:35 +00:00
|
|
|
public abstract void QueueSyncPlay();
|
2022-02-23 05:14:32 +00:00
|
|
|
public abstract void Pause();
|
2022-08-02 21:04:12 +00:00
|
|
|
public abstract void Stop();
|
|
|
|
public abstract void StopImmediate();
|
2022-02-23 05:14:32 +00:00
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
private unsafe void InitDSPSettings(uint srcChannels)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
|
|
|
dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
|
|
|
|
dspSettings.DopplerFactor = 1f;
|
|
|
|
dspSettings.SrcChannelCount = srcChannels;
|
|
|
|
dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
nuint memsize = (
|
2022-02-23 05:14:32 +00:00
|
|
|
4 *
|
2023-03-02 01:47:09 +00:00
|
|
|
dspSettings.SrcChannelCount *
|
|
|
|
dspSettings.DstChannelCount
|
2022-02-23 05:14:32 +00:00
|
|
|
);
|
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
|
2022-02-23 05:14:32 +00:00
|
|
|
unsafe
|
|
|
|
{
|
|
|
|
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
2023-03-02 01:47:09 +00:00
|
|
|
for (uint i = 0; i < memsize; i += 1)
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
|
|
|
memPtr[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SetPanMatrixCoefficients();
|
|
|
|
}
|
2021-01-20 02:06:10 +00:00
|
|
|
|
2021-02-27 00:17:26 +00:00
|
|
|
private void UpdatePitch()
|
|
|
|
{
|
|
|
|
float doppler;
|
|
|
|
float dopplerScale = Device.DopplerScale;
|
2022-04-07 21:19:43 +00:00
|
|
|
if (!Is3D || dopplerScale == 0.0f)
|
2021-02-27 00:17:26 +00:00
|
|
|
{
|
|
|
|
doppler = 1.0f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
doppler = dspSettings.DopplerFactor * dopplerScale;
|
|
|
|
}
|
|
|
|
|
|
|
|
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
2023-03-02 01:47:09 +00:00
|
|
|
Voice,
|
2022-08-30 05:45:28 +00:00
|
|
|
(float) System.Math.Pow(2.0, pitch) * doppler,
|
2021-02-27 00:17:26 +00:00
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-23 05:14:32 +00:00
|
|
|
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
|
|
|
private unsafe void SetPanMatrixCoefficients()
|
|
|
|
{
|
|
|
|
/* Two major things to notice:
|
2021-01-20 02:06:10 +00:00
|
|
|
* 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
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
|
|
|
|
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
|
2021-01-20 02:06:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (dspSettings.DstChannelCount == 1)
|
|
|
|
{
|
|
|
|
outputMatrix[0] = 1.0f;
|
|
|
|
outputMatrix[1] = 1.0f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-30 05:45:28 +00:00
|
|
|
if (pan <= 0.0f)
|
2021-01-20 02:06:10 +00:00
|
|
|
{
|
|
|
|
// Left speaker blends left/right channels
|
2022-08-30 05:45:28 +00:00
|
|
|
outputMatrix[0] = 0.5f * pan + 1.0f;
|
|
|
|
outputMatrix[1] = 0.5f * -pan;
|
2021-01-20 02:06:10 +00:00
|
|
|
// Right speaker gets less of the right channel
|
|
|
|
outputMatrix[2] = 0.0f;
|
2022-08-30 05:45:28 +00:00
|
|
|
outputMatrix[3] = pan + 1.0f;
|
2021-01-20 02:06:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Left speaker gets less of the left channel
|
2022-08-30 05:45:28 +00:00
|
|
|
outputMatrix[0] = -pan + 1.0f;
|
2021-01-20 02:06:10 +00:00
|
|
|
outputMatrix[1] = 0.0f;
|
|
|
|
// Right speaker blends right/left channels
|
2022-08-30 05:45:28 +00:00
|
|
|
outputMatrix[2] = 0.5f * pan;
|
|
|
|
outputMatrix[3] = 0.5f * -pan + 1.0f;
|
2021-01-20 02:06:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-23 05:14:32 +00:00
|
|
|
}
|
2021-01-20 02:06:10 +00:00
|
|
|
|
2023-03-02 01:47:09 +00:00
|
|
|
protected unsafe override void Destroy()
|
2022-02-23 05:14:32 +00:00
|
|
|
{
|
2022-08-02 21:04:12 +00:00
|
|
|
StopImmediate();
|
2023-03-02 01:47:09 +00:00
|
|
|
FAudio.FAudioVoice_DestroyVoice(Voice);
|
|
|
|
NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
|
|
|
|
|
|
|
|
if (ReverbEffect != null)
|
|
|
|
{
|
|
|
|
NativeMemory.Free((void*) ReverbSends.pSends);
|
|
|
|
}
|
2022-02-23 05:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-20 02:06:10 +00:00
|
|
|
}
|