context: //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
  //  REMAINS UNCHANGED.
  //
  //  Email:  gustavo_franco@hotmail.com
  //
  //  Copyright (C) 2005 Franco, Gustavo 
  //
  using System;
  using System.Diagnostics;
  using System.Windows.Forms;
  using System.Runtime.InteropServices;
  
  using WaveLib.WaveServices;
  
  namespace WaveLib.AudioMixer
  {
  	#region Aliases
  	using HMIXER	= System.IntPtr;
  	using HWAVEIN	= System.IntPtr;
  	using HWAVEOUT	= System.IntPtr;
  	#endregion
  
  	#region Enum
  	public enum MixerType 
  	{
  		Playback	= 0,
  		Recording	= 1
  	}
  	#endregion
  
  	#region Delegates
  	internal delegate void CallbackWindowControlChangeHandler(IntPtr hMixer, uint controlId);
  	internal delegate void CallbackWindowLineChangeHandler(IntPtr hMixer, uint lineId);
  	#endregion
  
  	[Author("Gustavo Franco")]
  	public class Mixer : IDisposable
  	{
  		#region Delegates
  		public delegate void MixerLineChangeHandler(Mixer mixer, MixerLine line);
  		#endregion
  
  		#region Events
  		public event MixerLineChangeHandler MixerLineChanged;
  		#endregion
  
  		#region Constants Declaration
  		#endregion
  
  		#region Variables Declaration
  		private MixerType		mMixerType;
  		private int				mDeviceId;
  		private IntPtr			mHMixer;
  		private MixerLines		mLines;
  		private MixerLines		mUserLines;
  		private CallbackWindow	mCallbackWindow; 
  		private CallbackWindowControlChangeHandler	mMixerControlChangeHandler;
  		private CallbackWindowLineChangeHandler		mMixerLineChangeHandler;
  		#endregion
  
  		#region Constructors
  		public Mixer(MixerType mixerType)
  		{
  			mMixerType					= mixerType;
  			mLines						= new MixerLines();
  			mUserLines					= new MixerLines();
  			mMixerControlChangeHandler	= new CallbackWindowControlChangeHandler(PtrMixerControlChange);
  			mMixerLineChangeHandler		= new CallbackWindowLineChangeHandler(PtrMixerLineChange);
  			mCallbackWindow				= new CallbackWindow(mMixerControlChangeHandler, mMixerLineChangeHandler);
  			DeviceId					= -1;
  		}
  		#endregion
  
  		#region Properties
  		internal IntPtr CallbackWindowHandle
  		{
  			get{return mCallbackWindow.Handle;}
  		}
  
  		#region MixerType
  		public MixerType MixerType
  		{
  			get{return mMixerType;}
  		}
  		#endregion
  
  		#region Lines
  		public MixerLines Lines
  		{
  			get{return mLines;}
  		}
  
  		public MixerLines UserLines
  		{
  			get{return mUserLines;}
  		}
  		#endregion
  
  		#region DeviceId
  		public int DeviceId
  		{
  			get{return mDeviceId;}
  			set
  			{
  				MMErrors errorCode = 0;
  
  				if (value == -1)
  					mDeviceId = DeviceIdDefault;
  				else
  					mDeviceId = value;
  
  				if (mHMixer != IntPtr.Zero)
  					MixerNative.mixerClose(mHMixer);
  
  				errorCode = (MMErrors) MixerNative.mixerOpen(out mHMixer, mDeviceId, mCallbackWindow.Handle, IntPtr.Zero, MixerNative.CALLBACK_WINDOW);
  				if (errorCode != MMErrors.MMSYSERR_NOERROR)
  					throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerOpen, errorCode));
  
  				ReloadLines();
  			}
  		}
  		#endregion
  
  		#region DeviceDetail
  		public MixerDetail DeviceDetail
  		{
  			get
  			{
  				foreach(MixerDetail mixerDetail in Devices)
  					if (mixerDetail.DeviceId == mDeviceId)
  						return mixerDetail;
  
  				return null;
  			}
  		}
  		#endregion
  
  		#region DeviceIdDefault
  		public int DeviceIdDefault
  		{
  			get
  			{
  				MMErrors		errorCode	= 0;
  				HWAVEOUT		hWave		= IntPtr.Zero;
  				HMIXER			hMixer		= IntPtr.Zero;
  				int				deviceId	= -1;
  
  				try
  				{
  					WAVEFORMATEX	waveFormat	= WAVEFORMATEX.Empty;
  
  					waveFormat.formatTag = WaveFormatTag.PCM;
  					waveFormat.nChannels = 1;
  					waveFormat.nSamplesPerSec = 8000;
  					waveFormat.wBitsPerSample = 8;
  					waveFormat.nBlockAlign = (short) (waveFormat.nChannels * (waveFormat.wBitsPerSample/8));
  					waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
  					waveFormat.cbSize = 0;
  
  					if (mMixerType == MixerType.Recording)
  						errorCode = (MMErrors) WaveNative.waveInOpen(out hWave, WaveNative.WAVE_MAPPER, ref waveFormat, IntPtr.Zero, IntPtr.Zero, (int) CallBackFlag.CALLBACK_NULL); 
  					else if (mMixerType == MixerType.Playback)
  						errorCode = (MMErrors) WaveNative.waveOutOpen(out hWave, WaveNative.WAVE_MAPPER, ref waveFormat, IntPtr.Zero, IntPtr.Zero, (int) CallBackFlag.CALLBACK_NULL); 
  					if (errorCode != MMErrors.MMSYSERR_NOERROR)
  						throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnWaveInOpen, errorCode));
  
  					if (mMixerType == MixerType.Recording)
  						errorCode = (MMErrors) MixerNative.mixerOpen(out hMixer, hWave, IntPtr.Zero, IntPtr.Zero, ((uint) MIXER_OBJECTFLAG.HWAVEIN));
  					else if (mMixerType == MixerType.Playback)
  						errorCode = (MMErrors) MixerNative.mixerOpen(out hMixer, hWave, IntPtr.Zero, IntPtr.Zero, ((uint) MIXER_OBJECTFLAG.HWAVEOUT));
  					if (errorCode != MMErrors.MMSYSERR_NOERROR)
  						throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerOpen, errorCode));
  
  					errorCode = (MMErrors) MixerNative.mixerGetID(hMixer, ref deviceId, MIXER_OBJECTFLAG.MIXER);
  					if (errorCode != MMErrors.MMSYSERR_NOERROR)
  						throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerGetID, errorCode));
  				}
  				finally
  				{
  					if (hMixer != IntPtr.Zero)
  						MixerNative.mixerClose(hMixer);
  					if (hWave != IntPtr.Zero && mMixerType == MixerType.Playback)
  						WaveNative.waveOutClose(hWave);
  					if (hWave != IntPtr.Zero && mMixerType == MixerType.Recording)
  						WaveNative.waveInClose(hWave);
  				}
  
  				return deviceId;
  			}
  		}
  		#endregion
  
  		#region Devices
  		public unsafe MixerDetails Devices
  		{
  			get
  			{
  				MMErrors		errorCode	= 0;
  				MixerDetails	mixDetails	= new MixerDetails();
  				HMIXER			hMixer		= IntPtr.Zero;  
  
  				try
  				{
  					MIXERCAPS	wic		= new MIXERCAPS();
  					
  					int iNumDevs = MixerNative.mixerGetNumDevs();
  
  					for (int i=0; i<iNumDevs; i++)
  					{
  						// Get info about the next device 
  						errorCode = (MMErrors) MixerNative.mixerGetDevCaps(i, ref wic, Marshal.SizeOf(wic));
  						if (errorCode != MMErrors.MMSYSERR_NOERROR)
  							throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerGetDevCaps, errorCode));
  
  						errorCode = (MMErrors) MixerNative.mixerOpen(out hMixer, i, IntPtr.Zero, IntPtr.Zero, 0);
  						if (errorCode != MMErrors.MMSYSERR_NOERROR)
  							throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerOpen, errorCode));
  
  						MixerDetail mixDetail	= new MixerDetail();
  						MIXERLINE	mxl			= new MIXERLINE();
  						
  						mxl.cbStruct = (uint) Marshal.SizeOf(mxl);
  						mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_WAVEIN;
  						if (MixerNative.mixerGetLineInfo(hMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE) == 0)
  						{
  							mixDetail.DeviceId		= i;
  							mixDetail.MixerName		= wic.szPname;
  							mixDetail.SupportWaveIn = true;
  						}
  
  						mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_SPEAKERS;
  						if (MixerNative.mixerGetLineInfo(hMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE) == 0)
  						{
  							mixDetail.DeviceId		= i;
  							mixDetail.MixerName		= wic.szPname;
  							mixDetail.SupportWaveOut= true;
  						}
  						else
  						{
  							mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_UNDEFINED;
  							if (MixerNative.mixerGetLineInfo(hMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE) == 0)
  							{
  								mixDetail.DeviceId		= i;
  								mixDetail.MixerName		= wic.szPname;
  								mixDetail.SupportWaveOut= true;
  							}
  						}
  
  						if (mMixerType == MixerType.Recording && mixDetail.SupportWaveIn == true)
  							mixDetails.Add(mixDetail);
  
  						if (mMixerType == MixerType.Playback && mixDetail.SupportWaveOut == true)
  							mixDetails.Add(mixDetail);
  					}
  				}
  				finally
  				{
  					if (hMixer != IntPtr.Zero)
  						MixerNative.mixerClose(hMixer);
  				}
  				return mixDetails;
  			}
  		}
  		#endregion
  		#endregion
  
  		#region Private Methods
  		private void ReloadLines()
  		{
  			MMErrors		errorCode	= 0;
  
  			mLines.Clear();
  			mUserLines.Clear();
  
  			MIXERLINE			mxl  = new MIXERLINE();
  			MIXERLINECONTROLS   mlc  = new MIXERLINECONTROLS();
  			MIXERCONTROL        mc   = new MIXERCONTROL();
  			uint dwDestination;
  			unchecked
  			{
  				dwDestination = (uint) -1;
  			}
  
  			mxl.cbStruct = (uint) Marshal.SizeOf(mxl);
  
  			if (mMixerType == MixerType.Recording)
  			{
  				mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_WAVEIN;
  				errorCode = (MMErrors) MixerNative.mixerGetLineInfo(mHMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE);
  				if (errorCode != MMErrors.MMSYSERR_NOERROR)
  					throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerGetLineInfo, errorCode));
  			}
  			else if (mMixerType == MixerType.Playback)
  			{
  				mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_SPEAKERS;
  				errorCode = (MMErrors) MixerNative.mixerGetLineInfo(mHMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE);
  				if (errorCode != MMErrors.MMSYSERR_NOERROR)
  				{
  					mxl.dwComponentType = MIXERLINE_COMPONENTTYPE.DST_UNDEFINED;
  					errorCode = (MMErrors) MixerNative.mixerGetLineInfo(mHMixer, ref mxl, MIXER_GETLINEINFOF.COMPONENTTYPE);
  					if (errorCode != MMErrors.MMSYSERR_NOERROR)
  						throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerGetLineInfo, errorCode));
  				}
  			}
  
  			dwDestination = mxl.dwDestination;
  
  			MixerLine mixLine = new MixerLine();
  
  			if (mMixerType == MixerType.Recording)
  				mixLine.Direction	= MixerType.Recording;
  			else if (mMixerType == MixerType.Playback)
  				mixLine.Direction	= MixerType.Playback;
  
  			mixLine.Mixer			= this;
  			mixLine.Channels		= mxl.cChannels;
  			mixLine.CControls		= mxl.cControls;
  			mixLine.Connections		= mxl.cConnections;
  			mixLine.Flag			= mxl.fdwLine;
  			mixLine.Destination		= dwDestination;
  			mixLine.Name			= mxl.szName;
  			mixLine.Id				= mxl.dwLineID;
  			mixLine.ComponentType	= mxl.dwComponentType;
  			mixLine.Source			= mxl.dwSource;
  			mixLine.HMixer			= mHMixer;
  
  			if (mixLine.CControls != 0 && !(mixLine.CControls == 1 && mixLine.Controls[0].Type == MIXERCONTROL_CONTROLTYPE.MUX))
  				mUserLines.Add(mixLine);
  			mLines.Add(mixLine);
  
  			int cConnections = (int) mxl.cConnections;
  			for(int i=0; i<cConnections; i++) 
  			{
  				mxl.cbStruct = (uint) Marshal.SizeOf(mxl);
  				mxl.dwDestination = dwDestination;
  				mxl.dwSource = (uint) i;
  
  				errorCode = (MMErrors) MixerNative.mixerGetLineInfo(mHMixer, ref mxl, MIXER_GETLINEINFOF.SOURCE);
  				if (errorCode != MMErrors.MMSYSERR_NOERROR)
  					throw new MixerException(errorCode, Mixers.GetErrorDescription(FuncName.fnMixerGetLineInfo, errorCode));
  
  
gt: 				MixerLine mixLineNew = new MixerLine();