#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using OpenTK.Audio.OpenAL;
namespace OpenTK.Audio
{
///
/// Provides methods to instantiate, use and destroy an audio context for playback.
/// Static methods are provided to list available devices known by the driver.
///
public sealed class AudioContext : IDisposable
{
#region --- Fields ---
bool disposed;
bool is_processing, is_synchronized;
IntPtr device_handle;
ContextHandle context_handle;
bool context_exists;
string device_name;
static object audio_context_lock = new object();
static Dictionary available_contexts = new Dictionary();
#endregion
#region --- Constructors ---
#region static AudioContext()
/// \internal
///
/// Runs before the actual class constructor, to load available devices.
///
static AudioContext()
{
if (AudioDeviceEnumerator.IsOpenALSupported) // forces enumeration
{ }
}
#endregion static AudioContext()
#region public AudioContext()
/// Constructs a new AudioContext, using the default audio device.
public AudioContext()
: this(null, 0, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }
#endregion
#region public AudioContext(string device)
///
/// Constructs a new AudioContext instance.
///
/// The device name that will host this instance.
public AudioContext(string device) : this(device, 0, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }
#endregion
#region public AudioContext(string device, int freq)
/// Constructs a new AudioContext, using the specified audio device and device parameters.
/// The name of the audio device to use.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
///
/// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
/// devices.
///
public AudioContext(string device, int freq) : this(device, freq, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }
#endregion
#region public AudioContext(string device, int freq, int refresh)
/// Constructs a new AudioContext, using the specified audio device and device parameters.
/// The name of the audio device to use.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
/// Refresh intervals, in units of Hz. Pass 0 for driver default.
///
/// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
/// devices.
///
public AudioContext(string device, int freq, int refresh)
: this(device, freq, refresh, false, true, MaxAuxiliarySends.UseDriverDefault) { }
#endregion
#region public AudioContext(string device, int freq, int refresh, bool sync)
/// Constructs a new AudioContext, using the specified audio device and device parameters.
/// The name of the audio device to use.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
/// Refresh intervals, in units of Hz. Pass 0 for driver default.
/// Flag, indicating a synchronous context.
///
/// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
/// devices.
///
public AudioContext(string device, int freq, int refresh, bool sync)
: this(AudioDeviceEnumerator.AvailablePlaybackDevices[0], freq, refresh, sync, true) { }
#endregion
#region public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx)
/// Creates the audio context using the specified device and device parameters.
/// The device descriptor obtained through AudioContext.AvailableDevices.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
/// Refresh intervals, in units of Hz. Pass 0 for driver default.
/// Flag, indicating a synchronous context.
/// Indicates whether the EFX extension should be initialized, if present.
/// Occurs when the device string is invalid.
/// Occurs when a specified parameter is invalid.
///
/// Occurs when the specified device is not available, or is in use by another program.
///
///
/// Occurs when an audio context could not be created with the specified parameters.
///
///
/// Occurs when an AudioContext already exists.
///
/// For maximum compatibility, you are strongly recommended to use the default constructor.
/// Multiple AudioContexts are not supported at this point.
///
/// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
/// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
/// Values higher than supported will be clamped by the driver.
///
///
public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx)
{
CreateContext(device, freq, refresh, sync, enableEfx, MaxAuxiliarySends.UseDriverDefault);
}
#endregion
#region public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxMaxAuxSends)
/// Creates the audio context using the specified device and device parameters.
/// The device descriptor obtained through AudioContext.AvailableDevices.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
/// Refresh intervals, in units of Hz. Pass 0 for driver default.
/// Flag, indicating a synchronous context.
/// Indicates whether the EFX extension should be initialized, if present.
/// Requires EFX enabled. The number of desired Auxiliary Sends per source.
/// Occurs when the device string is invalid.
/// Occurs when a specified parameter is invalid.
///
/// Occurs when the specified device is not available, or is in use by another program.
///
///
/// Occurs when an audio context could not be created with the specified parameters.
///
///
/// Occurs when an AudioContext already exists.
///
/// For maximum compatibility, you are strongly recommended to use the default constructor.
/// Multiple AudioContexts are not supported at this point.
///
/// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
/// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
/// Values higher than supported will be clamped by the driver.
///
///
public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxMaxAuxSends)
{
CreateContext(device, freq, refresh, sync, enableEfx, efxMaxAuxSends);
}
#endregion
#endregion --- Constructors ---
#region --- Private Methods ---
#region CreateContext
/// May be passed at context construction time to indicate the number of desired auxiliary effect slot sends per source.
public enum MaxAuxiliarySends:int
{
/// Will chose a reliably working parameter.
UseDriverDefault = 0,
/// One send per source.
One = 1,
/// Two sends per source.
Two = 2,
/// Three sends per source.
Three = 3,
/// Four sends per source.
Four = 4,
}
/// \internal
/// Creates the audio context using the specified device.
/// The device descriptor obtained through AudioContext.AvailableDevices, or null for the default device.
/// Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.
/// Refresh intervals, in units of Hz. Pass 0 for driver default.
/// Flag, indicating a synchronous context.
/// Indicates whether the EFX extension should be initialized, if present.
/// Requires EFX enabled. The number of desired Auxiliary Sends per source.
/// Occurs when a specified parameter is invalid.
///
/// Occurs when the specified device is not available, or is in use by another program.
///
///
/// Occurs when an audio context could not be created with the specified parameters.
///
///
/// Occurs when an AudioContext already exists.
///
/// For maximum compatibility, you are strongly recommended to use the default constructor.
/// Multiple AudioContexts are not supported at this point.
///
/// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
/// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
/// Values higher than supported will be clamped by the driver.
///
///
void CreateContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxAuxiliarySends)
{
if (!AudioDeviceEnumerator.IsOpenALSupported)
throw new DllNotFoundException("openal32.dll");
if (AudioDeviceEnumerator.Version == AudioDeviceEnumerator.AlcVersion.Alc1_1 && AudioDeviceEnumerator.AvailablePlaybackDevices.Count == 0) // Alc 1.0 does not support device enumeration.
throw new NotSupportedException("No audio hardware is available.");
if (context_exists) throw new NotSupportedException("Multiple AudioContexts are not supported.");
if (freq < 0) throw new ArgumentOutOfRangeException("freq", freq, "Should be greater than zero.");
if (refresh < 0) throw new ArgumentOutOfRangeException("refresh", refresh, "Should be greater than zero.");
if (!String.IsNullOrEmpty(device))
{
device_name = device;
device_handle = Alc.OpenDevice(device); // try to open device by name
}
if (device_handle == IntPtr.Zero)
{
device_name = "IntPtr.Zero (null string)";
device_handle = Alc.OpenDevice(null); // try to open unnamed default device
}
if (device_handle == IntPtr.Zero)
{
device_name = AudioContext.DefaultDevice;
device_handle = Alc.OpenDevice(AudioContext.DefaultDevice); // try to open named default device
}
if (device_handle == IntPtr.Zero)
{
device_name = "None";
throw new AudioDeviceException(String.Format("Audio device '{0}' does not exist or is tied up by another application.",
String.IsNullOrEmpty(device) ? "default" : device));
}
CheckErrors();
// Build the attribute list
List attributes = new List();
if (freq != 0)
{
attributes.Add((int)AlcContextAttributes.Frequency);
attributes.Add(freq);
}
if (refresh != 0)
{
attributes.Add((int)AlcContextAttributes.Refresh);
attributes.Add(refresh);
}
attributes.Add((int)AlcContextAttributes.Sync);
attributes.Add(sync ? 1 : 0);
if (enableEfx && Alc.IsExtensionPresent(device_handle, "ALC_EXT_EFX"))
{
int num_slots;
switch (efxAuxiliarySends)
{
case MaxAuxiliarySends.One:
case MaxAuxiliarySends.Two:
case MaxAuxiliarySends.Three:
case MaxAuxiliarySends.Four:
num_slots = (int)efxAuxiliarySends;
break;
default:
case MaxAuxiliarySends.UseDriverDefault:
Alc.GetInteger(device_handle, AlcGetInteger.EfxMaxAuxiliarySends, 1, out num_slots);
break;
}
attributes.Add((int)AlcContextAttributes.EfxMaxAuxiliarySends);
attributes.Add(num_slots);
}
attributes.Add(0);
context_handle = Alc.CreateContext(device_handle, attributes.ToArray());
if (context_handle == ContextHandle.Zero)
{
Alc.CloseDevice(device_handle);
throw new AudioContextException("The audio context could not be created with the specified parameters.");
}
CheckErrors();
// HACK: OpenAL SI on Linux/ALSA crashes on MakeCurrent. This hack avoids calling MakeCurrent when
// an old OpenAL version is detect - it may affect outdated OpenAL versions different than OpenAL SI,
// but it looks like a good compromise for now.
if (AudioDeviceEnumerator.AvailablePlaybackDevices.Count > 0)
MakeCurrent();
CheckErrors();
device_name = Alc.GetString(device_handle, AlcGetString.DeviceSpecifier);
lock (audio_context_lock)
{
available_contexts.Add(this.context_handle, this);
context_exists = true;
}
}
#endregion --- Private Methods ---
#region static void MakeCurrent(AudioContext context)
/// \internal
/// Makes the specified AudioContext current in the calling thread.
/// The OpenTK.Audio.AudioContext to make current, or null.
///
/// Occurs if this function is called after the AudioContext has been disposed.
///
///
/// Occurs when the AudioContext could not be made current.
///
static void MakeCurrent(AudioContext context)
{
lock (audio_context_lock)
{
if (!Alc.MakeContextCurrent(context != null ? context.context_handle : ContextHandle.Zero))
throw new AudioContextException(String.Format("ALC {0} error detected at {1}.",
Alc.GetError(context != null ? (IntPtr)context.context_handle : IntPtr.Zero).ToString(),
context != null ? context.ToString() : "null"));
}
}
#endregion
#region internal bool IsCurrent
///
/// Gets or sets a System.Boolean indicating whether the AudioContext
/// is current.
///
///
/// Only one AudioContext can be current in the application at any time,
/// regardless of the number of threads.
///
internal bool IsCurrent
{
get
{
lock (audio_context_lock)
{
if (available_contexts.Count == 0)
return false;
else
{
return AudioContext.CurrentContext == this;
}
}
}
set
{
if (value) AudioContext.MakeCurrent(this);
else AudioContext.MakeCurrent(null);
}
}
#endregion
#region IntPtr Device
IntPtr Device { get { return device_handle; } }
#endregion
#endregion
#region --- Public Members ---
#region CheckErrors
///
/// Checks for ALC error conditions.
///
/// Raised when an out of memory error is detected.
/// Raised when an invalid value is detected.
/// Raised when an invalid device is detected.
/// Raised when an invalid context is detected.
public void CheckErrors()
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
new AudioDeviceErrorChecker(device_handle).Dispose();
}
#endregion
#region CurrentError
///
/// Returns the ALC error code for this instance.
///
public AlcError CurrentError
{
get
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
return Alc.GetError(device_handle);
}
}
#endregion
#region MakeCurrent
/// Makes the AudioContext current in the calling thread.
///
/// Occurs if this function is called after the AudioContext has been disposed.
///
///
/// Occurs when the AudioContext could not be made current.
///
///
/// Only one AudioContext can be current in the application at any time,
/// regardless of the number of threads.
///
public void MakeCurrent()
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
AudioContext.MakeCurrent(this);
}
#endregion
#region IsProcessing
///
/// Gets a System.Boolean indicating whether the AudioContext is
/// currently processing audio events.
///
///
///
public bool IsProcessing
{
get
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
return is_processing;
}
private set { is_processing = value; }
}
#endregion
#region IsSynchronized
///
/// Gets a System.Boolean indicating whether the AudioContext is
/// synchronized.
///
///
public bool IsSynchronized
{
get
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
return is_synchronized;
}
private set { is_synchronized = value; }
}
#endregion
#region public void Process
///
/// Processes queued audio events.
///
///
///
/// If AudioContext.IsSynchronized is true, this function will resume
/// the internal audio processing thread. If AudioContext.IsSynchronized is false,
/// you will need to call this function multiple times per second to process
/// audio events.
///
///
/// In some implementations this function may have no effect.
///
///
/// Occurs when this function is called after the AudioContext had been disposed.
///
///
///
public void Process()
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
Alc.ProcessContext(this.context_handle);
IsProcessing = true;
}
#endregion
#region public void Suspend
///
/// Suspends processing of audio events.
///
///
///
/// To avoid audio artifacts when calling this function, set audio gain to zero before
/// suspending an AudioContext.
///
///
/// In some implementations, it can be faster to suspend processing before changing
/// AudioContext state.
///
///
/// In some implementations this function may have no effect.
///
///
/// Occurs when this function is called after the AudioContext had been disposed.
///
///
///
public void Suspend()
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
Alc.SuspendContext(this.context_handle);
IsProcessing = false;
}
#endregion
#region public bool SupportsExtension(string extension)
///
/// Checks whether the specified OpenAL extension is supported.
///
/// The name of the extension to check (e.g. "ALC_EXT_EFX").
/// true if the extension is supported; false otherwise.
public bool SupportsExtension(string extension)
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
return Alc.IsExtensionPresent(this.Device, extension);
}
#endregion
#region CurrentDevice
///
/// Gets a System.String with the name of the device used in this context.
///
public string CurrentDevice
{
get
{
if (disposed)
throw new ObjectDisposedException(this.GetType().FullName);
return device_name;
}
}
#endregion
#endregion --- Public Members ---
#region --- Static Members ---
#region public static AudioContext CurrentContext
///
/// Gets the OpenTK.Audio.AudioContext which is current in the application.
///
///
/// Only one AudioContext can be current in the application at any time,
/// regardless of the number of threads.
///
public static AudioContext CurrentContext
{
get
{
lock (audio_context_lock)
{
if (available_contexts.Count == 0)
return null;
else
{
AudioContext context;
AudioContext.available_contexts.TryGetValue(
(ContextHandle)Alc.GetCurrentContext(),
out context);
return context;
}
}
}
}
#endregion
#region AvailableDevices
///
/// Returns a list of strings containing all known playback devices.
///
public static IList AvailableDevices
{
get
{
return AudioDeviceEnumerator.AvailablePlaybackDevices;
}
}
#endregion public static IList AvailablePlaybackDevices
#region DefaultDevice
///
/// Returns the name of the device that will be used as playback default.
///
public static string DefaultDevice
{
get
{
return AudioDeviceEnumerator.DefaultPlaybackDevice;
}
}
#endregion
#endregion
#region --- IDisposable Members ---
///
/// Disposes of the AudioContext, cleaning up all resources consumed by it.
///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool manual)
{
if (!disposed)
{
if (this.IsCurrent)
this.IsCurrent = false;
if (context_handle != ContextHandle.Zero)
{
available_contexts.Remove(context_handle);
Alc.DestroyContext(context_handle);
}
if (device_handle != IntPtr.Zero)
Alc.CloseDevice(device_handle);
if (manual)
{
}
disposed = true;
}
}
///
/// Finalizes this instance.
///
~AudioContext()
{
this.Dispose(false);
}
#endregion
#region --- Overrides ---
///
/// Calculates the hash code for this instance.
///
///
public override int GetHashCode()
{
return base.GetHashCode();
}
///
/// Compares this instance with another.
///
/// The instance to compare to.
/// True, if obj refers to this instance; false otherwise.
public override bool Equals(object obj)
{
return base.Equals(obj);
}
///
/// Returns a that desrcibes this instance.
///
/// A that desrcibes this instance.
public override string ToString()
{
return String.Format("{0} (handle: {1}, device: {2})",
this.device_name, this.context_handle, this.device_handle);
}
#endregion
}
}