// ARSC.cs
// This is the interface between the C# code and the ARSC API in 
// arsc.dll.

using System;
using System.Runtime.InteropServices;
using System.IO;
using int32 = System.Int32;														// like a typedef
using real64 = System.Double;						

namespace WaveLib
{
	/// <summary>
	/// Summary description for ARSC.
	/// </summary>
	public unsafe class ARSC
	{
		static ARSC()
		{
			// constructor logic here
#if DBUG
			System.Security.Principal.WindowsIdentity id = System.Security.Principal.WindowsIdentity.GetCurrent();
			string strFile = @"C:\HAL2.txt";
			//strFile = @"U:\Rotolo\HAL2_"+id.Name.Replace(@"BTNRH\","")+".txt";
			fsDbug = new FileStream(strFile, FileMode.Create, FileAccess.Write);
			swDbug = new StreamWriter(fsDbug);
			swDbug.AutoFlush = true;
#endif

		}

		public class ARSCException : System.Exception
		{
			public ARSCException(string auxMessage, Exception inner ) :
				base( String.Format( "{0}",auxMessage ), inner )
			{
				this.HelpLink = "ARSC Library Error";
				this.Source = "ARSC Library Error";
			}
		}

		public static void ARSCWrite ( string astrFormat, params object[] aobj_ ) 
		{
			// This function allows passing of multiple arguments so that I can
			// emulate the String.Format() functionality.
			//
			// By having this as a separate function, I can turn it off with a
			// compiler directive.
#if DBUG
			swDbug.Write ( astrFormat, aobj_ );
			Console.Write (astrFormat, aobj_);
#endif
			
		}

		public static string ARSCGetDeviceName ( int32 aintDevice ) 
		{
			// Wrapper for C# to get the device name.  This automatically
			// converts to a C# string.
			byte[] bytDeviceName = new byte[ARSC_NAMLEN];
			ar_dev_name ( aintDevice, bytDeviceName, bytDeviceName.Length );
			string str = pByteArrayToString ( bytDeviceName );
			Console.WriteLine ( str );
			return str;
		}

		public static int32 ARSCOpenSoundCard (	real64 adblSampleRate,			// Sample rate, e.g. 44100.0
												int32 aintInputChannels,		// Input channels, e.g. 0 for playback, 2 for stereo record
												int32 aintOutputChannels,		// Output channels
												int32 aintBps,					// Application Bytes per sample.  Typically 4 for 32 bit integers.
												int32 aintInterleaved )			// Application Interleave, i.e. the data sent to ARSC is interleaved.
		{
			// Opens an ARSC soundcard; returns the device id or -1 if failed.

			int32	intDevice;
			int32[]	intFormat = new int32[2] { aintBps, aintInterleaved };

			try 
			{
				// Get the appropriate device number.  I do this in a try/catch
				// to ensure the DLL was found.
				intDevice = ARSC.ar_find_dev ( ARSC.ARSC_PREF_NONE );
				if ( intDevice < 0 ) return intDevice;
			}
			catch ( Exception err )
			{
				Console.WriteLine ( "ARSC Error: " + err.Message );
				return -1;
			}
			
			// Gets the device name
			strArscDeviceName = ARSC.ARSCGetDeviceName ( intDevice );

			// MME code loops and tries to open each device until one works.
			// I assume ar_find_dev() did its job.
			ARSC.intArscError = ARSC.ar_io_open ( intDevice, adblSampleRate, aintInputChannels, aintOutputChannels ); 
			if ( ARSC.intArscError != 0 ) throw ( new ARSC.ARSCException ( "Error on Device Open", null ) );

			// Set the format
			ARSC.intArscError = ARSC.ar_set_fmt ( intDevice, intFormat );
			if ( ARSC.intArscError != 0 ) throw ( new ARSC.ARSCException ( "Error on Set Format", null ) );

			return intDevice;

		}
		
		public static int32 ARSCOpenSoundCard (	real64 adblSampleRate,			// Sample rate, e.g. 44100.0
			int32 aintInputChannels,		// Input channels, e.g. 0 for playback, 2 for stereo record
			int32 aintOutputChannels,		// Output channels
			int32 aintBps,					// Application Bytes per sample.  Typically 4 for 32 bit integers.
			int32 aintInterleaved, 			// Application Interleave, i.e. the data sent to ARSC is interleaved.
			int32 aintDevice )				//device to be opened
		{
			// Opens an ARSC soundcard; returns the device id or -1 if failed.

			int32	intDevice;
			int32[]	intFormat = new int32[2] { aintBps, aintInterleaved };

			/*try 
			{
				// Get the appropriate device number.  I do this in a try/catch
				// to ensure the DLL was found.
				intDevice = ARSC.ar_find_dev ( ARSC.ARSC_PREF_SYNC );
				if ( intDevice < 0 ) return intDevice;
			}
			catch ( Exception err )
			{
				Console.WriteLine ( "ARSC Error: " + err.Message );
				return -1;
			}*/
			intDevice = aintDevice;
			// Gets the device name
			strArscDeviceName = ARSC.ARSCGetDeviceName ( intDevice );

			// MME code loops and tries to open each device until one works.
			// I assume ar_find_dev() did its job.
			ARSC.intArscError = ARSC.ar_io_open ( intDevice, adblSampleRate, aintInputChannels, aintOutputChannels ); 
			if ( ARSC.intArscError != 0 ) throw ( new ARSC.ARSCException ( "Error on Device Open", null ) );

			// Set the format
			ARSC.intArscError = ARSC.ar_set_fmt ( intDevice, intFormat );
			if ( ARSC.intArscError != 0 ) throw ( new ARSC.ARSCException ( "Error on Set Format", null ) );

			return intDevice;

		}
		private static string pByteArrayToString ( byte[] abytArray ) 
		{
			// Converst a byte array to a common string
			System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
			return enc.GetString ( abytArray );
		}


