Show More
Commit Description:
Update TODO.taskpaper.
Commit Description:
Update TODO.taskpaper.
File last commit:
Show/Diff file:
Action:
FNA/src/Audio/SoundEffect.cs
801 lines | 17.8 KiB | text/x-csharp | CSharpLexer
Early working version (including all dependencies, lol).
r0 #region License
/* FNA - XNA4 Reimplementation for Desktop Platforms
Upgrade FNA to 22.12...
r690 * Copyright 2009-2022 Ethan Lee and the MonoGame Team
Early working version (including all dependencies, lol).
r0 *
* Released under the Microsoft Public License.
* See LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.IO;
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.soundeffect.aspx
public sealed class SoundEffect : IDisposable
{
#region Public Properties
public TimeSpan Duration
{
get
{
return TimeSpan.FromSeconds(
(double) handle.PlayLength /
Upgrade FNA to 22.12...
r690 (double) sampleRate
Early working version (including all dependencies, lol).
r0 );
}
}
public bool IsDisposed
{
get;
private set;
}
public string Name
{
get;
set;
}
#endregion
#region Public Static Properties
public static float MasterVolume
{
get
{
float result;
FAudio.FAudioVoice_GetVolume(
Device().MasterVoice,
out result
);
return result;
}
set
{
FAudio.FAudioVoice_SetVolume(
Device().MasterVoice,
value,
0
);
}
}
public static float DistanceScale
{
get
{
return Device().CurveDistanceScaler;
}
set
{
if (value <= 0.0f)
{
throw new ArgumentOutOfRangeException("value <= 0.0f");
}
Device().CurveDistanceScaler = value;
}
}
public static float DopplerScale
{
get
{
return Device().DopplerScale;
}
set
{
if (value < 0.0f)
{
throw new ArgumentOutOfRangeException("value < 0.0f");
}
Device().DopplerScale = value;
}
}
public static float SpeedOfSound
{
get
{
return Device().SpeedOfSound;
}
set
{
FAudioContext dev = Device();
dev.SpeedOfSound = value;
FAudio.F3DAudioInitialize(
dev.DeviceDetails.OutputFormat.dwChannelMask,
dev.SpeedOfSound,
dev.Handle3D
);
}
}
#endregion
#region Internal Variables
internal List<WeakReference> Instances = new List<WeakReference>();
internal FAudio.FAudioBuffer handle;
Upgrade FNA to 22.12...
r690 internal IntPtr formatPtr;
internal ushort channels;
internal uint sampleRate;
Early working version (including all dependencies, lol).
r0 internal uint loopStart;
internal uint loopLength;
#endregion
#region Public Constructors
public SoundEffect(
byte[] buffer,
int sampleRate,
AudioChannels channels
) : this(
null,
buffer,
0,
buffer.Length,
Upgrade FNA to 22.12...
r690 null,
Early working version (including all dependencies, lol).
r0 1,
(ushort) channels,
(uint) sampleRate,
(uint) (sampleRate * ((ushort) channels * 2)),
(ushort) ((ushort) channels * 2),
16,
0,
0
) {
}
public SoundEffect(
byte[] buffer,
int offset,
int count,
int sampleRate,
AudioChannels channels,
int loopStart,
int loopLength
) : this(
null,
buffer,
offset,
count,
Upgrade FNA to 22.12...
r690 null,
Early working version (including all dependencies, lol).
r0 1,
(ushort) channels,
(uint) sampleRate,
(uint) (sampleRate * ((ushort) channels * 2)),
(ushort) ((ushort) channels * 2),
16,
loopStart,
loopLength
) {
}
#endregion
#region Internal Constructor
Upgrade FNA to 22.12...
r690 internal unsafe SoundEffect(
Early working version (including all dependencies, lol).
r0 string name,
byte[] buffer,
int offset,
int count,
Upgrade FNA to 22.12...
r690 byte[] extraData,
Early working version (including all dependencies, lol).
r0 ushort wFormatTag,
ushort nChannels,
uint nSamplesPerSec,
uint nAvgBytesPerSec,
ushort nBlockAlign,
ushort wBitsPerSample,
int loopStart,
int loopLength
) {
Device();
Name = name;
Upgrade FNA to 22.12...
r690 channels = nChannels;
sampleRate = nSamplesPerSec;
Early working version (including all dependencies, lol).
r0 this.loopStart = (uint) loopStart;
this.loopLength = (uint) loopLength;
/* Buffer format */
Upgrade FNA to 22.12...
r690 if (extraData == null)
{
formatPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(FAudio.FAudioWaveFormatEx))
);
}
else
{
formatPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(FAudio.FAudioWaveFormatEx)) +
extraData.Length
);
Marshal.Copy(
extraData,
0,
formatPtr + Marshal.SizeOf(typeof(FAudio.FAudioWaveFormatEx)),
extraData.Length
);
}
FAudio.FAudioWaveFormatEx* pcm = (FAudio.FAudioWaveFormatEx*) formatPtr;
pcm->wFormatTag = wFormatTag;
pcm->nChannels = nChannels;
pcm->nSamplesPerSec = nSamplesPerSec;
pcm->nAvgBytesPerSec = nAvgBytesPerSec;
pcm->nBlockAlign = nBlockAlign;
pcm->wBitsPerSample = wBitsPerSample;
pcm->cbSize = (ushort) ((extraData == null) ? 0 : extraData.Length);
Early working version (including all dependencies, lol).
r0
/* Easy stuff */
handle = new FAudio.FAudioBuffer();
handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
handle.pContext = IntPtr.Zero;
/* Buffer data */
handle.AudioBytes = (uint) count;
handle.pAudioData = Marshal.AllocHGlobal(count);
Marshal.Copy(
buffer,
offset,
handle.pAudioData,
count
);
/* Play regions */
handle.PlayBegin = 0;
if (wFormatTag == 1)
{
handle.PlayLength = (uint) (
count /
nChannels /
(wBitsPerSample / 8)
);
}
else if (wFormatTag == 2)
{
handle.PlayLength = (uint) (
count /
nBlockAlign *
(((nBlockAlign / nChannels) - 6) * 2)
);
}
Upgrade FNA to 22.12...
r690 else if (wFormatTag == 0x166)
{
FAudio.FAudioXMA2WaveFormatEx* xma2 = (FAudio.FAudioXMA2WaveFormatEx*) formatPtr;
// dwSamplesEncoded / nChannels / (wBitsPerSample / 8) doesn't always (if ever?) match up.
handle.PlayLength = xma2->dwPlayLength;
}
Early working version (including all dependencies, lol).
r0
/* Set by Instances! */
handle.LoopBegin = 0;
handle.LoopLength = 0;
handle.LoopCount = 0;
}
#endregion
#region Destructor
~SoundEffect()
{
if (Instances.Count > 0)
{
// STOP LEAKING YOUR INSTANCES, ARGH
GC.ReRegisterForFinalize(this);
return;
}
Dispose();
}
#endregion
#region Public Methods
public void Dispose()
{
if (!IsDisposed)
{
/* FIXME: Is it ironic that we're generating
* garbage with ToArray while cleaning up after
* the program's leaks?
* -flibit
*/
foreach (WeakReference instance in Instances.ToArray())
{
object target = instance.Target;
if (target != null)
{
(target as IDisposable).Dispose();
}
}
Instances.Clear();
Upgrade FNA to 22.12...
r690 Marshal.FreeHGlobal(formatPtr);
Early working version (including all dependencies, lol).
r0 Marshal.FreeHGlobal(handle.pAudioData);
IsDisposed = true;
}
}
public bool Play()
{
return Play(1.0f, 0.0f, 0.0f);
}
public bool Play(float volume, float pitch, float pan)
{
SoundEffectInstance instance = new SoundEffectInstance(this);
instance.Volume = volume;
instance.Pitch = pitch;
instance.Pan = pan;
instance.Play();
if (instance.State != SoundState.Playing)
{
// Ran out of AL sources, probably.
instance.Dispose();
return false;
}
return true;
}
public SoundEffectInstance CreateInstance()
{
return new SoundEffectInstance(this);
}
#endregion
#region Public Static Methods
public static TimeSpan GetSampleDuration(
int sizeInBytes,
int sampleRate,
AudioChannels channels
) {
sizeInBytes /= 2; // 16-bit PCM!
int ms = (int) (
(sizeInBytes / (int) channels) /
(sampleRate / 1000.0f)
);
return new TimeSpan(0, 0, 0, 0, ms);
}
public static int GetSampleSizeInBytes(
TimeSpan duration,
int sampleRate,
AudioChannels channels
) {
return (int) (
duration.TotalSeconds *
sampleRate *
(int) channels *
2 // 16-bit PCM!
);
}
public static SoundEffect FromStream(Stream stream)
{
// Sample data
byte[] data;
// WaveFormatEx data
ushort wFormatTag;
ushort nChannels;
uint nSamplesPerSec;
uint nAvgBytesPerSec;
ushort nBlockAlign;
ushort wBitsPerSample;
// ushort cbSize;
Upgrade FNA to 22.12...
r690 int samplerLoopStart = 0;
int samplerLoopEnd = 0;
Early working version (including all dependencies, lol).
r0 using (BinaryReader reader = new BinaryReader(stream))
{
// RIFF Signature
string signature = new string(reader.ReadChars(4));
if (signature != "RIFF")
{
throw new NotSupportedException("Specified stream is not a wave file.");
}
reader.ReadUInt32(); // Riff Chunk Size
string wformat = new string(reader.ReadChars(4));
if (wformat != "WAVE")
{
throw new NotSupportedException("Specified stream is not a wave file.");
}
// WAVE Header
string format_signature = new string(reader.ReadChars(4));
while (format_signature != "fmt ")
{
reader.ReadBytes(reader.ReadInt32());
format_signature = new string(reader.ReadChars(4));
}
int format_chunk_size = reader.ReadInt32();
wFormatTag = reader.ReadUInt16();
nChannels = reader.ReadUInt16();
nSamplesPerSec = reader.ReadUInt32();
nAvgBytesPerSec = reader.ReadUInt32();
nBlockAlign = reader.ReadUInt16();
wBitsPerSample = reader.ReadUInt16();
// Reads residual bytes
if (format_chunk_size > 16)
{
reader.ReadBytes(format_chunk_size - 16);
}
// data Signature
string data_signature = new string(reader.ReadChars(4));
while (data_signature.ToLowerInvariant() != "data")
{
reader.ReadBytes(reader.ReadInt32());
data_signature = new string(reader.ReadChars(4));
}
if (data_signature != "data")
{
throw new NotSupportedException("Specified wave file is not supported.");
}
int waveDataLength = reader.ReadInt32();
data = reader.ReadBytes(waveDataLength);
Upgrade FNA to 22.12...
r690
// Scan for other chunks
while (reader.PeekChar() != -1)
{
char[] chunkIDChars = reader.ReadChars(4);
if (chunkIDChars.Length < 4)
{
break; // EOL!
}
byte[] chunkSizeBytes = reader.ReadBytes(4);
if (chunkSizeBytes.Length < 4)
{
break; // EOL!
}
string chunk_signature = new string(chunkIDChars);
int chunkDataSize = BitConverter.ToInt32(chunkSizeBytes, 0);
if (chunk_signature == "smpl") // "smpl", Sampler Chunk Found
{
reader.ReadUInt32(); // Manufacturer
reader.ReadUInt32(); // Product
reader.ReadUInt32(); // Sample Period
reader.ReadUInt32(); // MIDI Unity Note
reader.ReadUInt32(); // MIDI Pitch Fraction
reader.ReadUInt32(); // SMPTE Format
reader.ReadUInt32(); // SMPTE Offset
uint numSampleLoops = reader.ReadUInt32();
int samplerData = reader.ReadInt32();
for (int i = 0; i < numSampleLoops; i += 1)
{
reader.ReadUInt32(); // Cue Point ID
reader.ReadUInt32(); // Type
int start = reader.ReadInt32();
int end = reader.ReadInt32();
reader.ReadUInt32(); // Fraction
reader.ReadUInt32(); // Play Count
if (i == 0) // Grab loopStart and loopEnd from first sample loop
{
samplerLoopStart = start;
samplerLoopEnd = end;
}
}
if (samplerData != 0) // Read Sampler Data if it exists
{
reader.ReadBytes(samplerData);
}
}
else // Read unwanted chunk data and try again
{
reader.ReadBytes(chunkDataSize);
}
}
// End scan
Early working version (including all dependencies, lol).
r0 }
return new SoundEffect(
null,
data,
0,
data.Length,
Upgrade FNA to 22.12...
r690 null,
Early working version (including all dependencies, lol).
r0 wFormatTag,
nChannels,
nSamplesPerSec,
nAvgBytesPerSec,
nBlockAlign,
wBitsPerSample,
Upgrade FNA to 22.12...
r690 samplerLoopStart,
samplerLoopEnd - samplerLoopStart
Early working version (including all dependencies, lol).
r0 );
}
#endregion
#region FAudio Context
internal class FAudioContext
{
public static FAudioContext Context = null;
public readonly IntPtr Handle;
public readonly byte[] Handle3D;
public readonly IntPtr MasterVoice;
public readonly FAudio.FAudioDeviceDetails DeviceDetails;
public float CurveDistanceScaler;
public float DopplerScale;
public float SpeedOfSound;
public IntPtr ReverbVoice;
private FAudio.FAudioVoiceSends reverbSends;
private FAudioContext(IntPtr ctx, uint devices)
{
Handle = ctx;
uint i;
for (i = 0; i < devices; i += 1)
{
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out DeviceDetails
);
if ((DeviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
{
break;
}
}
if (i == devices)
{
i = 0; /* Oh well. */
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out DeviceDetails
);
}
if (FAudio.FAudio_CreateMasteringVoice(
Handle,
out MasterVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
IntPtr.Zero
) != 0) {
FAudio.FAudio_Release(ctx);
Upgrade FNA to 22.12...
r690 Handle = IntPtr.Zero;
Early working version (including all dependencies, lol).
r0 FNALoggerEXT.LogError(
"Failed to create mastering voice!"
);
return;
}
CurveDistanceScaler = 1.0f;
DopplerScale = 1.0f;
SpeedOfSound = 343.5f;
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
FAudio.F3DAudioInitialize(
DeviceDetails.OutputFormat.dwChannelMask,
SpeedOfSound,
Handle3D
);
Context = this;
}
public void Dispose()
{
if (ReverbVoice != IntPtr.Zero)
{
FAudio.FAudioVoice_DestroyVoice(ReverbVoice);
ReverbVoice = IntPtr.Zero;
Marshal.FreeHGlobal(reverbSends.pSends);
}
Upgrade FNA to 22.12...
r690 if (MasterVoice != IntPtr.Zero)
{
FAudio.FAudioVoice_DestroyVoice(MasterVoice);
}
if (Handle != IntPtr.Zero)
{
FAudio.FAudio_Release(Handle);
}
Early working version (including all dependencies, lol).
r0 Context = null;
}
public unsafe void AttachReverb(IntPtr voice)
{
// Only create a reverb voice if they ask for it!
if (ReverbVoice == IntPtr.Zero)
{
IntPtr reverb;
FAudio.FAudioCreateReverb(out reverb, 0);
IntPtr chainPtr;
chainPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(FAudio.FAudioEffectChain))
);
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
reverbChain->EffectCount = 1;
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(FAudio.FAudioEffectDescriptor))
);
FAudio.FAudioEffectDescriptor* reverbDesc =
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
reverbDesc->InitialState = 1;
reverbDesc->OutputChannels = (uint) (
(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
);
reverbDesc->pEffect = reverb;
FAudio.FAudio_CreateSubmixVoice(
Handle,
out ReverbVoice,
1, /* Reverb will be omnidirectional */
DeviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
IntPtr.Zero,
chainPtr
);
FAudio.FAPOBase_Release(reverb);
Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
Marshal.FreeHGlobal(chainPtr);
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
IntPtr rvbParamsPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(FAudio.FAudioFXReverbParameters))
);
FAudio.FAudioFXReverbParameters* rvbParams = (FAudio.FAudioFXReverbParameters*) rvbParamsPtr;
rvbParams->WetDryMix = 100.0f;
rvbParams->ReflectionsDelay = 7;
rvbParams->ReverbDelay = 11;
rvbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
rvbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
rvbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
rvbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
rvbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
rvbParams->EarlyDiffusion = 15;
rvbParams->LateDiffusion = 15;
rvbParams->LowEQGain = 8;
rvbParams->LowEQCutoff = 4;
rvbParams->HighEQGain = 8;
rvbParams->HighEQCutoff = 6;
rvbParams->RoomFilterFreq = 5000f;
rvbParams->RoomFilterMain = -10f;
rvbParams->RoomFilterHF = -1f;
rvbParams->ReflectionsGain = -26.0200005f;
rvbParams->ReverbGain = 10.0f;
rvbParams->DecayTime = 1.49000001f;
rvbParams->Density = 100.0f;
rvbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
FAudio.FAudioVoice_SetEffectParameters(
ReverbVoice,
0,
rvbParamsPtr,
(uint) Marshal.SizeOf(typeof(FAudio.FAudioFXReverbParameters)),
0
);
Marshal.FreeHGlobal(rvbParamsPtr);
reverbSends = new FAudio.FAudioVoiceSends();
reverbSends.SendCount = 2;
reverbSends.pSends = Marshal.AllocHGlobal(
2 * Marshal.SizeOf(typeof(FAudio.FAudioSendDescriptor))
);
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) reverbSends.pSends;
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = MasterVoice;
sendDesc[1].Flags = 0;
sendDesc[1].pOutputVoice = ReverbVoice;
}
// Oh hey here's where we actually attach it
FAudio.FAudioVoice_SetOutputVoices(
voice,
ref reverbSends
);
}
public static void Create()
{
IntPtr ctx;
try
{
FAudio.FAudioCreate(
out ctx,
0,
FAudio.FAUDIO_DEFAULT_PROCESSOR
);
}
Upgrade FNA to 22.12...
r690 catch (Exception e)
Early working version (including all dependencies, lol).
r0 {
/* FAudio is missing, bail! */
Upgrade FNA to 22.12...
r690 FNALoggerEXT.LogWarn("FAudio failed to load: " + e.ToString());
Early working version (including all dependencies, lol).
r0 return;
}
uint devices;
FAudio.FAudio_GetDeviceCount(
ctx,
out devices
);
if (devices == 0)
{
/* No sound cards, bail! */
FAudio.FAudio_Release(ctx);
return;
}
Upgrade FNA to 22.12...
r690 FAudioContext context = new FAudioContext(ctx, devices);
if (context.Handle == IntPtr.Zero)
{
/* Soundcard failed to configure, bail! */
context.Dispose();
return;
}
Context = context;
Early working version (including all dependencies, lol).
r0 }
}
Upgrade FNA to 22.12...
r690 private static readonly object createLock = new object();
Early working version (including all dependencies, lol).
r0 internal static FAudioContext Device()
{
Upgrade FNA to 22.12...
r690 /* Ideally the device has been made, just return it. */
Early working version (including all dependencies, lol).
r0 if (FAudioContext.Context != null)
{
return FAudioContext.Context;
}
Upgrade FNA to 22.12...
r690
/* From here on out, it gets weird... */
lock (createLock)
Early working version (including all dependencies, lol).
r0 {
Upgrade FNA to 22.12...
r690 /* If this trips it's because another thread
* got here first. We do the check above to
* avoid the mutex lock for the 99.99% of the
* time where it's not necessary.
*/
if (FAudioContext.Context != null)
{
return FAudioContext.Context;
}
/* If you're here, you were the first caller!
* that, or there genuinely is no hardware and
* you're about to get a lot more of these.
*/
FAudioContext.Create();
if (FAudioContext.Context == null)
{
throw new NoAudioHardwareException();
}
Early working version (including all dependencies, lol).
r0 }
return FAudioContext.Context;
}
#endregion
}
}