From eebbaeb6ae4b5687f03d08c8565fce65be1b83c8 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 2 Aug 2023 14:10:48 -0700 Subject: [PATCH] new StreamingVoice and AudioData system --- src/Audio/AudioData.cs | 36 ++ src/Audio/AudioDataOgg.cs | 96 ++++ .../{StreamingSoundQoa.cs => AudioDataQoa.cs} | 133 ++--- src/Audio/AudioDevice.cs | 88 +-- src/Audio/IPoolable.cs | 7 + src/Audio/ReverbEffect.cs | 2 +- src/Audio/SoundInstance.cs | 508 ------------------ src/Audio/SoundSequence.cs | 21 +- src/Audio/SourceVoice.cs | 53 +- src/Audio/SourceVoicePool.cs | 16 +- src/Audio/StaticVoice.cs | 54 ++ src/Audio/StreamingSound.cs | 239 -------- src/Audio/StreamingSoundOgg.cs | 113 ---- src/Audio/StreamingSoundSeekable.cs | 40 -- src/Audio/StreamingVoice.cs | 76 ++- 15 files changed, 360 insertions(+), 1122 deletions(-) create mode 100644 src/Audio/AudioData.cs create mode 100644 src/Audio/AudioDataOgg.cs rename src/Audio/{StreamingSoundQoa.cs => AudioDataQoa.cs} (54%) create mode 100644 src/Audio/IPoolable.cs delete mode 100644 src/Audio/SoundInstance.cs create mode 100644 src/Audio/StaticVoice.cs delete mode 100644 src/Audio/StreamingSound.cs delete mode 100644 src/Audio/StreamingSoundOgg.cs delete mode 100644 src/Audio/StreamingSoundSeekable.cs 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); + } + } } }