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

namespace HTRA_CSharp_Examples
{
    // The NAudioPlay class mainly contains audio data processing functions used later in DSP_FMDemod and DSP_AMDemod
    public class NAudioPlay
    {
        private BufferedWaveProvider bufferedWaveProvider; // Declare an object to read audio data from memory into the buffer
        private WaveOutEvent waveOutEvent;                 // Declare an object to play audio data obtained from BufferedWaveProvider

        public NAudioPlay(int sampleRate, int channelCount) // Parameter meaning: sample rate and number of channels
        {
            bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(sampleRate, channelCount)); // Create a new BufferedWaveProvider instance assigned to bufferedWaveProvider, specifying the audio format as WaveFormat(sampleRate, channelCount) to set the sample rate and channel count
            bufferedWaveProvider.DiscardOnBufferOverflow = true; // Discard data if the buffer overflows

            waveOutEvent = new WaveOutEvent();       // Instantiate the playback object and assign it to waveOutEvent
            waveOutEvent.Init(bufferedWaveProvider); // Load demodulated data from the buffer
        }

        // Start playing audio
        public void StartPlay()
        {
            waveOutEvent.Play();
        }

        // Stop playing audio
        public void StopPlay()
        {
            waveOutEvent.Stop();
        }

        // Clamp value
        public static short Clamp(short value, short min, short max)  // Check whether the scaled data is between min and max
        {
            return (value < min) ? min : (value > max) ? max : value; // Return min if less than min, max if greater than max, ensuring the value stays between min and max
        }

        // Convert float type to short
        private short[] FloatAudioToByteAudio(float[] floatAudioData)
        {
            // Since we use 16-bit PCM, we need to scale float data to the short range first, cast to short type, and then convert to bytes using 2 bytes. Directly converting from float to byte would cause severe range and precision loss. Converting to short maintains a reasonable range and precision while being compatible with audio processing interfaces.
            short[] shortAudioData = new short[floatAudioData.Length];
            for (int i = 0; i < floatAudioData.Length; i++) // Data conversion: scale and cast float data to short type
            {
                float Audio32 = floatAudioData[i] * 32767f;
                short Audio16 = (short)Audio32;
                Audio16 = Clamp(Audio16, -32768, 32767);
                shortAudioData[i] = Audio16;
            }
            return shortAudioData;
        }


        // Store the demodulated data into bufferedWaveProvider
        public void AddAudioData(float[] audioData)
        {
            // NAudio's BufferedWaveProvider generally requires byte data. We need to convert float[] to byte[]. Here we use 16-bit PCM. Normally, audio processing only requires converting float to 16-bit integers, but NAudio requires bytes.
            byte[] ChangeAudioData = new byte[audioData.Length * 2];                                           // Convert to 2 bytes
            Buffer.BlockCopy(FloatAudioToByteAudio(audioData), 0, ChangeAudioData, 0, ChangeAudioData.Length); // Copy a series of bytes from the short array to the byte array. Parameter meaning: source array, source start index, destination array, destination start index, number of bytes to copy. The byte length is twice that of short, preventing overflow after transfer. Use byte length here.
            bufferedWaveProvider.AddSamples(ChangeAudioData, 0, ChangeAudioData.Length);                       // Input the byte data into the buffer
        }

