starting audio implementation
parent
55cd87ab28
commit
768bf38e3f
|
@ -4,3 +4,6 @@
|
||||||
[submodule "lib/Campari"]
|
[submodule "lib/Campari"]
|
||||||
path = lib/Campari
|
path = lib/Campari
|
||||||
url = https://gitea.moonside.games/MoonsideGames/Campari.git
|
url = https://gitea.moonside.games/MoonsideGames/Campari.git
|
||||||
|
[submodule "lib/FAudio"]
|
||||||
|
path = lib/FAudio
|
||||||
|
url = https://github.com/FNA-XNA/FAudio.git
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -13,5 +14,6 @@
|
||||||
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
|
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
|
||||||
<ProjectReference Include=".\lib\Campari\Campari.csproj" />
|
<ProjectReference Include=".\lib\Campari\Campari.csproj" />
|
||||||
<ProjectReference Include=".\lib\Campari\lib\RefreshCS\RefreshCS.csproj" />
|
<ProjectReference Include=".\lib\Campari\lib\RefreshCS\RefreshCS.csproj" />
|
||||||
|
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -4,6 +4,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
VisualStudioVersion = 16.0.30717.126
|
VisualStudioVersion = 16.0.30717.126
|
||||||
MinimumVisualStudioVersion = 15.0.26124.0
|
MinimumVisualStudioVersion = 15.0.26124.0
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{608AA31D-F163-4096-B4EF-B9C7D21D52BB} = {608AA31D-F163-4096-B4EF-B9C7D21D52BB}
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -11,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Campari", "lib\Campari\Camp
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\Campari\lib\RefreshCS\RefreshCS.csproj", "{66116A40-B360-4BA3-966A-A54F3E562EC1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\Campari\lib\RefreshCS\RefreshCS.csproj", "{66116A40-B360-4BA3-966A-A54F3E562EC1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudio\csharp\FAudio-CS.Core.csproj", "{608AA31D-F163-4096-B4EF-B9C7D21D52BB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
|
@ -33,6 +38,10 @@ Global
|
||||||
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Debug|x64.Build.0 = Debug|x64
|
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Debug|x64.Build.0 = Debug|x64
|
||||||
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.ActiveCfg = Release|x64
|
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.ActiveCfg = Release|x64
|
||||||
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.Build.0 = Release|x64
|
{66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.Build.0 = Release|x64
|
||||||
|
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 0f3f1e6df74da481d466dd97aa4345ea9fe56ca4
|
|
@ -0,0 +1,192 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public class AudioDevice
|
||||||
|
{
|
||||||
|
public IntPtr Handle { get; }
|
||||||
|
public byte[] Handle3D { get; }
|
||||||
|
public IntPtr MasteringVoice { get; }
|
||||||
|
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
||||||
|
public IntPtr ReverbVoice { get; }
|
||||||
|
|
||||||
|
public float CurveDistanceScalar = 1f;
|
||||||
|
public float DopplerScale = 1f;
|
||||||
|
public float SpeedOfSound = 343.5f;
|
||||||
|
|
||||||
|
private FAudio.FAudioVoiceSends reverbSends;
|
||||||
|
|
||||||
|
public unsafe AudioDevice()
|
||||||
|
{
|
||||||
|
FAudio.FAudioCreate(out var handle, 0, 0);
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
/* Find a suitable device */
|
||||||
|
|
||||||
|
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
|
||||||
|
|
||||||
|
if (devices == 0)
|
||||||
|
{
|
||||||
|
Logger.LogError("No audio devices found!");
|
||||||
|
Handle = IntPtr.Zero;
|
||||||
|
FAudio.FAudio_Release(Handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioDeviceDetails deviceDetails;
|
||||||
|
|
||||||
|
uint i = 0;
|
||||||
|
for (i = 0; i < devices; i++)
|
||||||
|
{
|
||||||
|
FAudio.FAudio_GetDeviceDetails(
|
||||||
|
Handle,
|
||||||
|
i,
|
||||||
|
out deviceDetails
|
||||||
|
);
|
||||||
|
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
|
||||||
|
{
|
||||||
|
DeviceDetails = deviceDetails;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == devices)
|
||||||
|
{
|
||||||
|
i = 0; /* whatever we'll just use the first one I guess */
|
||||||
|
FAudio.FAudio_GetDeviceDetails(
|
||||||
|
Handle,
|
||||||
|
i,
|
||||||
|
out deviceDetails
|
||||||
|
);
|
||||||
|
DeviceDetails = deviceDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init Mastering Voice */
|
||||||
|
IntPtr masteringVoice;
|
||||||
|
|
||||||
|
if (FAudio.FAudio_CreateMasteringVoice(
|
||||||
|
Handle,
|
||||||
|
out masteringVoice,
|
||||||
|
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||||
|
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||||
|
0,
|
||||||
|
i,
|
||||||
|
IntPtr.Zero
|
||||||
|
) != 0)
|
||||||
|
{
|
||||||
|
Logger.LogError("No mastering voice found!");
|
||||||
|
Handle = IntPtr.Zero;
|
||||||
|
FAudio.FAudio_Release(Handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MasteringVoice = masteringVoice;
|
||||||
|
|
||||||
|
/* Init 3D Audio */
|
||||||
|
|
||||||
|
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
||||||
|
FAudio.F3DAudioInitialize(
|
||||||
|
DeviceDetails.OutputFormat.dwChannelMask,
|
||||||
|
SpeedOfSound,
|
||||||
|
Handle3D
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Init reverb */
|
||||||
|
|
||||||
|
IntPtr reverbVoice;
|
||||||
|
|
||||||
|
IntPtr reverb;
|
||||||
|
FAudio.FAudioCreateReverb(out reverb, 0);
|
||||||
|
|
||||||
|
IntPtr chainPtr;
|
||||||
|
chainPtr = Marshal.AllocHGlobal(
|
||||||
|
Marshal.SizeOf<FAudio.FAudioEffectChain>()
|
||||||
|
);
|
||||||
|
|
||||||
|
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
|
||||||
|
reverbChain->EffectCount = 1;
|
||||||
|
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
|
||||||
|
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
|
||||||
|
);
|
||||||
|
|
||||||
|
FAudio.FAudioEffectDescriptor* reverbDescriptor =
|
||||||
|
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
|
||||||
|
|
||||||
|
reverbDescriptor->InitialState = 1;
|
||||||
|
reverbDescriptor->OutputChannels = (uint) (
|
||||||
|
(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
|
||||||
|
);
|
||||||
|
reverbDescriptor->pEffect = reverb;
|
||||||
|
|
||||||
|
FAudio.FAudio_CreateSubmixVoice(
|
||||||
|
Handle,
|
||||||
|
out reverbVoice,
|
||||||
|
1, /* omnidirectional reverb */
|
||||||
|
DeviceDetails.OutputFormat.Format.nSamplesPerSec,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
IntPtr.Zero,
|
||||||
|
chainPtr
|
||||||
|
);
|
||||||
|
FAudio.FAPOBase_Release(reverb);
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
|
||||||
|
Marshal.FreeHGlobal(chainPtr);
|
||||||
|
|
||||||
|
ReverbVoice = reverbVoice;
|
||||||
|
|
||||||
|
/* Init reverb params */
|
||||||
|
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
|
||||||
|
|
||||||
|
IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
|
||||||
|
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
|
||||||
|
);
|
||||||
|
|
||||||
|
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
|
||||||
|
reverbParams->WetDryMix = 100.0f;
|
||||||
|
reverbParams->ReflectionsDelay = 7;
|
||||||
|
reverbParams->ReverbDelay = 11;
|
||||||
|
reverbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
|
||||||
|
reverbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
|
||||||
|
reverbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
|
||||||
|
reverbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
|
||||||
|
reverbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
|
||||||
|
reverbParams->EarlyDiffusion = 15;
|
||||||
|
reverbParams->LateDiffusion = 15;
|
||||||
|
reverbParams->LowEQGain = 8;
|
||||||
|
reverbParams->LowEQCutoff = 4;
|
||||||
|
reverbParams->HighEQGain = 8;
|
||||||
|
reverbParams->HighEQCutoff = 6;
|
||||||
|
reverbParams->RoomFilterFreq = 5000f;
|
||||||
|
reverbParams->RoomFilterMain = -10f;
|
||||||
|
reverbParams->RoomFilterHF = -1f;
|
||||||
|
reverbParams->ReflectionsGain = -26.0200005f;
|
||||||
|
reverbParams->ReverbGain = 10.0f;
|
||||||
|
reverbParams->DecayTime = 1.49000001f;
|
||||||
|
reverbParams->Density = 100.0f;
|
||||||
|
reverbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
|
||||||
|
FAudio.FAudioVoice_SetEffectParameters(
|
||||||
|
ReverbVoice,
|
||||||
|
0,
|
||||||
|
reverbParamsPtr,
|
||||||
|
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
Marshal.FreeHGlobal(reverbParamsPtr);
|
||||||
|
|
||||||
|
/* Init reverb sends */
|
||||||
|
|
||||||
|
reverbSends = new FAudio.FAudioVoiceSends();
|
||||||
|
reverbSends.SendCount = 2;
|
||||||
|
reverbSends.pSends = Marshal.AllocHGlobal(
|
||||||
|
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
|
||||||
|
);
|
||||||
|
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) reverbSends.pSends;
|
||||||
|
sendDesc[0].Flags = 0;
|
||||||
|
sendDesc[0].pOutputVoice = MasteringVoice;
|
||||||
|
sendDesc[1].Flags = 0;
|
||||||
|
sendDesc[1].pOutputVoice = ReverbVoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
// for streaming long playback
|
||||||
|
public class Song
|
||||||
|
{
|
||||||
|
public IntPtr Handle { get; }
|
||||||
|
public FAudio.stb_vorbis_info Info { get; }
|
||||||
|
public uint BufferSize { get; }
|
||||||
|
public bool Loop { get; set; }
|
||||||
|
private readonly float[] buffer;
|
||||||
|
private const int bufferShrinkFactor = 8;
|
||||||
|
|
||||||
|
public TimeSpan Duration { get; set; }
|
||||||
|
|
||||||
|
public Song(FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
var filePointer = FAudio.stb_vorbis_open_filename(fileInfo.FullName, out var error, IntPtr.Zero);
|
||||||
|
|
||||||
|
if (error != 0)
|
||||||
|
{
|
||||||
|
throw new AudioLoadException("Error loading file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Info = FAudio.stb_vorbis_get_info(filePointer);
|
||||||
|
BufferSize = (uint)(Info.sample_rate * Info.channels) / bufferShrinkFactor;
|
||||||
|
|
||||||
|
buffer = new float[BufferSize];
|
||||||
|
|
||||||
|
|
||||||
|
FAudio.stb_vorbis_close(filePointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public class Sound
|
||||||
|
{
|
||||||
|
internal FAudio.FAudioBuffer Handle;
|
||||||
|
internal FAudio.FAudioWaveFormatEx Format;
|
||||||
|
|
||||||
|
public uint LoopStart { get; set; } = 0;
|
||||||
|
public uint LoopLength { get; set; } = 0;
|
||||||
|
|
||||||
|
public static Sound FromFile(FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
var filePointer = FAudio.stb_vorbis_open_filename(fileInfo.FullName, out var error, IntPtr.Zero);
|
||||||
|
|
||||||
|
if (error != 0)
|
||||||
|
{
|
||||||
|
throw new AudioLoadException("Error loading file!");
|
||||||
|
}
|
||||||
|
var info = FAudio.stb_vorbis_get_info(filePointer);
|
||||||
|
var bufferSize = (uint)(info.sample_rate * info.channels);
|
||||||
|
var buffer = new float[bufferSize];
|
||||||
|
var align = (ushort) (4 * info.channels);
|
||||||
|
|
||||||
|
FAudio.stb_vorbis_close(filePointer);
|
||||||
|
|
||||||
|
return new Sound(
|
||||||
|
buffer,
|
||||||
|
0,
|
||||||
|
(ushort) info.channels,
|
||||||
|
info.sample_rate,
|
||||||
|
align
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we only support float decoding! WAV sucks! */
|
||||||
|
public Sound(
|
||||||
|
float[] buffer,
|
||||||
|
uint bufferOffset,
|
||||||
|
ushort channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
|
ushort blockAlign
|
||||||
|
) {
|
||||||
|
var bufferLength = 4 * buffer.Length;
|
||||||
|
|
||||||
|
Format = new FAudio.FAudioWaveFormatEx();
|
||||||
|
Format.wFormatTag = 3;
|
||||||
|
Format.wBitsPerSample = 32;
|
||||||
|
Format.nChannels = channels;
|
||||||
|
Format.nBlockAlign = (ushort) (4 * Format.nChannels);
|
||||||
|
Format.nSamplesPerSec = samplesPerSecond;
|
||||||
|
Format.nAvgBytesPerSec = Format.nBlockAlign * Format.nSamplesPerSec;
|
||||||
|
Format.nBlockAlign = blockAlign;
|
||||||
|
Format.cbSize = 0;
|
||||||
|
|
||||||
|
Handle = new FAudio.FAudioBuffer();
|
||||||
|
Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
|
||||||
|
Handle.pContext = IntPtr.Zero;
|
||||||
|
Handle.AudioBytes = (uint) bufferLength;
|
||||||
|
Handle.pAudioData = Marshal.AllocHGlobal((int) bufferLength);
|
||||||
|
Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
|
||||||
|
Handle.PlayBegin = 0;
|
||||||
|
Handle.PlayLength = (
|
||||||
|
Handle.AudioBytes /
|
||||||
|
(uint) Format.nChannels /
|
||||||
|
(uint) (Format.wBitsPerSample / 8)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,308 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public abstract class SoundInstance : IDisposable
|
||||||
|
{
|
||||||
|
protected AudioDevice Device { get; }
|
||||||
|
internal IntPtr Handle { get; }
|
||||||
|
protected Sound Parent { get; }
|
||||||
|
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||||
|
public SoundState State { get; protected set; }
|
||||||
|
|
||||||
|
protected bool is3D;
|
||||||
|
|
||||||
|
private float _pan = 0;
|
||||||
|
private bool IsDisposed;
|
||||||
|
|
||||||
|
public float Pan
|
||||||
|
{
|
||||||
|
get => _pan;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_pan = value;
|
||||||
|
|
||||||
|
if (_pan < -1f)
|
||||||
|
{
|
||||||
|
_pan = -1f;
|
||||||
|
}
|
||||||
|
if (_pan > 1f)
|
||||||
|
{
|
||||||
|
_pan = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is3D) { return; }
|
||||||
|
|
||||||
|
SetPanMatrixCoefficients();
|
||||||
|
FAudio.FAudioVoice_SetOutputMatrix(
|
||||||
|
Handle,
|
||||||
|
Device.MasteringVoice,
|
||||||
|
dspSettings.SrcChannelCount,
|
||||||
|
dspSettings.DstChannelCount,
|
||||||
|
dspSettings.pMatrixCoefficients,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _pitch = 1;
|
||||||
|
public float Pitch
|
||||||
|
{
|
||||||
|
get => _pitch;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
float doppler;
|
||||||
|
if (!is3D || Device.DopplerScale == 0f)
|
||||||
|
{
|
||||||
|
doppler = 1f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
doppler = dspSettings.DopplerFactor * Device.DopplerScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pitch = value;
|
||||||
|
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||||
|
Handle,
|
||||||
|
(float) Math.Pow(2.0, _pitch) * doppler,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _volume = 1;
|
||||||
|
public float Volume
|
||||||
|
{
|
||||||
|
get => _volume;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_volume = value;
|
||||||
|
FAudio.FAudioVoice_SetVolume(Handle, _volume, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _reverb;
|
||||||
|
public unsafe float Reverb
|
||||||
|
{
|
||||||
|
get => _reverb;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_reverb = value;
|
||||||
|
|
||||||
|
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
||||||
|
outputMatrix[0] = _reverb;
|
||||||
|
if (dspSettings.SrcChannelCount == 2)
|
||||||
|
{
|
||||||
|
outputMatrix[1] = _reverb;
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_SetOutputMatrix(
|
||||||
|
Handle,
|
||||||
|
Device.ReverbVoice,
|
||||||
|
dspSettings.SrcChannelCount,
|
||||||
|
1,
|
||||||
|
dspSettings.pMatrixCoefficients,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _lowPassFilter;
|
||||||
|
public float LowPassFilter
|
||||||
|
{
|
||||||
|
get => _lowPassFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_lowPassFilter = value;
|
||||||
|
|
||||||
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
||||||
|
p.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
||||||
|
p.Frequency = _lowPassFilter;
|
||||||
|
p.OneOverQ = 1f;
|
||||||
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
|
Handle,
|
||||||
|
ref p,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _highPassFilter;
|
||||||
|
public float HighPassFilter
|
||||||
|
{
|
||||||
|
get => _highPassFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_highPassFilter = value;
|
||||||
|
|
||||||
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
||||||
|
p.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
||||||
|
p.Frequency = _highPassFilter;
|
||||||
|
p.OneOverQ = 1f;
|
||||||
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
|
Handle,
|
||||||
|
ref p,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _bandPassFilter;
|
||||||
|
public float BandPassFilter
|
||||||
|
{
|
||||||
|
get => _bandPassFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_bandPassFilter = value;
|
||||||
|
|
||||||
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
||||||
|
p.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
|
||||||
|
p.Frequency = _bandPassFilter;
|
||||||
|
p.OneOverQ = 1f;
|
||||||
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
|
Handle,
|
||||||
|
ref p,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundInstance(AudioDevice device, Sound parent, bool is3D)
|
||||||
|
{
|
||||||
|
Device = device;
|
||||||
|
Parent = parent;
|
||||||
|
|
||||||
|
FAudio.FAudioWaveFormatEx format = Parent.Format;
|
||||||
|
|
||||||
|
FAudio.FAudio_CreateSourceVoice(
|
||||||
|
Device.Handle,
|
||||||
|
out var handle,
|
||||||
|
ref format,
|
||||||
|
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||||
|
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero
|
||||||
|
);
|
||||||
|
|
||||||
|
if (handle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Logger.LogError("SoundInstance failed to initialize!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle = handle;
|
||||||
|
this.is3D = is3D;
|
||||||
|
InitDSPSettings(Parent.Format.nChannels);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!IsDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// dispose managed state (managed objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||||
|
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SoundInstance()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public enum SoundState
|
||||||
|
{
|
||||||
|
Playing,
|
||||||
|
Paused,
|
||||||
|
Stopped
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MoonWorks.Audio
|
||||||
|
{
|
||||||
|
public class StaticSoundInstance : SoundInstance
|
||||||
|
{
|
||||||
|
public bool Loop { get; protected set; }
|
||||||
|
|
||||||
|
public StaticSoundInstance(
|
||||||
|
AudioDevice device,
|
||||||
|
Sound parent,
|
||||||
|
bool is3D
|
||||||
|
) : base(device, parent, is3D) { }
|
||||||
|
|
||||||
|
public void Play(bool loop = false)
|
||||||
|
{
|
||||||
|
if (State == SoundState.Playing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loop)
|
||||||
|
{
|
||||||
|
Loop = true;
|
||||||
|
Parent.Handle.LoopCount = 255;
|
||||||
|
Parent.Handle.LoopBegin = 0;
|
||||||
|
Parent.Handle.LoopLength = Parent.LoopLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Loop = false;
|
||||||
|
Parent.Handle.LoopCount = 0;
|
||||||
|
Parent.Handle.LoopBegin = 0;
|
||||||
|
Parent.Handle.LoopLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||||
|
Handle,
|
||||||
|
ref Parent.Handle,
|
||||||
|
IntPtr.Zero
|
||||||
|
);
|
||||||
|
|
||||||
|
FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
|
||||||
|
State = SoundState.Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (State == SoundState.Paused)
|
||||||
|
{
|
||||||
|
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||||
|
State = SoundState.Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop(bool immediate = true)
|
||||||
|
{
|
||||||
|
if (immediate)
|
||||||
|
{
|
||||||
|
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||||
|
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||||
|
State = SoundState.Stopped;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public class AudioLoadException : Exception
|
||||||
|
{
|
||||||
|
public AudioLoadException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using SDL2;
|
using SDL2;
|
||||||
using Campari;
|
using Campari;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MoonWorks.Audio;
|
||||||
|
|
||||||
namespace MoonWorks
|
namespace MoonWorks
|
||||||
{
|
{
|
||||||
|
@ -14,6 +15,7 @@ namespace MoonWorks
|
||||||
|
|
||||||
public Window Window { get; }
|
public Window Window { get; }
|
||||||
public GraphicsDevice GraphicsDevice { get; }
|
public GraphicsDevice GraphicsDevice { get; }
|
||||||
|
public AudioDevice AudioDevice { get; }
|
||||||
public Input Input { get; }
|
public Input Input { get; }
|
||||||
|
|
||||||
private Dictionary<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
|
private Dictionary<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
|
||||||
|
@ -38,6 +40,8 @@ namespace MoonWorks
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Initialize();
|
||||||
|
|
||||||
Input = new Input();
|
Input = new Input();
|
||||||
|
|
||||||
Window = new Window(windowCreateInfo);
|
Window = new Window(windowCreateInfo);
|
||||||
|
@ -48,6 +52,8 @@ namespace MoonWorks
|
||||||
debugMode
|
debugMode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AudioDevice = new AudioDevice();
|
||||||
|
|
||||||
this.debugMode = debugMode;
|
this.debugMode = debugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using RefreshCS;
|
||||||
|
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
public static Action<string> LogInfo;
|
||||||
|
public static Action<string> LogWarn;
|
||||||
|
public static Action<string> LogError;
|
||||||
|
|
||||||
|
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
|
||||||
|
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
|
||||||
|
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
|
||||||
|
|
||||||
|
internal static void Initialize()
|
||||||
|
{
|
||||||
|
if (Logger.LogInfo == null)
|
||||||
|
{
|
||||||
|
Logger.LogInfo = Console.WriteLine;
|
||||||
|
}
|
||||||
|
if (Logger.LogWarn == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarn = Console.WriteLine;
|
||||||
|
}
|
||||||
|
if (Logger.LogError == null)
|
||||||
|
{
|
||||||
|
Logger.LogError = Console.WriteLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
Refresh.Refresh_HookLogFunctions(
|
||||||
|
LogInfoFunc,
|
||||||
|
LogWarnFunc,
|
||||||
|
LogErrorFunc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshLogInfo(IntPtr msg)
|
||||||
|
{
|
||||||
|
LogInfo(UTF8_ToManaged(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshLogWarn(IntPtr msg)
|
||||||
|
{
|
||||||
|
LogWarn(UTF8_ToManaged(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RefreshLogError(IntPtr msg)
|
||||||
|
{
|
||||||
|
LogError(UTF8_ToManaged(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe static string UTF8_ToManaged(IntPtr s)
|
||||||
|
{
|
||||||
|
byte* ptr = (byte*) s;
|
||||||
|
while (*ptr != 0)
|
||||||
|
{
|
||||||
|
ptr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string result = System.Text.Encoding.UTF8.GetString(
|
||||||
|
(byte*) s,
|
||||||
|
(int) (ptr - (byte*) s)
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ namespace MoonWorks
|
||||||
{
|
{
|
||||||
public class Window
|
public class Window
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; }
|
internal IntPtr Handle { get; }
|
||||||
public ScreenMode ScreenMode { get; }
|
public ScreenMode ScreenMode { get; }
|
||||||
|
|
||||||
public Window(WindowCreateInfo windowCreateInfo)
|
public Window(WindowCreateInfo windowCreateInfo)
|
||||||
|
|
Loading…
Reference in New Issue