﻿//------------------------------------------------------------------------------
//	窓関数法による FIR フィルタの設計，Ver. 1.3
//		使用する窓関数：方形窓，ハニング窓，ハミング窓，ブラックマン窓，
//		Kaiser 窓
//
//	DPI の異なるシステムへの対応を行っている
//
//		作成者：三上直樹，2023/04/04 作成，©三上直樹 2023
//------------------------------------------------------------------------------

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;

using Mikami.WindowsApplication;
using Mikami.GraphicsLib;

using static System.Math;

namespace FIR_Design_Windowing
{
	public partial class Form1 : Form
	{
		private const int MAX_ORDER_ = 500;	// 最大の次数
		private const int F_DIV_ = 1024;	// 振幅特性描画の際の点数
		private const double A_MIN_ = 1.0e-10;	// dB 値の最小値に対応する値
		private const int X_FR_ = 102;		// x軸の1目盛り（(fs/2)/5）
		private const int Y_DB_ =  56;		// y軸の1目盛り（表示dBの幅/8）
		private const int X0_ = 68;			// スペクトル表示原点の x 座標
		private const int Y0_ = 14;			// スペクトル表示原点の y 座標

		private double	  fSample_;			// 標本化周波数
		private double	  fSkHz_;			// 標本化周波数 [kHz]
		private double	  fcLow_;			// 遮断周波数（低域側）
		private double	  fcHigh_;			// 遮断周波数（高域側）
		private int		  nOrder_;			// 次数
		private int		  nTaps_;			// 係数の数
		private double	  att_;				// Kaiser 窓を使う場合の減衰量
		private LogScaleXY  yLog_;			// 周波数ログスケール表示用
		private float	  frqL_;			// 周波数ログスケール表示の際の下端の周波数
		private float	  frqH_;			// 周波数ログスケール表示の際の下端の周波数
		private const int DRAW_MAX_ = 10;		// 描画範囲の上限
		private const int DRAw_MIN_ = 8*Y_DB_+DRAW_MAX_;	// DRAW_MAX_ を基準とする描画範囲の下限

		private double[]  hk_;				// 係数
		private float[] logA_k_ = new float[F_DIV_+1];	// 計算された対数振幅特性の値

		private readonly float dx_;					// 特性描画の際の横方向間隔（ピクセル数）
		private readonly float dpiFactor_;			// DPI 調整のための係数
		private readonly RadioGroup windowType_;	// groupBox1 の RadioButton に対応
		private readonly RadioGroup passBand_;		// groupBox2 の RadioButton に対応

		public Form1()
		{
			InitializeComponent();

			MinimumSize = Size;		// フォームの最小サイズは設計時の Size とする

			windowType_ = new RadioGroup(groupBox1);	// 文字を全部使う
			passBand_ = new RadioGroup(groupBox2);		// 文字を全部使う

			dx_ = X_FR_*5.0f/F_DIV_;
			dpiFactor_ = (this.CreateGraphics().DpiX)/96.0f;

			comboBox1.SelectedIndex = 4;	// -80 dB まで表示
			EnableCtrlBand();
			textBox1.ToInt(out nOrder_);

			FormCloseConfirm.Set(this);		// 終了時に確認できるようにする
			EnterToTab.Set(this);			// Enter キーを Tab キーのように使う
			textBox1.Select();

			CommonProc.Disable(button3, textBox8, textBox9, label13, label12, label16);
			
			CommonProc.Do(c => c.Visible = false, textBox7, button4, label10, label11,
						  comboBox1);

			textBox2.ToDouble(out fSkHz_);	// 標本化周波数のデフォルト値設定
			pictureBox1.Enabled = false;	// 起動時は周波数特性の描画を禁止
			KeyPreview = true;				// キーイベントをフォームが先に受け取る
		}

		// ｢設計｣ ボタン
		private void button1_Click(object sender, EventArgs e)
		{
			if (!ParmInput()) return;

			Design();

			CommonProc.Enable(pictureBox1, richTextBox1, button3, button4, textBox7,
							  label10, label11);
			CommonProc.Do(c => c.Visible = true, textBox7, button4, label10, label11,
						  comboBox1, panel1);

			OutputToRitchTextBox();		// 係数の出力			
			CalcAmp(ref logA_k_);		// 振幅特性の計算

			textBox7.Text = (fcLow_ == 0.0) ? (fcHigh_/1000.0).ToString()
											: (fcLow_/1000.0).ToString();
			button4_Click(sender, e);
			pictureBox1.Invalidate();
		}

		// ｢説明｣ ボタン
		private void button2_Click(object sender, EventArgs e)
		{
			Form2 myForm2 = new Form2();
			myForm2.Show(this);
		}

		// ｢結果の保存｣ ボタン
		private void button3_Click(object sender, EventArgs e)
		{
			if (saveFileDialog1.ShowDialog() == DialogResult.OK)
				richTextBox1.SaveFile(saveFileDialog1.FileName,
									  RichTextBoxStreamType.PlainText);
		}

		// ｢入力｣ ボタン：指定された周波数における利得の表示
		private void button4_Click(object sender, EventArgs e)
		{
			double fr;
			if (!textBox7.ToDouble(out fr,
								   v => (v >= 0.0) && (v <= fSkHz_/2),
								   "周波数を 0 以上，標本化周波数／2 以下に設定してください．"
								   )) return;	// 遮断周波数
			
			// fr に対する振幅特性の値を計算する
			double amp = HwA(2.0*PI*fr/fSkHz_);

			label11.Text = (amp > 1.0E-10) ?
							(20.0*Log10(amp)).ToString("0.000") + " dB"
							: "-∞ dB";
			textBox7.Select();
			textBox7.SelectAll();
		}

		// ｢終了｣ ボタン
		private void button5_Click(object sender, EventArgs e)
		{
			Close();
		}

		// 使用する窓関数が変化した場合の処理
		private void radioButton1_CheckedChanged(object sender, EventArgs e)
		{
			if (radioButton5.Checked) CommonProc.Enable(label4, label5, textBox4);
			else					  CommonProc.Disable(label4, label5, textBox4);
			CommonProc.Disable(richTextBox1, button3, button4, textBox7, label10, label11);
		}

		// フィルタの通過／阻止帯域の指定が変化した場合の処理
		private void radioButton6_CheckedChanged(object sender, EventArgs e)
		{
			EnableCtrlBand();
			CommonProc.Disable(richTextBox1, button3, button4, textBox7, label10, label11);
		}

		// TextBox1 の内容（次数）が変更された場合の処理
		private void textBox1_TextChanged(object sender, EventArgs e)
		{
			CommonProc.Disable(richTextBox1, button3, button4, textBox7, label10, label11);
		}

		// 周波数特性の表示
		private void pictureBox1_Paint(object sender, PaintEventArgs e)
		{
			if (!pictureBox1.Enabled) return;

			Graphics gr = e.Graphics;
			gr.ResetTransform();
			gr.ScaleTransform(dpiFactor_, dpiFactor_);	// スケール変換
			gr.TranslateTransform(X0_, Y0_);			// y軸の原点：0 dB に対応

			Font myFont = new Font("メイリオ", 9/dpiFactor_);
			Brush myBrush = Brushes.Black;

			if (radioButton13.Checked) DrawLinear(gr, myFont, myBrush);
			if (radioButton14.Checked) DrawLog(gr, myFont, myBrush);
		}

		// 周波数特性の表示の際の周波数軸の切り替え
		private void radioButton13_CheckedChanged(object sender, EventArgs e)
		{
			if (radioButton14.Checked)
			{
				CommonProc.Enable(textBox8, textBox9, label13, label12, label16);
				SetDisplayRange();
			}
			else
				CommonProc.Disable(textBox8, textBox9, label13, label12, label16);
			pictureBox1.Invalidate();
		}

		// 縦軸のスケールを変更する
		private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
		{
			pictureBox1.Invalidate();
			ProcessTabKey(true);
		}

		// Enter キーで指定された周波数における利得を表示する
		private void textBox7_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter) button4_Click(sender, e);
		}

		// カーソルで指定した周波数における利得を表示する
		private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
		{
			if (!button3.Enabled) return;
			if ( (e.X < Dpi(X0_)) || (e.X > Dpi(X0_ + 5*X_FR_)) ) return;
			if ( (e.Y < Dpi(Y0_)) || (e.Y > Dpi(Y0_ + 8*Y_DB_)) ) return;

			Cursor.Current = new Cursor(new MemoryStream(Properties.Resources.MyCross));
			double fr;
			if (radioButton13.Checked)
				fr = (e.X - Dpi(X0_))*(fSkHz_/2.0)/Dpi(5*X_FR_);
			else
				fr = 0.001*yLog_.ToFrq(e.X/dpiFactor_ - X0_);

			textBox7.Text = fr.ToString("0.0000");
			button4_Click(sender, null);
		}

		// 出力形式を切り替えた場合の対応
		private void radioButton10_Click(object sender, EventArgs e)
		{
			if (button3.Enabled) OutputToRitchTextBox();
		}

		// F1 キーの押し下げで説明を表示する
		private void Form1_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.F1) button2_Click(sender, null);
		}

		// 全選択
		private void textBox1_Click(object sender, EventArgs e)
		{
			((TextBox)sender).SelectAll();
		}

		// 周波数軸ログ表示で表示範囲が変更されたときの処理
		private void textBox8_Leave(object sender, EventArgs e)
		{
			SetDisplayRange();
			pictureBox1.Invalidate();
		}

		// 標本化周波数の入力
		private void textBox2_Leave(object sender, EventArgs e)
		{
			if (!textBox2.ToDouble(out fSkHz_, x => x > 0,
				"標本化周波数は正の値にしてください．")) return;

			textBox8.Text = (fSkHz_/100).ToString("0.###");
			textBox9.Text = (fSkHz_/2).ToString("0.###");
		}

		// 異なる DPI への対応（expression-bodied function）
		private int Dpi(float x) => (int)Round(x*dpiFactor_);
	}
}
