//--------------------------------------------------------------
//	IIR フィルタ入出力信号の波形の描画
//		係数はファイルから読み込む
//
//		作成者：三上直樹，2023/01/30 作成，ⓒ三上直樹 2023
//--------------------------------------------------------------

using System;
using System.Linq;
using System.Windows.Forms;

using Mikami;
using Mikami.WindowsApplication;
using Mikami.Filter;

using static System.Math;

namespace IIRフィルタ_係数読込_正弦波入力_波形
{
	public partial class Form1 : Form
	{
		private const int N_START_ = 1200;
		private const int N_USE_ = 150;
		private const int N_DATA_ = N_START_ + N_USE_ + 40000/100;
		private const int OVS_ = 4;					// オーバーサンプリング比

		private const float F_MIN_ = 100.0f;		// 周波数の最小値
		private const float F_MAX_ = 4300.0f;		// 周波数の最大値
		private const float FS_ = 10000.0f;			// 標本化周波数 = 10 kHz

		// tFactor_: 周波数と TrackBar のツマミの位置の関係を表す係数
		private readonly double tFactor_ = Log10(F_MAX_/F_MIN_)/(F_MAX_ - F_MIN_);

		private readonly float dpiFactor_;			// 異なる DPI システム用スケール変換係数
		private readonly RadioGroup scaleV_;		// 縦倍率の情報を保持
		private readonly RadioGroup scaleH_;		// 横倍率の情報を保持
		private readonly RadioGroup dataType_;		// 演算のデータの型
		private readonly RadioGroup formType_;		// フィルタの構成法
		private readonly WaveformDrawer drawer_;	// 波形描画用

		private float[] xn_ = new float[N_DATA_];	// 入力信号に対応する配列
		// 直接形の係数，最初はそのまま出力
		private double[] aKD_ = { 0.0 };
		private double[] bKD_ = { 1.0, 0.0 };
		// 縦続形の係数，最初はそのまま出力
		private BiquadD.Coefs[] cKD_ = { new BiquadD.Coefs(0, 0, 0, 0) };
		private double g0D_ = 1;

		private float[] xm_, ymDF_, ymDD_,ymCF_, ymCD_;

		public Form1()
		{
			InitializeComponent();

			// 異なる DPI のシステムに対応するスケール変換の係数を取得
			dpiFactor_ = CreateGraphics().DpiX/96.0f;

			trackBar1.Minimum = (int)F_MIN_;	// TrackBar の最小値を設定
			trackBar1.Maximum = (int)F_MAX_;	// TrackBar の最大値を設定

			scaleV_ = new RadioGroup(groupBox1);
			scaleH_ = new RadioGroup(groupBox2);
			dataType_ = new RadioGroup(groupBox3);
			formType_ = new RadioGroup(groupBox4);
			radioButton13_Click(null, null);

			numericUpDown1.Maximum = (decimal)F_MAX_;
			numericUpDown1.Minimum = (decimal)F_MIN_;
			numericUpDown1.Value = numericUpDown1.Minimum;
			numericUpDown1_ValueChanged(null, null);	// 念のため

			drawer_ = new WaveformDrawer(Font, pictureBox1.Height, OVS_, dpiFactor_);

			FormCloseConfirm.Set(this);			// 終了時に確認できるようにする
		}

		// 係数読み込み
		private void button1_Click(object sender, EventArgs e)
		{
			if (!ReadFile(ref aKD_, ref bKD_, ref cKD_, ref g0D_)) return;
			Filtering();
			pictureBox2.Invalidate();
		}

		// 終了
		private void button2_Click(object sender, EventArgs e)
		{
			Close();
		}

		// 入力の正弦波の周波数を trackBar1 で設定し，正弦波を発生させる
		private void trackBar1_Scroll(object sender, EventArgs e)
		{
			int iFrq = (int)Round(F_MIN_*Pow(10, tFactor_*(trackBar1.Value - F_MIN_)));
			numericUpDown1.Value = iFrq;
		}

		// 周波数を numericUpDown1 で設定する
		private void numericUpDown1_ValueChanged(object sender, EventArgs e)
		{
			double frq = (double)numericUpDown1.Value;
			trackBar1.Value = (int)(F_MIN_ + Round(Log10(frq/F_MIN_)/tFactor_));
			label4.Text = (frq < 1000) ? frq.ToString() + " Hz"
									   : (frq/1000.0).ToString("#.000") + " kHz";

			SineGeneration(frq);
			Filtering();
		}

