さまざまな信号タイプを作成することから始めましょう。これは、振幅 1 の 1 つの波長を作成する関数です。
private PointF[] CreateBaseSignal(SignalType signalType)
{
switch (signalType)
{
case SignalType.Sine:
const int oversampling = 32;
PointF[] signal = new PointF[oversampling];
for (int i = 0; i < signal.Length; i++)
{
signal[i].X = (float) i / oversampling;
signal[i].Y = Convert.ToSingle(Math.Sin((double) i / oversampling * 2 * Math.PI));
}
return signal;
case SignalType.Square:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, -1.0f),
new PointF(0.5f, 1.0f),
new PointF(1.0f, 1.0f),
};
case SignalType.Triangle:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, 1.0f),
};
case SignalType.Sawtooth:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(1.0f, 1.0f),
};
default:
throw new ArgumentException("Invalid signal type", "signalType");
}
}
次に、選択した振幅と周波数で実際の信号を作成します。
private PointF[] CreateSignal(PointF[] baseSignal, float frequency, float amplitude)
{
PointF[] signal = new PointF[Convert.ToInt32(Math.Ceiling(baseSignal.Length * frequency))];
for(int i = 0; i < signal.Length; i++)
{
signal[i].X = baseSignal[i % baseSignal.Length].X / frequency + (i / baseSignal.Length) / frequency;
signal[i].Y = baseSignal[i % baseSignal.Length].Y * amplitude;
}
return signal;
}
この信号を PictureBox にプロットする前に、幅と高さに合わせて信号をスケーリングします。
private PointF[] ScaleSignal(PointF[] signal, int width, int height)
{
const float maximumAmplitude = 10.0f;
PointF[] scaledSignal = new PointF[signal.Length];
for(int i = 0; i < signal.Length; i++)
{
scaledSignal[i].X = signal[i].X * width;
scaledSignal[i].Y = signal[i].Y * height / 2 / maximumAmplitude;
}
return scaledSignal;
}
Graphics.DrawLine を使用して信号をプロットすると、Bitmap.SetPixel よりもはるかに優れています。これは、データ ポイントが高周波数でも接続されるためです。Bitmap.SetPixel も非常に遅いため、適切なパフォーマンスを実現するには、Bitmap.LockBits と安全でないコードを使用して単一のピクセルを操作する必要があります。Graphics.DrawLine を使用すると、線幅やアンチエイリアスなども制御できます。
信号を PointF 配列に保存したので、単純な Graphics.DrawLines メソッドを使用して、データ ポイントを反復処理する代わりに信号をプロットできます。
private void PlotSignal(PointF[] signal, PictureBox pictureBox)
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
signal = ScaleSignal(signal, bmp.Width, bmp.Height); // Scale signal to fit image
using(Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.TranslateTransform(0, bmp.Height / 2); // Move Y=0 to center of image
gfx.ScaleTransform(1, -1); // Make positive Y axis point upward
gfx.DrawLine(Pens.Black, 0, 0, bmp.Width, 0); // Draw zero axis
gfx.DrawLines(Pens.Blue, signal); // Draw signal
}
// Make sure the bitmap is disposed the next time around
Image old = pictureBox.Image;
pictureBox.Image = bmp;
if(old != null)
old.Dispose();
}
信号を頻繁に再描画する場合は、毎回新しいオブジェクトを作成するのではなく、Bitmap オブジェクトと Graphics オブジェクトを再利用することをお勧めします。各再描画の間に Graphics.Clear を呼び出すことを忘れないでください。
すべてを 1 つの大きなステートメントにまとめると、次のようになります。
PlotSignal(
CreateSignal(
CreateBaseSignal(signalType),
frequency,
amplitude),
thePictureBox);