refactor sound API to support generic decoding
parent
ab8a7846dd
commit
58427b8c90
|
@ -18,7 +18,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
internal FAudio.FAudioVoiceSends ReverbSends;
|
internal FAudio.FAudioVoiceSends ReverbSends;
|
||||||
|
|
||||||
private readonly List<WeakReference<DynamicSoundInstance>> dynamicSoundInstances = new List<WeakReference<DynamicSoundInstance>>();
|
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
||||||
|
|
||||||
public unsafe AudioDevice()
|
public unsafe AudioDevice()
|
||||||
{
|
{
|
||||||
|
@ -196,23 +196,23 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public void Update()
|
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];
|
var weakReference = streamingSounds[i];
|
||||||
if (weakReference.TryGetTarget(out var dynamicSoundInstance))
|
if (weakReference.TryGetTarget(out var streamingSound))
|
||||||
{
|
{
|
||||||
dynamicSoundInstance.Update();
|
streamingSound.Update();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dynamicSoundInstances.RemoveAt(i);
|
streamingSounds.RemoveAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddDynamicSoundInstance(DynamicSoundInstance instance)
|
internal void AddDynamicSoundInstance(StreamingSound instance)
|
||||||
{
|
{
|
||||||
dynamicSoundInstances.Add(new WeakReference<DynamicSoundInstance>(instance));
|
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// For streaming long playback. Reads an OGG file.
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
protected AudioDevice Device { get; }
|
protected AudioDevice Device { get; }
|
||||||
internal IntPtr Handle { get; }
|
internal IntPtr Handle { get; }
|
||||||
public Sound Parent { get; }
|
internal FAudio.FAudioWaveFormatEx Format { get; }
|
||||||
public bool Loop { get; }
|
public bool Loop { get; }
|
||||||
|
|
||||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||||
|
@ -118,10 +118,12 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
_lowPassFilter = value;
|
_lowPassFilter = value;
|
||||||
|
|
||||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||||
p.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
{
|
||||||
p.Frequency = _lowPassFilter;
|
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
|
||||||
p.OneOverQ = 1f;
|
Frequency = _lowPassFilter,
|
||||||
|
OneOverQ = 1f
|
||||||
|
};
|
||||||
FAudio.FAudioVoice_SetFilterParameters(
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
Handle,
|
Handle,
|
||||||
ref p,
|
ref p,
|
||||||
|
@ -138,10 +140,12 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
_highPassFilter = value;
|
_highPassFilter = value;
|
||||||
|
|
||||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||||
p.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
{
|
||||||
p.Frequency = _highPassFilter;
|
Type = FAudio.FAudioFilterType.FAudioHighPassFilter,
|
||||||
p.OneOverQ = 1f;
|
Frequency = _highPassFilter,
|
||||||
|
OneOverQ = 1f
|
||||||
|
};
|
||||||
FAudio.FAudioVoice_SetFilterParameters(
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
Handle,
|
Handle,
|
||||||
ref p,
|
ref p,
|
||||||
|
@ -158,10 +162,12 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
_bandPassFilter = value;
|
_bandPassFilter = value;
|
||||||
|
|
||||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters();
|
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||||
p.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
|
{
|
||||||
p.Frequency = _bandPassFilter;
|
Type = FAudio.FAudioFilterType.FAudioBandPassFilter,
|
||||||
p.OneOverQ = 1f;
|
Frequency = _bandPassFilter,
|
||||||
|
OneOverQ = 1f
|
||||||
|
};
|
||||||
FAudio.FAudioVoice_SetFilterParameters(
|
FAudio.FAudioVoice_SetFilterParameters(
|
||||||
Handle,
|
Handle,
|
||||||
ref p,
|
ref p,
|
||||||
|
@ -172,14 +178,25 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
public SoundInstance(
|
public SoundInstance(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
Sound parent,
|
ushort channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
bool is3D,
|
bool is3D,
|
||||||
bool loop
|
bool loop
|
||||||
) {
|
) {
|
||||||
Device = device;
|
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(
|
FAudio.FAudio_CreateSourceVoice(
|
||||||
Device.Handle,
|
Device.Handle,
|
||||||
|
@ -200,7 +217,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
Handle = handle;
|
Handle = handle;
|
||||||
this.is3D = is3D;
|
this.is3D = is3D;
|
||||||
InitDSPSettings(Parent.Format.nChannels);
|
InitDSPSettings(Format.nChannels);
|
||||||
|
|
||||||
FAudio.FAudioVoice_SetOutputVoices(
|
FAudio.FAudioVoice_SetOutputVoices(
|
||||||
handle,
|
handle,
|
||||||
|
@ -208,6 +225,7 @@ namespace MoonWorks.Audio
|
||||||
);
|
);
|
||||||
|
|
||||||
Loop = loop;
|
Loop = loop;
|
||||||
|
State = SoundState.Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitDSPSettings(uint srcChannels)
|
private void InitDSPSettings(uint srcChannels)
|
||||||
|
|
|
@ -4,10 +4,12 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
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;
|
internal FAudio.FAudioBuffer Handle;
|
||||||
|
public ushort Channels { get; }
|
||||||
|
public uint SamplesPerSecond { get; }
|
||||||
|
|
||||||
public uint LoopStart { get; set; } = 0;
|
public uint LoopStart { get; set; } = 0;
|
||||||
public uint LoopLength { get; set; } = 0;
|
public uint LoopLength { get; set; } = 0;
|
||||||
|
@ -37,33 +39,26 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
return new StaticSound(
|
return new StaticSound(
|
||||||
device,
|
device,
|
||||||
|
(ushort) info.channels,
|
||||||
|
info.sample_rate,
|
||||||
buffer,
|
buffer,
|
||||||
0,
|
0,
|
||||||
(uint) buffer.Length,
|
(uint) buffer.Length
|
||||||
(ushort) info.channels,
|
|
||||||
info.sample_rate
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StaticSound(
|
public StaticSound(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
|
ushort channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
float[] buffer,
|
float[] buffer,
|
||||||
uint bufferOffset, /* in floats */
|
uint bufferOffset, /* in floats */
|
||||||
uint bufferLength, /* in floats */
|
uint bufferLength /* in floats */
|
||||||
ushort channels,
|
) {
|
||||||
uint samplesPerSecond
|
Device = device;
|
||||||
) : base(device) {
|
|
||||||
var blockAlign = (ushort)(4 * channels);
|
|
||||||
|
|
||||||
Format = new FAudio.FAudioWaveFormatEx
|
Channels = channels;
|
||||||
{
|
SamplesPerSecond = samplesPerSecond;
|
||||||
wFormatTag = 3,
|
|
||||||
wBitsPerSample = 32,
|
|
||||||
nChannels = channels,
|
|
||||||
nBlockAlign = blockAlign,
|
|
||||||
nSamplesPerSec = samplesPerSecond,
|
|
||||||
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
|
||||||
};
|
|
||||||
|
|
||||||
var bufferLengthInBytes = (int) (bufferLength * sizeof(float));
|
var bufferLengthInBytes = (int) (bufferLength * sizeof(float));
|
||||||
Handle = new FAudio.FAudioBuffer();
|
Handle = new FAudio.FAudioBuffer();
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class StaticSoundInstance : SoundInstance
|
public class StaticSoundInstance : SoundInstance
|
||||||
{
|
{
|
||||||
|
public StaticSound Parent { get; }
|
||||||
|
|
||||||
private SoundState _state = SoundState.Stopped;
|
private SoundState _state = SoundState.Stopped;
|
||||||
public override SoundState State
|
public override SoundState State
|
||||||
{
|
{
|
||||||
|
@ -33,15 +35,13 @@ namespace MoonWorks.Audio
|
||||||
StaticSound parent,
|
StaticSound parent,
|
||||||
bool is3D,
|
bool is3D,
|
||||||
bool loop
|
bool loop
|
||||||
) : base(device, parent, is3D, loop)
|
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
|
||||||
{
|
{
|
||||||
State = SoundState.Stopped;
|
Parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
var parent = (StaticSound) Parent;
|
|
||||||
|
|
||||||
if (State == SoundState.Playing)
|
if (State == SoundState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -49,20 +49,20 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
parent.Handle.LoopCount = 255;
|
Parent.Handle.LoopCount = 255;
|
||||||
parent.Handle.LoopBegin = parent.LoopStart;
|
Parent.Handle.LoopBegin = Parent.LoopStart;
|
||||||
parent.Handle.LoopLength = parent.LoopLength;
|
Parent.Handle.LoopLength = Parent.LoopLength;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parent.Handle.LoopCount = 0;
|
Parent.Handle.LoopCount = 0;
|
||||||
parent.Handle.LoopBegin = 0;
|
Parent.Handle.LoopBegin = 0;
|
||||||
parent.Handle.LoopLength = 0;
|
Parent.Handle.LoopLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||||
Handle,
|
Handle,
|
||||||
ref parent.Handle,
|
ref Parent.Handle,
|
||||||
IntPtr.Zero
|
IntPtr.Zero
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,32 +4,26 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class DynamicSoundInstance : SoundInstance
|
/// <summary>
|
||||||
|
/// For streaming long playback. Reads an OGG file.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class StreamingSound : SoundInstance
|
||||||
{
|
{
|
||||||
private List<IntPtr> queuedBuffers;
|
private readonly List<IntPtr> queuedBuffers = new List<IntPtr>();
|
||||||
private List<uint> queuedSizes;
|
private readonly List<uint> queuedSizes = new List<uint>();
|
||||||
private const int MINIMUM_BUFFER_CHECK = 3;
|
private const int MINIMUM_BUFFER_CHECK = 3;
|
||||||
|
|
||||||
public int PendingBufferCount => queuedBuffers.Count;
|
public int PendingBufferCount => queuedBuffers.Count;
|
||||||
|
|
||||||
private readonly float[] buffer;
|
private bool IsDisposed;
|
||||||
|
|
||||||
public override SoundState State { get; protected set; }
|
public StreamingSound(
|
||||||
|
|
||||||
internal DynamicSoundInstance(
|
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
DynamicSound parent,
|
ushort channels,
|
||||||
|
uint samplesPerSecond,
|
||||||
bool is3D,
|
bool is3D,
|
||||||
bool loop
|
bool loop
|
||||||
) : base(device, parent, is3D, loop)
|
) : base(device, channels, samplesPerSecond, is3D, loop) { }
|
||||||
{
|
|
||||||
queuedBuffers = new List<IntPtr>();
|
|
||||||
queuedSizes = new List<uint>();
|
|
||||||
|
|
||||||
buffer = new float[DynamicSound.BUFFER_SIZE];
|
|
||||||
|
|
||||||
State = SoundState.Stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
|
@ -87,18 +81,19 @@ namespace MoonWorks.Audio
|
||||||
QueueBuffers();
|
QueueBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueBuffers()
|
protected void QueueBuffers()
|
||||||
{
|
{
|
||||||
for (
|
for (
|
||||||
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
|
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
|
||||||
i > 0;
|
i > 0;
|
||||||
i -= 1
|
i -= 1
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
AddBuffer();
|
AddBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearBuffers()
|
protected void ClearBuffers()
|
||||||
{
|
{
|
||||||
lock (queuedBuffers)
|
lock (queuedBuffers)
|
||||||
{
|
{
|
||||||
|
@ -111,23 +106,19 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddBuffer()
|
protected void AddBuffer()
|
||||||
{
|
{
|
||||||
var parent = (DynamicSound) Parent;
|
AddBuffer(
|
||||||
|
out var buffer,
|
||||||
/* NOTE: this function returns samples per channel, not total samples */
|
out var bufferOffset,
|
||||||
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
|
out var bufferLength,
|
||||||
parent.FileHandle,
|
out var reachedEnd
|
||||||
parent.Info.channels,
|
|
||||||
buffer,
|
|
||||||
buffer.Length
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var sampleCount = samples * parent.Info.channels;
|
var lengthInBytes = bufferLength * sizeof(float);
|
||||||
var lengthInBytes = (uint) sampleCount * sizeof(float);
|
|
||||||
|
|
||||||
IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes);
|
IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes);
|
||||||
Marshal.Copy(buffer, 0, next, sampleCount);
|
Marshal.Copy(buffer, (int) bufferOffset, next, (int) bufferLength);
|
||||||
|
|
||||||
lock (queuedBuffers)
|
lock (queuedBuffers)
|
||||||
{
|
{
|
||||||
|
@ -140,8 +131,8 @@ namespace MoonWorks.Audio
|
||||||
pAudioData = next,
|
pAudioData = next,
|
||||||
PlayLength = (
|
PlayLength = (
|
||||||
lengthInBytes /
|
lengthInBytes /
|
||||||
(uint) parent.Info.channels /
|
Format.nChannels /
|
||||||
(uint) (parent.Format.wBitsPerSample / 8)
|
(uint)(Format.wBitsPerSample / 8)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,11 +149,11 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We have reached the end of the file, what do we do? */
|
/* We have reached the end of the file, what do we do? */
|
||||||
if (sampleCount < buffer.Length)
|
if (reachedEnd)
|
||||||
{
|
{
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
FAudio.stb_vorbis_seek_start(parent.FileHandle);
|
SeekStart();
|
||||||
}
|
}
|
||||||
else
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue