forked from MoonsideGames/MoonWorks
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
|
||||
{
|
||||
public class Sound
|
||||
public abstract class Sound
|
||||
{
|
||||
internal FAudio.FAudioBuffer Handle;
|
||||
internal FAudio.FAudioWaveFormatEx Format;
|
||||
internal FAudio.FAudioWaveFormatEx Format { get; }
|
||||
|
||||
public uint LoopStart { get; set; } = 0;
|
||||
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! */
|
||||
/* NOTE: we only support float decoding! WAV sucks! */
|
||||
public Sound(
|
||||
float[] buffer,
|
||||
uint bufferOffset,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
ushort blockAlign
|
||||
uint samplesPerSecond
|
||||
) {
|
||||
var bufferLength = 4 * buffer.Length;
|
||||
var blockAlign = (ushort) (4 * channels);
|
||||
|
||||
Format = new FAudio.FAudioWaveFormatEx();
|
||||
Format.wFormatTag = 3;
|
||||
Format.wBitsPerSample = 32;
|
||||
Format.nChannels = channels;
|
||||
Format.nBlockAlign = (ushort) (4 * Format.nChannels);
|
||||
Format.nSamplesPerSec = samplesPerSecond;
|
||||
Format.nAvgBytesPerSec = Format.nBlockAlign * Format.nSamplesPerSec;
|
||||
Format.nBlockAlign = blockAlign;
|
||||
Format.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)
|
||||
);
|
||||
Format = new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
wFormatTag = 3,
|
||||
wBitsPerSample = 32,
|
||||
nChannels = channels,
|
||||
nBlockAlign = blockAlign,
|
||||
nSamplesPerSec = samplesPerSecond,
|
||||
nAvgBytesPerSec = blockAlign * samplesPerSecond,
|
||||
cbSize = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,15 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
protected AudioDevice Device { get; }
|
||||
internal IntPtr Handle { get; }
|
||||
protected Sound Parent { get; }
|
||||
public Sound Parent { get; }
|
||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
public SoundState State { get; protected set; }
|
||||
|
||||
protected bool is3D;
|
||||
|
||||
private float _pan = 0;
|
||||
private bool IsDisposed;
|
||||
|
||||
public abstract SoundState State { get; protected set; }
|
||||
|
||||
private float _pan = 0;
|
||||
public float 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;
|
||||
Parent = parent;
|
||||
|
||||
|
@ -195,7 +198,6 @@ namespace MoonWorks.Audio
|
|||
Handle = handle;
|
||||
this.is3D = is3D;
|
||||
InitDSPSettings(Parent.Format.nChannels);
|
||||
|
||||
}
|
||||
|
||||
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 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(
|
||||
AudioDevice device,
|
||||
Sound parent,
|
||||
bool is3D
|
||||
) : base(device, parent, is3D) { }
|
||||
|
||||
public void Play(bool loop = false)
|
||||
StaticSound parent,
|
||||
bool is3D,
|
||||
bool loop = false
|
||||
) : base(device, parent, is3D)
|
||||
{
|
||||
Loop = loop;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
var parent = (StaticSound) Parent;
|
||||
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (loop)
|
||||
if (Loop)
|
||||
{
|
||||
Loop = true;
|
||||
Parent.Handle.LoopCount = 255;
|
||||
Parent.Handle.LoopBegin = 0;
|
||||
Parent.Handle.LoopLength = Parent.LoopLength;
|
||||
parent.Handle.LoopCount = 255;
|
||||
parent.Handle.LoopBegin = parent.LoopStart;
|
||||
parent.Handle.LoopLength = parent.LoopLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
Loop = false;
|
||||
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
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue