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