diff --git a/DynamicSound.cs b/DynamicSound.cs
new file mode 100644
index 0000000..636c4c4
--- /dev/null
+++ b/DynamicSound.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+namespace MoonWorks.Audio
+{
+ ///
+ /// For streaming long playback.
+ ///
+ public class DynamicSound : Sound, IDisposable
+ {
+ public const int BUFFER_SIZE = 1024 * 128;
+
+ internal IntPtr FileHandle { get; }
+ internal FAudio.stb_vorbis_info Info { get; }
+
+ private bool IsDisposed;
+
+ // FIXME: what should this value be?
+
+ public DynamicSound(FileInfo fileInfo, ushort channels, uint samplesPerSecond) : base(channels, samplesPerSecond)
+ {
+ FileHandle = FAudio.stb_vorbis_open_filename(fileInfo.FullName, out var error, IntPtr.Zero);
+
+ if (error != 0)
+ {
+ Logger.LogError("Error opening OGG file!");
+ throw new AudioLoadException("Error opening OGG file!");
+ }
+
+ Info = FAudio.stb_vorbis_get_info(FileHandle);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed)
+ {
+ if (disposing)
+ {
+ // dispose managed state (managed objects)
+ }
+
+ FAudio.stb_vorbis_close(FileHandle);
+ IsDisposed = true;
+ }
+ }
+
+ // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ ~DynamicSound()
+ {
+ // 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/DynamicSoundInstance.cs b/DynamicSoundInstance.cs
new file mode 100644
index 0000000..a36824b
--- /dev/null
+++ b/DynamicSoundInstance.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace MoonWorks.Audio
+{
+ public class DynamicSoundInstance : SoundInstance
+ {
+ private List queuedBuffers;
+ private List queuedSizes;
+ private const int MINIMUM_BUFFER_CHECK = 3;
+
+ public int PendingBufferCount => queuedBuffers.Count;
+
+ private readonly float[] buffer;
+
+ public override SoundState State { get; protected set; }
+
+ public DynamicSoundInstance(
+ AudioDevice device,
+ DynamicSound parent,
+ bool is3D
+ ) : base(device, parent, is3D)
+ {
+ queuedBuffers = new List();
+ queuedSizes = new List();
+
+ buffer = new float[DynamicSound.BUFFER_SIZE];
+ }
+
+ public void Play()
+ {
+ Update();
+
+ if (State == SoundState.Playing)
+ {
+ return;
+ }
+
+ QueueBuffers();
+
+ FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
+ State = SoundState.Playing;
+ }
+
+ public void Pause()
+ {
+ if (State == SoundState.Playing)
+ {
+ FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
+ State = SoundState.Paused;
+ }
+ }
+
+ public void Stop()
+ {
+ FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
+ FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
+ State = SoundState.Stopped;
+ ClearBuffers();
+ }
+
+ private void Update()
+ {
+ if (State != SoundState.Playing)
+ {
+ return;
+ }
+
+ FAudio.FAudioSourceVoice_GetState(
+ Handle,
+ out var state,
+ FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
+ );
+
+ while (PendingBufferCount > state.BuffersQueued)
+ lock (queuedBuffers)
+ {
+ Marshal.FreeHGlobal(queuedBuffers[0]);
+ queuedBuffers.RemoveAt(0);
+ queuedSizes.RemoveAt(0);
+ }
+ }
+
+ private void QueueBuffers()
+ {
+ for (
+ int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
+ i > 0;
+ i -= 1
+ ) {
+ AddBuffer();
+ }
+ }
+
+ private void ClearBuffers()
+ {
+ lock (queuedBuffers)
+ {
+ foreach (IntPtr buf in queuedBuffers)
+ {
+ Marshal.FreeHGlobal(buf);
+ }
+ queuedBuffers.Clear();
+ queuedBuffers.Clear();
+ }
+ }
+
+ private void AddBuffer()
+ {
+ var parent = (DynamicSound) Parent;
+
+ var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
+ parent.FileHandle,
+ parent.Info.channels,
+ buffer,
+ buffer.Length
+ );
+
+ IntPtr next = Marshal.AllocHGlobal(buffer.Length);
+ Marshal.Copy(buffer, 0, next, buffer.Length);
+
+ lock (queuedBuffers)
+ {
+ var lengthInBytes = (uint) buffer.Length * sizeof(float);
+
+ queuedBuffers.Add(next);
+ if (State != SoundState.Stopped)
+ {
+ FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
+ {
+ AudioBytes = lengthInBytes,
+ pAudioData = next,
+ PlayLength = (
+ lengthInBytes /
+ (uint) parent.Info.channels /
+ (uint) (parent.Format.wBitsPerSample / 8)
+ )
+ };
+
+ FAudio.FAudioSourceVoice_SubmitSourceBuffer(
+ Handle,
+ ref buf,
+ IntPtr.Zero
+ );
+ }
+ else
+ {
+ queuedSizes.Add(lengthInBytes);
+ }
+ }
+ }
+ }
+}
diff --git a/Sound.cs b/Sound.cs
index 5fbe489..714824b 100644
--- a/Sound.cs
+++ b/Sound.cs
@@ -1,73 +1,26 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-
namespace MoonWorks.Audio
{
- public class Sound
+ public abstract class Sound
{
- internal FAudio.FAudioBuffer Handle;
- internal FAudio.FAudioWaveFormatEx Format;
+ internal FAudio.FAudioWaveFormatEx Format { get; }
- 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! */
+ /* NOTE: we only support float decoding! WAV sucks! */
public Sound(
- float[] buffer,
- uint bufferOffset,
ushort channels,
- uint samplesPerSecond,
- ushort blockAlign
+ uint samplesPerSecond
) {
- var bufferLength = 4 * buffer.Length;
+ var blockAlign = (ushort) (4 * channels);
- 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)
- );
+ Format = new FAudio.FAudioWaveFormatEx
+ {
+ wFormatTag = 3,
+ wBitsPerSample = 32,
+ nChannels = channels,
+ nBlockAlign = blockAlign,
+ nSamplesPerSec = samplesPerSecond,
+ nAvgBytesPerSec = blockAlign * samplesPerSecond,
+ cbSize = 0
+ };
}
}
}
diff --git a/SoundInstance.cs b/SoundInstance.cs
index ca2a62f..c4cc917 100644
--- a/SoundInstance.cs
+++ b/SoundInstance.cs
@@ -7,15 +7,15 @@ namespace MoonWorks.Audio
{
protected AudioDevice Device { get; }
internal IntPtr Handle { get; }
- protected Sound Parent { get; }
+ public 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 abstract SoundState State { get; protected set; }
+
+ private float _pan = 0;
public float Pan
{
get => _pan;
@@ -168,8 +168,11 @@ namespace MoonWorks.Audio
}
}
- public SoundInstance(AudioDevice device, Sound parent, bool is3D)
- {
+ public SoundInstance(
+ AudioDevice device,
+ Sound parent,
+ bool is3D
+ ) {
Device = device;
Parent = parent;
@@ -195,7 +198,6 @@ namespace MoonWorks.Audio
Handle = handle;
this.is3D = is3D;
InitDSPSettings(Parent.Format.nChannels);
-
}
private void InitDSPSettings(uint srcChannels)
diff --git a/StaticSound.cs b/StaticSound.cs
new file mode 100644
index 0000000..f812a70
--- /dev/null
+++ b/StaticSound.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace MoonWorks.Audio
+{
+ public class StaticSound : Sound, IDisposable
+ {
+ internal FAudio.FAudioBuffer Handle;
+ private bool IsDisposed;
+
+ public uint LoopStart { get; set; } = 0;
+ public uint LoopLength { get; set; } = 0;
+
+ public static StaticSound FromOgg(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];
+
+ FAudio.stb_vorbis_close(filePointer);
+
+ return new StaticSound(
+ buffer,
+ 0,
+ (ushort) info.channels,
+ info.sample_rate
+ );
+ }
+
+ public StaticSound(
+ float[] buffer,
+ uint bufferOffset,
+ ushort channels,
+ uint samplesPerSecond
+ ) : base(channels, samplesPerSecond) {
+ var bufferLength = 4 * buffer.Length;
+
+ 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)
+ );
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed)
+ {
+ if (disposing)
+ {
+ // dispose managed state (managed objects)
+ }
+
+ Marshal.FreeHGlobal(Handle.pAudioData);
+ IsDisposed = true;
+ }
+ }
+
+ // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ ~StaticSound()
+ {
+ // 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/StaticSoundInstance.cs b/StaticSoundInstance.cs
index 7938775..5f39f21 100644
--- a/StaticSoundInstance.cs
+++ b/StaticSoundInstance.cs
@@ -4,39 +4,67 @@ namespace MoonWorks.Audio
{
public class StaticSoundInstance : SoundInstance
{
- public bool Loop { get; protected set; }
+ public bool Loop { get; }
+
+ private SoundState _state = SoundState.Stopped;
+ public override SoundState State
+ {
+ get
+ {
+ FAudio.FAudioSourceVoice_GetState(
+ Handle,
+ out var state,
+ FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
+ );
+ if (state.BuffersQueued == 0)
+ {
+ Stop(true);
+ }
+
+ return _state;
+ }
+
+ protected set
+ {
+ _state = value;
+ }
+ }
public StaticSoundInstance(
AudioDevice device,
- Sound parent,
- bool is3D
- ) : base(device, parent, is3D) { }
-
- public void Play(bool loop = false)
+ StaticSound parent,
+ bool is3D,
+ bool loop = false
+ ) : base(device, parent, is3D)
{
+ Loop = loop;
+ }
+
+ public void Play()
+ {
+ var parent = (StaticSound) Parent;
+
if (State == SoundState.Playing)
{
return;
}
- if (loop)
+ if (Loop)
{
- Loop = true;
- Parent.Handle.LoopCount = 255;
- Parent.Handle.LoopBegin = 0;
- Parent.Handle.LoopLength = Parent.LoopLength;
+ parent.Handle.LoopCount = 255;
+ parent.Handle.LoopBegin = parent.LoopStart;
+ parent.Handle.LoopLength = parent.LoopLength;
}
else
{
- Loop = false;
- Parent.Handle.LoopCount = 0;
- Parent.Handle.LoopBegin = 0;
- Parent.Handle.LoopLength = 0;
+ parent.Handle.LoopCount = 0;
+ parent.Handle.LoopBegin = 0;
+ parent.Handle.LoopLength = 0;
}
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Handle,
- ref Parent.Handle,
+ ref parent.Handle,
IntPtr.Zero
);