diff --git a/.gitmodules b/.gitmodules
index 29b9556f..fd23d353 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "lib/Campari"]
path = lib/Campari
url = https://gitea.moonside.games/MoonsideGames/Campari.git
+[submodule "lib/FAudio"]
+ path = lib/FAudio
+ url = https://github.com/FNA-XNA/FAudio.git
diff --git a/MoonWorks.csproj b/MoonWorks.csproj
index 52cbe5c0..1eed3d58 100644
--- a/MoonWorks.csproj
+++ b/MoonWorks.csproj
@@ -3,6 +3,7 @@
netstandard2.0
x64
+ true
@@ -13,5 +14,6 @@
+
diff --git a/MoonWorks.sln b/MoonWorks.sln
index 2004c145..2e2bd359 100644
--- a/MoonWorks.sln
+++ b/MoonWorks.sln
@@ -4,6 +4,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 15.0.26124.0
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}"
EndProject
@@ -11,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Campari", "lib\Campari\Camp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\Campari\lib\RefreshCS\RefreshCS.csproj", "{66116A40-B360-4BA3-966A-A54F3E562EC1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudio\csharp\FAudio-CS.Core.csproj", "{608AA31D-F163-4096-B4EF-B9C7D21D52BB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|x64.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/lib/FAudio b/lib/FAudio
new file mode 160000
index 00000000..0f3f1e6d
--- /dev/null
+++ b/lib/FAudio
@@ -0,0 +1 @@
+Subproject commit 0f3f1e6df74da481d466dd97aa4345ea9fe56ca4
diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs
new file mode 100644
index 00000000..35b7d74d
--- /dev/null
+++ b/src/Audio/AudioDevice.cs
@@ -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* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
+ reverbChain->EffectCount = 1;
+ reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
+ Marshal.SizeOf()
+ );
+
+ 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* 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(),
+ 0
+ );
+ Marshal.FreeHGlobal(reverbParamsPtr);
+
+ /* Init reverb sends */
+
+ reverbSends = new FAudio.FAudioVoiceSends();
+ reverbSends.SendCount = 2;
+ reverbSends.pSends = Marshal.AllocHGlobal(
+ 2 * Marshal.SizeOf()
+ );
+ FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) reverbSends.pSends;
+ sendDesc[0].Flags = 0;
+ sendDesc[0].pOutputVoice = MasteringVoice;
+ sendDesc[1].Flags = 0;
+ sendDesc[1].pOutputVoice = ReverbVoice;
+ }
+ }
+}
diff --git a/src/Audio/SongOgg.cs b/src/Audio/SongOgg.cs
new file mode 100644
index 00000000..387e8a2c
--- /dev/null
+++ b/src/Audio/SongOgg.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Audio/Sound.cs b/src/Audio/Sound.cs
new file mode 100644
index 00000000..5fbe4896
--- /dev/null
+++ b/src/Audio/Sound.cs
@@ -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)
+ );
+ }
+ }
+}
diff --git a/src/Audio/SoundInstance.cs b/src/Audio/SoundInstance.cs
new file mode 100644
index 00000000..ca2a62f3
--- /dev/null
+++ b/src/Audio/SoundInstance.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Audio/SoundState.cs b/src/Audio/SoundState.cs
new file mode 100644
index 00000000..2a82ea23
--- /dev/null
+++ b/src/Audio/SoundState.cs
@@ -0,0 +1,9 @@
+namespace MoonWorks.Audio
+{
+ public enum SoundState
+ {
+ Playing,
+ Paused,
+ Stopped
+ }
+}
diff --git a/src/Audio/StaticSoundInstance.cs b/src/Audio/StaticSoundInstance.cs
new file mode 100644
index 00000000..7938775b
--- /dev/null
+++ b/src/Audio/StaticSoundInstance.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/src/Exceptions/AudioLoadException.cs b/src/Exceptions/AudioLoadException.cs
new file mode 100644
index 00000000..33bcdf32
--- /dev/null
+++ b/src/Exceptions/AudioLoadException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace MoonWorks
+{
+ public class AudioLoadException : Exception
+ {
+ public AudioLoadException(string message) : base(message)
+ {
+
+ }
+ }
+}
diff --git a/src/Game.cs b/src/Game.cs
index ca963017..e8b1eb8d 100644
--- a/src/Game.cs
+++ b/src/Game.cs
@@ -1,6 +1,7 @@
using SDL2;
using Campari;
using System.Collections.Generic;
+using MoonWorks.Audio;
namespace MoonWorks
{
@@ -14,6 +15,7 @@ namespace MoonWorks
public Window Window { get; }
public GraphicsDevice GraphicsDevice { get; }
+ public AudioDevice AudioDevice { get; }
public Input Input { get; }
private Dictionary moonWorksToRefreshPresentMode = new Dictionary
@@ -38,6 +40,8 @@ namespace MoonWorks
return;
}
+ Logger.Initialize();
+
Input = new Input();
Window = new Window(windowCreateInfo);
@@ -48,6 +52,8 @@ namespace MoonWorks
debugMode
);
+ AudioDevice = new AudioDevice();
+
this.debugMode = debugMode;
}
diff --git a/src/Logger.cs b/src/Logger.cs
new file mode 100644
index 00000000..37208063
--- /dev/null
+++ b/src/Logger.cs
@@ -0,0 +1,69 @@
+using System;
+using RefreshCS;
+
+namespace MoonWorks
+{
+ public static class Logger
+ {
+ public static Action LogInfo;
+ public static Action LogWarn;
+ public static Action 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;
+ }
+ }
+}
diff --git a/src/Window.cs b/src/Window.cs
index 385d2073..7e72f5a1 100644
--- a/src/Window.cs
+++ b/src/Window.cs
@@ -5,7 +5,7 @@ namespace MoonWorks
{
public class Window
{
- public IntPtr Handle { get; }
+ internal IntPtr Handle { get; }
public ScreenMode ScreenMode { get; }
public Window(WindowCreateInfo windowCreateInfo)