Sound instancing rework
parent
537517afb9
commit
5df08727c1
|
@ -1 +1 @@
|
||||||
Subproject commit aaf2568c3e5b202c5cfbd74734386e69f204482c
|
Subproject commit 63071f2c309f6fc2193de1c6b85da0e31df80040
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MoonWorks.Audio
|
namespace MoonWorks.Audio
|
||||||
|
@ -27,7 +28,8 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
||||||
private readonly HashSet<WeakReference> autoUpdateStreamingSoundReferences = new HashSet<WeakReference>();
|
private readonly List<WeakReference> autoUpdateStreamingSoundReferences = new List<WeakReference>();
|
||||||
|
private readonly List<WeakReference> autoFreeStaticSoundInstanceReferences = new List<WeakReference>();
|
||||||
|
|
||||||
private AudioTweenManager AudioTweenManager;
|
private AudioTweenManager AudioTweenManager;
|
||||||
|
|
||||||
|
@ -150,15 +152,35 @@ namespace MoonWorks.Audio
|
||||||
previousTickTime = TickStopwatch.Elapsed.Ticks;
|
previousTickTime = TickStopwatch.Elapsed.Ticks;
|
||||||
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
|
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
|
||||||
|
|
||||||
foreach (var weakReference in autoUpdateStreamingSoundReferences)
|
for (var i = autoUpdateStreamingSoundReferences.Count - 1; i >= 0; i -= 1)
|
||||||
{
|
{
|
||||||
if (weakReference.Target is StreamingSound streamingSound)
|
var weakReference = autoUpdateStreamingSoundReferences[i];
|
||||||
|
|
||||||
|
if (weakReference.Target is StreamingSound streamingSound && streamingSound.Loaded)
|
||||||
{
|
{
|
||||||
streamingSound.Update();
|
streamingSound.Update();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
autoUpdateStreamingSoundReferences.Remove(weakReference);
|
autoUpdateStreamingSoundReferences.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = autoFreeStaticSoundInstanceReferences.Count - 1; i >= 0; i -= 1)
|
||||||
|
{
|
||||||
|
var weakReference = autoFreeStaticSoundInstanceReferences[i];
|
||||||
|
|
||||||
|
if (weakReference.Target is StaticSoundInstance staticSoundInstance)
|
||||||
|
{
|
||||||
|
if (staticSoundInstance.State == SoundState.Stopped)
|
||||||
|
{
|
||||||
|
staticSoundInstance.Free();
|
||||||
|
autoFreeStaticSoundInstanceReferences.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
autoFreeStaticSoundInstanceReferences.RemoveAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,11 +235,6 @@ namespace MoonWorks.Audio
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
resources.Add(resource.weakReference);
|
resources.Add(resource.weakReference);
|
||||||
|
|
||||||
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
|
||||||
{
|
|
||||||
AddAutoUpdateStreamingSoundInstance(streamingSound);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,22 +243,17 @@ namespace MoonWorks.Audio
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
resources.Remove(resource.weakReference);
|
resources.Remove(resource.weakReference);
|
||||||
|
|
||||||
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
|
||||||
{
|
|
||||||
RemoveAutoUpdateStreamingSoundInstance(streamingSound);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
internal void AddAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
||||||
{
|
{
|
||||||
autoUpdateStreamingSoundReferences.Add(instance.weakReference);
|
autoUpdateStreamingSoundReferences.Add(instance.weakReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
internal void AddAutoFreeStaticSoundInstance(StaticSoundInstance instance)
|
||||||
{
|
{
|
||||||
autoUpdateStreamingSoundReferences.Remove(instance.weakReference);
|
autoFreeStaticSoundInstanceReferences.Add(instance.weakReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace MoonWorks.Audio
|
||||||
if (weakReference != null)
|
if (weakReference != null)
|
||||||
{
|
{
|
||||||
Device.RemoveResourceReference(this);
|
Device.RemoveResourceReference(this);
|
||||||
|
weakReference.Target = null;
|
||||||
weakReference = null;
|
weakReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ namespace MoonWorks.Audio
|
||||||
public uint LoopStart { get; set; } = 0;
|
public uint LoopStart { get; set; } = 0;
|
||||||
public uint LoopLength { get; set; } = 0;
|
public uint LoopLength { get; set; } = 0;
|
||||||
|
|
||||||
private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
|
private Stack<StaticSoundInstance> AvailableInstances = new Stack<StaticSoundInstance>();
|
||||||
|
private HashSet<StaticSoundInstance> UsedInstances = new HashSet<StaticSoundInstance>();
|
||||||
|
|
||||||
private bool OwnsBuffer;
|
private bool OwnsBuffer;
|
||||||
|
|
||||||
|
@ -267,22 +268,25 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a sound instance from the pool.
|
/// Gets a sound instance from the pool.
|
||||||
/// NOTE: If you lose track of instances, you will create garbage collection pressure!
|
/// NOTE: If AutoFree is false, you will have to call StaticSoundInstance.Free() yourself or leak the instance!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StaticSoundInstance GetInstance()
|
public StaticSoundInstance GetInstance(bool autoFree = true)
|
||||||
{
|
{
|
||||||
if (Instances.Count == 0)
|
if (AvailableInstances.Count == 0)
|
||||||
{
|
{
|
||||||
Instances.Push(new StaticSoundInstance(Device, this));
|
AvailableInstances.Push(new StaticSoundInstance(Device, this, autoFree));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Instances.Pop();
|
var instance = AvailableInstances.Pop();
|
||||||
|
UsedInstances.Add(instance);
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void FreeInstance(StaticSoundInstance instance)
|
internal void FreeInstance(StaticSoundInstance instance)
|
||||||
{
|
{
|
||||||
instance.Reset();
|
instance.Reset();
|
||||||
Instances.Push(instance);
|
UsedInstances.Remove(instance);
|
||||||
|
AvailableInstances.Push(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void Destroy()
|
protected override unsafe void Destroy()
|
||||||
|
|
|
@ -32,12 +32,21 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AutoFree { get; }
|
||||||
|
|
||||||
internal StaticSoundInstance(
|
internal StaticSoundInstance(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
StaticSound parent
|
StaticSound parent,
|
||||||
|
bool autoFree
|
||||||
) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
|
) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
|
AutoFree = autoFree;
|
||||||
|
|
||||||
|
if (AutoFree)
|
||||||
|
{
|
||||||
|
device.AddAutoFreeStaticSoundInstance(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Play()
|
public override void Play()
|
||||||
|
@ -113,9 +122,11 @@ namespace MoonWorks.Audio
|
||||||
Parent.Handle.PlayBegin = sampleFrame;
|
Parent.Handle.PlayBegin = sampleFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call this when you no longer need the sound instance.
|
||||||
|
// If AutoFree is set, this will automatically be called when the sound instance stops playing.
|
||||||
|
// If the sound isn't stopped when you call this, things might get weird!
|
||||||
public void Free()
|
public void Free()
|
||||||
{
|
{
|
||||||
StopImmediate();
|
|
||||||
Parent.FreeInstance(this);
|
Parent.FreeInstance(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,6 @@ namespace MoonWorks.Audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class StreamingSound : SoundInstance
|
public abstract class StreamingSound : SoundInstance
|
||||||
{
|
{
|
||||||
// Should the AudioDevice thread automatically update this class?
|
|
||||||
public abstract bool AutoUpdate { get; }
|
|
||||||
|
|
||||||
// Are we actively consuming buffers?
|
// Are we actively consuming buffers?
|
||||||
protected bool ConsumingBuffers = false;
|
protected bool ConsumingBuffers = false;
|
||||||
|
|
||||||
|
@ -24,6 +21,10 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
private readonly object StateLock = new object();
|
private readonly object StateLock = new object();
|
||||||
|
|
||||||
|
public bool AutoUpdate { get; }
|
||||||
|
|
||||||
|
public abstract bool Loaded { get; }
|
||||||
|
|
||||||
public unsafe StreamingSound(
|
public unsafe StreamingSound(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
ushort formatTag,
|
ushort formatTag,
|
||||||
|
@ -31,7 +32,8 @@ namespace MoonWorks.Audio
|
||||||
ushort blockAlign,
|
ushort blockAlign,
|
||||||
ushort channels,
|
ushort channels,
|
||||||
uint samplesPerSecond,
|
uint samplesPerSecond,
|
||||||
uint bufferSize
|
uint bufferSize,
|
||||||
|
bool autoUpdate // should the AudioDevice thread automatically update this sound?
|
||||||
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||||
{
|
{
|
||||||
BufferSize = bufferSize;
|
BufferSize = bufferSize;
|
||||||
|
@ -41,6 +43,8 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize);
|
buffers[i] = (IntPtr) NativeMemory.Alloc(bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AutoUpdate = autoUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Play()
|
public override void Play()
|
||||||
|
@ -57,6 +61,12 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
|
if (!Loaded)
|
||||||
|
{
|
||||||
|
Logger.LogError("Cannot play StreamingSound before calling Load!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (State == SoundState.Playing)
|
if (State == SoundState.Playing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -65,6 +75,11 @@ namespace MoonWorks.Audio
|
||||||
State = SoundState.Playing;
|
State = SoundState.Playing;
|
||||||
|
|
||||||
ConsumingBuffers = true;
|
ConsumingBuffers = true;
|
||||||
|
if (AutoUpdate)
|
||||||
|
{
|
||||||
|
Device.AddAutoUpdateStreamingSoundInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
QueueBuffers();
|
QueueBuffers();
|
||||||
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
||||||
}
|
}
|
||||||
|
@ -192,6 +207,9 @@ namespace MoonWorks.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void Load();
|
||||||
|
public abstract void Unload();
|
||||||
|
|
||||||
protected unsafe abstract void FillBuffer(
|
protected unsafe abstract void FillBuffer(
|
||||||
void* buffer,
|
void* buffer,
|
||||||
int bufferLengthInBytes, /* in bytes */
|
int bufferLengthInBytes, /* in bytes */
|
||||||
|
@ -208,6 +226,7 @@ namespace MoonWorks.Audio
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
StopImmediate();
|
StopImmediate();
|
||||||
|
Unload();
|
||||||
|
|
||||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,41 +6,38 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class StreamingSoundOgg : StreamingSoundSeekable
|
public class StreamingSoundOgg : StreamingSoundSeekable
|
||||||
{
|
{
|
||||||
private IntPtr VorbisHandle;
|
private IntPtr FileDataPtr = IntPtr.Zero;
|
||||||
private IntPtr FileDataPtr;
|
private IntPtr VorbisHandle = IntPtr.Zero;
|
||||||
private FAudio.stb_vorbis_info Info;
|
private FAudio.stb_vorbis_info Info;
|
||||||
public override bool AutoUpdate => true;
|
|
||||||
|
|
||||||
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
public override bool Loaded => VorbisHandle != IntPtr.Zero;
|
||||||
|
private string FilePath;
|
||||||
|
|
||||||
|
public unsafe static StreamingSoundOgg Create(AudioDevice device, string filePath)
|
||||||
{
|
{
|
||||||
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
var handle = FAudio.stb_vorbis_open_filename(filePath, out int error, IntPtr.Zero);
|
||||||
var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
|
|
||||||
var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
|
|
||||||
fileStream.ReadExactly(fileDataSpan);
|
|
||||||
fileStream.Close();
|
|
||||||
|
|
||||||
var vorbisHandle = FAudio.stb_vorbis_open_memory((IntPtr) fileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
|
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
{
|
{
|
||||||
NativeMemory.Free(fileDataPtr);
|
|
||||||
Logger.LogError("Error opening OGG file!");
|
|
||||||
Logger.LogError("Error: " + error);
|
Logger.LogError("Error: " + error);
|
||||||
throw new AudioLoadException("Error opening OGG file!");
|
throw new AudioLoadException("Error opening ogg file!");
|
||||||
}
|
}
|
||||||
var info = FAudio.stb_vorbis_get_info(vorbisHandle);
|
|
||||||
|
|
||||||
return new StreamingSoundOgg(
|
var info = FAudio.stb_vorbis_get_info(handle);
|
||||||
|
|
||||||
|
var streamingSound = new StreamingSoundOgg(
|
||||||
device,
|
device,
|
||||||
(IntPtr) fileDataPtr,
|
filePath,
|
||||||
vorbisHandle,
|
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FAudio.stb_vorbis_close(handle);
|
||||||
|
|
||||||
|
return streamingSound;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe StreamingSoundOgg(
|
internal unsafe StreamingSoundOgg(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
string filePath,
|
||||||
IntPtr vorbisHandle,
|
|
||||||
FAudio.stb_vorbis_info info,
|
FAudio.stb_vorbis_info info,
|
||||||
uint bufferSize = 32768
|
uint bufferSize = 32768
|
||||||
) : base(
|
) : base(
|
||||||
|
@ -50,11 +47,11 @@ namespace MoonWorks.Audio
|
||||||
(ushort) (4 * info.channels),
|
(ushort) (4 * info.channels),
|
||||||
(ushort) info.channels,
|
(ushort) info.channels,
|
||||||
info.sample_rate,
|
info.sample_rate,
|
||||||
bufferSize
|
bufferSize,
|
||||||
|
true
|
||||||
) {
|
) {
|
||||||
FileDataPtr = fileDataPtr;
|
|
||||||
VorbisHandle = vorbisHandle;
|
|
||||||
Info = info;
|
Info = info;
|
||||||
|
FilePath = filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Seek(uint sampleFrame)
|
public override void Seek(uint sampleFrame)
|
||||||
|
@ -62,6 +59,36 @@ namespace MoonWorks.Audio
|
||||||
FAudio.stb_vorbis_seek(VorbisHandle, 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<byte>((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(
|
protected unsafe override void FillBuffer(
|
||||||
void* buffer,
|
void* buffer,
|
||||||
int bufferLengthInBytes,
|
int bufferLengthInBytes,
|
||||||
|
@ -82,16 +109,5 @@ namespace MoonWorks.Audio
|
||||||
reachedEnd = sampleCount < lengthInFloats;
|
reachedEnd = sampleCount < lengthInFloats;
|
||||||
filledLengthInBytes = sampleCount * sizeof(float);
|
filledLengthInBytes = sampleCount * sizeof(float);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected unsafe override void Destroy()
|
|
||||||
{
|
|
||||||
base.Destroy();
|
|
||||||
|
|
||||||
if (!IsDisposed)
|
|
||||||
{
|
|
||||||
FAudio.stb_vorbis_close(VorbisHandle);
|
|
||||||
NativeMemory.Free((void*) FileDataPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,39 +6,31 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
public class StreamingSoundQoa : StreamingSoundSeekable
|
public class StreamingSoundQoa : StreamingSoundSeekable
|
||||||
{
|
{
|
||||||
private IntPtr QoaHandle;
|
private IntPtr QoaHandle = IntPtr.Zero;
|
||||||
private IntPtr FileDataPtr;
|
private IntPtr FileDataPtr = IntPtr.Zero;
|
||||||
|
|
||||||
public override bool AutoUpdate => true;
|
|
||||||
|
|
||||||
uint Channels;
|
uint Channels;
|
||||||
uint SamplesPerChannelPerFrame;
|
uint SamplesPerChannelPerFrame;
|
||||||
uint TotalSamplesPerChannel;
|
uint TotalSamplesPerChannel;
|
||||||
|
|
||||||
public unsafe static StreamingSoundQoa Load(AudioDevice device, string filePath)
|
public override bool Loaded => QoaHandle != IntPtr.Zero;
|
||||||
{
|
private string FilePath;
|
||||||
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
|
||||||
var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
|
|
||||||
var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
|
|
||||||
fileStream.ReadExactly(fileDataSpan);
|
|
||||||
fileStream.Close();
|
|
||||||
|
|
||||||
var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
|
public unsafe static StreamingSoundQoa Create(AudioDevice device, string filePath)
|
||||||
if (qoaHandle == 0)
|
{
|
||||||
|
var handle = FAudio.qoa_open_from_filename(filePath);
|
||||||
|
if (handle == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
NativeMemory.Free(fileDataPtr);
|
|
||||||
Logger.LogError("Error opening QOA file!");
|
|
||||||
throw new AudioLoadException("Error opening QOA file!");
|
throw new AudioLoadException("Error opening QOA file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
FAudio.qoa_attributes(qoaHandle, out var channels, out var sampleRate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel);
|
FAudio.qoa_attributes(handle, out var channels, out var samplerate, out var samplesPerChannelPerFrame, out var totalSamplesPerChannel);
|
||||||
|
|
||||||
return new StreamingSoundQoa(
|
return new StreamingSoundQoa(
|
||||||
device,
|
device,
|
||||||
(IntPtr) fileDataPtr,
|
filePath,
|
||||||
qoaHandle,
|
|
||||||
channels,
|
channels,
|
||||||
sampleRate,
|
samplerate,
|
||||||
samplesPerChannelPerFrame,
|
samplesPerChannelPerFrame,
|
||||||
totalSamplesPerChannel
|
totalSamplesPerChannel
|
||||||
);
|
);
|
||||||
|
@ -46,8 +38,7 @@ namespace MoonWorks.Audio
|
||||||
|
|
||||||
internal unsafe StreamingSoundQoa(
|
internal unsafe StreamingSoundQoa(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
string filePath,
|
||||||
IntPtr qoaHandle,
|
|
||||||
uint channels,
|
uint channels,
|
||||||
uint samplesPerSecond,
|
uint samplesPerSecond,
|
||||||
uint samplesPerChannelPerFrame,
|
uint samplesPerChannelPerFrame,
|
||||||
|
@ -59,13 +50,13 @@ namespace MoonWorks.Audio
|
||||||
(ushort) (2 * channels),
|
(ushort) (2 * channels),
|
||||||
(ushort) channels,
|
(ushort) channels,
|
||||||
samplesPerSecond,
|
samplesPerSecond,
|
||||||
samplesPerChannelPerFrame * channels * sizeof(short)
|
samplesPerChannelPerFrame * channels * sizeof(short),
|
||||||
|
true
|
||||||
) {
|
) {
|
||||||
FileDataPtr = fileDataPtr;
|
|
||||||
QoaHandle = qoaHandle;
|
|
||||||
Channels = channels;
|
Channels = channels;
|
||||||
SamplesPerChannelPerFrame = samplesPerChannelPerFrame;
|
SamplesPerChannelPerFrame = samplesPerChannelPerFrame;
|
||||||
TotalSamplesPerChannel = totalSamplesPerChannel;
|
TotalSamplesPerChannel = totalSamplesPerChannel;
|
||||||
|
FilePath = filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Seek(uint sampleFrame)
|
public override void Seek(uint sampleFrame)
|
||||||
|
@ -73,6 +64,35 @@ namespace MoonWorks.Audio
|
||||||
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
|
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<byte>((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)
|
||||||
|
{
|
||||||
|
FAudio.qoa_close(QoaHandle);
|
||||||
|
NativeMemory.Free((void*) FileDataPtr);
|
||||||
|
|
||||||
|
QoaHandle = IntPtr.Zero;
|
||||||
|
FileDataPtr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override unsafe void FillBuffer(
|
protected override unsafe void FillBuffer(
|
||||||
void* buffer,
|
void* buffer,
|
||||||
int bufferLengthInBytes,
|
int bufferLengthInBytes,
|
||||||
|
@ -88,16 +108,5 @@ namespace MoonWorks.Audio
|
||||||
reachedEnd = sampleCount < lengthInShorts;
|
reachedEnd = sampleCount < lengthInShorts;
|
||||||
filledLengthInBytes = (int) (sampleCount * sizeof(short));
|
filledLengthInBytes = (int) (sampleCount * sizeof(short));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void Destroy()
|
|
||||||
{
|
|
||||||
base.Destroy();
|
|
||||||
|
|
||||||
if (!IsDisposed)
|
|
||||||
{
|
|
||||||
FAudio.qoa_close(QoaHandle);
|
|
||||||
NativeMemory.Free((void*) FileDataPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ namespace MoonWorks.Audio
|
||||||
ushort blockAlign,
|
ushort blockAlign,
|
||||||
ushort channels,
|
ushort channels,
|
||||||
uint samplesPerSecond,
|
uint samplesPerSecond,
|
||||||
uint bufferSize
|
uint bufferSize,
|
||||||
|
bool autoUpdate
|
||||||
) : base(
|
) : base(
|
||||||
device,
|
device,
|
||||||
formatTag,
|
formatTag,
|
||||||
|
@ -19,7 +20,8 @@ namespace MoonWorks.Audio
|
||||||
blockAlign,
|
blockAlign,
|
||||||
channels,
|
channels,
|
||||||
samplesPerSecond,
|
samplesPerSecond,
|
||||||
bufferSize
|
bufferSize,
|
||||||
|
autoUpdate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
QueueDestroyFunction(Device.Handle, Handle);
|
QueueDestroyFunction(Device.Handle, Handle);
|
||||||
Device.RemoveResourceReference(weakReference);
|
Device.RemoveResourceReference(weakReference);
|
||||||
|
weakReference.SetTarget(null);
|
||||||
weakReference = null;
|
weakReference = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@ using MoonWorks.Audio;
|
||||||
|
|
||||||
namespace MoonWorks.Video
|
namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
public unsafe class StreamingSoundTheora : StreamingSound
|
// TODO: should we just not handle theora sound? it sucks!
|
||||||
|
internal unsafe class StreamingSoundTheora : StreamingSound
|
||||||
{
|
{
|
||||||
private IntPtr VideoHandle;
|
private IntPtr VideoHandle;
|
||||||
|
public override bool Loaded => true;
|
||||||
// Theorafile is not thread safe, so let's update on the main thread.
|
|
||||||
public override bool AutoUpdate => false;
|
|
||||||
|
|
||||||
internal StreamingSoundTheora(
|
internal StreamingSoundTheora(
|
||||||
AudioDevice device,
|
AudioDevice device,
|
||||||
|
@ -23,11 +22,22 @@ namespace MoonWorks.Video
|
||||||
(ushort) (4 * channels),
|
(ushort) (4 * channels),
|
||||||
(ushort) channels,
|
(ushort) channels,
|
||||||
sampleRate,
|
sampleRate,
|
||||||
bufferSize
|
bufferSize,
|
||||||
|
false // Theorafile is not thread safe, so let's update on the main thread
|
||||||
) {
|
) {
|
||||||
VideoHandle = videoHandle;
|
VideoHandle = videoHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override unsafe void Load()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe void Unload()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
protected override unsafe void FillBuffer(
|
protected override unsafe void FillBuffer(
|
||||||
void* buffer,
|
void* buffer,
|
||||||
int bufferLengthInBytes,
|
int bufferLengthInBytes,
|
||||||
|
|
Loading…
Reference in New Issue