diff --git a/Source/OpenTK/OpenAL/AudioContext.cs b/Source/OpenTK/OpenAL/AudioContext.cs index b08131df..70b1d0fe 100644 --- a/Source/OpenTK/OpenAL/AudioContext.cs +++ b/Source/OpenTK/OpenAL/AudioContext.cs @@ -11,6 +11,7 @@ using System.Text; using OpenTK.OpenAL; using OpenTK.OpenAL.Enums; +using System.Diagnostics; namespace OpenTK.Audio { @@ -22,55 +23,155 @@ namespace OpenTK.Audio #region --- Fields --- bool disposed; - static List available_devices; - IntPtr device_handle, context_handle; + 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(); #endregion #region --- Constructors --- - // Runs before the actual class constructor, to load available devices. + #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() - { - if (available_devices.Count == 0) - throw new NotSupportedException("No audio hardware is available."); - CreateContext(available_devices[0], 44100, 0, false); - } + public AudioContext() : this(available_devices[0], 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 + /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices. /// devices. /// - public AudioContext(string device) + 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 maxEfxSends) + + /// 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. + /// + /// + /// + /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices. + /// + /// + /// 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 not supported will be clamped by the driver. + /// + /// + public AudioContext(string device, int freq, int refresh, bool sync, int maxEfxSends) { - + CreateContext(device, freq, refresh, sync, maxEfxSends); } #endregion + #endregion + #region --- Private Members --- #region static void LoadAvailableDevices() - // Loads all available audio devices into the available_devices array. + /// + /// + /// + /// 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 == null) + if (available_devices.Count == 0) { - available_devices = new List(); - available_devices.AddRange(Alc.GetString(IntPtr.Zero, OpenTK.OpenAL.Enums.AlcGetStringList.AllDevicesSpecifier)); + if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT")) + { + available_devices = new List(); + available_devices.AddRange(Alc.GetString(IntPtr.Zero, AlcGetStringList.AllDevicesSpecifier)); + } } } } @@ -82,47 +183,104 @@ namespace OpenTK.Audio /// /// Creates the audio context using the specified device. /// The device descriptor obtained through AudioContext.AvailableDevices. - /// Frequency for mixing output buffer, in units of Hz. - /// Refresh intervalls, in units of Hz. + /// 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. /// - void CreateContext(string device, int freq, int refresh, bool sync) + /// + /// 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 (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 (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 InvalidAudioDeviceException(); + throw new AudioDeviceException("The specified audio device does not exist or is tied up by another application."); + device_name = device; + + // Build the attribute liist 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 InvalidAudioContextException(); + throw new AudioContextException("The audio context could not be created with the specified parameters."); + } + + MakeCurrent(); + + //device_name = Alc.GetString(device_handle, AlcGetString.DeviceSpecifier); + //Debug.Print(device_name); + lock (audio_context_lock) + { + available_contexts.Add(this.context_handle, this); } } + #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 (context != null && context.disposed) + // throw new ObjectDisposedException("OpenTK.Audio.AudioContext"); + 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 @@ -135,11 +293,152 @@ namespace OpenTK.Audio /// /// Gets a System.String array containing all available audio devices. /// - public static IEnumerable AvailableDevices + /// This property allocates memory. + public static string[] AvailableDevices { get { - return (IEnumerable)available_devices; + return available_devices.ToArray(); + } + } + + #endregion + + #region public 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. + /// + public void MakeCurrent() + { + AudioContext.MakeCurrent(this); + } + + #endregion + + #region public 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. + /// + public bool IsCurrent + { + get + { + lock (audio_context_lock) + { + if (available_contexts.Count == 0) + return false; + else + return AudioContext.available_contexts[(ContextHandle)Alc.GetCurrentContext()] == this; + } + } + set + { + if (value) AudioContext.MakeCurrent(this); + else AudioContext.MakeCurrent(null); + } + } + + #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); + } + + #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); + } + + #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; + } + } } } @@ -149,6 +448,9 @@ namespace OpenTK.Audio #region --- IDisposable Members --- + /// + /// Disposes of the AudioContext, cleaning up all resources consumed by it. + /// public void Dispose() { this.Dispose(true); @@ -159,6 +461,17 @@ namespace OpenTK.Audio { 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) { } @@ -172,18 +485,42 @@ namespace OpenTK.Audio } #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 --- - public class InvalidAudioDeviceException : Exception + /// Represents errors related to an Audio device. + public class AudioDeviceException : Exception { - public InvalidAudioDeviceException() : base("The specified audio device does not exist or is tied up by another application.") { } + public AudioDeviceException() : base() { } + public AudioDeviceException(string message) : base(message) { } } - public class InvalidAudioContextException : Exception + /// Represents errors related to an AudioContext. + public class AudioContextException : Exception { - public InvalidAudioContextException() : base("The audio context could not be created with the specified parameters.") { } + public AudioContextException() : base() { } + public AudioContextException(string message) : base(message) { } } #endregion