Introduction
This project demonstrates the use of the Fast Fourier Transform and Windows GDI to produce near real-time visualizations of the time and frequency domains of sound.
Background
While looking for information on signal processing for a robotics project, it became apparent that examples written in C# were hard to find. This led me to create some of my own classes, which I have provided here for this demonstration.
Using the Code
Audio input for this demonstration is provided by the Wave
classes developed by Ianier Munoz. Wave
samples are further processed using the AudioFrame
class.
class AudioFrame
{
private Bitmap _canvasTimeDomain;
private Bitmap _canvasFrequencyDomain;
private double[] _waveLeft;
private double[] _waveRight;
private double[] _fftLeft;
private double[] _fftRight;
private SignalGenerator _signalGenerator;
private bool _isTest = false;
public AudioFrame(bool isTest)
{
_isTest = isTest;
}
public void Process(ref byte[] wave)
{
_waveLeft = new double[wave.Length / 4];
_waveRight = new double[wave.Length / 4];
if (_isTest == false)
{
int h = 0;
for (int i = 0; i < wave.Length; i += 4)
{
_waveLeft[h] = (double)BitConverter.ToInt16(wave, i);
_waveRight[h] = (double)BitConverter.ToInt16(wave, i + 2);
h++;
}
}
else
{
_signalGenerator = new SignalGenerator();
_signalGenerator.SetWaveform("Sine");
_signalGenerator.SetSamplingRate(44100);
_signalGenerator.SetSamples(16384);
_signalGenerator.SetFrequency(5000);
_signalGenerator.SetAmplitude(32768);
_waveLeft = _signalGenerator.GenerateSignal();
_waveRight = _signalGenerator.GenerateSignal();
}
_fftLeft = FourierTransform.FFTDb(ref _waveLeft);
_fftRight = FourierTransform.FFTDb(ref _waveRight);
}
public void RenderTimeDomain(ref PictureBox pictureBox)
{
_canvasTimeDomain = new Bitmap(pictureBox.Width, pictureBox.Height);
Graphics offScreenDC = Graphics.FromImage(_canvasTimeDomain);
SolidBrush brush = new System.Drawing.SolidBrush
(Color.FromArgb(0, 0, 0));
Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);
int width = _canvasTimeDomain.Width;
int center = _canvasTimeDomain.Height / 2;
int height = _canvasTimeDomain.Height;
offScreenDC.DrawLine(pen, 0, center, width, center);
int leftLeft = 0;
int leftTop = 0;
int leftRight = width;
int leftBottom = center - 1;
int rightLeft = 0;
int rightTop = center + 1;
int rightRight = width;
int rightBottom = height;
double yCenterLeft = (leftBottom - leftTop) / 2;
double yScaleLeft = 0.5 * (leftBottom - leftTop) / 32768;
int xPrevLeft = 0, yPrevLeft = 0;
for (int xAxis = leftLeft; xAxis < leftRight; xAxis++)
{
int yAxis = (int)(yCenterLeft + (_waveLeft[_waveLeft.Length /
(leftRight - leftLeft) * xAxis] * yScaleLeft));
if (xAxis == 0)
{
xPrevLeft = 0;
yPrevLeft = yAxis;
}
else
{
pen.Color = Color.LimeGreen;
offScreenDC.DrawLine(pen, xPrevLeft, yPrevLeft, xAxis,yAxis);
xPrevLeft = xAxis;
yPrevLeft = yAxis;
}
}
int xCenterRight = rightTop + ((rightBottom - rightTop) / 2);
double yScaleRight = 0.5 * (rightBottom - rightTop) / 32768;
int xPrevRight = 0, yPrevRight = 0;
for (int xAxis = rightLeft; xAxis < rightRight; xAxis++)
{
int yAxis = (int)(xCenterRight + (_waveRight[_waveRight.Length /
(rightRight - rightLeft) * xAxis] * yScaleRight));
if (xAxis == 0)
{
xPrevRight = 0;
yPrevRight = yAxis;
}
else
{
pen.Color = Color.LimeGreen;
offScreenDC.DrawLine
(pen, xPrevRight, yPrevRight, xAxis, yAxis);
xPrevRight = xAxis;
yPrevRight = yAxis;
}
}
pictureBox.Image = _canvasTimeDomain;
offScreenDC.Dispose();
}
public void RenderFrequencyDomain(ref PictureBox pictureBox)
{
_canvasFrequencyDomain = new Bitmap
(pictureBox.Width, pictureBox.Height);
Graphics offScreenDC = Graphics.FromImage(_canvasFrequencyDomain);
SolidBrush brush = new System.Drawing.SolidBrush
(Color.FromArgb(0, 0, 0));
Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);
int width = _canvasFrequencyDomain.Width;
int center = _canvasFrequencyDomain.Height / 2;
int height = _canvasFrequencyDomain.Height;
offScreenDC.DrawLine(pen, 0, center, width, center);
int leftLeft = 0;
int leftTop = 0;
int leftRight = width;
int leftBottom = center - 1;
int rightLeft = 0;
int rightTop = center + 1;
int rightRight = width;
int rightBottom = height;
for (int xAxis = leftLeft; xAxis < leftRight; xAxis++)
{
double amplitude = (int)_fftLeft[(int)(((double)(_fftLeft.Length)
/ (double)(width)) * xAxis)];
if (amplitude < 0)
amplitude = 0;
int yAxis = (int)(leftTop +
((leftBottom - leftTop) * amplitude) / 100);
pen.Color = Color.FromArgb(0, 0, (int)amplitude % 255);
offScreenDC.DrawLine(pen, xAxis, leftTop, xAxis, yAxis);
}
for (int xAxis = rightLeft; xAxis < rightRight; xAxis++)
{
double amplitude = (int)_fftRight[(int)(((double)
(_fftRight.Length) / (double)(width)) * xAxis)];
if (amplitude < 0)
amplitude = 0;
int yAxis = (int)(rightBottom -
((rightBottom - rightTop) * amplitude) / 100);
pen.Color = Color.FromArgb(0, 0, (int)amplitude % 255);
offScreenDC.DrawLine(pen, xAxis, rightBottom, xAxis, yAxis);
}
pictureBox.Image = _canvasFrequencyDomain;
offScreenDC.Dispose();
}
}