diff --git a/AudioDevice.cs b/AudioDevice.cs index c7eb03e..d4acd2e 100644 --- a/AudioDevice.cs +++ b/AudioDevice.cs @@ -18,7 +18,7 @@ namespace MoonWorks.Audio internal FAudio.FAudioVoiceSends ReverbSends; - private readonly List> dynamicSoundInstances = new List>(); + private readonly List> streamingSounds = new List>(); public unsafe AudioDevice() { @@ -196,23 +196,23 @@ namespace MoonWorks.Audio public void Update() { - for (var i = dynamicSoundInstances.Count - 1; i >= 0; i--) + for (var i = streamingSounds.Count - 1; i >= 0; i--) { - var weakReference = dynamicSoundInstances[i]; - if (weakReference.TryGetTarget(out var dynamicSoundInstance)) + var weakReference = streamingSounds[i]; + if (weakReference.TryGetTarget(out var streamingSound)) { - dynamicSoundInstance.Update(); + streamingSound.Update(); } else { - dynamicSoundInstances.RemoveAt(i); + streamingSounds.RemoveAt(i); } } } - internal void AddDynamicSoundInstance(DynamicSoundInstance instance) + internal void AddDynamicSoundInstance(StreamingSound instance) { - dynamicSoundInstances.Add(new WeakReference(instance)); + streamingSounds.Add(new WeakReference(instance)); } } } diff --git a/DynamicSound.cs b/DynamicSound.cs deleted file mode 100644 index 2c0f3f6..0000000 --- a/DynamicSound.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.IO; - -namespace MoonWorks.Audio -{ - /// - /// For streaming long playback. Reads an OGG file. - /// - public class DynamicSound : Sound, IDisposable - { - internal override FAudio.FAudioWaveFormatEx Format { get; } - - // FIXME: what should this value be? - public const int BUFFER_SIZE = 1024 * 128; - - internal IntPtr FileHandle { get; } - internal FAudio.stb_vorbis_info Info { get; } - - private bool IsDisposed; - - public DynamicSound(AudioDevice device, FileInfo fileInfo) : base(device) - { - 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); - - var blockAlign = (ushort)(4 * Info.channels); - - Format = new FAudio.FAudioWaveFormatEx - { - wFormatTag = 3, - wBitsPerSample = 32, - nChannels = (ushort) Info.channels, - nBlockAlign = blockAlign, - nSamplesPerSec = Info.sample_rate, - nAvgBytesPerSec = blockAlign * Info.sample_rate, - cbSize = 0 - }; - } - - public DynamicSoundInstance CreateInstance(bool loop = false) - { - var instance = new DynamicSoundInstance(Device, this, false, loop); - Device.AddDynamicSoundInstance(instance); - return instance; - } - - 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/Sound.cs b/Sound.cs deleted file mode 100644 index d890fc3..0000000 --- a/Sound.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MoonWorks.Audio -{ - public abstract class Sound - { - internal AudioDevice Device { get; } - internal abstract FAudio.FAudioWaveFormatEx Format { get; } - - public Sound(AudioDevice device) - { - Device = device; - } - } -} diff --git a/SoundInstance.cs b/SoundInstance.cs index 441549d..42b2794 100644 --- a/SoundInstance.cs +++ b/SoundInstance.cs @@ -7,7 +7,7 @@ namespace MoonWorks.Audio { protected AudioDevice Device { get; } internal IntPtr Handle { get; } - public Sound Parent { get; } + internal FAudio.FAudioWaveFormatEx Format { get; } public bool Loop { get; } protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; @@ -118,10 +118,12 @@ namespace MoonWorks.Audio { _lowPassFilter = value; - FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters(); - p.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; - p.Frequency = _lowPassFilter; - p.OneOverQ = 1f; + FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters + { + Type = FAudio.FAudioFilterType.FAudioLowPassFilter, + Frequency = _lowPassFilter, + OneOverQ = 1f + }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, @@ -138,10 +140,12 @@ namespace MoonWorks.Audio { _highPassFilter = value; - FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters(); - p.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; - p.Frequency = _highPassFilter; - p.OneOverQ = 1f; + FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters + { + Type = FAudio.FAudioFilterType.FAudioHighPassFilter, + Frequency = _highPassFilter, + OneOverQ = 1f + }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, @@ -158,10 +162,12 @@ namespace MoonWorks.Audio { _bandPassFilter = value; - FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters(); - p.Type = FAudio.FAudioFilterType.FAudioBandPassFilter; - p.Frequency = _bandPassFilter; - p.OneOverQ = 1f; + FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters + { + Type = FAudio.FAudioFilterType.FAudioBandPassFilter, + Frequency = _bandPassFilter, + OneOverQ = 1f + }; FAudio.FAudioVoice_SetFilterParameters( Handle, ref p, @@ -172,14 +178,25 @@ namespace MoonWorks.Audio public SoundInstance( AudioDevice device, - Sound parent, + ushort channels, + uint samplesPerSecond, bool is3D, bool loop ) { Device = device; - Parent = parent; - FAudio.FAudioWaveFormatEx format = Parent.Format; + var blockAlign = (ushort)(4 * channels); + var format = new FAudio.FAudioWaveFormatEx + { + wFormatTag = 3, + wBitsPerSample = 32, + nChannels = channels, + nBlockAlign = blockAlign, + nSamplesPerSec = samplesPerSecond, + nAvgBytesPerSec = blockAlign * samplesPerSecond + }; + + Format = format; FAudio.FAudio_CreateSourceVoice( Device.Handle, @@ -200,7 +217,7 @@ namespace MoonWorks.Audio Handle = handle; this.is3D = is3D; - InitDSPSettings(Parent.Format.nChannels); + InitDSPSettings(Format.nChannels); FAudio.FAudioVoice_SetOutputVoices( handle, @@ -208,6 +225,7 @@ namespace MoonWorks.Audio ); Loop = loop; + State = SoundState.Stopped; } private void InitDSPSettings(uint srcChannels) diff --git a/StaticSound.cs b/StaticSound.cs index b716c62..664b5be 100644 --- a/StaticSound.cs +++ b/StaticSound.cs @@ -4,10 +4,12 @@ using System.Runtime.InteropServices; namespace MoonWorks.Audio { - public class StaticSound : Sound, IDisposable + public class StaticSound : IDisposable { - internal override FAudio.FAudioWaveFormatEx Format { get; } + internal AudioDevice Device { get; } internal FAudio.FAudioBuffer Handle; + public ushort Channels { get; } + public uint SamplesPerSecond { get; } public uint LoopStart { get; set; } = 0; public uint LoopLength { get; set; } = 0; @@ -37,33 +39,26 @@ namespace MoonWorks.Audio return new StaticSound( device, + (ushort) info.channels, + info.sample_rate, buffer, 0, - (uint) buffer.Length, - (ushort) info.channels, - info.sample_rate + (uint) buffer.Length ); } public StaticSound( AudioDevice device, + ushort channels, + uint samplesPerSecond, float[] buffer, uint bufferOffset, /* in floats */ - uint bufferLength, /* in floats */ - ushort channels, - uint samplesPerSecond - ) : base(device) { - var blockAlign = (ushort)(4 * channels); + uint bufferLength /* in floats */ + ) { + Device = device; - Format = new FAudio.FAudioWaveFormatEx - { - wFormatTag = 3, - wBitsPerSample = 32, - nChannels = channels, - nBlockAlign = blockAlign, - nSamplesPerSec = samplesPerSecond, - nAvgBytesPerSec = blockAlign * samplesPerSecond - }; + Channels = channels; + SamplesPerSecond = samplesPerSecond; var bufferLengthInBytes = (int) (bufferLength * sizeof(float)); Handle = new FAudio.FAudioBuffer(); diff --git a/StaticSoundInstance.cs b/StaticSoundInstance.cs index 10eb23a..c514204 100644 --- a/StaticSoundInstance.cs +++ b/StaticSoundInstance.cs @@ -4,6 +4,8 @@ namespace MoonWorks.Audio { public class StaticSoundInstance : SoundInstance { + public StaticSound Parent { get; } + private SoundState _state = SoundState.Stopped; public override SoundState State { @@ -33,15 +35,13 @@ namespace MoonWorks.Audio StaticSound parent, bool is3D, bool loop - ) : base(device, parent, is3D, loop) + ) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop) { - State = SoundState.Stopped; + Parent = parent; } public void Play() { - var parent = (StaticSound) Parent; - if (State == SoundState.Playing) { return; @@ -49,20 +49,20 @@ namespace MoonWorks.Audio if (Loop) { - parent.Handle.LoopCount = 255; - parent.Handle.LoopBegin = parent.LoopStart; - parent.Handle.LoopLength = parent.LoopLength; + Parent.Handle.LoopCount = 255; + Parent.Handle.LoopBegin = Parent.LoopStart; + Parent.Handle.LoopLength = Parent.LoopLength; } else { - 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 ); diff --git a/DynamicSoundInstance.cs b/StreamingSound.cs similarity index 63% rename from DynamicSoundInstance.cs rename to StreamingSound.cs index 78a3fc8..4fa1cf9 100644 --- a/DynamicSoundInstance.cs +++ b/StreamingSound.cs @@ -4,32 +4,26 @@ using System.Runtime.InteropServices; namespace MoonWorks.Audio { - public class DynamicSoundInstance : SoundInstance + /// + /// For streaming long playback. Reads an OGG file. + /// + public abstract class StreamingSound : SoundInstance { - private List queuedBuffers; - private List queuedSizes; + private readonly List queuedBuffers = new List(); + private readonly List queuedSizes = new List(); private const int MINIMUM_BUFFER_CHECK = 3; public int PendingBufferCount => queuedBuffers.Count; - private readonly float[] buffer; + private bool IsDisposed; - public override SoundState State { get; protected set; } - - internal DynamicSoundInstance( + public StreamingSound( AudioDevice device, - DynamicSound parent, + ushort channels, + uint samplesPerSecond, bool is3D, bool loop - ) : base(device, parent, is3D, loop) - { - queuedBuffers = new List(); - queuedSizes = new List(); - - buffer = new float[DynamicSound.BUFFER_SIZE]; - - State = SoundState.Stopped; - } + ) : base(device, channels, samplesPerSecond, is3D, loop) { } public void Play() { @@ -78,27 +72,28 @@ namespace MoonWorks.Audio ); while (PendingBufferCount > state.BuffersQueued) - lock (queuedBuffers) - { - Marshal.FreeHGlobal(queuedBuffers[0]); - queuedBuffers.RemoveAt(0); - } + lock (queuedBuffers) + { + Marshal.FreeHGlobal(queuedBuffers[0]); + queuedBuffers.RemoveAt(0); + } QueueBuffers(); } - private void QueueBuffers() + protected void QueueBuffers() { for ( int i = MINIMUM_BUFFER_CHECK - PendingBufferCount; i > 0; i -= 1 - ) { + ) + { AddBuffer(); } } - private void ClearBuffers() + protected void ClearBuffers() { lock (queuedBuffers) { @@ -111,23 +106,19 @@ namespace MoonWorks.Audio } } - private void AddBuffer() + protected void AddBuffer() { - var parent = (DynamicSound) Parent; - - /* NOTE: this function returns samples per channel, not total samples */ - var samples = FAudio.stb_vorbis_get_samples_float_interleaved( - parent.FileHandle, - parent.Info.channels, - buffer, - buffer.Length + AddBuffer( + out var buffer, + out var bufferOffset, + out var bufferLength, + out var reachedEnd ); - var sampleCount = samples * parent.Info.channels; - var lengthInBytes = (uint) sampleCount * sizeof(float); + var lengthInBytes = bufferLength * sizeof(float); IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes); - Marshal.Copy(buffer, 0, next, sampleCount); + Marshal.Copy(buffer, (int) bufferOffset, next, (int) bufferLength); lock (queuedBuffers) { @@ -140,8 +131,8 @@ namespace MoonWorks.Audio pAudioData = next, PlayLength = ( lengthInBytes / - (uint) parent.Info.channels / - (uint) (parent.Format.wBitsPerSample / 8) + Format.nChannels / + (uint)(Format.wBitsPerSample / 8) ) }; @@ -158,11 +149,11 @@ namespace MoonWorks.Audio } /* We have reached the end of the file, what do we do? */ - if (sampleCount < buffer.Length) + if (reachedEnd) { if (Loop) { - FAudio.stb_vorbis_seek_start(parent.FileHandle); + SeekStart(); } else { @@ -170,5 +161,31 @@ namespace MoonWorks.Audio } } } + + protected abstract void AddBuffer( + out float[] buffer, + out uint bufferOffset, /* in floats */ + out uint bufferLength, /* in floats */ + out bool reachedEnd + ); + + protected abstract void SeekStart(); + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // dispose managed state (managed objects) + Stop(true); + } + + // dispose unmanaged state + IsDisposed = true; + } + + base.Dispose(disposing); + } } } diff --git a/StreamingSoundOgg.cs b/StreamingSoundOgg.cs new file mode 100644 index 0000000..9bec539 --- /dev/null +++ b/StreamingSoundOgg.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; + +namespace MoonWorks.Audio +{ + public class StreamingSoundOgg : StreamingSound + { + // FIXME: what should this value be? + public const int BUFFER_SIZE = 1024 * 128; + + internal IntPtr FileHandle { get; } + internal FAudio.stb_vorbis_info Info { get; } + + private readonly float[] buffer; + + public override SoundState State { get; protected set; } + + private bool IsDisposed; + + public static StreamingSoundOgg Load( + AudioDevice device, + FileInfo fileInfo, + bool is3D = false, + bool loop = false + ) { + var 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!"); + } + + var info = FAudio.stb_vorbis_get_info(fileHandle); + + return new StreamingSoundOgg( + device, + fileHandle, + info, + is3D, + loop + ); + } + + internal StreamingSoundOgg( + AudioDevice device, + IntPtr fileHandle, + FAudio.stb_vorbis_info info, + bool is3D, + bool loop + ) : base(device, (ushort) info.channels, info.sample_rate, is3D, loop) + { + FileHandle = fileHandle; + Info = info; + buffer = new float[BUFFER_SIZE]; + + device.AddDynamicSoundInstance(this); + } + + protected override void AddBuffer( + out float[] buffer, + out uint bufferOffset, + out uint bufferLength, + out bool reachedEnd + ) { + buffer = this.buffer; + + /* NOTE: this function returns samples per channel, not total samples */ + var samples = FAudio.stb_vorbis_get_samples_float_interleaved( + FileHandle, + Info.channels, + buffer, + buffer.Length + ); + + var sampleCount = samples * Info.channels; + bufferOffset = 0; + bufferLength = (uint) sampleCount; + reachedEnd = sampleCount < buffer.Length; + } + + protected override void SeekStart() + { + FAudio.stb_vorbis_seek_start(FileHandle); + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // dispose managed state (managed objects) + } + + // dispose unmanaged state + FAudio.stb_vorbis_close(FileHandle); + + IsDisposed = true; + } + + base.Dispose(disposing); + } + } +}