418 lines
8.6 KiB
C#
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
|
|
}
|
|
}
|