MoonWorks/src/Audio/SoundInstance.cs

368 lines
7.8 KiB
C#
Raw Normal View History

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
{
internal IntPtr Handle;
internal FAudio.FAudioWaveFormatEx Format;
2022-02-23 05:14:32 +00:00
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
2022-04-07 21:19:43 +00:00
public bool Is3D { get; protected set; }
2022-02-23 05:14:32 +00:00
public virtual SoundState State { get; protected set; }
2022-02-23 05:14:32 +00:00
private float pan = 0;
2022-02-23 05:14:32 +00:00
public float Pan
{
get => pan;
2022-02-23 05:14:32 +00:00
set
{
pan = value;
2022-02-23 05:14:32 +00:00
if (pan < -1f)
2022-02-23 05:14:32 +00:00
{
pan = -1f;
2022-02-23 05:14:32 +00:00
}
if (pan > 1f)
2022-02-23 05:14:32 +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(
Handle,
Device.MasteringVoice,
dspSettings.SrcChannelCount,
dspSettings.DstChannelCount,
dspSettings.pMatrixCoefficients,
0
);
}
}
private float pitch = 0;
2022-02-23 05:14:32 +00:00
public float Pitch
{
get => pitch;
2022-02-23 05:14:32 +00:00
set
{
pitch = Math.MathHelper.Clamp(value, -1f, 1f);
2022-02-23 05:14:32 +00:00
UpdatePitch();
}
}
private float volume = 1;
2022-02-23 05:14:32 +00:00
public float Volume
{
get => volume;
2022-02-23 05:14:32 +00:00
set
{
volume = value;
FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
2022-02-23 05:14:32 +00:00
}
}
private float reverb;
2022-02-23 05:14:32 +00:00
public unsafe float Reverb
{
get => reverb;
2022-02-23 05:14:32 +00:00
set
{
reverb = value;
2022-02-23 05:14:32 +00:00
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
outputMatrix[0] = reverb;
2022-02-23 05:14:32 +00:00
if (dspSettings.SrcChannelCount == 2)
{
outputMatrix[1] = reverb;
2022-02-23 05:14:32 +00:00
}
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
Device.ReverbVoice,
dspSettings.SrcChannelCount,
1,
dspSettings.pMatrixCoefficients,
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
};
private float FilterFrequency
2022-02-23 05:14:32 +00:00
{
get => filterParameters.Frequency;
2022-02-23 05:14:32 +00:00
set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
filterParameters.Frequency = value;
2022-02-23 05:14:32 +00:00
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
2022-02-23 05:14:32 +00:00
0
);
}
}
private float FilterOneOverQ
2022-02-23 05:14:32 +00:00
{
get => filterParameters.OneOverQ;
2022-02-23 05:14:32 +00:00
set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
filterParameters.OneOverQ = value;
2022-02-23 05:14:32 +00:00
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
2022-02-23 05:14:32 +00:00
0
);
}
}
private FilterType filterType;
public FilterType FilterType
2022-02-23 05:14:32 +00:00
{
get => filterType;
2022-02-23 05:14:32 +00:00
set
{
filterType = value;
2022-02-23 05:14:32 +00:00
switch (filterType)
2022-02-23 05:14:32 +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(
Handle,
ref filterParameters,
2022-02-23 05:14:32 +00:00
0
);
}
}
public SoundInstance(
AudioDevice device,
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
{
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,
out Handle,
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
);
if (Handle == IntPtr.Zero)
2022-02-23 05:14:32 +00:00
{
Logger.LogError("SoundInstance failed to initialize!");
return;
}
InitDSPSettings(Format.nChannels);
// FIXME: not everything should be running through reverb...
/*
2022-02-23 05:14:32 +00:00
FAudio.FAudioVoice_SetOutputVoices(
Handle,
2022-02-23 05:14:32 +00:00
ref Device.ReverbSends
);
*/
2022-02-23 05:14:32 +00:00
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(
Handle,
Device.MasteringVoice,
dspSettings.SrcChannelCount,
dspSettings.DstChannelCount,
dspSettings.pMatrixCoefficients,
0
);
}
public abstract void Play();
public abstract void QueueSyncPlay();
2022-02-23 05:14:32 +00:00
public abstract void Pause();
public abstract void Stop();
public abstract void StopImmediate();
2022-02-23 05:14:32 +00:00
private void InitDSPSettings(uint srcChannels)
{
dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
dspSettings.DopplerFactor = 1f;
dspSettings.SrcChannelCount = srcChannels;
dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
int memsize = (
4 *
(int) dspSettings.SrcChannelCount *
(int) dspSettings.DstChannelCount
);
dspSettings.pMatrixCoefficients = Marshal.AllocHGlobal(memsize);
unsafe
{
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
for (int i = 0; i < memsize; i += 1)
{
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(
Handle,
(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
{
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
{
if (pan <= 0.0f)
2021-01-20 02:06:10 +00:00
{
// Left speaker blends left/right channels
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;
outputMatrix[3] = pan + 1.0f;
2021-01-20 02:06:10 +00:00
}
else
{
// Left speaker gets less of the left channel
outputMatrix[0] = -pan + 1.0f;
2021-01-20 02:06:10 +00:00
outputMatrix[1] = 0.0f;
// Right speaker blends right/left channels
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
2022-02-23 05:14:32 +00:00
protected override void Destroy()
{
StopImmediate();
2022-02-23 05:14:32 +00:00
FAudio.FAudioVoice_DestroyVoice(Handle);
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
}
}
2021-01-20 02:06:10 +00:00
}