using System;
using NAudio.Wave;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HTRA_CSharp_Examples
{
	//NAudioPlay类中主要包含了后续DSP_FMDemod与DSP_AMDemod中使用的音频数据处理函数
	public class NAudioPlay
	{
		private BufferedWaveProvider bufferedWaveProvider; //声明对象从内存中向缓冲区读取音频数据
		private WaveOutEvent waveOutEvent;                 //声明对象播放从BufferedWaveProvider获取的音频数据

		public NAudioPlay(int sampleRate, int channelCount) //参数含义：采样率和通道数
		{
			bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(sampleRate, channelCount)); //创建一个新的BufferedWaveProvider实例赋给bufferedWaveProvider，规定音频格式为WaveFormat(sampleRate, channelCount)，确定采样率和通道数
			bufferedWaveProvider.DiscardOnBufferOverflow = true; //如果缓冲区溢出，则丢失数据

			waveOutEvent = new WaveOutEvent();		 //实例化播放对象并赋给waveOutEvent
			waveOutEvent.Init(bufferedWaveProvider); //读入缓冲区解调数据
		}

		//开始播放音频
		public void StartPlay()
		{
			waveOutEvent.Play();
		}

		//停止播放音频
		public void StopPlay()
		{
			waveOutEvent.Stop();
		}

		//缩放值判断
		public static short Clamp(short value, short min, short max)  //判断经过缩放的数据是否在min到max之间
		{
			return (value < min) ? min : (value > max) ? max : value; //如果小于min则返回min，大于max返回max，保证我们的值一直在min和max之间
		}

		//Float类型转为short
		private short[] FloatAudioToByteAudio(float[] floatAudioData)
		{
			//因为我们是使用16位PCM，所以需要将float类型先缩放至short范围，强制转换为short类型，然后再转为byte用2个字节表示，直接从float转为byte会导致数据范围和精度严重损失，转为short能保持合理范围和精度的同时，与音频处理接口兼容
			short[] shortAudioData = new short[floatAudioData.Length];
			for (int i = 0; i < floatAudioData.Length; i++) //数据转换，将float数据进行缩放和强制转换，转为short类型
			{
				float Audio32 = floatAudioData[i] * 32767f;
				short Audio16 = (short)Audio32;
				Audio16 = Clamp(Audio16, -32768, 32767);
				shortAudioData[i] = Audio16;
			}
			return shortAudioData;
		}

		//将解调后的数据存放到bufferedWaveProvider中
		public void AddAudioData(float[] audioData)
		{
			//NAudio的BufferedWaveProvider一般需要byte的数据，需要将float[]转为byte[]，这里我们使用16位PCM，一般音频处理只需要将float转为16位有效整数就可以使用，但是Naudio需要使用byte
			byte[] ChangeAudioData = new byte[audioData.Length * 2];										   //转为2个字节
			Buffer.BlockCopy(FloatAudioToByteAudio(audioData), 0, ChangeAudioData, 0, ChangeAudioData.Length); //将short数组中的一系列字节复制到byte数组中，参数含义：原数组，原数组起始位置，目标数组，目标数组起始位置，复制的字节数，byte的长度是short的两倍，防止转移之后溢出，长度用byte的
			bufferedWaveProvider.AddSamples(ChangeAudioData, 0, ChangeAudioData.Length);					   //转为byte的数据输入缓冲区
		}

		//清理资源
		public void Dispose()
		{
			waveOutEvent.Stop();
			waveOutEvent.Dispose();
		}
	}

	//范例函数
	class Demodulation
	{
		public void DSP_FMDemod()
		{
			#region 1 打开设备

			int Status = 0;              //函数的返回。
			IntPtr Device = IntPtr.Zero; //当前设备的内存地址。
			int DevNum = 0;              //指定设备号。

			HtraApi.BootProfile_TypeDef BootProfile = new HtraApi.BootProfile_TypeDef(); //启动配置结构体，包括物理接口、供电方式等。
			HtraApi.BootInfo_TypeDef BootInfo = new HtraApi.BootInfo_TypeDef();          //启动信息结构体，包括设备信息、USB速率等。

			BootProfile.DevicePowerSupply = HtraApi.DevicePowerSupply_TypeDef.USBPortAndPowerPort; //使用USB数据端口及独立电源端口双供电。

			//设备数据接口为USB时直接运行，为网口时将 #if true 改为 #if false
#if true
			BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.USB;
#else
			//配置网络相关参数
			BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.ETH;
			BootProfile.ETH_IPVersion = HtraApi.IPVersion_TypeDef.IPv4;
			BootProfile.ETH_RemotePort = 5000;
			BootProfile.ETH_ReadTimeOut = 5000;
			BootProfile.ETH_IPAddress = new byte[16];
			BootProfile.ETH_IPAddress[0] = 192;
			BootProfile.ETH_IPAddress[1] = 168;
			BootProfile.ETH_IPAddress[2] = 1;
			BootProfile.ETH_IPAddress[3] = 100;
#endif

			Status = HtraApi.Device_Open(ref Device, DevNum, ref BootProfile, ref BootInfo); //打开设备

			if (Status == 0)
			{
				System.Console.WriteLine("Device is opened successfully");
			}

			/*设备打开失败，返回错误提示，发生以下错误时，设备无法正常运行，建议按照提示操作后重新打开设备*/
			else
			{
				switch (Status)
				{
					case HtraApi.APIRETVAL_ERROR_BusOpenFailed:
						System.Console.WriteLine("Error - Check the device power supply, data cable connection and driver installation");
						return;

					case HtraApi.APIRETVAL_ERROR_RFACalFileIsMissing:
						System.Console.WriteLine("Error - RF calibration file is missing");
						return;

					case HtraApi.APIRETVAL_ERROR_IFACalFileIsMissing:
						System.Console.WriteLine("Error - IF calibration file is missing");
						return;

					case HtraApi.APIRETVAL_ERROR_DeviceConfigFileIsMissing:
						System.Console.WriteLine("Error - Configuration file missing");
						return;

					case HtraApi.APIRETVAL_ERROR_DeviceSpecFileIsMissing:
						System.Console.WriteLine("Error - Device specification file is missing");
						return;

					default:
						System.Console.WriteLine("Return other errors！ Status = " + Status);
						return;
				}
			}
			#endregion

			#region 2 配置参数
			HtraApi.IQStream_TypeDef IQStream = new HtraApi.IQStream_TypeDef();               //存放IQ数据包数据，包括IQ数据、配置信息等。
			HtraApi.IQS_Profile_TypeDef IQS_ProfileIn = new HtraApi.IQS_Profile_TypeDef();    //IQS输入配置，包括起始频率、终止频率、RBW、参考电平等。
			HtraApi.IQS_Profile_TypeDef IQS_ProfileOut = new HtraApi.IQS_Profile_TypeDef();   //IQS输出配置。
			HtraApi.IQS_StreamInfo_TypeDef StreamInfo = new HtraApi.IQS_StreamInfo_TypeDef(); //当前配置下IQ数据信息，包括带宽,IQ单路采样率等。

			HtraApi.IQS_ProfileDeInit(ref Device, ref IQS_ProfileIn); //初始化配置IQS模式下的相关参数。

			IQS_ProfileIn.CenterFreq_Hz = 101.1e6;                                //配置中心频率（此处也就是电台频率）。
			IQS_ProfileIn.RefLevel_dBm = -40;                                      //配置参考电平。
			IQS_ProfileIn.DecimateFactor = 512;                                  //配置抽取倍数。
			IQS_ProfileIn.DataFormat = HtraApi.DataFormat_TypeDef.Complex16bit;  //配置IQ数据格式。
			IQS_ProfileIn.TriggerMode = HtraApi.TriggerMode_TypeDef.Adaptive;    //配置触发模式。
			IQS_ProfileIn.TriggerSource = HtraApi.IQS_TriggerSource_TypeDef.Bus; //配置触发源为内部总线触发。
			IQS_ProfileIn.BusTimeout_ms = 7000;                                  //配置Bus超时时间。

			Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo); //下发IQS模式的相关配置。
			if (Status == 0)
			{
				System.Console.WriteLine("configuration delievery succeeded");
			}
			else
			{
				System.Console.WriteLine("SWP_Configuration call is incorrect,Status = " + Status);
				HtraApi.Device_Close(ref Device);
			}

			//Demod
			IntPtr AnalogMod = IntPtr.Zero;  //此参数为API存储调制解调相关信息的内存地址。
			HtraApi.ADM_Open(ref AnalogMod); //打开调制解调功能，函数将返回当前调制解调的内存地址。后续在调用其他API时，必须通过此地址参数来调用其他函数。

			float[] audio = new float[StreamInfo.PacketSamples];     //开辟内存用于存放解调后的音频数据
			float[] LPF_audio = new float[StreamInfo.PacketSamples]; //开辟内存用于存放解调并且低通滤波的音频数据

			//DSP
			IntPtr DSP = IntPtr.Zero;  //此参数为API存储数字下变频相关信息的内存地址。
			HtraApi.DSP_Open(ref DSP); //打开数字下变频功能，函数将返回当前数字下变频的内存地址。后续在调用其他API时，必须通过此地址参数来调用其他函数。

			//低通滤波器配置
			HtraApi.Filter_TypeDef filter = new HtraApi.Filter_TypeDef(); //配置低通滤波器，用于对音频数据进行低通滤波
			filter.As = 90;												  //配置滤波器的阻带衰减
			filter.fc = 0.025f;											  //配置滤波器截止频率
			filter.n = 90;												  //配置滤波器阶数
			filter.mu = 0;												  //配置分数采样偏移量

			HtraApi.DSP_LPF_DeInit(ref filter);
			HtraApi.DSP_LPF_Configuration(ref DSP, ref filter, ref filter);

			NAudioPlay audioPlay = new NAudioPlay(240000, 1); //配置采样率和通道数512抽下采样率为240kHz，我们采取单通道也就是单声道
			audioPlay.StartPlay();                            //开始音频播放，缓冲区有数据后直接播放
			#endregion

			#region 3 数据采集
			Status = HtraApi.IQS_BusTriggerStart(ref Device); //调用IQS_BusTriggerStart触发设备。若触发源为外部触发，则不需要调用此函数

			//循环获取并播放FM广播
			while (true)
			{

				Status = HtraApi.IQS_GetIQStream_PM1(ref Device, ref IQStream);							   //获取IQ数据包
				HtraApi.ADM_FMDemod(ref AnalogMod, IQStream.AlternIQStream, IQStream.IQS_Profile.DataFormat, StreamInfo.PacketSamples, IQStream.IQS_StreamInfo.IQSampleRate, false, audio);
				if (Status == 0)
				{
					for (int i = 0; i < StreamInfo.PacketSamples; i++)
					{
						audio[i] /= 280000;
						//保证获取到的数据位于-1到1之间，-1到1之间是音频处理中浮点表示的常见约定,可以自己调节这个数据，但是如果过大会导致运算后的数据过小，缩放为short时可能会丢失太多数据，导致没有声音，如果过小会导致噪声过大，听不到电台声音
					}
					HtraApi.DSP_LPF_Execute_Real(ref DSP, audio, LPF_audio); //对音频数据进行低通滤波
					audioPlay.AddAudioData(LPF_audio);                       //播放音频数据
				}

				else
				{
					/*获取数据失败时，返回错误提示，发生以下错误时，建议按照提示进行操作*/
					switch (Status)
					{
						case HtraApi.APIRETVAL_ERROR_BusError:  //IQS_GetIQStream返回-8时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Error - Bus communication error");
							Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo);
							break;

						case HtraApi.APIRETVAL_ERROR_BusDataError: //IQS_GetIQStream返回-9时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Error - The data content is incorrect");
							Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo);
							break;

						case HtraApi.APIRETVAL_WARNING_BusTimeOut: //IQS_GetIQStream返回-10时，建议检查触发源是否正常触发，再进行获取
							System.Console.WriteLine("Warning - Get data timed out, check if the trigger source is triggered normally");
							break;

						case HtraApi.APIRETVAL_WARNING_IFOverflow: //IQS_GetIQStream返回-12时，建议重新配置参数，再进行获取
							System.Console.WriteLine("WARNING - IF saturation is recommended to be reconfigured, reference level <= signal power");
							break;

						case HtraApi.APIRETVAL_WARNING_ReconfigurationIsRecommended: //IQS_GetIQStream返回-14时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Warning - The current device temperature has changed significantly relative to the configured temperature, and it is recommended to reconfigure \n");
							break;

						case HtraApi.APIRETVAL_WARNING_ClockUnlocked_SYSCLK: //IQS_GetIQStream返回-15时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Warning - There may be an anomaly in the hardware status of the device and reconfiguration is recommended\n");
							break;
						default:
							break;
					}
					System.Console.WriteLine("Status = " + Status);
				}

			}
			#endregion

			#region 4 关闭
			Status = HtraApi.IQS_BusTriggerStop(ref Device); //调用IQS_BusTriggerStop停止触发设备。若触发源为外部触发，则不需要调用此函数。
			audioPlay.StopPlay();							 //停止音频播放
			audioPlay.Dispose();							 //清理资源
			HtraApi.DSP_Close(ref DSP);						 //释放DDC分配的所有资源
			HtraApi.ADM_Close(ref AnalogMod);				 //释放调制解调分配的所有资源
			HtraApi.Device_Close(ref Device);				 //释放htra_api分配的所有资源
			#endregion
		}

		public void DSP_AMDemod()
		{
			#region 1 打开设备

			int Status = 0;              //函数的返回。
			IntPtr Device = IntPtr.Zero; //当前设备的内存地址。
			int DevNum = 0;              //指定设备号。

			HtraApi.BootProfile_TypeDef BootProfile = new HtraApi.BootProfile_TypeDef(); //启动配置结构体，包括物理接口、供电方式等。
			HtraApi.BootInfo_TypeDef BootInfo = new HtraApi.BootInfo_TypeDef();          //启动信息结构体，包括设备信息、USB速率等。

			BootProfile.DevicePowerSupply = HtraApi.DevicePowerSupply_TypeDef.USBPortAndPowerPort; //使用USB数据端口及独立电源端口双供电。

			//设备数据接口为USB时直接运行，为网口时将 #if true 改为 #if false
#if true
			BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.USB;
#else
			//配置网络相关参数
			BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.ETH;
			BootProfile.ETH_IPVersion = HtraApi.IPVersion_TypeDef.IPv4;
			BootProfile.ETH_RemotePort = 5000;
			BootProfile.ETH_ReadTimeOut = 5000;
			BootProfile.ETH_IPAddress = new byte[16];
			BootProfile.ETH_IPAddress[0] = 192;
			BootProfile.ETH_IPAddress[1] = 168;
			BootProfile.ETH_IPAddress[2] = 1;
			BootProfile.ETH_IPAddress[3] = 100;
#endif

			Status = HtraApi.Device_Open(ref Device, DevNum, ref BootProfile, ref BootInfo); //打开设备

			if (Status == 0)
			{
				System.Console.WriteLine("Device is opened successfully");
			}

			/*设备打开失败，返回错误提示，发生以下错误时，设备无法正常运行，建议按照提示操作后重新打开设备*/
			else
			{
				switch (Status)
				{
					case HtraApi.APIRETVAL_ERROR_BusOpenFailed:
						System.Console.WriteLine("Error - Check the device power supply, data cable connection and driver installation");
						return;

					case HtraApi.APIRETVAL_ERROR_RFACalFileIsMissing:
						System.Console.WriteLine("Error - RF calibration file is missing");
						return;

					case HtraApi.APIRETVAL_ERROR_IFACalFileIsMissing:
						System.Console.WriteLine("Error - IF calibration file is missing");
						return;

					case HtraApi.APIRETVAL_ERROR_DeviceConfigFileIsMissing:
						System.Console.WriteLine("Error - Configuration file missing");
						return;

					case HtraApi.APIRETVAL_ERROR_DeviceSpecFileIsMissing:
						System.Console.WriteLine("Error - Device specification file is missing");
						return;

					default:
						System.Console.WriteLine("Return other errors！ Status = " + Status);
						return;
				}
			}
			#endregion

			#region 2 配置参数
			HtraApi.IQStream_TypeDef IQStream = new HtraApi.IQStream_TypeDef();				  //存放IQ数据包数据，包括IQ数据、配置信息等。
			HtraApi.IQS_Profile_TypeDef IQS_ProfileIn = new HtraApi.IQS_Profile_TypeDef();    //IQS输入配置，包括起始频率、终止频率、RBW、参考电平等。
			HtraApi.IQS_Profile_TypeDef IQS_ProfileOut = new HtraApi.IQS_Profile_TypeDef();   //IQS输出配置。
			HtraApi.IQS_StreamInfo_TypeDef StreamInfo = new HtraApi.IQS_StreamInfo_TypeDef(); //当前配置下IQ数据信息，包括带宽,IQ单路采样率等。

			HtraApi.IQS_ProfileDeInit(ref Device, ref IQS_ProfileIn); //初始化配置IQS模式下的相关参数。

			IQS_ProfileIn.CenterFreq_Hz = 97.6e6;                                //配置中心频率。
			IQS_ProfileIn.RefLevel_dBm = -40;                                    //配置参考电平。
			IQS_ProfileIn.DecimateFactor = 512;                                  //配置抽取倍数。
			IQS_ProfileIn.DataFormat = HtraApi.DataFormat_TypeDef.Complex16bit;  //配置IQ数据格式。
			IQS_ProfileIn.TriggerMode = HtraApi.TriggerMode_TypeDef.Adaptive;    //配置触发模式。
			IQS_ProfileIn.TriggerSource = HtraApi.IQS_TriggerSource_TypeDef.Bus; //配置触发源为内部总线触发。
			IQS_ProfileIn.BusTimeout_ms = 50000;                                 //配置Bus超时时间。

			Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo); //通过调用此函数下发IQS模式的相关配置。
			if (Status == 0)
			{
				System.Console.WriteLine("configuration delievery succeeded");
			}
			else
			{
				System.Console.WriteLine("SWP_Configuration call is incorrect,Status = " + Status);
				HtraApi.Device_Close(ref Device);
			}

			//Demod
			IntPtr AnalogMod = IntPtr.Zero;  //此参数为API存储调制解调相关信息的内存地址。
			HtraApi.ADM_Open(ref AnalogMod); //打开调制解调功能，函数将返回当前调制解调的内存地址。后续在调用其他API时，必须通过此地址参数来调用其他函数。
			double carrierOffsetHz = 0;

			float[] audio = new float[StreamInfo.PacketSamples];     //开辟内存用于存放解调后的音频数据
			float[] LPF_audio = new float[StreamInfo.PacketSamples]; //开辟内存用于存放解调并且低通滤波的音频数据

			//DSP
			IntPtr DSP = IntPtr.Zero;  //此参数为API存储数字下变频相关信息的内存地址。
			HtraApi.DSP_Open(ref DSP); //打开数字下变频功能，函数将返回当前数字下变频的内存地址。后续在调用其他API时，必须通过此地址参数来调用其他函数。

			//低通滤波器配置
			HtraApi.Filter_TypeDef filter = new HtraApi.Filter_TypeDef(); //配置低通滤波器，用于对音频数据进行低通滤波
			filter.As = 90;												  //配置滤波器的阻带衰减
			filter.fc = 0.025f;											  //配置滤波器截止频率
			filter.n = 90;												  //配置滤波器阶数
			filter.mu = 0;												  //配置分数采样偏移量

			HtraApi.DSP_LPF_DeInit(ref filter);
			HtraApi.DSP_LPF_Configuration(ref DSP, ref filter, ref filter);

			NAudioPlay audioPlay = new NAudioPlay(240000, 1); //配置采样率和通道数512抽下采样率为240kHz，我们采取单通道也就是单声道
			audioPlay.StartPlay();                            //开始音频播放，缓冲区有数据后直接播放
			#endregion

			#region 3 数据采集
			Status = HtraApi.IQS_BusTriggerStart(ref Device); //调用IQS_BusTriggerStart触发设备。若触发源为外部触发，则不需要调用此函数

			while (true)
			{

				Status = HtraApi.IQS_GetIQStream_PM1(ref Device, ref IQStream); //获取IQ数据包
				HtraApi.ADM_AMDemod(ref AnalogMod, IQStream.AlternIQStream, IQStream.IQS_Profile.DataFormat, StreamInfo.PacketSamples, IQStream.IQS_StreamInfo.IQSampleRate, audio);

				if (Status == 0)
				{
					for (int i = 0; i < StreamInfo.PacketSamples; i++)
					{
						audio[i] /= 28; //保证获取到的数据位于-1到1之间，-1到1之间是音频处理中浮点表示的常见约定,可以自己调节这个数据
					}
					HtraApi.DSP_LPF_Execute_Real(ref DSP, audio, LPF_audio); //对音频数据进行低通滤波
					audioPlay.AddAudioData(LPF_audio);                       //播放音频数据
				}

				else
				{
					/*获取数据失败时，返回错误提示，发生以下错误时，建议按照提示进行操作*/
					switch (Status)
					{
						case HtraApi.APIRETVAL_ERROR_BusError:  //IQS_GetIQStream返回-8时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Error - Bus communication error");
							Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo);
							break;

						case HtraApi.APIRETVAL_ERROR_BusDataError: //IQS_GetIQStream返回-9时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Error - The data content is incorrect");
							Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo);
							break;

						case HtraApi.APIRETVAL_WARNING_BusTimeOut: //IQS_GetIQStream返回-10时，建议检查触发源是否正常触发，再进行获取
							System.Console.WriteLine("Warning - Get data timed out, check if the trigger source is triggered normally");
							break;

						case HtraApi.APIRETVAL_WARNING_IFOverflow: //IQS_GetIQStream返回-12时，建议重新配置参数，再进行获取
							System.Console.WriteLine("WARNING - IF saturation is recommended to be reconfigured, reference level <= signal power");
							break;

						case HtraApi.APIRETVAL_WARNING_ReconfigurationIsRecommended: //IQS_GetIQStream返回-14时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Warning - The current device temperature has changed significantly relative to the configured temperature, and it is recommended to reconfigure \n");
							break;

						case HtraApi.APIRETVAL_WARNING_ClockUnlocked_SYSCLK: //IQS_GetIQStream返回-15时，建议重新配置参数，再进行获取
							System.Console.WriteLine("Warning - There may be an anomaly in the hardware status of the device and reconfiguration is recommended\n");
							break;
						default:
							break;
					}
					System.Console.WriteLine("Status = " + Status);
				}

			}
			#endregion

			#region 4 关闭
			Status = HtraApi.IQS_BusTriggerStop(ref Device); //调用IQS_BusTriggerStop停止触发设备。若触发源为外部触发，则不需要调用此函数。
			audioPlay.StopPlay();							 //停止音频播放
			audioPlay.Dispose();							 //清理资源
			HtraApi.DSP_Close(ref DSP);						 //释放DDC分配的所有资源
			HtraApi.ADM_Close(ref AnalogMod);				 //释放调制解调分配的所有资源
			HtraApi.Device_Close(ref Device);				 //释放htra_api分配的所有资源
			#endregion
		}

	}
}
