#region --- License --- /* Copyright (c) 2008 the OpenTK team * See license.txt for license details * http://www.opentk.com */ #endregion using System; using System.Collections.Generic; using System.Text; using OpenTK.OpenAL; using OpenTK.OpenAL.Enums; using System.Diagnostics; namespace OpenTK.Audio { /// /// Provides methods to create and use an audio context. /// public sealed class AudioContext : IDisposable { #region --- Fields --- bool disposed; bool is_processing; ContextHandle device_handle, context_handle; string device_name; static object audio_context_lock = new object(); static List available_devices = new List(); static Dictionary available_contexts = new Dictionary(); bool context_exists; #endregion #region --- Constructors --- #region static AudioContext() /// /// /// /// Runs before the actual class constructor, to load available devices. /// static AudioContext() { LoadAvailableDevices(); } #endregion #region public AudioContext() /// Constructs a new AudioContext, using the default audio device. /// Occurs when no audio devices are available. public AudioContext() : this(available_devices.Count > 0 ? available_devices[0] : null, 0, 0, false, 0) { } #endregion #region public AudioContext(string device) /// Constructs a new AudioContext, using the specified audio device. /// The name of the audio device to use. /// /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices. /// devices. /// public AudioContext(string device) : this(device, 0, 0, false, 0) { } #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, 0) { } #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, 0) { } #endregion #region public AudioContext(string device) /// 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(available_devices[0], freq, refresh, sync, 0) { } #endregion #region public AudioContext(string device, int freq, int refresh, bool sync, int maxSends) /// 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. /// Number of auxilliary send slots for the EFX extensions. Can be 0 (use driver default) or higher. /// 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, int maxSends) { CreateContext(device, freq, refresh, sync, maxSends); } #endregion #endregion #region --- Private Members --- #region static void LoadAvailableDevices() /// /// /// /// Loads all available audio devices into the available_devices array. /// /// /// Only called by the static AudioContext constructor. /// static void LoadAvailableDevices() { lock (audio_context_lock) { if (available_devices.Count == 0) { if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT")) available_devices.AddRange(Alc.GetString(IntPtr.Zero, AlcGetStringList.DeviceSpecifier)); else Debug.Print("Device enumeration extension not available. Failed to enumerate devices."); } } } #endregion #region void CreateContext(string device) /// /// 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. /// Number of auxilliary send slots for the EFX extensions. Can be 0 (use driver default) or higher. /// 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. /// /// void CreateContext(string device, int freq, int refresh, bool sync, int maxEfxSends) { if (context_exists) throw new NotSupportedException("Multiple AudioContexts not supported."); //if (String.IsNullOrEmpty(device)) throw new ArgumentNullException("device"); 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 (maxEfxSends < 0) throw new ArgumentOutOfRangeException("maxEfxSends", maxEfxSends, "Should be greater than zero."); //if (available_devices.Count == 0) throw new NotSupportedException("No audio hardware is available."); device_handle = Alc.OpenDevice(device); if (device_handle == IntPtr.Zero) throw new AudioDeviceException("The specified audio device does not exist or is tied up by another application."); CheckForAlcErrors(); device_name = device; // 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.Frequency); attributes.Add(sync ? 1 : 0); if (maxEfxSends > 0) { throw new NotImplementedException(); //if (Alc.IsExtensionPresent(device_handle, "ALC_EXT_EFX")) //attributes.Add((int)AlcContextAttributes.MaxAuxilliarySends); //attributes.Add(maxEfxSends); } context_handle = Alc.CreateContext(device_handle, attributes.ToArray()); if (context_handle == IntPtr.Zero) { Alc.CloseDevice(device_handle); throw new AudioContextException("The audio context could not be created with the specified parameters."); } CheckForAlcErrors(); MakeCurrent(); CheckForAlcErrors(); //device_name = Alc.GetString(device_handle, AlcGetString.DeviceSpecifier); //Debug.Print(device_name); lock (audio_context_lock) { available_contexts.Add(this.context_handle, this); context_exists = true; } } #endregion #region void CheckForAlcErrors() void CheckForAlcErrors() { AlcError err = Alc.GetError(device_handle); if (err != AlcError.NoError) throw new AudioContextException(err.ToString()); } #endregion #region static void MakeCurrent(AudioContext context) /// /// 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 ? (IntPtr)context.context_handle : IntPtr.Zero)) throw new AudioContextException(Alc.GetError( context != null ? (IntPtr)context.context_handle : IntPtr.Zero).ToString()); } } #endregion #endregion // TODO: Remove before release! public IntPtr Device { get { return device_handle.Handle; } } #region public bool IsProcessing /// /// Gets a System.Boolean indicating whether the AudioContext is /// currently processing audio events. /// /// /// public bool IsProcessing { get { return is_processing; } private set { is_processing = 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.ToString()); 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.ToString()); Alc.SuspendContext(this.context_handle); IsProcessing = false; } #endregion #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 --- Public Members --- #region public static string[] AvailableDevices /// /// Gets a System.String array containing all available audio devices. /// /// This property allocates memory. public static string[] AvailableDevices { get { if (available_devices.Count == 0) LoadAvailableDevices(); return available_devices.ToArray(); } } #endregion #endregion #region internal void 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. /// internal void MakeCurrent() { AudioContext.MakeCurrent(this); } #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 --- 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) { available_contexts.Remove(this.context_handle); if (this.IsCurrent) this.IsCurrent = false; if (context_handle != IntPtr.Zero) Alc.DestroyContext(context_handle); if (device_handle != IntPtr.Zero) Alc.CloseDevice(device_handle); if (manual) { } disposed = true; } } ~AudioContext() { this.Dispose(false); } #endregion #region --- Overrides --- public override int GetHashCode() { return base.GetHashCode(); } public override bool Equals(object obj) { return base.Equals(obj); } public override string ToString() { return String.Format("{0} (handle: {1}, device: {2})", this.device_name, this.context_handle, this.device_handle); } #endregion } #region --- Exceptions --- /// Represents errors related to an Audio device. public class AudioDeviceException : Exception { public AudioDeviceException() : base() { } public AudioDeviceException(string message) : base(message) { } } /// Represents errors related to an AudioContext. public class AudioContextException : Exception { public AudioContextException() : base() { } public AudioContextException(string message) : base(message) { } } #endregion }