context: #region Copyright & License Information
  /*
   * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.FileFormats;
  using OpenRA.FileSystem;
  using OpenRA.GameRules;
  using OpenRA.Primitives;
  using OpenRA.Traits;
  using OpenTK;
  using OpenTK.Audio.OpenAL;
  
  namespace OpenRA
  {
  	class OpenAlSoundEngine : ISoundEngine
  	{
  		class PoolSlot
  		{
  			public bool IsActive;
  			public int FrameStarted;
  			public WPos Pos;
  			public bool IsRelative;
  			public ISoundSource Sound;
  		}
  
  		const int MaxInstancesPerFrame = 3;
  		const int GroupDistance = 2730;
  		const int GroupDistanceSqr = GroupDistance * GroupDistance;
  		const int PoolSize = 32;
  
  		float volume = 1f;
  		Dictionary<int, PoolSlot> sourcePool = new Dictionary<int, PoolSlot>();
  
  		static string[] QueryDevices(string label, AlcGetStringList type)
  		{
  			// Clear error bit
  			AL.GetError();
  
  			var devices = Alc.GetString(IntPtr.Zero, type).ToArray();
  			if (AL.GetError() != ALError.NoError)
  			{
  				Log.Write("sound", "Failed to query OpenAL device list using {0}", label);
  				return new string[] { };
  			}
  
  			return devices;
  		}
  
  		public static string[] AvailableDevices()
  		{
  			// Returns all devices under Windows Vista and newer
  			if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATE_ALL_EXT"))
  				return QueryDevices("ALC_ENUMERATE_ALL_EXT", AlcGetStringList.AllDevicesSpecifier);
  
  			if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT"))
  				return QueryDevices("ALC_ENUMERATION_EXT", AlcGetStringList.DeviceSpecifier);
  
  			return new string[] { };
  		}
  
  		public OpenAlSoundEngine()
  		{
  			Console.WriteLine("Using OpenAL sound engine");
  
  			if (Game.Settings.Sound.Device != null)
  				Console.WriteLine("Using device `{0}`", Game.Settings.Sound.Device);
  			else
  				Console.WriteLine("Using default device");
  
  			var dev = Alc.OpenDevice(Game.Settings.Sound.Device);
  			if (dev == IntPtr.Zero)
  			{
  				Console.WriteLine("Failed to open device. Falling back to default");
  				dev = Alc.OpenDevice(null);
  				if (dev == IntPtr.Zero)
  					throw new InvalidOperationException("Can't create OpenAL device");
  			}
  
  			var ctx = Alc.CreateContext(dev, (int[])null);
  			if (ctx == ContextHandle.Zero)
  				throw new InvalidOperationException("Can't create OpenAL context");
  			Alc.MakeContextCurrent(ctx);
  
  			for (var i = 0; i < PoolSize; i++)
  			{
  				var source = 0;
  				AL.GenSources(1, out source);
  				if (0 != AL.GetError())
  				{
  					Log.Write("sound", "Failed generating OpenAL source {0}", i);
  					return;
  				}
  
  				sourcePool.Add(source, new PoolSlot() { IsActive = false });
  			}
  		}
  
  		int GetSourceFromPool()
  		{
  			foreach (var kvp in sourcePool)
  			{
  				if (!kvp.Value.IsActive)
  				{
  					sourcePool[kvp.Key].IsActive = true;
  					return kvp.Key;
  				}
  			}
  
  			var freeSources = new List<int>();
  			foreach (var key in sourcePool.Keys)
  			{
  				int state;
  				AL.GetSource(key, ALGetSourcei.SourceState, out state);
  				if (state != (int)ALSourceState.Playing && state != (int)ALSourceState.Paused)
  					freeSources.Add(key);
  			}
  
  			if (freeSources.Count == 0)
  				return -1;
  
  			foreach (var i in freeSources)
  				sourcePool[i].IsActive = false;
  
  			sourcePool[freeSources[0]].IsActive = true;
  
  			return freeSources[0];
  		}
  
  		public ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate)
  		{
  			return new OpenAlSoundSource(data, channels, sampleBits, sampleRate);
  		}
  
  		public ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume)
  		{
  			if (sound == null)
  			{
  				Log.Write("sound", "Attempt to Play2D a null `ISoundSource`");
  				return null;
  			}
  
  			var currFrame = Game.orderManager.LocalFrameNumber;
  			var atten = 1f;
  
  			// Check if max # of instances-per-location reached:
  			if (attenuateVolume)
  			{
  				int instances = 0, activeCount = 0;
  				foreach (var s in sourcePool.Values)
  				{
  					if (!s.IsActive)
  						continue;
  					if (s.IsRelative != relative)
  						continue;
  
  					++activeCount;
  					if (s.Sound != sound)
  						continue;
  					if (currFrame - s.FrameStarted >= 5)
  						continue;
  
  					// Too far away to count?
  					var lensqr = (s.Pos - pos).LengthSquared;
  					if (lensqr >= GroupDistanceSqr)
  						continue;
  
  					// If we are starting too many instances of the same sound within a short time then stop this one:
  					if (++instances == MaxInstancesPerFrame)
  						return null;
  				}
  
  				// Attenuate a little bit based on number of active sounds:
  				atten = 0.66f * ((PoolSize - activeCount * 0.5f) / PoolSize);
  			}
  
  			var source = GetSourceFromPool();
  			if (source == -1)
  				return null;
  
  			var slot = sourcePool[source];
  			slot.Pos = pos;
  			slot.FrameStarted = currFrame;
  			slot.Sound = sound;
  			slot.IsRelative = relative;
  			return new OpenAlSound(source, ((OpenAlSoundSource)sound).Buffer, loop, relative, pos, volume * atten);
  		}
  
  		public float Volume
  		{
  			get { return volume; }
  			set { AL.Listener(ALListenerf.Gain, volume = value); }
  		}
  
  		public void PauseSound(ISound sound, bool paused)
  		{
  			if (sound == null)
  				return;
  
  			var key = ((OpenAlSound)sound).Source;
  			int state;
  			AL.GetSource(key, ALGetSourcei.SourceState, out state);
  			if (state == (int)ALSourceState.Playing && paused)
  				AL.SourcePause(key);
  
gt: 			else if (state == (int)ALSourceState.Paused && !paused)