PC上で、ディジタル処理をするための準備として、CICフィルタを作成しました。
CICフィルタは、Cascaded Integrator Comb filterで、名前通り、積分器と微分器をカスケード接続したものです。
ステージ数 = 3、デシメーション率 = 8のCICフィルタを作成して、疑似ホワイトノイズを入力し、出力をスペクトル表示させてフィルタ特性を確認しました。 サンプリング周波数は、44100です。
スペクトル表示には、FFTを使いました。 このコードは、「C#によるデジタル信号処理プログラミング」という書籍に掲載されているものを使いました。
この本は、名前通りデジタル処理に関するコードがたくさん掲載されていて、非常に参考になります。
全体のコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
using System; using System.Numerics; using System.Windows.Forms; namespace CIC_Fil_Test { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private DrawGraph dg; private Speana sa; private Fft sigFFT; private Complex[] noise; private double[] sig_fft_data; private bool dataready, fftdataready; private Random rnd; private const double samplerate = 44100; private const int datapoint = 1024; private void button1_Click(object sender, EventArgs e) { noiseGen(); var cicdata = cicFilter(noise, 8, 3); sig_fft_data = sigFFT.GetDecibel(sigFFT.ExecuteFft(sigFFT.SetupWin(cicdata))); dataready = true; fftdataready = true; pictureBox1.Refresh(); pictureBox2.Refresh(); } private void noiseGen() { double phase = 0; for (int i = 0; i < datapoint * 8; i++) { phase = rnd.Next((int)samplerate) * 2 * Math.PI / samplerate; noise[i] = new Complex(511 * Math.Sin(phase), 0); } } private Complex[] cicFilter(Complex[] data, int decirate, int stage) { int[] offsetcom = new int[stage]; double[] buff_integrator = new double[stage]; double[,] buf_com = new double[stage, decirate]; int num = data.Length / decirate; int cnt = 0; double[] tempout = new double[num]; Complex[] outdata = new Complex[num]; Array.Clear(outdata, 0, outdata.Length); Array.Clear(offsetcom, 0, offsetcom.Length); Array.Clear(buff_integrator, 0, buff_integrator.Length); Array.Clear(buf_com, 0, buf_com.Length); Array.Clear(tempout, 0, tempout.Length); int i = 0; while (cnt < data.Length) { for (int j = 0; j < decirate; j++) { tempout[i] = data[cnt++].Real; for (int k = 0; k < stage; k++) { buff_integrator[k] = buff_integrator[k] + tempout[i]; tempout[i] = buff_integrator[k]; } } for (int j = 0; j < stage; j++) { offsetcom[j] = (offsetcom[j] + 1) % decirate; double tmp = buf_com[j, offsetcom[j]]; buf_com[j, offsetcom[j]] = tempout[i]; tempout[i] = tempout[i] - tmp; } outdata[i] = new Complex(tempout[i], 0); i++; } return outdata; } private void Form1_Load(object sender, EventArgs e) { dg = new DrawGraph(pictureBox1); sa = new Speana(pictureBox2, panel1); sigFFT = new Fft(datapoint); dataready = false; fftdataready = false; noise = new Complex[datapoint*8]; sig_fft_data = new double[datapoint]; rnd = new Random(); } private void pictureBox1_Paint(object sender, PaintEventArgs e) { dg.DrawAxis(e.Graphics); if (dataready) dg.DrawData(e.Graphics, noise); } private void panel1_Paint(object sender, PaintEventArgs e) { sa.DrawChar(e.Graphics); } private void pictureBox2_Paint(object sender, PaintEventArgs e) { sa.DrawAxis(e.Graphics); if (fftdataready) sa.DrawSpectrum(e.Graphics, sig_fft_data); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
using System; using System.Windows.Forms; using System.Numerics; // // 本ソースコードは、三上直樹氏著 「C#によるデジタル信号処理プログラミング」 // のP187~189 FftComplex.csに掲載されているものです。 // namespace CIC_Fil_Test { class Fft { private readonly int nFft_; //FFTの点数 private readonly int mask_; //IFFTの場合にReorder()で使用 private readonly double nInverse_; //FFTの点数の逆数 private readonly Complex[] wTable_; //回転子用 private readonly int[] bTable_; //ビット逆順用 private Complex[] g_; //作業領域 private Complex[] u_; //出力用 private double[] HumW; public Fft(int numberOfPoints) { nFft_ = numberOfPoints; if (nFft_ < 2) ErrorMessage("データ数が2以上"); int pw2 = nFft_; while ((pw2 & 0x1) == 0) pw2 = pw2 >> 1; if (pw2 != 0x1) ErrorMessage("データ数が2のべき乗"); mask_ = nFft_ - 1; nInverse_ = 1.0 / nFft_; wTable_ = TwiddleFactor(); bTable_ = BitReverse(); g_ = new Complex[nFft_]; u_ = new Complex[nFft_]; HumW = new double[nFft_]; } public Complex[] ExecuteFft(Complex[] xn) { if (xn.Length != nFft_) return null; Execute(xn); for (int n = 0; n < nFft_; n++) u_[n] = g_[bTable_[n]]; return u_; } public Complex[] ExecuteInverseFft(Complex[] xn) { if (xn.Length != nFft_) return null; Execute(xn); for (int n = 0; n < nFft_; n++) u_[n] = g_[Reorder(n)]*nInverse_; return u_; } public double[] GetDecibel(Complex[] data) { double[] outdata = new double[data.Length]; for (int i = 0; i < data.Length; i++) { outdata[i] = Math.Log10(data[i].Magnitude+1e-9) * 20; } return outdata; } public Complex[] SetupWin(Complex[] data) { Complex[] temp = new Complex[nFft_]; for (int i=0; i<nFft_; i++) temp[i] = (0.54 - 0.46 * Math.Cos(2 * Math.PI * i / nFft_))*data[i]; return temp; } private void Execute(Complex[] xn) { for(int n=0; n<nFft_; n++) g_[n] = xn[n]; int increment = nFft_; for(int stage =1; stage<nFft_; stage <<=1) { int half = increment / 2; for(int m=0; m<nFft_; m+=increment) { int k = 0; for(int n=m; n<m+half; n++) { Complex tmp = g_[n]; g_[n] = tmp + g_[n + half]; g_[n + half] = (tmp - g_[n + half]) * wTable_[k]; k = k + stage; } } increment = half; } } private int Reorder(int n) { return bTable_[(nFft_ - n) & mask_]; } private Complex[] TwiddleFactor() { Complex arg = -2.0*Math.PI*Complex.ImaginaryOne*nInverse_; Complex[] w = new Complex[nFft_ / 2]; for (int k = 0; k < nFft_ / 2; k++) w[k] = Complex.Exp(arg * k); return w; } private int[] BitReverse() { int offset = nFft_ >> 1; int[] br = new int[nFft_]; br[0] = 0; for (int n = 1; n < nFft_; n <<= 1) { for (int k = 0; k < n; k++) br[k + n] = br[k] + offset; offset = offset >> 1; } return br; } private void ErrorMessage(string message) { MessageBox.Show(message + "ではないので、強制終了!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); Environment.Exit(0); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
using System.Windows.Forms; using System.Drawing; namespace CIC_Fil_Test { class Speana { private PictureBox picturebox_; private Panel panel_; private float x0_; //x軸原点 private float y0_; //y軸原点 private float y1_; private float width_; private float xp0_; //x軸原点 private float yp0_; //y軸原点 private float widthp_ ; private Font myfont_ = new Font("MS UI Gothic", 8); private SolidBrush mybrush_ = new SolidBrush(Color.Black); private const float x_offset = 10.0F; private const float y_offset = 10.0F; public Speana(PictureBox myPitureBox, Panel myPanel) { picturebox_ = myPitureBox; x0_ = picturebox_.Left; y0_ = picturebox_.ClientSize.Height - 1; y1_ = y0_ / (float)(200); width_ = picturebox_.ClientSize.Width - 1; panel_ = myPanel; xp0_ = panel_.Left; yp0_ = panel_.ClientSize.Height - 1; widthp_ = panel_.ClientSize.Width - 1; } public void DrawAxis(Graphics gr) { float step = width_ / 24; for (int i = 0; i <= 24; i++) gr.DrawLine(Pens.Black, step * i, 0, step * i, y0_); step = y0_ / 10; for (int i = 0; i <= 10; i++) gr.DrawLine(Pens.Black, 0, y0_ - step * i, width_, y0_ - step * i); step = step/2; for (int i = 0; i <= 20; i++) gr.DrawLine(Pens.Black, width_/2, y0_ - step * i, width_/2 + 3, y0_ - step * i); } public void DrawChar(Graphics gr) { for (int i = 0; i < 13; i++) gr.DrawString((-24 + 4 * i).ToString(), myfont_, mybrush_, new PointF((float)((width_ / 12 * i) -4), yp0_-20)); } public void DrawSpectrum(Graphics gr, double[] data) { float xtick = width_ / 1024f; PointF oldPoint = new PointF(0, (float)(y0_ - data[512] * y1_)); PointF newPoint = new PointF(); for (int i = 1; i < data.Length / 2; i++) { newPoint.X = i * xtick; newPoint.Y = (float)(y0_ - data[512 + i] * y1_); gr.DrawLine(Pens.Green, oldPoint, newPoint); oldPoint = newPoint; } oldPoint = new PointF(width_/2, (float)(y0_ - data[0] * y1_)); for (int i = 1; i < data.Length/2; i++) { newPoint.X = width_/2 + i * xtick; newPoint.Y = (float)(y0_ - data[i] * y1_); gr.DrawLine(Pens.Red, oldPoint, newPoint); oldPoint = newPoint; } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
using System.Drawing; using System.Windows.Forms; using System.Numerics; namespace CIC_Fil_Test { class DrawGraph { private PictureBox picturebox_; private float x0_; //x軸原点 private float y0_; //y軸原点 private float y1_; //y軸1に対するピクセル値 private float width_; private Font myfont_ = new Font("MS UI Gothic", 10); private SolidBrush mysolidbrush_ = new SolidBrush(Color.Black); private const float x_offset = 10.0F; private const float y_offset = 10.0F; //コンストラクタ public DrawGraph(PictureBox myPictureBox) { picturebox_ = myPictureBox; x0_ = picturebox_.Left; y0_ = picturebox_.ClientSize.Height - 1; y1_ = y0_ / (float)(1.2 * 1024); width_ = picturebox_.ClientSize.Width - 1; } public void DrawAxis(Graphics gr) { DrawVerticalLines(gr, Pens.Black); DrawHolizontalLines(gr, Pens.Black); } private void DrawHolizontalLines(Graphics gr, Pen mypen) { float yn = y0_ / 10; for (int i = 0; i <= y0_; i += 1) gr.DrawLine(mypen, 0, y0_ - yn * i, width_, y0_ - yn * i); } private void DrawVerticalLines(Graphics gr, Pen mypen) { float xn = width_ / 10; for (int i = 0; i <= (int)width_; i += 1) gr.DrawLine(mypen, xn * i, 0, xn * i, y0_); } public void DrawData(Graphics gr, Complex[] gdata) { Pen mypen = new Pen(Color.Red); PointF ioldPoint = new PointF(0, (float)(y0_ + y1_ * gdata[0].Real - y0_ / 2)); PointF qoldPoint = new PointF(0, (float)(y0_ + y1_ * gdata[0].Imaginary - y0_ / 2)); float tick = width_ / gdata.Length * 40; for (int i = 1; i < gdata.Length - 1; i++) { mypen.Color = Color.Red; PointF inewPoint = new PointF(i * tick, (float)(y0_ + y1_ * gdata[i].Real - y0_ / 2)); gr.DrawLine(mypen, ioldPoint, inewPoint); ioldPoint = inewPoint; mypen.Color = Color.Green; PointF qnewPoint = new PointF(i * tick, (float)(y0_ + y1_ * gdata[i].Imaginary - y0_ / 2)); gr.DrawLine(mypen, qoldPoint, qnewPoint); qoldPoint = qnewPoint; } } } } |
結果です。 上は、疑似ホワイトノイズのオシロ波形です。下がフィルター後のスペクトルです。
44100Hzのサンプリング周波数で、1/8にデシメーションしているので、最初のヌル周波数が5512.5Hz付近にあることが判ります。