using System;
using System.Runtime.InteropServices; // DLL Import
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.ComponentModel;
using static System.Net.Mime.MediaTypeNames;
using System.Threading;
using static System.Net.WebRequestMethods;
using System.Runtime.InteropServices.ComTypes;
using System.Text;

namespace SDR
{
    public partial class MainForm : Form // メインのフォームのクラス
    {
        Graphics spGrfx;
        Graphics timeGrfx;
        Osc osc1 = new Osc();   // 入力信号用の発信器を作成
        Osc osc2 = new Osc();   // ミキサ用の発信器を作成
        Osc osc3 = new Osc();   // BFO用の発信器を作成
        bool abortSignalProcThread = false; // スレッド中止フラグ
        WaveFile wave = new WaveFile();    // WAVEファイル読み込みクラス作成
        Fft fft = new Fft(1024, 2); // FFT演算用クラス作成
        Spectrum sp = new Spectrum();   // スペクトラム表示用クラス
        TimeView tv = new TimeView();
        Signal signal = new Signal(); // 信号処理用のクラス作成
        Filter filter = new Filter(); // 選局用のフィルタクラスを作成
        BinarizationFilter binFilter = new BinarizationFilter();
        JjyDecoder jjyDecoder = new JjyDecoder();
        JJYForm jjyForm = new JJYForm();
        Agc agc = new Agc();

        Thread signalProcThread;
        object signalProcThreadLock = new object();
        double oscFreq = 1000.0;
        double inputLevel = 0.1;
        double mix1Freq = 0;
        double mix2Freq = 0;
        double afgain = 1.0;    // 出力ボリューム値
        int timeDiv;
        int amScale;

        // Variables used in ProcessSignal
        Int16[] soundData = new Int16[2048];    // サウンドカードとやりとりするデータ用のバッファ
        double[] signalReal = new double[1024]; // Signal data (1024 samples)
        double[] signalImag = new double[1024]; // Imag data (1024 samples)
        double[] localReal = new double[1024];  // Local signal (real)
        double[] localImag = new double[1024];  // Local signal (imag)
        double[] signalMax = new double[1024];
        double[] signalMin = new double[1024];
        double[] signalCenter = new double[1024];
        double[] viewSignalReal = new double[1024];
        double[] viewSignalImag = new double[1024];
        double[] viewSignalMax = null;
        double[] viewSignalMin = null;
        double[] viewSignalCenter = null;

        delegate void UpdateViewDelegate(double[] real, double[] imag, int displayStyle, double cursor,
            double[] max, double[] min, double[] center);
        delegate void JJYUpdateCodeDelegate(JJYForm.JJYCode code, int lowCount, int highCount);
        delegate void UpdateReceiveFreqDelegate(double receiveFreq);
        delegate void SetLineInputDelegate();

        UpdateViewDelegate updateViewDelegate;
        JJYUpdateCodeDelegate jJYUpdateCodeDelegate;
        UpdateReceiveFreqDelegate updateReceiveFreqDelegate;
        SetLineInputDelegate setLineInputDelegate;

        public MainForm()
        {
            updateViewDelegate = new UpdateViewDelegate(UpdateView);
            jJYUpdateCodeDelegate = new JJYUpdateCodeDelegate(jjyForm.UpdateCode);
            updateReceiveFreqDelegate = new UpdateReceiveFreqDelegate(UpdateReceiveFreq);
            setLineInputDelegate = new SetLineInputDelegate(SetLineInput);

            InitializeComponent();  // デフォルトの初期設定処理を行なう
            spcViewPictureBox.Image = new Bitmap(spcViewPictureBox.Width, spcViewPictureBox.Height);//FFT表示用のイメージを作成
            spGrfx = Graphics.FromImage(spcViewPictureBox.Image);// 作成したイメージをフォームに貼り付ける
            windowFuncComboBox.SelectedIndex = 2;    // FFT窓関数をBlackmannに指定
            timeViewPictureBox.Image = new Bitmap(timeViewPictureBox.Width, timeViewPictureBox.Height);//FFT表示用のイメージを作成
            timeGrfx = Graphics.FromImage(timeViewPictureBox.Image);// 作成したイメージをフォームに貼り付ける
            jjyForm.Show();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            SetParam();
            signalProcThread = new Thread(ProcessSignal);
            signalProcThread.Start();
        }

        private void MainForm_Closed(object sender, FormClosedEventArgs e)
        {
            abortSignalProcThread = true;
            signalProcThread.Join();
        }

        private int GetDispStyle()
        {
            if (freqRange0RadioButton.Checked)
                return 0;   // 0kHzから4kHzモード
            else if (freqRangem1RadioButton.Checked)
                return 1;   // -4kHzから4kHzモード
            else return 2;  // -8kHzから8kHzモード
        }

        private void UpdateReceiveFreq(double receiveFreq)
        {
            mix1ReceiveTextBox.Text = string.Format("{0:F0}", receiveFreq);
        }