        // Release resources
        public void Dispose()
        {
            waveOutEvent.Stop();
            waveOutEvent.Dispose();
        }
    }


    // Sample function
    class Demodulation
    {
        public void DSP_FMDemod()
        {
            #region 1 Open Device

            int Status = 0;              // Function return value.
            IntPtr Device = IntPtr.Zero; // Memory address of the current device.
            int DevNum = 0;              // Specify device number.

            HtraApi.BootProfile_TypeDef BootProfile = new HtraApi.BootProfile_TypeDef(); // Boot configuration structure, including physical interface, power supply, etc.
            HtraApi.BootInfo_TypeDef BootInfo = new HtraApi.BootInfo_TypeDef();          // Boot information structure, including device info, USB speed, etc.

            BootProfile.DevicePowerSupply = HtraApi.DevicePowerSupply_TypeDef.USBPortAndPowerPort; // Use both USB data port and independent power port.

            // If device data interface is USB, run directly. If using network, change #if true to #if false
#if true
            BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.USB;
#else
			// Configure network parameters
			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); // Open device

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

            /* If the device fails to open, return an error message. When the following errors occur, the device cannot operate normally. It is recommended to follow the instructions and try opening the device again */

            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 Configure Parameters
            HtraApi.IQStream_TypeDef IQStream = new HtraApi.IQStream_TypeDef();               // Store IQ data packets, including IQ data, configuration info, etc.
            HtraApi.IQS_Profile_TypeDef IQS_ProfileIn = new HtraApi.IQS_Profile_TypeDef();    // IQS input configuration, including start frequency, stop frequency, RBW, reference level, etc.
            HtraApi.IQS_Profile_TypeDef IQS_ProfileOut = new HtraApi.IQS_Profile_TypeDef();   // IQS output configuration.
            HtraApi.IQS_StreamInfo_TypeDef StreamInfo = new HtraApi.IQS_StreamInfo_TypeDef(); // IQ data information under the current configuration, including bandwidth, IQ single-channel sampling rate, etc.

            HtraApi.IQS_ProfileDeInit(ref Device, ref IQS_ProfileIn); // Initialize related parameters for IQS mode configuration.

            IQS_ProfileIn.CenterFreq_Hz = 101.1e6;                                // Set center frequency (here it is the radio station frequency).
            IQS_ProfileIn.RefLevel_dBm = -40;                                      // Set reference level.
            IQS_ProfileIn.DecimateFactor = 512;                                    // Set decimation factor.
            IQS_ProfileIn.DataFormat = HtraApi.DataFormat_TypeDef.Complex16bit;    // Set IQ data format.
            IQS_ProfileIn.TriggerMode = HtraApi.TriggerMode_TypeDef.Adaptive;      // Set trigger mode.
            IQS_ProfileIn.TriggerSource = HtraApi.IQS_TriggerSource_TypeDef.Bus;   // Set trigger source to internal bus.
            IQS_ProfileIn.BusTimeout_ms = 7000;                                     // Set bus timeout.

            Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo); // Apply IQS mode configuration.
            if (Status == 0)
            {
                System.Console.WriteLine("configuration delivery succeeded");
            }
            else
            {
                System.Console.WriteLine("SWP_Configuration call is incorrect, Status = " + Status);
                HtraApi.Device_Close(ref Device);
            }

            // Demodulation
            IntPtr AnalogMod = IntPtr.Zero;  // This parameter stores the memory address for modulation/demodulation related info in the API.
            HtraApi.ADM_Open(ref AnalogMod); // Open modulation/demodulation functionality. The function returns the memory address for later API calls that require this address.

            float[] audio = new float[StreamInfo.PacketSamples];     // Allocate memory to store demodulated audio data
            float[] LPF_audio = new float[StreamInfo.PacketSamples]; // Allocate memory to store demodulated and low-pass filtered audio data

            // DSP
            IntPtr DSP = IntPtr.Zero;  // This parameter stores the memory address for digital down-conversion info in the API.
            HtraApi.DSP_Open(ref DSP); // Open digital down-conversion functionality. The function returns the memory address for later API calls that require this address.

            // Low-pass filter configuration
            HtraApi.Filter_TypeDef filter = new HtraApi.Filter_TypeDef(); // Configure low-pass filter for audio data
            filter.As = 90;                                               // Set filter stopband attenuation
            filter.fc = 0.025f;                                           // Set filter cutoff frequency
            filter.n = 90;                                                // Set filter order
            filter.mu = 0;                                                // Set fractional sample offset

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

            NAudioPlay audioPlay = new NAudioPlay(240000, 1); // Set sample rate and number of channels. 512-decimated sample rate is 240 kHz. Single channel (mono) is used.
            audioPlay.StartPlay();                            // Start audio playback; playback begins as soon as buffer has data
            #endregion

            #region 3 Data Acquisition
            Status = HtraApi.IQS_BusTriggerStart(ref Device); //Call IQS_BusTriggerStart to trigger the device. If the trigger source is external, this function does not need to be called.

            //Loop to acquire and play FM broadcast
            while (true)
            {

                Status = HtraApi.IQS_GetIQStream_PM1(ref Device, ref IQStream);                            //Acquire IQ data packet
                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;
                        //Ensure the acquired data is within -1 to 1. -1 to 1 is a common convention for floating-point representation in audio processing. You can adjust this value yourself, but if it is too large, the processed data may become too small and too much data may be lost when scaling to short, resulting in no sound. If it is too small, noise may be too high, making it impossible to hear the radio.
                    }
                    HtraApi.DSP_LPF_Execute_Real(ref DSP, audio, LPF_audio); //Apply low-pass filter to audio data
                    audioPlay.AddAudioData(LPF_audio);                       //Play audio data
                }

                else
                {
                    /*When data acquisition fails, return an error prompt. When the following errors occur, it is recommended to follow the prompts.*/
                    switch (Status)
                    {
                        case HtraApi.APIRETVAL_ERROR_BusError:  //When IQS_GetIQStream returns -8, it is recommended to reconfigure parameters and then acquire again
                            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: //When IQS_GetIQStream returns -9, it is recommended to reconfigure parameters and then acquire again
                            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: //When IQS_GetIQStream returns -10, it is recommended to check whether the trigger source is triggered normally, then acquire again
                            System.Console.WriteLine("Warning - Get data timed out, check if the trigger source is triggered normally");
                            break;

                        case HtraApi.APIRETVAL_WARNING_IFOverflow: //When IQS_GetIQStream returns -12, it is recommended to reconfigure parameters and then acquire again
                            System.Console.WriteLine("WARNING - IF saturation is recommended to be reconfigured, reference level <= signal power");
                            break;

                        case HtraApi.APIRETVAL_WARNING_ReconfigurationIsRecommended: //When IQS_GetIQStream returns -14, it is recommended to reconfigure parameters and then acquire again
                            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: //When IQS_GetIQStream returns -15, it is recommended to reconfigure parameters and then acquire again
                            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 Close
            Status = HtraApi.IQS_BusTriggerStop(ref Device); //Call IQS_BusTriggerStop to stop triggering the device. If the trigger source is external, this function does not need to be called.
            audioPlay.StopPlay();                            //Stop audio playback
            audioPlay.Dispose();                             //Release resources
            HtraApi.DSP_Close(ref DSP);                      //Release all resources allocated by DDC
            HtraApi.ADM_Close(ref AnalogMod);                //Release all resources allocated by the modulator/demodulator
            HtraApi.Device_Close(ref Device);                //Release all resources allocated by htra_api
            #endregion
        }

        public void DSP_AMDemod()
        {
            #region 1 Open Device

            int Status = 0;              //Return value of the function.
            IntPtr Device = IntPtr.Zero; //Memory address of the current device.
            int DevNum = 0;              //Specified device number.

            HtraApi.BootProfile_TypeDef BootProfile = new HtraApi.BootProfile_TypeDef(); //Boot configuration structure, including physical interface, power supply method, etc.
            HtraApi.BootInfo_TypeDef BootInfo = new HtraApi.BootInfo_TypeDef();          //Boot information structure, including device info, USB speed, etc.

            BootProfile.DevicePowerSupply = HtraApi.DevicePowerSupply_TypeDef.USBPortAndPowerPort; //Use dual power supply: USB data port and independent power port.

            //When the device data interface is USB, run directly. For Ethernet, change #if true to #if false
#if true
            BootProfile.PhysicalInterface = HtraApi.PhysicalInterface_TypeDef.USB;
#else
			//Configure network parameters
			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); //Open device

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

            /*If the device fails to open, return an error prompt. When the following errors occur, the device cannot operate normally. It is recommended to follow the prompts and reopen the device*/
            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 Configure Parameters
            HtraApi.IQStream_TypeDef IQStream = new HtraApi.IQStream_TypeDef();               //Store IQ data packet data, including IQ data, configuration info, etc.
            HtraApi.IQS_Profile_TypeDef IQS_ProfileIn = new HtraApi.IQS_Profile_TypeDef();    //IQS input configuration, including start frequency, stop frequency, RBW, reference level, etc.
            HtraApi.IQS_Profile_TypeDef IQS_ProfileOut = new HtraApi.IQS_Profile_TypeDef();   //IQS output configuration.
            HtraApi.IQS_StreamInfo_TypeDef StreamInfo = new HtraApi.IQS_StreamInfo_TypeDef(); //IQ data info under current configuration, including bandwidth, IQ single-channel sample rate, etc.

            HtraApi.IQS_ProfileDeInit(ref Device, ref IQS_ProfileIn); //Initialize relevant parameters in IQS mode.

            IQS_ProfileIn.CenterFreq_Hz = 97.6e6;                                //Set center frequency.
            IQS_ProfileIn.RefLevel_dBm = -40;                                    //Set reference level.
            IQS_ProfileIn.DecimateFactor = 512;                                  //Set decimation factor.
            IQS_ProfileIn.DataFormat = HtraApi.DataFormat_TypeDef.Complex16bit;  //Set IQ data format.
            IQS_ProfileIn.TriggerMode = HtraApi.TriggerMode_TypeDef.Adaptive;    //Set trigger mode.
            IQS_ProfileIn.TriggerSource = HtraApi.IQS_TriggerSource_TypeDef.Bus; //Set trigger source to internal bus trigger.
            IQS_ProfileIn.BusTimeout_ms = 50000;                                 //Set bus timeout.

            Status = HtraApi.IQS_Configuration(ref Device, ref IQS_ProfileIn, ref IQS_ProfileOut, ref StreamInfo); //Deliver IQS mode configuration by calling this function.
            if (Status == 0)
            {
                System.Console.WriteLine("configuration delivery succeeded");
            }
            else
            {
                System.Console.WriteLine("SWP_Configuration call is incorrect, Status = " + Status);
                HtraApi.Device_Close(ref Device);
            }

            //Demod
            IntPtr AnalogMod = IntPtr.Zero;  //Memory address for storing modulator/demodulator info by API.
            HtraApi.ADM_Open(ref AnalogMod); //Open modulator/demodulator function. The function returns the memory address for subsequent API calls.
            double carrierOffsetHz = 0;

            float[] audio = new float[StreamInfo.PacketSamples];     //Allocate memory for demodulated audio data
            float[] LPF_audio = new float[StreamInfo.PacketSamples]; //Allocate memory for demodulated and low-pass filtered audio data

            //DSP
            IntPtr DSP = IntPtr.Zero;  //Memory address for storing digital down-conversion info by API.
            HtraApi.DSP_Open(ref DSP); //Open digital down-conversion function. The function returns the memory address for subsequent API calls.

            //Low-pass filter configuration
            HtraApi.Filter_TypeDef filter = new HtraApi.Filter_TypeDef(); //Configure low-pass filter for audio data
            filter.As = 90;                                               //Set filter stopband attenuation
            filter.fc = 0.025f;                                           //Set filter cutoff frequency
            filter.n = 90;                                                //Set filter order
            filter.mu = 0;                                                //Set fractional sample offset

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

            NAudioPlay audioPlay = new NAudioPlay(240000, 1); //Set sampling rate and channel count. 512 decimated sample rate is 240kHz, using single channel (mono)
            audioPlay.StartPlay();                            //Start audio playback; plays as soon as buffer has data
            #endregion

            #region 3 Data Acquisition
            Status = HtraApi.IQS_BusTriggerStart(ref Device); //Call IQS_BusTriggerStart to trigger the device. If the trigger source is external, this function does not need to be called

            while (true)
            {

                Status = HtraApi.IQS_GetIQStream_PM1(ref Device, ref IQStream); //Acquire IQ data packet
                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; //Ensure the acquired data is within -1 to 1. -1 to 1 is a common convention in floating-point audio processing. You can adjust this value.
                    }
                    HtraApi.DSP_LPF_Execute_Real(ref DSP, audio, LPF_audio); //Apply low-pass filter to audio data
                    audioPlay.AddAudioData(LPF_audio);                       //Play audio data
                }

                else
                {
                    /*When data acquisition fails, return an error prompt. For the following errors, it is recommended to follow the suggestions.*/
                    switch (Status)
                    {
                        case HtraApi.APIRETVAL_ERROR_BusError:  //When IQS_GetIQStream returns -8, it is recommended to reconfigure parameters and acquire again
                            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: //When IQS_GetIQStream returns -9, it is recommended to reconfigure parameters and acquire again
                            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: //When IQS_GetIQStream returns -10, check whether the trigger source is triggered normally, then acquire again
                            System.Console.WriteLine("Warning - Get data timed out, check if the trigger source is triggered normally");
                            break;

                        case HtraApi.APIRETVAL_WARNING_IFOverflow: //When IQS_GetIQStream returns -12, it is recommended to reconfigure parameters and acquire again
                            System.Console.WriteLine("WARNING - IF saturation is recommended to be reconfigured, reference level <= signal power");
                            break;

                        case HtraApi.APIRETVAL_WARNING_ReconfigurationIsRecommended: //When IQS_GetIQStream returns -14, it is recommended to reconfigure parameters and acquire again
                            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: //When IQS_GetIQStream returns -15, it is recommended to reconfigure parameters and acquire again
                            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 Close
            Status = HtraApi.IQS_BusTriggerStop(ref Device); //Call IQS_BusTriggerStop to stop triggering the device. If the trigger source is external, this function does not need to be called.
            audioPlay.StopPlay();                            //Stop audio playback
            audioPlay.Dispose();                             //Release resources
            HtraApi.DSP_Close(ref DSP);                      //Release all resources allocated by DDC
            HtraApi.ADM_Close(ref AnalogMod);                //Release all resources allocated by the modulator/demodulator
            HtraApi.Device_Close(ref Device);                //Release all resources allocated by htra_api
            #endregion
        }
    }
}
