#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; 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() /// /// /// /// 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) 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, } /// /// 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) /// /// 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() { new AudioDeviceErrorChecker(device_handle).Dispose(); } #endregion #region CurrentError /// Returns the ALC error code for this device. public AlcError CurrentError { get { return Alc.GetError(device_handle); } } #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 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 bool IsProcessing /// /// Gets a System.Boolean indicating whether the AudioContext is /// synchronized. /// /// public bool IsSynchronized { get { 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.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 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 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 #region public string CurrentDeviceName /// Returns the name of the used device for the current context. public string CurrentDeviceName { get { return device_name; } } #endregion public string CurrentDeviceName #region public AlcError CurrentAlcError /// Returns the first encountered error by Alc for this device. public AlcError CurrentAlcError { get { if (disposed) throw new ObjectDisposedException(this.ToString()); return Alc.GetError(this.device_handle); } } #endregion public AlcError CurrentAlcError #endregion --- Public Members --- #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; } } ~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 } }