		#region Dll Imports

		// http://support.microsoft.com/?kbid=321078
		//[DllImport("arsc.dll", EntryPoint="_ar_dev_name@12")] public static extern int32 ar_dev_name ( int32 dev, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpName, int32 len );
		[DllImport("arsc.dll", EntryPoint="_ar_dev_name@12")] public static extern int32 ar_dev_name ( int32 dev, byte[] name, int32 len );
		[DllImport("arsc.dll", EntryPoint="_ar_err_msg@12")] public static extern int32 ar_err_msg ( int32 code, byte[] message, int32 len );
		[DllImport("arsc.dll", EntryPoint="_ar_find_dev@4")] public extern static int32 ar_find_dev ( int32 flags );
		[DllImport("arsc.dll", EntryPoint="_ar_find_dev_name@4")] public static extern int32 ar_find_dev_name ( string name );
		[DllImport("arsc.dll", EntryPoint="_ar_get_rate@4")] public static extern real64 ar_get_rate ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_io_close@4")] public static extern int32 ar_io_close ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_io_cur_seg@4")] public static extern int32 ar_io_cur_seg ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_io_open@20")] public static extern int32 ar_io_open ( int32 dev, real64 rate, int32 in_chan, int32 out_chan );
		[DllImport("arsc.dll", EntryPoint="_ar_io_prepare@24")] public static extern int32 ar_io_prepare ( int32 dev, int32[] in_data, int32[] out_data, int32[] size, int32 nseg, int32 nswp );
		[DllImport("arsc.dll", EntryPoint="_ar_out_open@16")] public static extern int32 ar_out_open ( int32 dev, real64 rate, int32 out_chan );
		[DllImport("arsc.dll", EntryPoint="_ar_out_prepare@20")] public static extern int32 ar_out_prepare ( int32 dev, int32[] out_data, int32[] size, int32 nseg, int32 nswp );
		[DllImport("arsc.dll", EntryPoint="_ar_out_prepare_vb@20")] public static extern int32 ar_out_prepare_vb ( int32 dev, byte[] out_data, int32[] size, int32 nseg, int32 nswp );
		[DllImport("arsc.dll", EntryPoint="_ar_io_prepare_vb@24")] public static extern int32 ar_io_prepare_vb ( int32 dev, byte *in_data, byte *out_data, int32[] size, int32 nseg, int32 nswp );
		[DllImport("arsc.dll", EntryPoint="_ar_out_prepare_vb@20")]  public  static extern int32 ar_out_prepare_vb_byte ( int32 dev, byte *out_data, int32[] size, int32 nseg, int32 nswp );	// Easier to use with memory streams
		[DllImport("arsc.dll", EntryPoint="_ar_io_start@4")] public static extern int32 ar_io_start ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_io_stop@4")] public static extern int32 ar_io_stop ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_io_wait_seg@4")] public static extern int32 ar_io_wait_seg ( int32 dev );
		[DllImport("arsc.dll", EntryPoint="_ar_set_fmt@8")] public static extern int32 ar_set_fmt ( int32 dev, int32[] fmt );
		[DllImport("arsc.dll", EntryPoint="_fill_tone@32")] public static extern int32 fill_tone ( int32 *ptrBlock, int32 aintBlockSize, real64 adblDesiredFrequency, real64 aSAMPLE_RATE, real64 adblAmplitude );
		[DllImport("arsc.dll", EntryPoint="_ar_set_xfer_stdcall@12" )] public static extern int32 ar_set_xfer_stdcall ( int32 dev, XferDelegate fnIn, XferDelegate fnOut );
		[DllImport("arsc.dll", EntryPoint="_ar_num_devs@0")] public static extern int32 ar_num_devs (  );

#if GCHANDLE
		[DllImport("arsc.dll", EntryPoint="_ar_io_prepare_vb@24")] public static extern int32 ar_io_prepare_vb_GCHandle ( int32 dev, byte *in_data, IntPtr out_data, int32[] size, int32 nseg, int32 nswp );	// Easier to use with memory streams
#else
		[DllImport("arsc.dll", EntryPoint="_ar_io_prepare_vb@24")] public static extern int32 ar_io_prepare_vb_byte ( int32 dev, byte *in_data, byte *out_data, int32[] size, int32 nseg, int32 nswp );	// Easier to use with memory streams
#endif

		#endregion Dll Imports

		#region Constants
		public const int32	ARSC_PREF_NONE = 0;
		public const int32	ARSC_PREF_SYNC = 1;
		public const int32	ARSC_PREF_ASIO = 2;
		public const int32	ARSC_NAMLEN = 40;
		public const int32	SCALE_32BIT = 2147483647;							// 2^31 - 1 (it swings -/+ for 2^32)
		public const int32  SCALE_31BIT = 1073741824;
		public const int32	SCALE_24BIT = 8388607;								// 2^23 - 1 (it swings -/+ for 2^24)
		public const int32  SCALE_23BIT = 4144304;
		#endregion Constants

		#region Delegates

		// Delegates are safe function pointers
		// Aparently, they are time consuming.  See:
		// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/fastmanagedcode.asp
		public delegate void XferDelegate ( int32 di );

		#endregion Delegates

		#region ArscGlobals

		public static int32 intArscError = 0;									// Error code returned from an ARSC function
		public static string strArscDeviceName;									// Name of the ARSC device

#if DBUG
		// These are for dumping data to an external file
		public static FileStream fsDbug;
		public static StreamWriter swDbug;
#endif
		#endregion ArscGlobals

	}

}