        private void ProcessSignal(object param)
        {
            // Configure sound interface
            SoundCard.WaveFormatSetup(16000); // Sampling frequency = 16kHz
            if (SoundCard.SetupInBuffer() != 0)
            {
                MessageBox.Show("入力デバイスの初期化に失敗しました");
                return;
            }
            if (SoundCard.SetupOutBuffer() != 0)
            {
                MessageBox.Show("出力デバイスの初期化に失敗しました");
                return;
            }
            SoundCard.StartCapture();

            while (!abortSignalProcThread)
            {
                lock (signalProcThreadLock)
                {
                    if (SoundCard.CheckQueue() != 0)
                    {
                        double cursor = 10.0; // まずカーソル表示はデフォルトでOFFにする(10.0は常に表示範囲外)
                        viewSignalMax = null;
                        viewSignalMin = null;
                        viewSignalCenter = null;

                        // Get data from sound card
                        SoundCard.GetRecData(soundData);
                        SoundCard.SoundToFloat(signalReal, signalImag, soundData, inputLevel);
                        if (viewLineRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                        }

                        // Input Selector
                        if (inputFileRadioButton.Checked)
                        {
                            wave.Read(signalReal, signalImag, inputLevel);
                        }
                        else if (inputOscRadioButton.Checked)
                        {
                            if (modOffRadioButton.Checked)
                            {
                                osc1.CreateSignal(signalReal, signalImag, oscFreq, inputLevel);
                            }
                            else
                            {
                                // If modulation is turn on, modulate the carrier with the line real signal.
                                Array.Copy(signalReal, localReal, 1024);
                                osc1.CreateSignal(signalReal, signalImag, oscFreq, 0.1); // -20dB carrier
                                if (modAmRadioButton.Checked)
                                {
                                    signal.AmMod(signalReal, signalImag, localReal);
                                }
                                else
                                {
                                    signal.FmMod(signalReal, signalImag, localReal);
                                }
                            }
                        }
                        else if (inputNoiseRadioButton.Checked)
                        {
                            signal.CreateNoise(signalReal, signalImag, inputLevel);
                        }
                        if (inputIQReverseCheckBox.Checked)
                        {
                            signal.SwapData(signalReal, signalImag);
                        }
                        if (viewInputRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                        }

                        // Mixer #1
                        if (mix1OnCheckBox.Checked)
                        {
                            osc2.CreateSignal(localReal, localImag, mix1Freq * (mix1ReverseCheckBox.Checked ? -1 : 1), 1.0);  // 局部発振を作成する
                            signal.MultiplySignal(signalReal, signalImag, localReal, localImag);    // 掛け算して周波数変換を行なう
                            double freq = mix1Freq / 1000.0;   // 現在のチューニング周波数を求める
                            if (mix2OnCheckBox.Checked)   // もしBFOがオンなら
                                freq -= mix2Freq / 1000.0;   // チューニング周波数をBFO周波数分だけずらす
                            BeginInvoke(updateReceiveFreqDelegate, new object[] { freq });
                            if (viewInputRadioButton.Checked && mix1ReverseCheckBox.Checked)    // FFT表示が入力信号でリバースなら赤チューニングカーソルを表示する
                                cursor = freq;   // チューニングカーソルを表示するため周波数を指定する
                        }
                        if (viewMixRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                        }

                        // Filter
                        if (filterOnCheckBox.Checked)
                        {
                            filter.FirFilter(signalReal, signalImag);
                        }
                        if (viewFilterRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                        }

                        // Mixer #2s
                        if (mix2OnCheckBox.Checked)
                        {
                            osc3.CreateSignal(localReal, localImag, mix2Freq, 1.0);  // 局部発振(BFO)を作成する
                            signal.MultiplySignal(signalReal, signalImag, localReal, localImag);    // 掛け算して周波数変換を行なう
                        }
                        double level = signal.GetAbs(null, signalReal, signalImag);    // このパケット内で最大の信号強度を求める
                        if (agcOnCheckBox.Checked)
                        {
                            //signal.ApplyAgc(SignalReal, SignalImag, level);    // AGC処理を行なう
                            agc.ApplyAgc(signalReal, signalImag);
                        }
                        if (viewBfoRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                        }

                        // Rectifier
                        if (rectSssbRadioButton.Checked)
                        {
                            // すでにBFOで振幅成分が得られているのでなにもしない
                        }
                        else if (rectAmRadioButton.Checked)
                        {
                            signal.GetAbs(signalReal, signalReal, signalImag);  // AMなら振幅成分を取り出す
                            signal.MulConst(signalReal, signalImag, 3.16);      // 他のモードの出力と揃える為10dB増幅する
                        }
                        else if (rectFmRadioButton.Checked)
                        {
                            signal.GetPhase(signalReal, signalReal, signalImag);   // FMならまず位相成分を取り出す
                            signal.GetDivision(signalReal); // 位相を微分して周波数に変換する
                        }
                        if (!rectThroughRadioButton.Checked)
                        {
                            // スルー指定なら実信号化しない
                            Array.Copy(signalReal, signalImag, 1024);   // 得られた振幅成分を虚部にもコピーして実信号化する
                        }

                        // Binarization
                        if (binOnCheckBox.Checked)
                        {
                            binFilter.Filter(signalReal, signalImag, signalMax, signalMin, signalCenter);
                            int lowCount;
                            int highCount;
                            int code = jjyDecoder.Decode(signalReal, out lowCount, out highCount);
                            BeginInvoke(jJYUpdateCodeDelegate, new object[] { code, lowCount, highCount });
                        }
                        if (viewAfRadioButton.Checked)
                        {
                            Array.Copy(signalReal, viewSignalReal, 1024);
                            Array.Copy(signalImag, viewSignalImag, 1024);
                            viewSignalMax = signalMax;
                            viewSignalMin = signalMin;
                            viewSignalCenter = signalCenter;
                        }

                        // Sound output
                        signal.MulConst(signalReal, signalImag, muteCheckBox.Checked ? 0 : afgain);    // ミュート時には0を、それ以外はafgainを掛ける
                        SoundCard.FloatToSound(soundData, signalReal, signalImag); // 浮動小数点からサウンドデータに変換
                        SoundCard.PutPlayData(soundData);

                        // Update Time/ FFT View
                        BeginInvoke(updateViewDelegate, new object[] { viewSignalReal, viewSignalImag, GetDispStyle(), cursor,
                             viewSignalMax, viewSignalMin, viewSignalCenter});
                    }
                }
                System.Threading.Thread.Sleep(10);
            }
            SoundCard.HeapCleanUp();
        }

        private void SetLineInput()
        {
            inputLineRadioButton.Checked = true;
            SetParam();
        }

