diff --git a/src/Audio/AudioData.cs b/src/Audio/AudioData.cs
new file mode 100644
index 0000000..9998179
--- /dev/null
+++ b/src/Audio/AudioData.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace MoonWorks.Audio
+{
+ public abstract class AudioData
+ {
+ public Format Format { get; protected set; }
+ public abstract uint DecodeBufferSize { get; }
+
+ public abstract bool Loaded { get; }
+
+ ///
+ /// Loads the raw audio data into memory.
+ ///
+ public abstract void Load();
+
+ ///
+ /// Seeks to the given sample frame.
+ ///
+ public abstract void Seek(uint sampleFrame);
+
+ ///
+ /// Attempts to decodes data of length bufferLengthInBytes into the provided buffer.
+ ///
+ /// The buffer that decoded bytes will be placed into.
+ /// Requested length of decoded audio data.
+ /// How much data was actually filled in by the decode.
+ /// Whether the end of the data was reached on this decode.
+ public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
+
+ ///
+ /// Unloads the raw audio data from memory.
+ ///
+ public abstract void Unload();
+ }
+}
diff --git a/src/Audio/AudioDataOgg.cs b/src/Audio/AudioDataOgg.cs
new file mode 100644
index 0000000..dbb988d
--- /dev/null
+++ b/src/Audio/AudioDataOgg.cs
@@ -0,0 +1,96 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace MoonWorks.Audio
+{
+ public class AudioDataOgg : AudioData
+ {
+ private IntPtr FileDataPtr = IntPtr.Zero;
+ private IntPtr VorbisHandle = IntPtr.Zero;
+
+ private string FilePath;
+
+ public override bool Loaded => VorbisHandle != IntPtr.Zero;
+ public override uint DecodeBufferSize => 32768;
+
+ public AudioDataOgg(string filePath)
+ {
+ FilePath = filePath;
+
+ var handle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
+
+ if (error != 0)
+ {
+ throw new AudioLoadException("Error loading file!");
+ }
+
+ var info = FAudio.stb_vorbis_get_info(handle);
+
+ Format = new Format
+ {
+ Tag = FormatTag.IEEE_FLOAT,
+ BitsPerSample = 32,
+ Channels = (ushort) info.channels,
+ SampleRate = info.sample_rate
+ };
+
+ FAudio.stb_vorbis_close(handle);
+ }
+
+ public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
+ {
+ var lengthInFloats = bufferLengthInBytes / sizeof(float);
+
+ /* NOTE: this function returns samples per channel, not total samples */
+ var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
+ VorbisHandle,
+ Format.Channels,
+ (IntPtr) buffer,
+ lengthInFloats
+ );
+
+ var sampleCount = samples * Format.Channels;
+ reachedEnd = sampleCount < lengthInFloats;
+ filledLengthInBytes = sampleCount * sizeof(float);
+ }
+
+ public override unsafe void Load()
+ {
+ if (!Loaded)
+ {
+ var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
+ FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
+ var fileDataSpan = new Span((void*) FileDataPtr, (int) fileStream.Length);
+ fileStream.ReadExactly(fileDataSpan);
+ fileStream.Close();
+
+ VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
+ if (error != 0)
+ {
+ NativeMemory.Free((void*) FileDataPtr);
+ Logger.LogError("Error opening OGG file!");
+ Logger.LogError("Error: " + error);
+ throw new AudioLoadException("Error opening OGG file!");
+ }
+ }
+ }
+
+ public override void Seek(uint sampleFrame)
+ {
+ FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
+ }
+
+ public override unsafe void Unload()
+ {
+ if (Loaded)
+ {
+ FAudio.stb_vorbis_close(VorbisHandle);
+ NativeMemory.Free((void*) FileDataPtr);
+
+ VorbisHandle = IntPtr.Zero;
+ FileDataPtr = IntPtr.Zero;
+ }
+ }
+ }
+}
diff --git a/src/Audio/StreamingSoundQoa.cs b/src/Audio/AudioDataQoa.cs
similarity index 54%
rename from src/Audio/StreamingSoundQoa.cs
rename to src/Audio/AudioDataQoa.cs
index 6c4340d..5322095 100644
--- a/src/Audio/StreamingSoundQoa.cs
+++ b/src/Audio/AudioDataQoa.cs
@@ -1,37 +1,28 @@
-using System;
+using System;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio
{
- public class StreamingSoundQoa : StreamingSoundSeekable
+ public class AudioDataQoa : AudioData
{
private IntPtr QoaHandle = IntPtr.Zero;
private IntPtr FileDataPtr = IntPtr.Zero;
- uint Channels;
- uint SamplesPerChannelPerFrame;
- uint TotalSamplesPerChannel;
-
- public override bool Loaded => QoaHandle != IntPtr.Zero;
private string FilePath;
private const uint QOA_MAGIC = 0x716f6166; /* 'qoaf' */
- private static unsafe UInt64 ReverseEndianness(UInt64 value)
- {
- byte* bytes = (byte*) &value;
+ public override bool Loaded => QoaHandle != IntPtr.Zero;
- return
- ((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
- ((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
- ((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
- ((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
- }
+ private uint decodeBufferSize;
+ public override uint DecodeBufferSize => decodeBufferSize;
- public unsafe static StreamingSoundQoa Create(AudioDevice device, string filePath)
+ public AudioDataQoa(string filePath)
{
- using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
+ FilePath = filePath;
+
+ using var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
UInt64 fileHeader = ReverseEndianness(reader.ReadUInt64());
@@ -51,37 +42,47 @@ namespace MoonWorks.Audio
uint samplerate = (uint) ((frameHeader >> 32) & 0xFFFFFF);
uint samplesPerChannelPerFrame = (uint) ((frameHeader >> 16) & 0x00FFFF);
- return new StreamingSoundQoa(
- device,
- filePath,
- channels,
- samplerate,
- samplesPerChannelPerFrame,
- totalSamplesPerChannel
- );
+ Format = new Format
+ {
+ Tag = FormatTag.PCM,
+ BitsPerSample = 16,
+ Channels = (ushort) channels,
+ SampleRate = samplerate
+ };
+
+ decodeBufferSize = channels * samplesPerChannelPerFrame * sizeof(short);
}
- internal unsafe StreamingSoundQoa(
- AudioDevice device,
- string filePath,
- uint channels,
- uint samplesPerSecond,
- uint samplesPerChannelPerFrame,
- uint totalSamplesPerChannel
- ) : base(
- device,
- 1,
- 16,
- (ushort) (2 * channels),
- (ushort) channels,
- samplesPerSecond,
- samplesPerChannelPerFrame * channels * sizeof(short),
- true
- ) {
- Channels = channels;
- SamplesPerChannelPerFrame = samplesPerChannelPerFrame;
- TotalSamplesPerChannel = totalSamplesPerChannel;
- FilePath = filePath;
+ public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
+ {
+ var lengthInShorts = bufferLengthInBytes / sizeof(short);
+
+ // NOTE: this function returns samples per channel!
+ var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
+
+ var sampleCount = samples * Format.Channels;
+ reachedEnd = sampleCount < lengthInShorts;
+ filledLengthInBytes = (int) (sampleCount * sizeof(short));
+ }
+
+ public override unsafe void Load()
+ {
+ if (!Loaded)
+ {
+ var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
+ FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
+ var fileDataSpan = new Span((void*) FileDataPtr, (int) fileStream.Length);
+ fileStream.ReadExactly(fileDataSpan);
+ fileStream.Close();
+
+ QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
+ if (QoaHandle == IntPtr.Zero)
+ {
+ NativeMemory.Free((void*) FileDataPtr);
+ Logger.LogError("Error opening QOA file!");
+ throw new AudioLoadException("Error opening QOA file!");
+ }
+ }
}
public override void Seek(uint sampleFrame)
@@ -89,23 +90,6 @@ namespace MoonWorks.Audio
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
}
- public override unsafe void Load()
- {
- var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
- FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
- var fileDataSpan = new Span((void*) FileDataPtr, (int) fileStream.Length);
- fileStream.ReadExactly(fileDataSpan);
- fileStream.Close();
-
- QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
- if (QoaHandle == IntPtr.Zero)
- {
- NativeMemory.Free((void*) FileDataPtr);
- Logger.LogError("Error opening QOA file!");
- throw new AudioLoadException("Error opening QOA file!");
- }
- }
-
public override unsafe void Unload()
{
if (Loaded)
@@ -118,20 +102,15 @@ namespace MoonWorks.Audio
}
}
- protected override unsafe void FillBuffer(
- void* buffer,
- int bufferLengthInBytes,
- out int filledLengthInBytes,
- out bool reachedEnd
- ) {
- var lengthInShorts = bufferLengthInBytes / sizeof(short);
+ private static unsafe UInt64 ReverseEndianness(UInt64 value)
+ {
+ byte* bytes = (byte*) &value;
- // NOTE: this function returns samples per channel!
- var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
-
- var sampleCount = samples * Channels;
- reachedEnd = sampleCount < lengthInShorts;
- filledLengthInBytes = (int) (sampleCount * sizeof(short));
+ return
+ ((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
+ ((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
+ ((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
+ ((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
}
}
}
diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs
index f198920..ce3f76e 100644
--- a/src/Audio/AudioDevice.cs
+++ b/src/Audio/AudioDevice.cs
@@ -22,13 +22,12 @@ namespace MoonWorks.Audio
public float SpeedOfSound = 343.5f;
private readonly HashSet resources = new HashSet();
- private readonly List autoUpdateStreamingSoundReferences = new List();
- private readonly List> soundSequenceReferences = new List>();
- private readonly List autoFreeSourceVoices = new List();
+ private readonly HashSet activeSourceVoices = new HashSet();
private AudioTweenManager AudioTweenManager;
private SourceVoicePool VoicePool;
+ private List VoicesToReturn = new List();
private const int Step = 200;
private TimeSpan UpdateInterval;
@@ -106,7 +105,7 @@ namespace MoonWorks.Audio
return;
}
- fauxMasteringVoice = new SubmixVoice(this, FAudio.FAUDIO_DEFAULT_CHANNELS, FAudio.FAUDIO_DEFAULT_SAMPLERATE);
+ fauxMasteringVoice = new SubmixVoice(this, DeviceDetails.OutputFormat.Format.nChannels, DeviceDetails.OutputFormat.Format.nSamplesPerSec);
/* Init 3D Audio */
@@ -159,45 +158,21 @@ namespace MoonWorks.Audio
previousTickTime = TickStopwatch.Elapsed.Ticks;
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
- // TODO: call an Update on all active voices
-
- for (var i = autoUpdateStreamingSoundReferences.Count - 1; i >= 0; i -= 1)
- {
- var streamingSound = autoUpdateStreamingSoundReferences[i];
-
- if (streamingSound.Loaded)
- {
- streamingSound.Update();
- }
- else
- {
- autoUpdateStreamingSoundReferences.RemoveAt(i);
- }
- }
-
- for (var i = soundSequenceReferences.Count - 1; i >= 0; i -= 1)
- {
- if (soundSequenceReferences[i].TryGetTarget(out var soundSequence))
- {
- soundSequence.OnUpdate();
- }
- else
- {
- soundSequenceReferences.RemoveAt(i);
- }
- }
-
- for (var i = autoFreeSourceVoices.Count - 1; i >= 0; i -= 1)
- {
- var voice = autoFreeSourceVoices[i];
- if (voice.BuffersQueued == 0)
- {
- Return(voice);
- autoFreeSourceVoices.RemoveAt(i);
- }
- }
-
AudioTweenManager.Update(elapsedSeconds);
+
+ foreach (var voice in activeSourceVoices)
+ {
+ voice.Update();
+ }
+
+ foreach (var voice in VoicesToReturn)
+ {
+ voice.Reset();
+ activeSourceVoices.Remove(voice);
+ VoicePool.Return(voice);
+ }
+
+ VoicesToReturn.Clear();
}
///
@@ -208,26 +183,18 @@ namespace MoonWorks.Audio
FAudio.FAudio_CommitChanges(Handle, syncGroup);
}
- // TODO: is pooling SourceVoices generically a good idea? there are a lot of different kinds
-
///
/// Obtains an appropriate source voice from the voice pool.
///
/// The format that the voice must match.
/// A source voice with the given format.
- public SourceVoice Obtain(Format format)
+ public T Obtain(Format format) where T : SourceVoice, IPoolable
{
lock (StateLock)
{
- return VoicePool.Obtain(format);
- }
- }
-
- internal void ReturnWhenIdle(SourceVoice voice)
- {
- lock (StateLock)
- {
- autoFreeSourceVoices.Add(voice);
+ var voice = VoicePool.Obtain(format);
+ activeSourceVoices.Add(voice);
+ return voice;
}
}
@@ -239,8 +206,7 @@ namespace MoonWorks.Audio
{
lock (StateLock)
{
- voice.Reset();
- VoicePool.Return(voice);
+ VoicesToReturn.Add(voice);
}
}
@@ -298,16 +264,6 @@ namespace MoonWorks.Audio
}
}
- internal void AddAutoUpdateStreamingSoundInstance(StreamingSound instance)
- {
- autoUpdateStreamingSoundReferences.Add(instance);
- }
-
- internal void AddSoundSequenceReference(SoundSequence sequence)
- {
- soundSequenceReferences.Add(new WeakReference(sequence));
- }
-
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
diff --git a/src/Audio/IPoolable.cs b/src/Audio/IPoolable.cs
new file mode 100644
index 0000000..2e0bf92
--- /dev/null
+++ b/src/Audio/IPoolable.cs
@@ -0,0 +1,7 @@
+namespace MoonWorks.Audio
+{
+ public interface IPoolable
+ {
+ static abstract T Create(AudioDevice device, Format format);
+ }
+}
diff --git a/src/Audio/ReverbEffect.cs b/src/Audio/ReverbEffect.cs
index eac7cda..064ab72 100644
--- a/src/Audio/ReverbEffect.cs
+++ b/src/Audio/ReverbEffect.cs
@@ -16,7 +16,7 @@ namespace MoonWorks.Audio
var descriptor = new FAudio.FAudioEffectDescriptor();
descriptor.InitialState = 1;
- descriptor.OutputChannels = Device.DeviceDetails.OutputFormat.Format.nChannels;
+ descriptor.OutputChannels = 1;
descriptor.pEffect = reverb;
chain.EffectCount = 1;
diff --git a/src/Audio/SoundInstance.cs b/src/Audio/SoundInstance.cs
deleted file mode 100644
index 11d320e..0000000
--- a/src/Audio/SoundInstance.cs
+++ /dev/null
@@ -1,508 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using EasingFunction = System.Func;
-
-namespace MoonWorks.Audio
-{
- public abstract class SoundInstance : AudioResource
- {
- internal IntPtr Voice;
-
- private FAudio.FAudioWaveFormatEx format;
- public FAudio.FAudioWaveFormatEx Format => format;
-
- protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
-
- private ReverbEffect ReverbEffect;
- private FAudio.FAudioVoiceSends ReverbSends;
-
- public bool Is3D { get; protected set; }
-
- public virtual SoundState State { get; protected set; }
-
- private float pan = 0;
- public float Pan
- {
- get => pan;
- internal set
- {
- value = Math.MathHelper.Clamp(value, -1f, 1f);
- if (pan != value)
- {
- pan = value;
-
- if (pan < -1f)
- {
- pan = -1f;
- }
- if (pan > 1f)
- {
- pan = 1f;
- }
-
- if (Is3D) { return; }
-
- SetPanMatrixCoefficients();
- FAudio.FAudioVoice_SetOutputMatrix(
- Voice,
- Device.MasteringVoice,
- dspSettings.SrcChannelCount,
- dspSettings.DstChannelCount,
- dspSettings.pMatrixCoefficients,
- 0
- );
- }
- }
- }
-
- private float pitch = 0;
- public float Pitch
- {
- get => pitch;
- internal set
- {
- value = Math.MathHelper.Clamp(value, -1f, 1f);
- if (pitch != value)
- {
- pitch = value;
- UpdatePitch();
- }
- }
- }
-
- private float volume = 1;
- public float Volume
- {
- get => volume;
- internal set
- {
- value = Math.MathHelper.Max(0, value);
- if (volume != value)
- {
- volume = value;
- FAudio.FAudioVoice_SetVolume(Voice, volume, 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
- };
-
- public float FilterFrequency
- {
- get => filterParameters.Frequency;
- internal set
- {
- value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
- if (filterParameters.Frequency != value)
- {
- filterParameters.Frequency = value;
-
- FAudio.FAudioVoice_SetFilterParameters(
- Voice,
- ref filterParameters,
- 0
- );
- }
- }
- }
-
- public float FilterOneOverQ
- {
- get => filterParameters.OneOverQ;
- internal set
- {
- value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
- if (filterParameters.OneOverQ != value)
- {
- filterParameters.OneOverQ = value;
-
- FAudio.FAudioVoice_SetFilterParameters(
- Voice,
- ref filterParameters,
- 0
- );
- }
- }
- }
-
- private FilterType filterType;
- public FilterType FilterType
- {
- get => filterType;
- set
- {
- if (filterType != value)
- {
- filterType = value;
-
- switch (filterType)
- {
- case FilterType.None:
- filterParameters = new FAudio.FAudioFilterParameters
- {
- Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
- Frequency = 1f,
- OneOverQ = 1f
- };
- break;
-
- case FilterType.LowPass:
- filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
- filterParameters.Frequency = 1f;
- break;
-
- case FilterType.BandPass:
- filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
- break;
-
- case FilterType.HighPass:
- filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
- filterParameters.Frequency = 0f;
- break;
- }
-
- FAudio.FAudioVoice_SetFilterParameters(
- Voice,
- ref filterParameters,
- 0
- );
- }
- }
- }
-
- private float reverb;
- public unsafe float Reverb
- {
- get => reverb;
- internal set
- {
- if (ReverbEffect != null)
- {
- value = MathF.Max(0, value);
- if (reverb != value)
- {
- 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
- }
- }
-
- public unsafe SoundInstance(
- AudioDevice device,
- ushort formatTag,
- ushort bitsPerSample,
- ushort blockAlign,
- ushort channels,
- uint samplesPerSecond
- ) : base(device)
- {
- format = new FAudio.FAudioWaveFormatEx
- {
- wFormatTag = formatTag,
- wBitsPerSample = bitsPerSample,
- nChannels = channels,
- nBlockAlign = blockAlign,
- nSamplesPerSec = samplesPerSecond,
- nAvgBytesPerSec = blockAlign * samplesPerSecond
- };
-
- FAudio.FAudio_CreateSourceVoice(
- Device.Handle,
- out Voice,
- ref format,
- FAudio.FAUDIO_VOICE_USEFILTER,
- FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
- IntPtr.Zero,
- IntPtr.Zero,
- IntPtr.Zero
- );
-
- if (Voice == IntPtr.Zero)
- {
- Logger.LogError("SoundInstance failed to initialize!");
- return;
- }
-
- InitDSPSettings(Format.nChannels);
-
- State = SoundState.Stopped;
- }
-
- public void Apply3D(AudioListener listener, AudioEmitter emitter)
- {
- Is3D = true;
-
- 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(
- Voice,
- Device.MasteringVoice,
- dspSettings.SrcChannelCount,
- dspSettings.DstChannelCount,
- dspSettings.pMatrixCoefficients,
- 0
- );
- }
-
- public unsafe void ApplyReverb(ReverbEffect reverbEffect)
- {
- ReverbSends = new FAudio.FAudioVoiceSends();
- ReverbSends.SendCount = 2;
- ReverbSends.pSends = (nint) NativeMemory.Alloc((nuint) (2 * Marshal.SizeOf()));
-
- 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;
- }
-
- public void SetPan(float targetValue)
- {
- Pan = targetValue;
- Device.ClearTweens(this, AudioTweenProperty.Pan);
- }
-
- public void SetPan(float targetValue, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
- }
-
- public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
- }
-
- public void SetPitch(float targetValue)
- {
- Pitch = targetValue;
- Device.ClearTweens(this, AudioTweenProperty.Pitch);
- }
-
- public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, 0);
- }
-
- public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, delayTime);
- }
-
- public void SetVolume(float targetValue)
- {
- Volume = targetValue;
- Device.ClearTweens(this, AudioTweenProperty.Volume);
- }
-
- public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
- }
-
- public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
- }
-
- public void SetFilterFrequency(float targetValue)
- {
- FilterFrequency = targetValue;
- Device.ClearTweens(this, AudioTweenProperty.FilterFrequency);
- }
-
- public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
- }
-
- public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
- }
-
- public void SetFilterOneOverQ(float targetValue)
- {
- FilterOneOverQ = targetValue;
- }
-
- public void SetReverb(float targetValue)
- {
- Reverb = targetValue;
- Device.ClearTweens(this, AudioTweenProperty.Reverb);
- }
-
- public void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
- }
-
- public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
- {
- Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
- }
-
- public abstract void Play();
- public abstract void QueueSyncPlay();
- public abstract void Pause();
- public abstract void Stop();
- public abstract void StopImmediate();
-
- private unsafe void InitDSPSettings(uint srcChannels)
- {
- dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
- dspSettings.DopplerFactor = 1f;
- dspSettings.SrcChannelCount = srcChannels;
- dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
-
- nuint memsize = (
- 4 *
- dspSettings.SrcChannelCount *
- dspSettings.DstChannelCount
- );
-
- dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
- byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
- for (uint i = 0; i < memsize; i += 1)
- {
- memPtr[i] = 0;
- }
-
- SetPanMatrixCoefficients();
- }
-
- private void UpdatePitch()
- {
- float doppler;
- float dopplerScale = Device.DopplerScale;
- if (!Is3D || dopplerScale == 0.0f)
- {
- doppler = 1.0f;
- }
- else
- {
- doppler = dspSettings.DopplerFactor * dopplerScale;
- }
-
- FAudio.FAudioSourceVoice_SetFrequencyRatio(
- Voice,
- (float) System.Math.Pow(2.0, pitch) * doppler,
- 0
- );
- }
-
- // 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 unsafe override void Destroy()
- {
- StopImmediate();
- FAudio.FAudioVoice_DestroyVoice(Voice);
- NativeMemory.Free((void*) dspSettings.pMatrixCoefficients);
-
- if (ReverbEffect != null)
- {
- NativeMemory.Free((void*) ReverbSends.pSends);
- }
- }
- }
-}
diff --git a/src/Audio/SoundSequence.cs b/src/Audio/SoundSequence.cs
index 12c7145..dece5df 100644
--- a/src/Audio/SoundSequence.cs
+++ b/src/Audio/SoundSequence.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace MoonWorks.Audio
{
// NOTE: all sounds played with a SoundSequence must have the same audio format!
@@ -11,17 +9,15 @@ namespace MoonWorks.Audio
public SoundSequence(AudioDevice device, Format format) : base(device, format)
{
- device.AddSoundSequenceReference(this);
- OnUpdate += Update;
+
}
public SoundSequence(AudioDevice device, StaticSound templateSound) : base(device, templateSound.Format)
{
- device.AddSoundSequenceReference(this);
- OnUpdate += Update;
+
}
- private void Update()
+ public override void Update()
{
lock (StateLock)
{
@@ -30,14 +26,7 @@ namespace MoonWorks.Audio
if (NeedSoundThreshold > 0)
{
- FAudio.FAudioSourceVoice_GetState(
- Handle,
- out var state,
- FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
- );
-
- var queuedBufferCount = state.BuffersQueued;
- for (int i = 0; i < NeedSoundThreshold - queuedBufferCount; i += 1)
+ for (int i = 0; i < NeedSoundThreshold - BuffersQueued; i += 1)
{
if (OnSoundNeeded != null)
{
@@ -59,7 +48,7 @@ namespace MoonWorks.Audio
lock (StateLock)
{
- Submit(sound);
+ Submit(sound.Buffer);
}
}
}
diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs
index 753abb4..69fe34c 100644
--- a/src/Audio/SourceVoice.cs
+++ b/src/Audio/SourceVoice.cs
@@ -45,9 +45,6 @@ namespace MoonWorks.Audio
}
}
- public delegate void OnUpdateFunc();
- public OnUpdateFunc OnUpdate; // called by AudioDevice thread
-
public SourceVoice(
AudioDevice device,
Format format
@@ -126,26 +123,6 @@ namespace MoonWorks.Audio
}
}
- ///
- /// Adds a static sound to the voice queue.
- /// The voice processes and plays back the buffers in its queue in the order that they were submitted.
- ///
- /// The sound to submit to the voice.
- /// Designates that the voice will loop the submitted buffer.
- public void Submit(StaticSound sound, bool loop = false)
- {
- if (loop)
- {
- sound.Buffer.LoopCount = FAudio.FAUDIO_LOOP_INFINITE;
- }
- else
- {
- sound.Buffer.LoopCount = 0;
- }
-
- Submit(sound.Buffer);
- }
-
///
/// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
@@ -153,31 +130,31 @@ namespace MoonWorks.Audio
/// The buffer to submit to the voice.
public void Submit(FAudio.FAudioBuffer buffer)
{
- FAudio.FAudioSourceVoice_SubmitSourceBuffer(
- Handle,
- ref buffer,
- IntPtr.Zero
- );
+ lock (StateLock)
+ {
+ FAudio.FAudioSourceVoice_SubmitSourceBuffer(
+ Handle,
+ ref buffer,
+ IntPtr.Zero
+ );
+ }
}
///
- /// Designates that this source voice will return to the voice pool once all its buffers are exhausted.
- ///
- public void ReturnWhenIdle()
- {
- Device.ReturnWhenIdle(this);
- }
-
- ///
- /// Returns this source voice to the voice pool.
+ /// Specifies that this source voice can be returned to the voice pool.
+ /// Holding on to the reference after calling this will cause problems!
///
public void Return()
{
Stop();
- Reset();
Device.Return(this);
}
+ ///
+ /// Called automatically by AudioDevice in the audio thread.
+ ///
+ public virtual void Update() { }
+
protected override unsafe void Destroy()
{
Stop();
diff --git a/src/Audio/SourceVoicePool.cs b/src/Audio/SourceVoicePool.cs
index 3299a1f..6c1ef84 100644
--- a/src/Audio/SourceVoicePool.cs
+++ b/src/Audio/SourceVoicePool.cs
@@ -6,33 +6,33 @@ namespace MoonWorks.Audio
{
private AudioDevice Device;
- Dictionary> VoiceLists = new Dictionary>();
+ Dictionary<(System.Type, Format), Queue> VoiceLists = new Dictionary<(System.Type, Format), Queue>();
public SourceVoicePool(AudioDevice device)
{
Device = device;
}
- public SourceVoice Obtain(Format format)
+ public T Obtain(Format format) where T : SourceVoice, IPoolable
{
- if (!VoiceLists.ContainsKey(format))
+ if (!VoiceLists.ContainsKey((typeof(T), format)))
{
- VoiceLists.Add(format, new Queue());
+ VoiceLists.Add((typeof(T), format), new Queue());
}
- var list = VoiceLists[format];
+ var list = VoiceLists[(typeof(T), format)];
if (list.Count == 0)
{
- list.Enqueue(new SourceVoice(Device, format));
+ list.Enqueue(T.Create(Device, format));
}
- return list.Dequeue();
+ return (T) list.Dequeue();
}
public void Return(SourceVoice voice)
{
- var list = VoiceLists[voice.Format];
+ var list = VoiceLists[(voice.GetType(), voice.Format)];
list.Enqueue(voice);
}
}
diff --git a/src/Audio/StaticVoice.cs b/src/Audio/StaticVoice.cs
new file mode 100644
index 0000000..57600df
--- /dev/null
+++ b/src/Audio/StaticVoice.cs
@@ -0,0 +1,54 @@
+namespace MoonWorks.Audio
+{
+ public class StaticVoice : SourceVoice, IPoolable
+ {
+ ///
+ /// Indicates if the voice should return to the voice pool when the voice is idle.
+ /// If you set this and then hold on to the voice reference there will be problems!
+ ///
+ public bool DeactivateWhenIdle { get; set; }
+
+ public static StaticVoice Create(AudioDevice device, Format format)
+ {
+ return new StaticVoice(device, format);
+ }
+
+ public StaticVoice(AudioDevice device, Format format) : base(device, format)
+ {
+ }
+
+ public override void Update()
+ {
+ lock (StateLock)
+ {
+ if (DeactivateWhenIdle)
+ {
+ if (BuffersQueued == 0)
+ {
+ Return();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Adds a static sound to the voice queue.
+ /// The voice processes and plays back the buffers in its queue in the order that they were submitted.
+ ///
+ /// The sound to submit to the voice.
+ /// Designates that the voice will loop the submitted buffer.
+ public void Submit(StaticSound sound, bool loop = false)
+ {
+ if (loop)
+ {
+ sound.Buffer.LoopCount = FAudio.FAUDIO_LOOP_INFINITE;
+ }
+ else
+ {
+ sound.Buffer.LoopCount = 0;
+ }
+
+ Submit(sound.Buffer);
+ }
+ }
+}
diff --git a/src/Audio/StreamingSound.cs b/src/Audio/StreamingSound.cs
deleted file mode 100644
index f38e64e..0000000
--- a/src/Audio/StreamingSound.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace MoonWorks.Audio
-{
- ///
- /// For streaming long playback.
- /// Must be extended with a decoder routine called by FillBuffer.
- /// See StreamingSoundOgg for an example.
- ///
- public abstract class StreamingSound : SoundInstance
- {
- // Are we actively consuming buffers?
- protected bool ConsumingBuffers = false;
-
- private const int BUFFER_COUNT = 3;
- private nuint BufferSize;
- private readonly IntPtr[] buffers;
- private int nextBufferIndex = 0;
- private uint queuedBufferCount = 0;
-
- private readonly object StateLock = new object();
-
- public bool AutoUpdate { get; }
-
- public abstract bool Loaded { get; }
-
- public unsafe StreamingSound(
- AudioDevice device,
- ushort formatTag,
- ushort bitsPerSample,
- ushort blockAlign,
- ushort channels,
- uint samplesPerSecond,
- uint bufferSize,
- bool autoUpdate // should the AudioDevice thread automatically update this sound?
- ) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
- {
- BufferSize = bufferSize;
-
- buffers = new IntPtr[BUFFER_COUNT];
- for (int i = 0; i < BUFFER_COUNT; i += 1)
- {
- buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize);
- }
-
- AutoUpdate = autoUpdate;
- }
-
- public override void Play()
- {
- PlayUsingOperationSet(0);
- }
-
- public override void QueueSyncPlay()
- {
- PlayUsingOperationSet(1);
- }
-
- private void PlayUsingOperationSet(uint operationSet)
- {
- lock (StateLock)
- {
- if (!Loaded)
- {
- Logger.LogError("Cannot play StreamingSound before calling Load!");
- return;
- }
-
- if (State == SoundState.Playing)
- {
- return;
- }
-
- State = SoundState.Playing;
-
- ConsumingBuffers = true;
- if (AutoUpdate)
- {
- Device.AddAutoUpdateStreamingSoundInstance(this);
- }
-
- QueueBuffers();
- FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
- }
- }
-
- public override void Pause()
- {
- lock (StateLock)
- {
- if (State == SoundState.Playing)
- {
- ConsumingBuffers = false;
- FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
- State = SoundState.Paused;
- }
- }
- }
-
- public override void Stop()
- {
- lock (StateLock)
- {
- ConsumingBuffers = false;
- State = SoundState.Stopped;
- }
- }
-
- public override void StopImmediate()
- {
- lock (StateLock)
- {
- ConsumingBuffers = false;
- FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
- FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
- ClearBuffers();
-
- State = SoundState.Stopped;
- }
- }
-
- internal unsafe void Update()
- {
- lock (StateLock)
- {
- if (!IsDisposed)
- {
- if (State != SoundState.Playing)
- {
- return;
- }
-
- QueueBuffers();
- }
- }
- }
-
- protected void QueueBuffers()
- {
- FAudio.FAudioSourceVoice_GetState(
- Voice,
- out var state,
- FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
- );
-
- queuedBufferCount = state.BuffersQueued;
-
- if (ConsumingBuffers)
- {
- for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
- {
- AddBuffer();
- }
- }
- else if (queuedBufferCount == 0)
- {
- Stop();
- }
- }
-
- protected unsafe void ClearBuffers()
- {
- nextBufferIndex = 0;
- queuedBufferCount = 0;
- }
-
- public unsafe void AddBuffer()
- {
- var buffer = buffers[nextBufferIndex];
- nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
-
- FillBuffer(
- (void*) buffer,
- (int) BufferSize,
- out int filledLengthInBytes,
- out bool reachedEnd
- );
-
- if (filledLengthInBytes > 0)
- {
- FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
- {
- AudioBytes = (uint) filledLengthInBytes,
- pAudioData = (IntPtr) buffer,
- PlayLength = (
- (uint) (filledLengthInBytes /
- Format.nChannels /
- (uint) (Format.wBitsPerSample / 8))
- )
- };
-
- FAudio.FAudioSourceVoice_SubmitSourceBuffer(
- Voice,
- ref buf,
- IntPtr.Zero
- );
-
- queuedBufferCount += 1;
- }
-
- if (reachedEnd)
- {
- /* We have reached the end of the data, what do we do? */
- ConsumingBuffers = false;
- OnReachedEnd();
- }
- }
-
- public abstract void Load();
- public abstract void Unload();
-
- protected unsafe abstract void FillBuffer(
- void* buffer,
- int bufferLengthInBytes, /* in bytes */
- out int filledLengthInBytes, /* in bytes */
- out bool reachedEnd
- );
-
- protected abstract void OnReachedEnd();
-
- protected unsafe override void Destroy()
- {
- lock (StateLock)
- {
- if (!IsDisposed)
- {
- StopImmediate();
- Unload();
-
- for (int i = 0; i < BUFFER_COUNT; i += 1)
- {
- NativeMemory.Free((void*) buffers[i]);
- }
- }
- }
- }
- }
-}
diff --git a/src/Audio/StreamingSoundOgg.cs b/src/Audio/StreamingSoundOgg.cs
deleted file mode 100644
index 3f02c87..0000000
--- a/src/Audio/StreamingSoundOgg.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-
-namespace MoonWorks.Audio
-{
- public class StreamingSoundOgg : StreamingSoundSeekable
- {
- private IntPtr FileDataPtr = IntPtr.Zero;
- private IntPtr VorbisHandle = IntPtr.Zero;
- private FAudio.stb_vorbis_info Info;
-
- public override bool Loaded => VorbisHandle != IntPtr.Zero;
- private string FilePath;
-
- public unsafe static StreamingSoundOgg Create(AudioDevice device, string filePath)
- {
- var handle = FAudio.stb_vorbis_open_filename(filePath, out int error, IntPtr.Zero);
- if (error != 0)
- {
- Logger.LogError("Error: " + error);
- throw new AudioLoadException("Error opening ogg file!");
- }
-
- var info = FAudio.stb_vorbis_get_info(handle);
-
- var streamingSound = new StreamingSoundOgg(
- device,
- filePath,
- info
- );
-
- FAudio.stb_vorbis_close(handle);
-
- return streamingSound;
- }
-
- internal unsafe StreamingSoundOgg(
- AudioDevice device,
- string filePath,
- FAudio.stb_vorbis_info info,
- uint bufferSize = 32768
- ) : base(
- device,
- 3, /* float type */
- 32, /* size of float */
- (ushort) (4 * info.channels),
- (ushort) info.channels,
- info.sample_rate,
- bufferSize,
- true
- ) {
- Info = info;
- FilePath = filePath;
- }
-
- public override void Seek(uint sampleFrame)
- {
- FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
- }
-
- public override unsafe void Load()
- {
- var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
- FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
- var fileDataSpan = new Span((void*) FileDataPtr, (int) fileStream.Length);
- fileStream.ReadExactly(fileDataSpan);
- fileStream.Close();
-
- VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
- if (error != 0)
- {
- NativeMemory.Free((void*) FileDataPtr);
- Logger.LogError("Error opening OGG file!");
- Logger.LogError("Error: " + error);
- throw new AudioLoadException("Error opening OGG file!");
- }
- }
-
- public override unsafe void Unload()
- {
- if (Loaded)
- {
- FAudio.stb_vorbis_close(VorbisHandle);
- NativeMemory.Free((void*) FileDataPtr);
-
- VorbisHandle = IntPtr.Zero;
- FileDataPtr = IntPtr.Zero;
- }
- }
-
- protected unsafe override void FillBuffer(
- void* buffer,
- int bufferLengthInBytes,
- out int filledLengthInBytes,
- out bool reachedEnd
- ) {
- var lengthInFloats = bufferLengthInBytes / sizeof(float);
-
- /* NOTE: this function returns samples per channel, not total samples */
- var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
- VorbisHandle,
- Info.channels,
- (IntPtr) buffer,
- lengthInFloats
- );
-
- var sampleCount = samples * Info.channels;
- reachedEnd = sampleCount < lengthInFloats;
- filledLengthInBytes = sampleCount * sizeof(float);
- }
- }
-}
diff --git a/src/Audio/StreamingSoundSeekable.cs b/src/Audio/StreamingSoundSeekable.cs
deleted file mode 100644
index 2bc4905..0000000
--- a/src/Audio/StreamingSoundSeekable.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-namespace MoonWorks.Audio
-{
- public abstract class StreamingSoundSeekable : StreamingSound
- {
- public bool Loop { get; set; }
-
- protected StreamingSoundSeekable(
- AudioDevice device,
- ushort formatTag,
- ushort bitsPerSample,
- ushort blockAlign,
- ushort channels,
- uint samplesPerSecond,
- uint bufferSize,
- bool autoUpdate
- ) : base(
- device,
- formatTag,
- bitsPerSample,
- blockAlign,
- channels,
- samplesPerSecond,
- bufferSize,
- autoUpdate
- ) {
-
- }
-
- public abstract void Seek(uint sampleFrame);
-
- protected override void OnReachedEnd()
- {
- if (Loop)
- {
- ConsumingBuffers = true;
- Seek(0);
- }
- }
- }
-}
diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs
index 2b1ae05..3acfbe1 100644
--- a/src/Audio/StreamingVoice.cs
+++ b/src/Audio/StreamingVoice.cs
@@ -1,8 +1,9 @@
using System;
+using System.Runtime.InteropServices;
namespace MoonWorks.Audio
{
- public abstract class StreamingVoice : SourceVoice
+ public class StreamingVoice : SourceVoice, IPoolable
{
private const int BUFFER_COUNT = 3;
private readonly IntPtr[] buffers;
@@ -11,18 +12,55 @@ namespace MoonWorks.Audio
public bool Loop { get; set; }
- public StreamingVoice(AudioDevice device, Format format, uint bufferSize) : base(device, format)
+ public AudioData AudioData { get; protected set; }
+
+ public static StreamingVoice Create(AudioDevice device, Format format)
{
- BufferSize = bufferSize;
+ return new StreamingVoice(device, format);
}
- internal unsafe void Update()
+ public unsafe StreamingVoice(AudioDevice device, Format format) : base(device, format)
+ {
+ buffers = new IntPtr[BUFFER_COUNT];
+ }
+
+ public void Load(AudioData data)
+ {
+ lock (StateLock)
+ {
+ if (AudioData != null)
+ {
+ AudioData.Unload();
+ }
+
+ data.Load();
+ AudioData = data;
+
+ InitializeBuffers();
+ QueueBuffers();
+ }
+ }
+
+ public void Unload()
+ {
+ lock (StateLock)
+ {
+ if (AudioData != null)
+ {
+ Stop();
+ AudioData.Unload();
+ AudioData = null;
+ }
+ }
+ }
+
+ public override void Update()
{
lock (StateLock)
{
if (!IsDisposed)
{
- if (State != SoundState.Playing)
+ if (AudioData == null || State != SoundState.Playing)
{
return;
}
@@ -34,8 +72,8 @@ namespace MoonWorks.Audio
protected void QueueBuffers()
{
- var buffersQueued = BuffersQueued;
- for (int i = 0; i < BUFFER_COUNT - buffersQueued; i += 1)
+ int buffersNeeded = BUFFER_COUNT - (int) BuffersQueued; // don't get got by uint underflow!
+ for (int i = 0; i < buffersNeeded; i += 1)
{
AddBuffer();
}
@@ -46,7 +84,7 @@ namespace MoonWorks.Audio
var buffer = buffers[nextBufferIndex];
nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
- FillBuffer(
+ AudioData.Decode(
(void*) buffer,
(int) BufferSize,
out int filledLengthInBytes,
@@ -74,19 +112,25 @@ namespace MoonWorks.Audio
/* We have reached the end of the data, what do we do? */
if (Loop)
{
- SeekStart();
+ AudioData.Seek(0);
AddBuffer();
}
}
}
- protected unsafe abstract void FillBuffer(
- void* buffer,
- int bufferLengthInBytes, /* in bytes */
- out int filledLengthInBytes, /* in bytes */
- out bool reachedEnd
- );
+ private unsafe void InitializeBuffers()
+ {
+ BufferSize = AudioData.DecodeBufferSize;
- protected abstract void SeekStart();
+ for (int i = 0; i < BUFFER_COUNT; i += 1)
+ {
+ if (buffers[i] != IntPtr.Zero)
+ {
+ NativeMemory.Free((void*) buffers[i]);
+ }
+
+ buffers[i] = (IntPtr) NativeMemory.Alloc(BufferSize);
+ }
+ }
}
}