		// numericUpDown1 の入力文字列を全選択の状態にする
		private void numericUpDown1_Click(object sender, EventArgs e)
		{
			numericUpDown1.Select(0, numericUpDown1.Value.ToString().Length);
		}

		// numericUpDown1 で Enter キー入力時の Beep 音を消す
		private void numericUpDown1_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter) e.SuppressKeyPress = true;
		}

		// 出力波形の倍率（振幅）の設定
		private void radioButton1_Click(object sender, EventArgs e)
		{
			pictureBox2.Invalidate();
		}

		// 時間軸の倍率の設定
		private void radioButton20_Click(object sender, EventArgs e)
		{
			pictureBox1.Invalidate();
			pictureBox2.Invalidate();
		}

		// 直接形と縦続形，double型とfloat型の切り替え
		private void radioButton13_Click(object sender, EventArgs e)
		{
			toolStripStatusLabel1.Text = formType_.Checked + "， " +
										 dataType_.Checked + " 型を使用";
			pictureBox2.Invalidate();
		}

		// 入力波形の描画指令
		private void pictureBox1_Paint(object sender, PaintEventArgs e)
		{
			drawer_.Draw(e.Graphics, xm_, N_START_, "1", scaleH_.Checked);
		}

		// 出力波形の描画指令
		private void pictureBox2_Paint(object sender, PaintEventArgs e)
		{
			if (formType_.Checked == "直接形")
			{
				if (dataType_.Checked == "float") Draw(ymDF_);
				else							  Draw(ymDD_);
			}
			else
			{
				if (dataType_.Checked == "float") Draw(ymCF_);
				else							  Draw(ymCD_);
			}

			void Draw(float[] ym)
			{
				string v = scaleV_.Checked;
				string h = scaleH_.Checked;
				if (ym != null)
					drawer_.Draw(e.Graphics, ym, N_START_, v, h);
			}
		}
		// 入力正弦波の周波数の目盛描画
		private void pictureBox3_Paint(object sender, PaintEventArgs e)
		{
			DrawTickValue.Execute(e.Graphics, this, F_MAX_/F_MIN_);
		}

		// 正弦波発生
		private void SineGeneration(double frq)
		{
			double pi2fT = 2*PI*frq/FS_;
			for (int n=0; n<N_DATA_; n++)
				xn_[n] = (float)Sin(pi2fT*(n-N_START_));

			xm_ = Interpolator.Execute(xn_, N_DATA_);	// 帯域制限補間
			pictureBox1.Invalidate();
		}

		// フィルタ処理
		private void Filtering()
		{
			float[] yn = new float[N_DATA_];	// 出力信号に対応する配列

			// 直接形，float 型使用
			float[] ak = aKD_.Select(x => (float)x).ToArray();
			float[] bk = bKD_.Select(x => (float)x).ToArray();
			IIR_DirectF hDF = new IIR_DirectF(ak, bk);
			for (int n=0; n<N_DATA_; n++) yn[n] = hDF.Execute(xn_[n]);
			ymDF_ = IsDiverged("直接形，float 型使用");

			// 直接形，double 型使用
			IIR_DirectD hDD = new IIR_DirectD(aKD_, bKD_);
			for (int n=0; n<N_DATA_; n++) yn[n] = hDD.Execute(xn_[n]);
			ymDD_ = IsDiverged("直接形，double 型使用");

			// 縦続形，float 型使用
			BiquadF.Coefs[] ckF = cKD_
				.Select(c => new BiquadF.Coefs(
					(float)c.a1, (float)c.a2, (float)c.b1, (float)c.b2))
				.ToArray();
			IIR_CascadeF hCF = new IIR_CascadeF(ckF, (float)g0D_);
			for (int n=0; n<N_DATA_; n++) yn[n] = hCF.Execute(xn_[n]);
			ymCF_ = IsDiverged("直接形，double 型使用");

			// 縦続形，double 型使用
			IIR_CascadeD hCD = new IIR_CascadeD(cKD_, g0D_);
			for (int n=0; n<N_DATA_; n++) yn[n] = hCD.Execute(xn_[n]);
			ymCD_ =  IsDiverged("直接形，double 型使用");

			pictureBox2.Invalidate();

			float[] IsDiverged(string str)	// ローカル関数
			{
				if (yn.Max(x => Abs(x)) < 1.0e5f)
					return Interpolator.Execute(yn, N_DATA_);
				else
				{
					MessageBox.Show(str + "で出力信号が発散しました．");
					return null;
				}
			}
		}
	}
}
