Wander/FNA/src/Audio/DynamicSoundEffectInstance.cs

418 lines
8.6 KiB
C#

#region License
/* FNA - XNA4 Reimplementation for Desktop Platforms
* Copyright 2009-2018 Ethan Lee and the MonoGame Team
*
* Released under the Microsoft Public License.
* See LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
#endregion
namespace Microsoft.Xna.Framework.Audio
{
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.audio.dynamicsoundeffectinstance.aspx
public sealed class DynamicSoundEffectInstance : SoundEffectInstance
{
#region Public Properties
public int PendingBufferCount
{
get;
private set;
}
public override bool IsLooped
{
get
{
return false;
}
set
{
// No-op, DynamicSoundEffectInstance cannot be looped!
}
}
#endregion
#region Private XNA Variables
private int sampleRate;
private AudioChannels channels;
private const int MINIMUM_BUFFER_CHECK = 3;
#endregion
#region Private AL Variables
private Queue<IALBuffer> queuedBuffers;
private Queue<IALBuffer> buffersToQueue;
private Queue<IALBuffer> availableBuffers;
#endregion
#region BufferNeeded Event
public event EventHandler<EventArgs> BufferNeeded;
#endregion
#region Public Constructor
public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) : base(null)
{
this.sampleRate = sampleRate;
this.channels = channels;
PendingBufferCount = 0;
isDynamic = true;
queuedBuffers = new Queue<IALBuffer>();
buffersToQueue = new Queue<IALBuffer>();
availableBuffers = new Queue<IALBuffer>();
}
#endregion
#region Destructor
~DynamicSoundEffectInstance()
{
Dispose();
}
#endregion
#region Public Dispose Method
public override void Dispose()
{
if (!IsDisposed)
{
base.Dispose(); // Will call Stop(true);
// Delete all known buffer objects
while (queuedBuffers.Count > 0)
{
AudioDevice.ALDevice.DeleteBuffer(queuedBuffers.Dequeue());
}
queuedBuffers = null;
while (availableBuffers.Count > 0)
{
AudioDevice.ALDevice.DeleteBuffer(availableBuffers.Dequeue());
}
availableBuffers = null;
while (buffersToQueue.Count > 0)
{
AudioDevice.ALDevice.DeleteBuffer(buffersToQueue.Dequeue());
}
buffersToQueue = null;
IsDisposed = true;
}
}
#endregion
#region Public Time/Sample Information Methods
public TimeSpan GetSampleDuration(int sizeInBytes)
{
return SoundEffect.GetSampleDuration(
sizeInBytes,
sampleRate,
channels
);
}
public int GetSampleSizeInBytes(TimeSpan duration)
{
return SoundEffect.GetSampleSizeInBytes(
duration,
sampleRate,
channels
);
}
#endregion
#region Public SubmitBuffer Methods
public void SubmitBuffer(byte[] buffer)
{
this.SubmitBuffer(buffer, 0, buffer.Length);
}
public void SubmitBuffer(byte[] buffer, int offset, int count)
{
// Generate a buffer if we don't have any to use.
if (availableBuffers.Count == 0)
{
availableBuffers.Enqueue(
AudioDevice.ALDevice.GenBuffer(sampleRate, channels)
);
}
// Push buffer to the AL.
IALBuffer newBuf = availableBuffers.Dequeue();
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
AudioDevice.ALDevice.SetBufferData(
newBuf,
handle.AddrOfPinnedObject(),
offset,
count
);
handle.Free();
// If we're already playing, queue immediately.
if (INTERNAL_alSource != null)
{
AudioDevice.ALDevice.QueueSourceBuffer(
INTERNAL_alSource,
newBuf
);
queuedBuffers.Enqueue(newBuf);
// If the source stopped, reboot it now.
if (AudioDevice.ALDevice.GetSourceState(INTERNAL_alSource) == SoundState.Stopped)
{
AudioDevice.ALDevice.PlaySource(INTERNAL_alSource);
}
}
else
{
buffersToQueue.Enqueue(newBuf);
}
PendingBufferCount += 1;
}
#endregion
#region Public Play Method
public override void Play()
{
Play(true);
}
#endregion
#region Internal Play Method
internal void Play(bool isManaged)
{
// No-op if we're already playing.
if (State != SoundState.Stopped)
{
if (State == SoundState.Paused)
{
// ... but be sure pause/resume still works
Resume();
}
return;
}
if (INTERNAL_alSource != null)
{
// The sound has stopped, but hasn't cleaned up yet...
AudioDevice.ALDevice.StopAndDisposeSource(INTERNAL_alSource);
INTERNAL_alSource = null;
}
while (queuedBuffers.Count > 0)
{
availableBuffers.Enqueue(queuedBuffers.Dequeue());
PendingBufferCount -= 1;
}
if (AudioDevice.ALDevice == null)
{
throw new NoAudioHardwareException();
}
INTERNAL_alSource = AudioDevice.ALDevice.GenSource();
if (INTERNAL_alSource == null)
{
FNALoggerEXT.LogWarn("AL SOURCE WAS NOT AVAILABLE, SKIPPING.");
return;
}
// Queue the buffers to this source
while (buffersToQueue.Count > 0)
{
IALBuffer nextBuf = buffersToQueue.Dequeue();
queuedBuffers.Enqueue(nextBuf);
AudioDevice.ALDevice.QueueSourceBuffer(INTERNAL_alSource, nextBuf);
}
// Apply Pan/Position
if (INTERNAL_positionalAudio)
{
INTERNAL_positionalAudio = false;
AudioDevice.ALDevice.SetSourcePosition(
INTERNAL_alSource,
position
);
}
else
{
Pan = Pan;
}
// Reassign Properties, in case the AL properties need to be applied.
Volume = Volume;
Pitch = Pitch;
// ... but wait! What if we need moar buffers?
for (
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
(i > 0) && BufferNeeded != null;
i -= 1
) {
BufferNeeded(this, null);
}
// Finally.
AudioDevice.ALDevice.PlaySource(INTERNAL_alSource);
if (isManaged)
{
AudioDevice.DynamicInstancePool.Add(this);
}
}
#endregion
#region Internal Update Method
internal void Update()
{
// Get the number of processed buffers.
int finishedBuffers = AudioDevice.ALDevice.CheckProcessedBuffers(
INTERNAL_alSource
);
if (finishedBuffers == 0)
{
// Nothing to do... yet.
return;
}
// Dequeue the processed buffers, error checking as needed.
AudioDevice.ALDevice.DequeueSourceBuffers(
INTERNAL_alSource,
finishedBuffers,
queuedBuffers
);
// The processed buffers are now available.
for (int i = 0; i < finishedBuffers; i += 1)
{
availableBuffers.Enqueue(queuedBuffers.Dequeue());
}
// PendingBufferCount changed during playback, trigger now!
PendingBufferCount -= finishedBuffers;
if (BufferNeeded != null)
{
BufferNeeded(this, null);
}
// Do we need even moar buffers?
for (
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
(i > 0) && BufferNeeded != null;
i -= 1
) {
BufferNeeded(this, null);
}
}
#endregion
#region Internal Sample Data Retrieval Method
internal void GetSamples(float[] samples)
{
if (INTERNAL_alSource != null && queuedBuffers.Count > 0)
{
GCHandle handle = GCHandle.Alloc(samples, GCHandleType.Pinned);
AudioDevice.ALDevice.GetBufferData(
INTERNAL_alSource,
queuedBuffers.ToArray(), // FIXME: Blech -flibit
handle.AddrOfPinnedObject(),
samples.Length,
channels
);
handle.Free();
}
else
{
Array.Clear(samples, 0, samples.Length);
}
}
#endregion
#region Public FNA Extension Methods
public void SubmitFloatBufferEXT(float[] buffer)
{
SubmitFloatBufferEXT(buffer, 0, buffer.Length);
}
public void SubmitFloatBufferEXT(float[] buffer, int offset, int count)
{
/* Float samples are the typical format received from decoders.
* We currently use this for the VideoPlayer.
* -flibit
*/
// Generate a buffer if we don't have any to use.
if (availableBuffers.Count == 0)
{
availableBuffers.Enqueue(AudioDevice.ALDevice.GenBuffer(sampleRate, channels));
}
// Push buffer to the AL.
IALBuffer newBuf = availableBuffers.Dequeue();
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
AudioDevice.ALDevice.SetBufferFloatData(
newBuf,
handle.AddrOfPinnedObject(),
offset,
count
);
handle.Free();
// If we're already playing, queue immediately.
if (INTERNAL_alSource != null)
{
AudioDevice.ALDevice.QueueSourceBuffer(
INTERNAL_alSource,
newBuf
);
queuedBuffers.Enqueue(newBuf);
// If the source stopped, reboot it now.
if (AudioDevice.ALDevice.GetSourceState(INTERNAL_alSource) == SoundState.Stopped)
{
AudioDevice.ALDevice.PlaySource(INTERNAL_alSource);
}
}
else
{
buffersToQueue.Enqueue(newBuf);
}
PendingBufferCount += 1;
}
#endregion
}
}