        private void UpdateView(double[] real, double[] imag, int displayStyle, double cursor,
            double[] max, double[] min, double[] center)
        {
            if (viewTabControl.SelectedIndex == 0)
            {
                //
                //  スペクトラム表示
                //
                fft.ApplyWindow(real, imag);   // 信号に窓関数を適用する
                fft.GetFft(real, imag, false); // 順方向のFFTを行い周波数成分(複素数)を得る
                double[] Spectrum = new double[1024];   // スペクトラム表示するデータ(dB単位)
                fft.GetDecibel(Spectrum, real, imag); // 複素数の絶対値をdB値に変換する
                sp.Display(spGrfx, Spectrum, displayStyle, cursor); // スペクトラム表示をアップデートする
                try { spcViewPictureBox.Invalidate(); }              // スペクトラム表示画面を更新する
                catch { };  // 画面更新の際にエラーが起きた際の受け皿(実際には発生しない)
            }
            else
            {
                // Time Waveform View
                Rectangle invlaidRect;
                tv.Display(timeGrfx, real, imag, max, min, center, timeDiv, amScale, out invlaidRect);
                try { timeViewPictureBox.Invalidate(invlaidRect); }
                catch { };  // 画面更新の際にエラーが起きた際の受け皿(実際には発生しない)
            }
        }

        private void windowFuncComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (fft != null)
            {
                fft.SetupWindow(windowFuncComboBox.SelectedIndex);
            }
        }

        private void volumeTrackBar_Scroll(object sender, EventArgs e)
        {
            afgain = (double)volumeTrackBar.Value / volumeTrackBar.Maximum;
        }

        private void tuneFreqTrackBar_Scroll(object sender, EventArgs e)
        {
            if (mix1SliderCheckBox.Checked)
            {
                // Should not call lock() here. It's too heavy to process.
                mix1Freq = tuneFreqTrackBar.Value * 16000 / 256 - 8000;
                mix1FreqTextBox.Text = mix1Freq.ToString();
            }
        }

        private void filterApplyButton_Click(object sender, EventArgs e)
        {
            SetupFilter();  // フィルタ設定を反映する
        }

        private void agcApplyButton_Click(object sender, EventArgs e)
        {
            SetupAgc();
        }

        private void inputOscFreqTextBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = double.Parse(inputOscFreqTextBox.Text);
                if (v < -8000.0 || 8000.0 < v)
                    throw new Exception("発振器の周波数は-8000～8000Hzを指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "発振器の周波数", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void inputOscFreqTextBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void inputLevelTextBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = double.Parse(inputLevelTextBox.Text);
                if (v <= 0 || 8000.0 < v)
                    throw new Exception("入力レベルは0.01～1.0を指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "入力レベル", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void inputLevelTextBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void inputFileTextBox_Validated(object sender, EventArgs e)
        {
            SetupFile();
        }

        private void mix1FreqTextBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = double.Parse(mix1FreqTextBox.Text);
                if (v < -8000.0 || 8000.0 < v)
                    throw new Exception("ミキサ1の発振周波数は-8000～8000Hzを指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "ミキサ1の発振周波数", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void mix1FreqTextBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void mix2FreqTextBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = double.Parse(mix2FreqTextBox.Text);
                if (v < -8000.0 || 8000.0 < v)
                    throw new Exception("ミキサ2の発振周波数は-8000～8000Hzを指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "ミキサ2の発振周波数", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void mix2FreqTextBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void binApplyButton_Click(object sender, EventArgs e)
        {
            SetupBinFilter();
        }

        private void timeDivComboBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = int.Parse(timeDivComboBox.Text);
                if (v < 1 || 1024 < v)
                    throw new Exception("時間割率は1～1024を指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "時間割率", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void timeDivComboBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void amScaleComboBox_Validating(object sender, CancelEventArgs e)
        {
            try
            {
                double v = int.Parse(amScaleComboBox.Text);
                if (v < 1 || 100 < v)
                    throw new Exception("増幅倍率は1～100を指定してください");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "増幅倍率", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

        private void amScaleComboBox_Validated(object sender, EventArgs e)
        {
            SetParam();
        }

        private void inputButton_CheckedChanged(object sender, EventArgs e)
        {
            SetupFile();
            SetupAgc();
        }

        private void SetupFile()
        {
            if (inputFileRadioButton.Checked)
            {
                try
                {
                    wave.Open("..\\..\\data\\" + inputFileTextBox.Text + ".wav");
                }
                catch (Exception exp)
                {
                    MessageBox.Show(exp.Message, "ファイルのオープンエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    inputLineRadioButton.Checked = true;
                }
            }
            else
            {
                wave.Close();
            }
        }
        private void SetParam()
        {
            lock (signalProcThreadLock)
            {
                oscFreq = double.Parse(inputOscFreqTextBox.Text);
                inputLevel = double.Parse(inputLevelTextBox.Text);
                mix1Freq = double.Parse(mix1FreqTextBox.Text);
                mix2Freq = double.Parse(mix2FreqTextBox.Text);
                timeDiv = int.Parse(timeDivComboBox.Text);
                amScale = int.Parse(amScaleComboBox.Text);
            }
        }

        private void filterOnCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            SetupFilter();
        }

        private void SetupFilter()
        {
            int taps;
            double lowcut;
            double highcut;
            try
            {
                taps = int.Parse(filterNumOfTapsTextBox.Text);  // TAP数を得る
                lowcut = double.Parse(filterLowerFreqTextBox.Text) / 16000.0;   // 下限周波数を得る
                highcut = double.Parse(filterUpperFreqTextBox.Text) / 16000.0;  // 上限周波数を得る
                if (taps < 3 || 1024 < taps) throw new Exception("TAP数は3～1024で設定してください"); // TAP数の下限は3, TAP数の上限は1023
                if (highcut > 0.5) throw new Exception("上限周波数は8000以下で設定してください");     // 上限周波数は0.5まで(ナイキスト周波数)
                if (lowcut > 0.5) throw new Exception("下限周波数は08000以下で設定してください");     // 下限周波数は0.5まで(ナイキスト周波数)
                lock (signalProcThreadLock)
                {
                    filter.CreateFirFilter(taps, lowcut, highcut);    // FIRフィルタの帯域を設定する
                    filter.DumpCoef("filter_coef.txt");
                }
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "フィルタ設定", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            filter.GetParam(out taps, out lowcut, out highcut);
            filterNumOfTapsTextBox.Text = string.Format("{0:D0}", taps);
            filterLowerFreqTextBox.Text = string.Format("{0:F0}", lowcut * 16000.0);
            filterUpperFreqTextBox.Text = string.Format("{0:F0}", highcut * 16000.0);
        }

        private void SetupAgc()
        {
            double peakDetTime;
            double applyTime;
            try
            {
                peakDetTime = double.Parse(agcPeakDetTimeTextBox.Text);
                applyTime = double.Parse(agcApplyTimeTextBox.Text);
                if (peakDetTime < 0.01 || 10.0 < peakDetTime) throw new Exception("最大検出時間は0.01～10.0secで指定してください");
                if (applyTime < 1.0 || 60.0 < peakDetTime) throw new Exception("適用時間は1～60.0secで指定してください");
                lock (signalProcThreadLock)
                {
                    agc.SetParam((int)(peakDetTime * 16000.0), (int)(applyTime / peakDetTime));
                }
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "AGC設定", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            int maxSampleCount;
            int timeCoef;
            agc.GetParam(out maxSampleCount, out timeCoef);
            agcPeakDetTimeTextBox.Text = string.Format("{0:F3}", maxSampleCount / 16000.0);
            agcApplyTimeTextBox.Text = string.Format("{0:F1}", maxSampleCount / 16000.0 * timeCoef);
        }

        private void SetupBinFilter()
        {
            double aveDetTime;
            double peakDetTime;
            double applyTime;
            double decisionTime;
            try
            {
                aveDetTime = double.Parse(binAveDetTimeTextBox.Text);
                peakDetTime = double.Parse(binPeakDetTimeTextBox.Text);
                applyTime = double.Parse(binApplyTimeTextBox.Text);
                decisionTime = double.Parse(decisionTimeTextBox.Text);
                if (aveDetTime < 0.001 || 1.0 < aveDetTime) throw new Exception("平均検出時間は0.001～1.0secで指定してください");
                if (peakDetTime < 0.01 || 10.0 < peakDetTime) throw new Exception("ピーク検出時間は0.01～10.0secで指定してください");
                if (applyTime < 1.0 || 60.0 < applyTime) throw new Exception("適用時間は1～60.0secで指定してください");
                if (aveDetTime >= peakDetTime) throw new Exception("ピーク検出時間は平均検出時間より大きく設定してください");
                if (peakDetTime >= applyTime) throw new Exception("適用時間はピーク検出時間より大きく設定してください");
                if (decisionTime < 0 || 0.1 < decisionTime) throw new Exception("確定時間は0～0.1secで指定してください");
                lock (signalProcThreadLock)
                {
                    binFilter.SetParam((int)(aveDetTime * 16000.0), (int)(peakDetTime / aveDetTime),
                        (int)(applyTime / peakDetTime), (int)(decisionTime * 16000.0));
                }
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message, "AGC設定", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            int averageMaxCount;
            int levelDetMaxCount;
            int filterCount;
            int decisionCount;
            binFilter.GetParam(out averageMaxCount, out levelDetMaxCount, out filterCount, out decisionCount);
            binAveDetTimeTextBox.Text = string.Format("{0:F3}", averageMaxCount / 16000.0);
            binPeakDetTimeTextBox.Text = string.Format("{0:F3}", (averageMaxCount * levelDetMaxCount) / 16000.0);
            binApplyTimeTextBox.Text = string.Format("{0:F1}", (averageMaxCount * levelDetMaxCount * filterCount) / 16000.0);
            decisionTimeTextBox.Text = string.Format("{0:F3}", decisionCount / 16000.0);
        }

        private void operationTestButton_Click(object sender, EventArgs e)
        {
            lock (signalProcThreadLock)
            {
                inputOscFreqTextBox.Text = "1000";   // 発振器の周波数は100Hzに
                inputFileTextBox.Text = "7m";   // 入力ファイル名は7m
                inputLevelTextBox.Text = "0.1";    // ゲインは-20dBに
                inputIQReverseCheckBox.Checked = false;
                inputFileRadioButton.Checked = true;    // 入力はファイルモード

                mix1OnCheckBox.Checked = true;   // ミクサON
                mix1FreqTextBox.Text = "-7000";   // 受信周波数は-7000Hzに
                mix1ReceiveTextBox.Text = "0";    // オフセット周波数は0Hzに
                mix1ReverseCheckBox.Checked = true;   // リバースON

                filterOnCheckBox.Checked = true;   // フィルタON
                filterNumOfTapsTextBox.Text = "255";    // TAP数は255に
                filterUpperFreqTextBox.Text = "1500";  // 上限周波数は1500Hzに
                filterLowerFreqTextBox.Text = "0";    // 下限周波数は0Hzに

                mix2OnCheckBox.Checked = true;   // 第2ミクサON
                mix2FreqTextBox.Text = "-1500";   // BFO周波数は-1500Hzに
                mix1SliderCheckBox.Checked = true;   // スライダON

                agcOnCheckBox.Checked = true;   // AGC ON
                agcPeakDetTimeTextBox.Text = "0.064";
                agcApplyTimeTextBox.Text = "4.096";

                rectSssbRadioButton.Checked = true;    // 検波はSSBモード

                binOnCheckBox.Checked = false;

                tuneFreqTrackBar.Value = 16;   // -7000Hzにスライダを移動
                windowFuncComboBox.SelectedIndex = 2;    // FFT窓関数をBlackmannに指定
                freqRangem2RadioButton.Checked = true;    // スペクトラム表示は-8kHzから8kHz
                viewInputRadioButton.Checked = true;    // スペクトラム表示はミキサ入力
                volumeTrackBar.Value = 20;  // ボリューム最大の位置に
                viewTabControl.SelectedIndex = 0;

                SetParam();
                SetupFile();
                SetupFilter();
                SetupAgc();
                SetupBinFilter();
            }
        }

        private void resetButton_Click(object sender, EventArgs e)
        {
            lock (signalProcThreadLock)
            {
                inputOscFreqTextBox.Text = "1000";   // 発振器の周波数は100Hzに
                inputFileTextBox.Text = "lsb";   // 受信ファイル名はlsbに
                inputLevelTextBox.Text = "0.1";    // ゲインは-20dBに
                inputLineRadioButton.Checked = true;   // リバースON
                inputIQReverseCheckBox.Checked = false;   // リバースOFF

                mix1OnCheckBox.Checked = false;   // ミクサOFF
                mix1FreqTextBox.Text = "1000";    // チューニング周波数は1000Hzに
                mix1ReceiveTextBox.Text = "0";    // オフセット周波数は0KHzに
                mix1SliderCheckBox.Checked = false;   // スライダOFF
                mix1ReverseCheckBox.Checked = false;   // リバースOFF

                filterOnCheckBox.Checked = false;   // フィルタOFF
                filterNumOfTapsTextBox.Text = "255";    // TAP数は255に
                filterUpperFreqTextBox.Text = "1500";  // 上限周波数は1500Hzに
                filterLowerFreqTextBox.Text = "0";    // 下限周波数は0Hzに

                mix2OnCheckBox.Checked = false;   // 第2ミクサOFF
                mix2FreqTextBox.Text = "-1500";   // BFO周波数は0KHzに

                agcOnCheckBox.Checked = false;   // AGC OFF
                agcPeakDetTimeTextBox.Text = "0.064";
                agcApplyTimeTextBox.Text = "4.096";

                rectSssbRadioButton.Checked = true;   // SSBモード

                modOffRadioButton.Checked = true;   // 変調はなし

                binOnCheckBox.Checked = false;

                viewInputRadioButton.Checked = true;   // ライン入力を指定
                windowFuncComboBox.SelectedIndex = 2;    // FFT窓関数をBlackmannに指定
                freqRangem2RadioButton.Checked = true;   // -8KHzから8kHz表示モードに
                muteCheckBox.Checked = false;   // ミュートOFF
                volumeTrackBar.Value = 20;  // ボリューム最大の位置に
                tuneFreqTrackBar.Value = 144;  // 1000Hzの位置に
                viewTabControl.SelectedIndex = 0;

                SetParam();
                SetupFile();
                SetupFilter();
                SetupAgc();
                SetupBinFilter();
            }
        }

        private void setJjyButton_Click(object sender, EventArgs e)
        {
            lock (signalProcThreadLock)
            {
                inputFileTextBox.Text = "20250105_2035_";  
                inputLevelTextBox.Text = "1.0";
                inputFileRadioButton.Checked = true;
                inputIQReverseCheckBox.Checked = false;

                mix1OnCheckBox.Checked = true;
                mix1FreqTextBox.Text = "1000";
                mix1ReceiveTextBox.Text = "0";
                mix1SliderCheckBox.Checked = false;
                mix1ReverseCheckBox.Checked = true;

                filterOnCheckBox.Checked = true;
                filterNumOfTapsTextBox.Text = "255";
                filterUpperFreqTextBox.Text = "16";
                filterLowerFreqTextBox.Text = "0";

                mix2OnCheckBox.Checked = false;
                mix2FreqTextBox.Text = "1000";

                agcOnCheckBox.Checked = true;
                agcPeakDetTimeTextBox.Text = "1";
                agcApplyTimeTextBox.Text = "8";

                rectAmRadioButton.Checked = true;

                modOffRadioButton.Checked = true;

                viewAfRadioButton.Checked = true;
                windowFuncComboBox.SelectedIndex = 2;
                freqRangem2RadioButton.Checked = true;
                muteCheckBox.Checked = true;
                volumeTrackBar.Value = 20;
                tuneFreqTrackBar.Value = 144;

                binOnCheckBox.Checked = true;
                binAveDetTimeTextBox.Text = "0.02";
                binPeakDetTimeTextBox.Text = "1.0";
                binApplyTimeTextBox.Text = "8.0";
                decisionTimeTextBox.Text = "0.02";
                viewTabControl.SelectedIndex = 1;
                amScaleComboBox.Text = "2";

                SetParam();
                SetupFile();
                SetupFilter();
                SetupAgc();
                SetupBinFilter();
            }
        }
        private void setJjy2Button_Click(object sender, EventArgs e)
        {
            lock (signalProcThreadLock)
            {
                inputFileTextBox.Text = "20250105_2035_";
                inputLevelTextBox.Text = "1.0";
                inputFileRadioButton.Checked = true;
                inputIQReverseCheckBox.Checked = false;

                mix1OnCheckBox.Checked = true;
                mix1FreqTextBox.Text = "1000";
                mix1ReceiveTextBox.Text = "0";
                mix1SliderCheckBox.Checked = false;
                mix1ReverseCheckBox.Checked = true;

                filterOnCheckBox.Checked = true;
                filterNumOfTapsTextBox.Text = "255";
                filterUpperFreqTextBox.Text = "16";
                filterLowerFreqTextBox.Text = "0";

                mix2OnCheckBox.Checked = false;
                mix2FreqTextBox.Text = "1000";

                agcOnCheckBox.Checked = true;
                agcPeakDetTimeTextBox.Text = "0.1";
                agcApplyTimeTextBox.Text = "10";

                rectAmRadioButton.Checked = true;

                modOffRadioButton.Checked = true;

                viewAfRadioButton.Checked = true;
                windowFuncComboBox.SelectedIndex = 2;
                freqRangem2RadioButton.Checked = true;
                muteCheckBox.Checked = true;
                volumeTrackBar.Value = 20;
                tuneFreqTrackBar.Value = 144;

                binOnCheckBox.Checked = true;
                binAveDetTimeTextBox.Text = "0.04";
                binPeakDetTimeTextBox.Text = "1.0";
                binApplyTimeTextBox.Text = "10.0";
                decisionTimeTextBox.Text = "0.04";
                viewTabControl.SelectedIndex = 1;
                amScaleComboBox.Text = "2";

                SetParam();
                SetupFile();
                SetupFilter();
                SetupAgc();
                SetupBinFilter();
            }
        }

        class Spectrum  // スペクトラム表示をするクラス
        {
            Pen pen = new Pen(Color.Magenta);
            Pen green = new Pen(Color.LightGreen);
            Pen red = new Pen(Color.Red);
            SolidBrush white = new SolidBrush(Color.White);
            SolidBrush black = new SolidBrush(Color.Black);
            Font font = new Font("MS UI Gothic", 8);
            int bias = 80;  // スペアナ表示の際のレベル調整値
                            //
                            //  スペクトラムアナライザの描画
                            //
            public void Display(Graphics spGrfx, double[] spectrum, int style, double cursor)
            {
                int i, j;
                spGrfx.FillRectangle(white, 0, 0, 512, 200);  // 画面を白でクリア
                if (style < 2)   // 0-8K または -4k -4k(表示幅が8KHzの場合)
                {
                    for (i = 1; i < 8; i++)  // 1KHzから7KHzまで描く
                    {
                        spGrfx.DrawString((i - ((style == 1) ? 4 : 0)).ToString() + "k", font, black, i * 64 - 8, 181);//周波数を表示
                        spGrfx.DrawLine(green, i * 64, 0, i * 64, 180);// 緑で縦線を描画
                    }
                }
                else
                {   // -8K to 8K 表示(表示幅が16KHzの場合)
                    for (i = 1; i < 16; i++) // 1khzから15kHzまで描く
                    {
                        spGrfx.DrawString((i - 8).ToString() + "k", font, black, i * 32 - 8, 181);// 周波数を表示
                        spGrfx.DrawLine(green, i * 32, 0, i * 32, 180);   //緑で縦線を描画
                    }
                }
                for (i = 1; i < 9; i++)  // dB表示の横線を描画する
                {
                    spGrfx.DrawString("-" + (i * 10).ToString() + "dB", font, black, 480, i * 20 - 6);//dB表示を右側に
                    spGrfx.DrawLine(green, 0, i * 20, 480, i * 20);   // 緑で横線を描く
                }
                if (style == 0)
                {   // 0KHzから8KHz表示モード？
                    for (i = 0; i < 512; i++)
                    {
                        j = (int)(spectrum[i] * 2 + bias);  // 表示座標を得る
                        if (j > 0)   // 表示範囲か？
                            spGrfx.DrawLine(pen, i, 180, i, 180 - j);//そうならマゼンタで描画する
                    }
                    if (cursor > 0 && cursor < 8.0)  // 表示範囲内にカーソルがあるか？
                        spGrfx.DrawLine(red, (int)(cursor * 64), 180, (int)(cursor * 64), 0);//そうなら赤で描画する
                }
                else if (style == 1)
                {   // -4KHzから4KHz表示モード
                    for (i = 0; i < 256; i++)    // 左半分
                    {
                        j = (int)(spectrum[i + 768] * 2 + bias);    // 表示範囲を得る
                        if (j > 0)// 表示範囲か？
                            spGrfx.DrawLine(pen, i, 180, i, 180 - j); //そうならマゼンタで描画する
                    }
                    for (i = 0; i < 256; i++)    // 右半分
                    {
                        j = (int)(spectrum[i] * 2 + bias);// 表示範囲を得る
                        if (j > 0)// 表示範囲か？
                            spGrfx.DrawLine(pen, i + 256, 180, i + 256, 180 - j);//そうならマゼンタで描画する
                    }
                    if (Math.Abs(cursor) < 4.0)  // 表示範囲内にカーソルがあるか？
                        spGrfx.DrawLine(red, (int)(cursor * 64 + 256), 180, (int)(cursor * 64 + 256), 0);//そうなら赤で描画する

                }
                else
                {       // -8Kから8K表示モード
                    for (i = 0; i < 256; i++)    // 左半分
                    {
                        j = (int)(spectrum[i * 2 + 512] + spectrum[i * 2 + 513] + bias);// 表示範囲を得る(2点の平均を求める)
                        if (j > 0)// 表示範囲か？
                            spGrfx.DrawLine(pen, i, 180, i, 180 - j);//そうならマゼンタで描画する
                    }
                    for (i = 0; i < 256; i++)    // 右半分
                    {
                        j = (int)(spectrum[i * 2] + spectrum[i * 2 + 1] + bias);// 表示範囲を得る(2点の平均を求める)
                        if (j > 0)// 表示範囲か？
                            spGrfx.DrawLine(pen, i + 256, 180, i + 256, 180 - j);//そうならマゼンタで描画する
                    }
                    if (Math.Abs(cursor) < 8.0)  // 表示範囲内にカーソルがあるか？
                        spGrfx.DrawLine(red, (int)(cursor * 32 + 256), 180, (int)(cursor * 32 + 256), 0);//そうなら赤で描画する
                }
                spGrfx.DrawLine(green, 0, 180, 512, 180); // グラフの一番下の線を描く
            }
        }
        class TimeView
        {
            Pen pen = new Pen(Color.Magenta);
            Pen greenPen = new Pen(Color.LightGreen);
            Pen cyanPen = new Pen(Color.Cyan);
            Pen redPen = new Pen(Color.Red);
            Pen bluePen = new Pen(Color.Blue);
            SolidBrush white = new SolidBrush(Color.White);
            SolidBrush black = new SolidBrush(Color.Black);
            Font font = new Font("MS UI Gothic", 8);
            int time_div = -1;
            int time_slot = 0;
            int prev_y0 = 15 * 3;
            int prev_y1 = 15 * 9;
            int prev_y2 = 15 * 9;
            int prev_y3 = 15 * 9;
            int prev_y4 = 15 * 9;
            public void Display(Graphics spGrfx, double[] signalReal, double[] signalImag,
                double[] signalMax, double[] signalMin, double[] signalCenter, int time_div,
                int am_scale, out Rectangle invaidRect)
            {
                if (time_div < 1) time_div = 2;
                if (1024 < time_div) time_div = 1024;
                if (this.time_div != time_div)
                {
                    spGrfx.FillRectangle(white, 0, 0, 512, 200);
                    double t = 32 * 1.0 / (16 * 1000) * time_div; // duration of one time tick (32pixels)
                    string t_str = "";
                    if ((t * 1.0e6) < 1000)
                    {
                        t_str = string.Format("{0:F1}us", (t * 1.0e6));
                    }
                    else if ((t * 1.0e3) < 1000)
                    {
                        t_str = string.Format("{0:F1}ms", (t * 1.0e3));
                    }
                    else
                    {
                        t_str = string.Format("{0:F1}s", t);
                    }
                    for (int i = 0; i <= 15; i++)
                    {
                        spGrfx.DrawLine(greenPen, i * 32, 0, i * 32, 180);
                    }
                    for (int i = 0; i <= 12; i++)
                    {
                        spGrfx.DrawLine(greenPen, 0, i * 15, 480, i * 15);
                    }
                    spGrfx.DrawString(t_str, font, black, 0, 182);
                    this.time_div = time_div;
                    time_slot = 0;
                    invaidRect = new Rectangle(0, 0, 512, 200);
                }
                else
                {
                    int slot_sx = (time_slot * 1024) / this.time_div;
                    int slot_ex = ((time_slot + 1) * 1024) / this.time_div;
                    if (slot_ex >= (32 * 15)) slot_ex = (32 * 15);
                    spGrfx.FillRectangle(white, slot_sx, 0, slot_ex - slot_sx, 180);
                    for (int i = (slot_sx + 31) / 32; i <= 15; i++)
                    {
                        if ((i * 32) > slot_ex)
                            break;
                        spGrfx.DrawLine(greenPen, i * 32, 0, i * 32, 180);
                    }
                    for (int i = 0; i <= 12; i++)
                    {
                        spGrfx.DrawLine(greenPen, slot_sx, i * 15, slot_ex - 1, i * 15);
                    }
                    invaidRect = new Rectangle(slot_sx, 0, slot_ex, 180);
                }

                int prev_x = (time_slot * 1024) / this.time_div;
                for (int i = 0; i < 1024; i++)
                {
                    int x = (time_slot * 1024 + i) / this.time_div;
                    if (x >= (32 * 15))
                        break;

                    double re = signalReal[i] * am_scale;
                    if (re > 1.0) re = 1.0;
                    if (re < -1.0) re = -1.0;
                    double im = signalImag[i] * am_scale;
                    if (im > 1.0) im = 1.0;
                    if (im < -1.0) im = -1.0;

                    int y0 = 15 * 3 - (int)(re * 15 * 3);
                    int y1 = 15 * 9 - (int)(im * 15 * 3);
                    spGrfx.DrawLine(pen, prev_x, prev_y0, x, y0);
                    spGrfx.DrawLine(pen, prev_x, prev_y1, x, y1);

                    int y2 = 15 * 9;
                    if (signalMax != null)
                    {
                        double max = signalMax[i] * am_scale;
                        if (max > 1.0) max = 1.0;
                        if (max < -1.0) max = -1.0;
                        y2 = 15 * 9 - (int)(max * 15 * 3);
                        spGrfx.DrawLine(cyanPen, prev_x, prev_y2, x, y2);
                    }

                    int y3 = 15 * 9;
                    if (signalMin != null)
                    {
                        double min = signalMin[i] * am_scale;
                        if (min > 1.0) min = 1.0;
                        if (min < -1.0) min = -1.0;
                        y3 = 15 * 9 - (int)(min * 15 * 3);
                        spGrfx.DrawLine(bluePen, prev_x, prev_y3, x, y3);
                    }

                    int y4 = 15 * 9;
                    if (signalCenter != null)
                    {
                        double center = signalCenter[i] * am_scale;
                        if (center > 1.0) center = 1.0;
                        if (center < -1.0) center = -1.0;
                        y4 = 15 * 9 - (int)(center * 15 * 3);
                        spGrfx.DrawLine(redPen, prev_x, prev_y4, x, y4);
                    }

                    prev_x = x;
                    prev_y0 = y0;
                    prev_y1 = y1;
                    prev_y2 = y2;
                    prev_y3 = y3;
                    prev_y4 = y4;
                }
                time_slot++;
                if (((time_slot * 1024) / this.time_div) >= (32 * 15))
                {
                    time_slot = 0;
                }
            }
        }
        class WaveFile
        {
            FileStream fst = null;
            byte[] buff = new byte[2 * 2 * 1024];
            long dataChunkPos;

            public void Open(string fileName)
            {
                try
                {
                    fst = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                    SearchDataChunk();
                }
                catch (Exception exp)
                {
                    if (fst != null)
                    {
                        fst.Close();
                        fst = null;
                    }
                    throw new Exception("Opeingin Wave file error\n" + exp.Message);
                }
            }

            private void SearchDataChunk()
            {
                ASCIIEncoding encoder = new ASCIIEncoding();

                fst.Read(buff, 0, 12);
                string riffId = encoder.GetString(buff, 0, 4);
                if (riffId != "RIFF")
                    throw new Exception("Not RIFF file.");
                uint fileSize = BitConverter.ToUInt32(buff, 4);
                string fileFormat = encoder.GetString(buff, 8, 4);
                if (fileFormat != "WAVE")
                    throw new Exception("Not WAVE file.");

                while (fst.Position < fst.Length)
                {
                    fst.Read(buff, 0, 8);
                    string chunkId = encoder.GetString(buff, 0, 4);
                    int chunkSize = BitConverter.ToInt32(buff, 4);

                    if (chunkId == "fmt ")
                    {
                        fst.Read(buff, 0, chunkSize);
                        ushort wFormatTag = BitConverter.ToUInt16(buff, 0);
                        ushort nChannels = BitConverter.ToUInt16(buff, 2);
                        uint nSamplesPerSec = BitConverter.ToUInt32(buff, 4);
                        uint nAvgBytesPerSec = BitConverter.ToUInt32(buff, 8);
                        ushort nBlockAlign = BitConverter.ToUInt16(buff, 12);
                        ushort wBitsPerSample = BitConverter.ToUInt16(buff, 14);
                        // There is a case that there is no cbSize in.
                        ushort cbSize = (chunkSize == 18) ? BitConverter.ToUInt16(buff, 16) : (ushort)0;
                        if (wFormatTag != 1 || nChannels != 2 || nSamplesPerSec != 16000 || wBitsPerSample != 16)
                        {
                            throw new Exception("Unsupported WAV file\n" +
                                string.Format("wFormatTag: {0:D}\n", wFormatTag) +
                                string.Format("nChannels: {0:D}\n", nChannels) +
                                string.Format("nSamplesPerSec: {0:D}\n", nSamplesPerSec) +
                                string.Format("wBitsPerSample: {0:D}\n", wBitsPerSample));
                        }
                    }
                    else if (chunkId == "data")
                    {
                        dataChunkPos = fst.Position;
                        return;
                    }
                    else
                    {
                        fst.Seek(chunkSize, SeekOrigin.Current);
                    }
                }
                throw new Exception("No data chunk\n");
            }
            public void Read(double[] ch0, double[] ch1, double level)
            {
                if (fst == null)
                {
                    Array.Clear(ch0, 0, 1024);
                    Array.Clear(ch1, 0, 1024);
                    return;
                }

                if ((fst.Position + (2 * 2 * 1024)) >= fst.Length)
                {
                    fst.Seek(dataChunkPos, SeekOrigin.Begin);
                }
                fst.Read(buff, 0, 4096);
                for (int i = 0; i < 1024; i++)
                {
                    ch0[i] = BitConverter.ToInt16(buff, 4 * i) / 32768.0 * level;
                    ch1[i] = BitConverter.ToInt16(buff, 4 * i + 2) / 32768.0 * level;
                }
            }

            public void Close()
            {
                if (fst != null)
                {
                    fst.Close();
                    fst = null;
                }
            }
        }

        class SoundCard // サウンドカードに直接アクセスするクラス
        {
            //
            //  C#からサウンドカードを扱うためのDLLのライブラリの定義
            //
            [DllImport("SoundDll.dll", EntryPoint = "WaveFormatSetup")]
            public static extern void WaveFormatSetup(int speed);
            [DllImport("SoundDll.dll", EntryPoint = "SetupInBuffer")]
            public static extern int SetupInBuffer();
            [DllImport("SoundDll.dll", EntryPoint = "SetupOutBuffer")]
            public static extern int SetupOutBuffer();
            [DllImport("SoundDll.dll", EntryPoint = "StartCapture")]
            public static extern void StartCapture();
            [DllImport("SoundDll.dll", EntryPoint = "CheckQueue")]
            public static extern int CheckQueue();
            [DllImport("SoundDll.dll", EntryPoint = "GetRecData")]
            public static extern void GetRecData(Int16[] a);
            [DllImport("SoundDll.dll", EntryPoint = "PutPlayData")]
            public static extern void PutPlayData(Int16[] a);
            [DllImport("SoundDll.dll", EntryPoint = "HeapCleanUp")]
            public static extern void HeapCleanUp();
            //
            //  サウンドデータ(16ビット整数)から浮動小数点に変換する
            //
            public static void SoundToFloat(double[] real, double[] imag, Int16[] sound, double level)
            {
                for (int i = 0; i < 1024; i++)
                {
                    real[i] = (double)sound[i * 2] / 32767.0 * level;   // +-32767を浮動小数点に
                    imag[i] = (double)sound[i * 2 + 1] / 32767.0 * level;// +-32767を浮動小数点に
                }
            }
            //
            //  浮動小数点からサウンドデータ(16ビット整数)に変換する
            //
            public static void FloatToSound(Int16[] sound, double[] real, double[] imag)
            {
                for (int i = 0; i < 1024; i++)
                {
                    if (real[i] >= 1.0)  // 1より大きければ
                        real[i] = 1.0;  // 上限の1にする
                    else if (real[i] <= -1.0)    // 1より小さければ
                        real[i] = -1.0;  // 下限の-1にする
                    if (imag[i] >= 1.0)  // 1より大きければ
                        imag[i] = 1.0;  // 上限の1にする
                    else if (imag[i] <= -1.0)    // 1より小さければ
                        imag[i] = -1.0;  // 下限の-1にする
                    sound[i * 2] = (Int16)(real[i] * 32767.0);  // +-1の浮動小数点を+-32767の整数にする
                    sound[i * 2 + 1] = (Int16)(imag[i] * 32767.0);// 同上(虚部)
                }
            }
        }
    }
}
