JavaSpectrum Analysis Using Java

Spectrum Analysis Using Java

Java Programming, Notes # 1485


Preface

Spectral analysis

A previous lesson entitled
Fun with Java,
How and Why Spectral Analysis Works
explained some of the fundamentals
regarding spectral analysis.

The lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
presented and explained several Java
programs for doing spectral analysis, including both DFT programs and FFT
programs.  That lesson illustrated the fundamental aspects of
spectral analysis that center around the sampling frequency and the Nyquist
folding frequency.

The lesson entitled
Spectrum
Analysis using Java, Frequency Resolution versus Data Length
used similar Java programs to explain spectral
frequency resolution.

The lesson entitled
Spectrum
Analysis using Java, Complex Spectrum and Phase Angle
explained issues involving the complex spectrum, the phase angle,
and shifts in the time domain.

This lesson will illustrate and explain forward and inverse Fourier
transforms using both DFT and FFT algorithms.  I will also illustrate and
explain the implementation of frequency filtering by modifying the complex
spectrum in the frequency domain and then transforming the modified complex
spectra back into the time
domain.

Viewing tip

You may find it useful to open another copy of this lesson in a separate
browser window.  That will make it easier for you to scroll back and
forth among the different figures and listings while you are reading about
them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection
of online Java tutorials.  You will find those lessons published at
Gamelan.com.  However,
as of the date of this writing, Gamelan doesn’t maintain a consolidated index
of my Java tutorial lessons, and sometimes they are difficult to locate there. 
You will find a consolidated index at www.DickBaldwin.com.

Preview

In this lesson, I will present and explain the following new programs:

  • Dsp035 – Illustrates the reversible nature of the Fourier
    transform.  This program transforms a real time series into a complex
    spectrum, and then reproduces the real time series by performing an inverse
    Fourier transform on the complex spectrum.  This is accomplished using
    a DFT algorithm.
  • InverseComplexToReal01 – Class that implements an inverse DFT
    algorithm for transforming a complex spectrum into a real time series.
  • Dsp036 – Replicates the behavior of the program named Dsp035 but
    uses an FFT algorithm instead of a DFT algorithm.
  • InverseComplexToRealFFT01 – Class that implements an inverse FFT
    algorithm for transforming a complex spectrum into a real time series.
  • Dsp037 – Illustrates filtering in the frequency domain. 
    Uses an FFT algorithm to transform a time-domain impulse into the frequency
    domain.  Modifies the complex spectrum, eliminating energy within a
    specific band of frequencies.  Uses an inverse FFT algorithm to produce
    the filtered version of the impulse in the time domain.

In addition, I will use the following programs that I explained in the lesson
entitled
Spectrum Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.

  • ForwardRealToComplex01 – Class that implements a forward DFT algorithm
    for transforming a real time series into a complex spectrum.
  • ForwardRealToComplexFFT01 – Class that implements a forward FFT
    algorithm for transforming a real time series into a complex spectrum.
  • Graph03 – Used to display various types of data. (The concepts were explained in an earlier lesson.)
  • Graph06 – Also used to display various types of data in a
    somewhat different format. (The concepts were also explained in an earlier lesson.)

Discussion and Sample
Code

Description of the program named Dsp035

The program named Dsp035 illustrates forward and inverse Fourier
transforms using DFT algorithms.

The program performs spectral analysis on a time series consisting of pulses
and a sinusoid.  Then it passes the resulting real and complex parts of the
spectrum to an inverse Fourier transform program.  This program performs an
inverse Fourier transform on the complex spectral data to reconstruct the
original time series.

This program can be run with either Graph03 or Graph06 in order
to plot the results.  Enter the following at the command-line prompt to run
the program with Graph03:

java Graph03 Dsp035

The program was tested using J2SE 1.4.2 under WinXP.

The order of the plotted results

When the data is plotted (see Figure 1) using the programs Graph03 or Graph06, the plots
appear in the following order from top to bottom:

  • The input time series
  • The real spectrum of the input time series
  • The imaginary spectrum of the input time series
  • The amplitude spectrum of the input time series
  • The output time series produced by the inverse Fourier transform

The format of the plots

There were 256 values plotted horizontally in each section.  I plotted the values on a grid
that is 270 units wide to make it easier to view the plots on the rightmost end.  This leaves some blank space on the rightmost end
to contain the numbers, preventing the numbers from being mixed in with the
plotted values.  The last actual data value coincides with the rightmost
tick mark on each plot.

The forward Fourier transform

A static method named transform belonging to the
class named ForwardRealToComplex01 was used to
perform the forward Fourier transform.

(I explained this class and the transform method in the earlier
lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.)

The
method named transform does not implement an FFT algorithm.  Rather,
it implements a DFT algorithm, which is more general than, but much slower than
an FFT algorithm.

(See the
program named Dsp036 later in the lesson for the use of an FFT
algorithm.)

The inverse Fourier transform

A static method named inverseTransform belonging to the class named
InvereComplexToReal01
was used to perform the inverse Fourier transform. 
I will explain this method later in this lesson.

Let’s see some results

Before getting into the technical details of the program, let’s take a look
at the results shown in Figure 1.

The top plot in Figure 1 shows the input time series used in this experiment.



Figure 1 Forward and inverse transform of a time
         series using DFT algorithm

Length is a power of two

The time series is 256 samples long.  Although the DFT algorithm can
accommodate time series of arbitrary lengths, I set the length of this time
series to a power of two so that I can compare the results with results produced
by an FFT algorithm later in the lesson.

(Recall that most FFT algorithms are restricted to input data lengths
that are a power of two.)

The input time series

As you can see, the input time series consists of three concatenated pulses
separated by blank spaces.  The pulse on the leftmost end consists simply
of some values that I entered into the time series to create a pulse with an
interesting shape.

The middle pulse is a truncated sinusoid.

The rightmost pulse is a truncated square wave.

The objective

The objective of the experiment is to confirm that it is possible to transform this
time series into the frequency domain using a forward Fourier transform, and
then to recreate the time series by using an inverse Fourier transform to
transform the complex spectrum back into the time domain.

The real part of the spectrum is symmetrical

The real part of the complex spectrum is shown in the second plot from the
top in Figure 1.  It will become important later to note that the real part of the
spectrum is symmetrical about the folding frequency near the center of the plot
(at the eighth tick mark).

Without attempting to explain why, I will simply tell you that the real part
of the Fourier transform of a complex series whose imaginary part is all zeros
is always symmetrical about the folding frequency.

The imaginary part of the spectrum is asymmetrical

The imaginary part of the complex spectrum is shown in the third plot from
the top.  Again, it will become important later to note that the imaginary
part of the spectrum is asymmetrical about the folding frequency.

Once again, without attempting to explain why, the imaginary part of the
Fourier transform of a complex series whose imaginary part is all zeros is
always asymmetrical about the folding frequency.

The converse is also true

It is also true that the values of the imaginary part of the Fourier
transform of a complex spectrum whose real part is symmetrical about the folding
frequency and whose imaginary part is asymmetrical about the folding frequency
will be all be zero.  I will take advantage of these facts later to
simplify the computing and plotting process.

The amplitude spectrum

The amplitude spectrum is shown in the fourth plot down from the top. 
Recall from previous lessons that the amplitude values are always positive,
consisting of the square root of the sum of the squares of the real and
imaginary parts.

The output time series

The output time series, produced by performing an inverse Fourier transform
on the complex spectrum is shown in the bottom plot in Figure 1.  Compare
the bottom plot to the top plot.  As you can see, they are the same,
demonstrating the reversible nature of the Fourier transform.

Let’s see some code

I will discuss this program in fragments.  A complete listing of the
program is provided in Listing 14 near the end of the lesson.

The beginning of the class for Dsp035, including the declaration of
some variables and the creation of some array objects is shown in Listing 1. 
This code is straightforward.

class Dsp035 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeDataOut = new double[len];
  int zero = 0;

Listing 1

The constructor

The constructor begins in Listing 2.  The code in Listing 2 creates the
input time series data shown in the top plot of Figure 1.

  public Dsp035(){//constructor

    //Create the raw data pulses
    timeDataIn[0] = 0;
    timeDataIn[1] = 50;
//...
//code deleted for brevity
//...
    timeDataIn[254] = -80;
    timeDataIn[255] = -80;

    //Create raw data sinusoid
    for(int x = len/3;x < 3*len/4;x++){
      timeDataIn[x] = 80.0 * Math.sin(
                              2*pi*(x)*1.0/20.0);
    }//end for loop

Listing 2

Note that I deleted much of the code from Listing 2 for brevity.  You
can view the missing code in Listing 14 near the end of the lesson.

Compute the complex spectrum

The code in Listing 3 invokes the static transform method of the
ForwardRealToComplex01
class to compute and save the complex spectrum of the
time series shown in the top plot of Figure 1.

    ForwardRealToComplex01.transform(timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude,
                                     zero,
                                     0.0,
                                     1.0);

Listing 3

The method parameters

I explained the transform method in the earlier lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.  The three middle plots in Figure 1 are plots of the data returned
in the arrays referred to by realSpect, imagSpect, and
magnitude
by the transform method.

The angle results returned by the transform program are not used in
this lesson.

One of the parameters (zero) establishes that the first sample in the
time series array referred to by
timeDataIn represents the zero time origin.

The parameters also specify that the complex spectrum is to be computed at a
set of equally spaced frequencies ranging from zero (0.0) to the sampling
frequency (1.0).

Perform the inverse Fourier transform

The code in Listing 4 invokes the static inverseTransform method of
the InverseComplexToReal01 class to perform an inverse Fourier transform
on the complex spectral data, producing the output time series shown in the
bottom plot in Figure 1.

    InverseComplexToReal01.inverseTransform(
                    realSpect,
                    imagSpect,
                    timeDataOut);
  }//end constructor

Listing 4

I will explain the inverseTransform method later.

An object of the class Dsp035

Listing 4 also signals the end of the constructor.  Once the constructor
has completed executing, an object of the Dsp035 class exists.  The
array objects belonging to the object have been populated with the original time
series, the complex spectrum of the original time series, and the output time
series produced by performing an inverse Fourier transform on that complex
spectrum.  This data is ready for plotting.

The interface methods

All of the remaining code in Dsp035 consists of the six methods
necessary to satisfy the interface named GraphIntfc01.  Those
methods are required to provide data to the plotting program, as explained in
earlier lessons in this series.

If you have studied the previous lessons in this series, you probably don’t
want to hear any more about those methods, so I won’t discuss them further. 
You can view the six interface methods in Listing 14 near the end of the
lessons.

The inverseTransform method of the
InverseComplexToReal01 class

The static method named inverseTransform performs
a complex-to-real inverse discrete Fourier
transform returning a real result only.  In other
words, the method transforms a complex input to a
real output.

There are more efficient ways to write
this method taking known symmetry and asymmetry conditions into account. 
However, I wrote the method the way that I did because I wanted it to
mimic the behavior of an FFT algorithm. 
Therefore, the complex input must extend from
zero to the sampling frequency.

The method does not implement an FFT algorithm.  Rather, the
inverseTransform
method implements
a straight forward sampled-data version of the
continuous inverse Fourier transform that is defined
using integral calculus.

The parameters

The parameters to the inverseTransform are:

  • double[] realIn – incoming real data
  • double[] imagIn – incoming imag data
  • double[] realOut – outgoing real data

The method considers the data length to be realIn.length, and considers the computational time increment to be
1.0/realIn.length.

Assumptions

The method returns a number of points equal to the data
length.  It assumes that the real input consists of positive
frequency points for a symmetric real frequency
function.  That is, the real input is assumed to
be symmetric about the folding frequency.  The method does
not test this assumption.

The method assumes that imaginary input consists of positive
frequency points for an asymmetric imaginary
frequency function.  That is, the imaginary input
is assumed to be asymmetric about the
folding frequency.  Once again, the method does not test this
assumption.

A real output

A symmetric real part and an
asymmetric imaginary part guarantee that the
imaginary output will be all zero values.  Having made that assumption, the program makes no attempt
to compute an imaginary output.  If the assumptions described above are not valid, the
results won’t be valid.

The program was tested using J2SE v1.4.2 under WinXP.

The inverseTransform method

The beginning of the class and the beginning of the static
inverseTransform
method is shown in Listing 5.

public class InverseComplexToReal01{

  public static void inverseTransform(
                               double[] realIn,
                               double[] imagIn,
                               double[] realOut){

    int dataLen = realIn.length;
    double delT = 1.0/realIn.length;
    double startTime = 0.0;
    
Listing 5

Listing 5 declares and initializes some variables that will be used later.

The inverse transform computation

Listing 6 contains a pair of nested for loops that perform the actual
inverse transform computation.

    //Outer loop interates on time domain
    // values.
    for(int i=0; i < dataLen;i++){
      double time = startTime + i*delT;
      double real = 0;
      //Inner loop iterates on frequency
      // domain values.
      for(int j=0; j < dataLen; j++){
        real += realIn[j]*
                      Math.cos(2*Math.PI*time*j)
             + imagIn[j]*
                      Math.sin(2*Math.PI*time*j);
      }//end inner loop
      realOut[i] = real;
    }//end outer loop
  }//end inverseTransform

}//end class InverseComplexToReal01

Listing 6

If you have been studying the earlier lessons in this series, you should be
able to understand the code in Listing 6 without further explanation.  Pay
particular attention to the comments that describe the two for loops.

The program named Dsp036

The program named Dsp036 replicates the behavior of the program named Dsp035, except that it uses an FFT algorithm to perform the
inverse Fourier transform instead of using a DFT algorithm as in Dsp035.

The output from Dsp036

The output produced by running the program named Dsp036 and plotting
the output using the program named Graph03 is shown in Figure 2.



Figure 2 Forward and inverse transform of a time
         series using FFT algorithm

Compare Figure 2 with Figure 1.  The two should be identical.  The
program named Dsp036 was designed to use an FFT algorithm for the inverse
Fourier transform and to replicate the behavior of the program named Dsp035,
which uses a DFT algorithm for the inverse Fourier transform.  In addition,
the same plotting parameters were used for both figures.

The code

I’m only going to show you one short code fragment from the program named
Dsp036
.  Listing 7 shows the code that invokes the methods to perform
the forward and inverse Fourier transforms using the FFT algorithm.  A
complete listing of the program named Dsp036 is shown in Listing 16 near
the end of the lesson.

    //Compute FFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplexFFT01.transform(
                                     timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude);

    //Compute inverse FFT of the spectral data
    InverseComplexToRealFFT01.
                                inverseTransform(
                                     realSpect,
                                     imagSpect,
                                     timeOut);

Listing 7

The forward Fourier transform

The transform method used to perform the forward Fourier transform in
Listing 7 was
discussed in an earlier lesson, so I won’t discuss it further here.

The inverse Fourier transform

The static inverseTransform method of the InverseComplexToRealFFT01
class was used to perform the inverse Fourier transform in Listing 7.  You
can view this method in Listing 17 near the end of the lesson.

I’m not going to discuss
this method in detail either, because it is very similar to the method named
InverseComplexToReal01
discussed earlier in conjunction with Listing 4 and
the listings following that one.

A couple of things to note

There are a couple of things, however, that I do want to point out. 

The transform method and the inverseTransform method each
invoke a method named complexToComplex to actually perform the Fourier
transform.  This method implements a classical FFT algorithm accepting
complex input data and producing complex output data.  The restriction of
real-to-complex and complex-to-real is imposed by the methods named transform
and inverseTransform.

(The method named complexToComplex is also suitable for use if you have
a need to perform complex-to-complex Fourier transforms.)

The signature of the complexToComplex method

The signature for the complexToComplex method is shown in Figure 3.

  public static void complexToComplex(
                                  int sign,
                                  int len,
                                  double real[],
                                  double imag[]){

Figure 3

The complexToComplex method can be used to perform either a forward or
an inverse transform.  The value of the first parameter determines whether the method performs a
forward or an inverse Fourier transform.

The first parameter of the complexToComplex method

A value of +1 for the first
parameter causes the complexToComplex method to perform a forward Fourier
transform.

A value of -1 for the first parameter causes the
complexToComplex
method to perform an inverse Fourier transform.

The forward transform

Although I didn’t include the code in this lesson, (because it was shown
in an earlier lesson),
the transform method in Figure 7 passes a
value of +1 to the complexToComplex method to cause it to perform a
forward Fourier transform.

The inverse transform

Similarly, the inverseTransform method shown in Listing 17 passes a
value of -1 to the complexToComplex method to cause it to perform an
inverse Fourier transform.

FFT and DFT produce equivalent results

As evidenced in Figure 1 and Figure 2, the program named Dsp035, which
uses a DFT algorithm, produces the same results as the program named Dsp036,
which uses an FFT algorithm.  However, if you were to put a timer on each
of the programs, you
would find that Dsp036 runs faster due to the improved speed of the FFT
algorithm over the DFT algorithm.

Using a Fourier transform to perform frequency
filtering

The program named Dsp037 illustrates frequency filtering accomplished
by modifying the complex spectrum in the frequency
domain and then performing an inverse Fourier transform on the modified
frequency-domain data.  The results are shown in Figure 4.



Figure 4 Illustrates filtering in the frequency
         domain

Operation of the program

The program begins by using an FFT algorithm to perform a forward
Fourier transform on a single impulse in the time domain.

(A DFT algorithm could have been used equally as well, but it would
have been slower.)

The impulse is shown as the input time series in the topmost plot in Figure
4.

(Although I didn’t show the complex spectrum of the impulse, we know
that the magnitude of the spectrum of an impulse is constant across all
frequencies.  In other words, the magnitude spectrum of an impulse is a
flat line from zero to the sampling frequency and above.)

Modify the complex spectrum

Then the program
eliminates all energy between one-sixth and five-sixths of the sampling
frequency by setting the real and imaginary parts of the FFT output to zero.

The second, third, and fourth plots in Figure 4 show the real part, imaginary
part, and amplitude respectively of the modified complex spectrum.

(The two boxes in the fourth plot in Figure 4 show what’s left of the
spectral energy after the energy in the middle of the band has been
eliminated.)

The folding frequency in these three plots is near the center of the plot at
the eighth tick mark.

The plotting format

The input data length was 256 samples.  All but one of the input data
values was set to zero resulting in a single impulse in the input time series near the
second tick mark in Figure 4.

(The real and complex parts of the frequency spectrum were computed at
256 frequencies between zero and the sampling frequency.)

There were 256 values plotted horizontally in each separate plot.  Once
again, to make it
easier to view the plots on the rightmost end, I plotted the values on a grid
that is 270 units wide.  This leaves some blank space on the rightmost end to
contain the numbers, thus preventing the numbers from being mixed in with the
plotted values.  The last actual data value coincides with the rightmost tick
mark on each plot.

Perform an inverse Fourier transform

After modifying the complex spectrum as described above, the
program performs an inverse Fourier transform on the modified complex spectrum to produce the
filtered impulse.

The filtered impulse

The filtered impulse is shown as the bottom plot in
Figure 4.  As you can see, the pulse is smeared out in time relative to the
input pulse in the top plot.  This is the typical result of reducing the
bandwidth of a pulse. 

(This particular modification of the complex spectrum resulted in a
filtered pulse that has the waveform of a SIN(X)/X function.  A
different modification of the complex spectrum would have resulted in a
filtered pulse with a different waveform.

This example also illustrates one of the miracles of digital signal
processing.  Energy appears in the output before it occurs in the
input.  Obviously that is not possible in the real world of analog
systems, but many things are possible in the digital world that are not
possible in the real world.)

The code for Dsp037

Listing 8 shows the beginning of the class definition for the program named
Dsp037.

class Dsp037 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeOut = new double[len];

Listing 8

Listing 8 simply declares and initializes some variables that will be used
later.

The constructor

The constructor begins in Listing 9.

  public Dsp037(){//constructor

    timeDataIn[32] = 90;

Listing 9

Listing 9 creates the raw pulse data shown in the topmost plot in Figure 4.

When the array object referred to by timeDataIn is created, the values
of all array elements are set to zero by default.  Listing 9 modifies one
of the elements to have a value of 90.  This results in a single impulse at
an index of 32.

Compute the Fourier transform

Still in the constructor, the code in Listing 10 uses an FFT algorithm in the
method named transform (discussed earlier) to compute the Fourier
transform of the impulse.

    //Compute FFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplexFFT01.transform(
                                     timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude);

Listing 10

The results of the Fourier transform are stored in the array objects referred
to by realSpect, imagSpect, and magnitude.

(The phase angle is also computed but is of no interest in this
example.)

Apply the filter to the frequency data

Listing 11 applies the filter by setting sample values in a portion of the
real and imaginary parts of the complex spectrum to zero.

    for(int cnt = len/6;cnt < 5*len/6;cnt++){
      realSpect[cnt] = 0.0;
      imagSpect[cnt] = 0.0;
    }//end for loop

Listing 11

This code eliminates all energy between one-sixth and five-sixths of the
sampling frequency.  The modified data for the real and imaginary parts of
the complex spectrum are shown in the second and third plots in Figure 4.

Recompute the magnitude

Listing 12 recomputes the magnitude values for the modified real and
imaginary values of the complex spectrum.

    //Recompute the magnitude based on the
    // modified real and imaginary spectra.
    for(int cnt = 0;cnt < len;cnt++){
      magnitude[cnt] =
        (Math.sqrt(
           realSpect[cnt]*realSpect[cnt]
           + imagSpect[cnt]*imagSpect[cnt])/len);
    }//end for loop

Listing 12

The modified data for the amplitude of the complex spectrum are shown in the
fourth plot in Figure 4.

Compute the inverse Fourier transform

Listing 13 uses the inverseTransform method to compute the inverse
Fourier transform of the modified complex spectrum stored in realSpect
and imagSpect.  The results of the inverse transform are stored in
timeOut.

    InverseComplexToRealFFT01.inverseTransform(
                                       realSpect,
                                       imagSpect,
                                       timeOut);
  }//end constructor

Listing 13

The results of the inverse transform are shown in the bottom plot in Figure
4.

Listing 13 also signals the end of the constructor.

Display the results

Once the constructor returns, all of the data that is to be plotted has been
stored in the various array objects.  The remaining code in the program
consists of the definition of the six methods required by the interface named
GraphIntfc01
.  These methods are required to make it possible to use
the program named Graph03 to plot the results as shown in Figure 4.

I have discussed these methods on numerous previous occasions, and won’t
repeat that discussion here.

One more example, Dsp038

Figure 5 illustrates one more example of performing frequency filtering by
modifying the complex spectrum and then performing an inverse transform on the
modified spectrum.

While discussing the program named Dsp037, I told you that performing
a different modification on the complex spectrum would result in a different
waveform for the filtered impulse.  The program named Dsp038 applies
a different modification to the complex spectrum, but is otherwise the same as
Dsp037.

(Because of the similarity of the two programs, I won’t discuss the
code in Dsp038.  You can view that code in Listing 19 near the
end of the lesson.)

Figure 5 shows the output produced by the program named Dsp038.



Figure 5 Illustrates filtering in the frequency
         domain

Compare Figure 5 with Figure 4

The basic plotting format of Figure 5 is the same as Figure 4.

The first difference to note between the two figures is that I moved the
impulse in the input time series in the topmost plot sixteen samples further to
the right in Dsp038.

(This has no impact on the final result, which you can verify by
modifying the program to move the impulse to a different position and then
compiling and running the modified program.)

Compare the bandwidth of the pass band

The second difference to note is shown in the modified amplitude spectrum in
the fourth plot in the two figures.  The bandwidth of the pass band is
significantly narrower in Figure 5 than in Figure 4.  Also, the pass band
in Figure 4 extends all the way down to zero frequency, while Figure 5
eliminates all energy below a frequency of three thirty-seconds of the sampling
frequency.

Waveforms of filtered impulse

Finally, note the waveforms of the two filtered impulses.  The overall
amplitude of the filtered impulse in Figure 5 is less than in Figure 4, simply
because it contains less total energy.  In addition, the filtered impulse
in Figure 5 is broader than the filtered impulse in Figure 4.  This is
because it has a narrower bandwidth.

(Pulses that are narrow in terms of time duration require a wider
bandwidth than pulses that have a longer time duration.  The time
duration of the pulse tends to be inversely related to the bandwidth of the
pulse.)

Run the Programs

I encourage you to copy, compile, and run the programs provided in this
lesson.  Experiment with them, making changes and observing the results of your
changes.

Create more complex experiments.  For example, use more complex input
time series when experimenting with frequency filtering.  Apply different
modifications to the complex spectrum when experimenting with frequency
filtering.

Most of all enjoy yourself and learn something in the process.

Summary

This lesson illustrates and explains forward and inverse Fourier
transforms using both DFT and FFT algorithms.

The lesson also illustrates and
explains the implementation of frequency filtering by modifying the complex
spectrum in the frequency domain and then transforming the modified complex
spectrum back into the time
domain.

Complete Program Listings

Complete listings of the programs discussed in this lesson follow.

import java.util.*;

class Dsp035 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeDataOut = new double[len];
  int zero = 0;

  public Dsp035(){//constructor

    //Create the raw data pulses
    timeDataIn[0] = 0;
    timeDataIn[1] = 50;
    timeDataIn[2] = 75;
    timeDataIn[3] = 80;
    timeDataIn[4] = 75;
    timeDataIn[5] = 50;
    timeDataIn[6] = 25;
    timeDataIn[7] = 0;
    timeDataIn[8] = -25;
    timeDataIn[9] = -50;
    timeDataIn[10] = -75;
    timeDataIn[11] = -80;
    timeDataIn[12] = -60;
    timeDataIn[13] = -40;
    timeDataIn[14] = -26;
    timeDataIn[15] = -17;
    timeDataIn[16] = -11;
    timeDataIn[17] = -8;
    timeDataIn[18] = -5;
    timeDataIn[19] = -3;
    timeDataIn[20] = -2;
    timeDataIn[21] = -1;

    timeDataIn[240] = 80;
    timeDataIn[241] = 80;
    timeDataIn[242] = 80;
    timeDataIn[243] = 80;
    timeDataIn[244] = -80;
    timeDataIn[245] = -80;
    timeDataIn[246] = -80;
    timeDataIn[247] = -80;
    timeDataIn[248] = 80;
    timeDataIn[249] = 80;
    timeDataIn[250] = 80;
    timeDataIn[251] = 80;
    timeDataIn[252] = -80;
    timeDataIn[253] = -80;
    timeDataIn[254] = -80;
    timeDataIn[255] = -80;

    //Create raw data sinusoid
    for(int x = len/3;x < 3*len/4;x++){
      timeDataIn[x] = 80.0 * Math.sin(
                              2*pi*(x)*1.0/20.0);
    }//end for loop

    //Compute DFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplex01.transform(timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude,
                                     zero,
                                     0.0,
                                     1.0);

    //Compute inverse DFT of spectral data and
    // save output time data in output array
    InverseComplexToReal01.inverseTransform(
                    realSpect,
                    imagSpect,
                    timeDataOut);
  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of curves to plot.  Must not
    // exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > timeDataIn.length-1){
      return 0;
    }else{
      return timeDataIn[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > realSpect.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*realSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > imagSpect.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*imagSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > magnitude.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*magnitude[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                   index > timeDataOut.length-1){
      return 0;
    }else{
      return timeDataOut[index];
    }//end else
  }//end function

}//end sample class Dsp035

Listing 14
/*File InverseComplexToReal01.java
Copyright 2004, R.G.Baldwin
Rev 5/24/04

Although there are more efficient ways to write
this program, it was written the way it was to
mimic the behavior of an FFT algorithm.
Therefore, the complex input must extend from
zero to the sampling frequency.

The static method named inverseTransform performs
a complex to real inverse discrete Fourier
transform returning a real result only.  In other
words, the method transforms a complex input to a
real output.

Does not implement the FFT algorithm. Implements
a straight-forward sampled-data version of the
continuous inverse Fourier transform defined
using integral calculus.

The parameters are:
double[] realIn - incoming real data
double[] imagIn - incoming imag data
double[] realOut - outgoing real data

Considers the data length to be
 realIn.length
Computational time increment is
 1.0/realIn.length

Returns a number of points equal to the data
length.

Assumes real input consists of positive
frequency points for a symmetric real frequency
function.  That is, the real input is assumed to
be symmetric about the folding frequency.  Does
not test this assumption.

Assumes imaginary input consists of positive
frequency points for an asymmetric imaginary
frequency function.  That is, the imaginary input
is assumed to be asymmetric about  the
folding frequency.  Does not test this
assumption.

The assumption of a symmetric real part and an
asymmetric imaginary part guarantees that the
imaginary output would be all zero if it were to
be computed.  Thus the program makes no attempt
to compute an imaginary output.

Tested using J2SE v1.4.2 under WinXP.
************************************************/

public class InverseComplexToReal01{

  public static void inverseTransform(
                               double[] realIn,
                               double[] imagIn,
                               double[] realOut){
    int dataLen = realIn.length;
    double delT = 1.0/realIn.length;
    double startTime = 0.0;
    //Outer loop interates on time domain
    // values.
    for(int i=0; i < dataLen;i++){
      double time = startTime + i*delT;
      double real = 0;
      //Inner loop iterates on frequency
      // domain values.
      for(int j=0; j < dataLen; j++){
        real += realIn[j]*
                      Math.cos(2*Math.PI*time*j)
             + imagIn[j]*
                      Math.sin(2*Math.PI*time*j);
      }//end inner loop
      realOut[i] = real;
    }//end outer loop
  }//end inverseTransform

}//end class InverseComplexToReal01

Listing 15
/* File Dsp036.java
Copyright 2004, R.G.Baldwin
Revised 5/24/04

Illustrates forward and inverse Fourier
 transforms using FFT algorithms.

Performs spectral analysis on a time series
consisting of pulses and a sinusoid.

Passes resulting real and complex parts to
inverse Fourier transform program to reconstruct
the original time series.

Run with Graph03.

Tested using J2SE 1.4.2 under WinXP.
************************************************/
import java.util.*;

class Dsp036 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeOut = new double[len];

  public Dsp036(){//constructor

    //Create the raw data pulses
    timeDataIn[0] = 0;
    timeDataIn[1] = 50;
    timeDataIn[2] = 75;
    timeDataIn[3] = 80;
    timeDataIn[4] = 75;
    timeDataIn[5] = 50;
    timeDataIn[6] = 25;
    timeDataIn[7] = 0;
    timeDataIn[8] = -25;
    timeDataIn[9] = -50;
    timeDataIn[10] = -75;
    timeDataIn[11] = -80;
    timeDataIn[12] = -60;
    timeDataIn[13] = -40;
    timeDataIn[14] = -26;
    timeDataIn[15] = -17;
    timeDataIn[16] = -11;
    timeDataIn[17] = -8;
    timeDataIn[18] = -5;
    timeDataIn[19] = -3;
    timeDataIn[20] = -2;
    timeDataIn[21] = -1;


    timeDataIn[240] = 80;
    timeDataIn[241] = 80;
    timeDataIn[242] = 80;
    timeDataIn[243] = 80;
    timeDataIn[244] = -80;
    timeDataIn[245] = -80;
    timeDataIn[246] = -80;
    timeDataIn[247] = -80;

    timeDataIn[248] = 80;
    timeDataIn[249] = 80;
    timeDataIn[250] = 80;
    timeDataIn[251] = 80;
    timeDataIn[252] = -80;
    timeDataIn[253] = -80;
    timeDataIn[254] = -80;
    timeDataIn[255] = -80;

    //Create raw data sinusoid
    for(int x = len/3;x < 3*len/4;x++){
      timeDataIn[x] = 80.0 * Math.sin(
                              2*pi*(x)*1.0/20.0);
    }//end for loop

    //Compute FFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplexFFT01.transform(
                                     timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude);

    //Compute inverse FFT of spectral data
    InverseComplexToRealFFT01.
                                inverseTransform(
                                     realSpect,
                                     imagSpect,
                                     timeOut);
  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of curves to plot.  Must not
    // exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > timeDataIn.length-1){
      return 0;
    }else{
      return timeDataIn[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > realSpect.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*realSpect[index]/len;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > imagSpect.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*imagSpect[index]/len;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                    index > magnitude.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 5*magnitude[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                   index > timeOut.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return timeOut[index]/len;
    }//end else
  }//end function

}//end sample class Dsp036

Listing 16
/*File InverseComplexToRealFFT01.java
Copyright 2004, R.G.Baldwin
Rev 5/24/04

The static method named inverseTransform performs
a complex to real Fourier transform using a
complex-to-complex FFT algorithm.  A specific
parameter is passed to the FFT algorithm that
causes this to be an inverse Fourier transform.

See InverseComplexToReal01 for a version that
does not use an FFT algorithm but uses a DFT
algorithm instead.

Incoming parameters are:
  double[] realIn - incoming real data
  double[] imagIn - incoming imaginary data
  double[] realOut - outgoing real data

Requires spectral input data extending from zero
to the sampling frequency.

Assumes real input consists of positive
frequency points for a symmetric real frequency
function.  That is, the real input is assumed to
be symmetric about the folding frequency.  Does
not test this assumption.

Assumes imaginary input consists of positive
frequency points for an asymmetric imaginary
frequency function.  That is, the imaginary input
is assumed to be asymmetric about  the
folding frequency.  Does not test this
assumption.

The assumption of a symmetric real part and an
asymmetric imaginary part guarantees that the
imaginary output is all zeros.  Thus, the
program does not return an imaginary output.
Does not test the assumption that the imaginary
is all zeros.

CAUTION: THE INCOMING DATA LENGTH MUST BE A
POWER OF TWO. OTHERWISE, THIS PROGRAM WILL FAIL
TO RUN PROPERLY.

Returns a number of points equal to the incoming
data length.  Those points are uniformly
distributed beginning at zero.
************************************************/

public class InverseComplexToRealFFT01{

  public static void inverseTransform(
                             double[] realIn,
                             double[] imagIn,
                             double[] realOut){
    double pi = Math.PI;//for convenience
    int dataLen = realIn.length;
    double[] imagOut = new double[dataLen];
    //The complexToComplex FFT method does an
    // in-place transform causing the output
    // complex data to be stored in the arrays
    // containing the input complex data.
    // Therefore, it is necessary to copy the
    // input data into the output arrays before
    // passing them to the FFT algorithm.
    System.arraycopy(realIn,0,realOut,0,dataLen);
    System.arraycopy(imagIn,0,imagOut,0,dataLen);

    //Perform the spectral analysis.  The results
    // are stored in realOut and imagOut.  Note
    // that the -1 value for the first
    // parameter causes the transform to be an
    // inverse transform.  A +1 value would cause
    // it to be a forward transform.
    complexToComplex(-1,dataLen,realOut,imagOut);

  }//end inverseTransform method
  //-------------------------------------------//

  //This method computes a complex-to-complex
  // FFT.  The value of sign must be 1 for a
  // forward FFT and -1 for an inverse FFT.
  public static void complexToComplex(
                                  int sign,
                                  int len,
                                  double real[],
                                  double imag[]){
    double scale = 1.0;
    //Reorder the input data into reverse binary
    // order.
    int i,j;
    for (i=j=0; i < len; ++i) {
      if (j>=i) {
        double tempr = real[j]*scale;
        double tempi = imag[j]*scale;
        real[j] = real[i]*scale;
        imag[j] = imag[i]*scale;
        real[i] = tempr;
        imag[i] = tempi;
      }//end if
      int m = len/2;
      while (m>=1 && j>=m) {
        j -= m;
        m /= 2;
      }//end while loop
      j += m;
    }//end for loop

    //Input data has been reordered.
    int stage = 0;
    int maxSpectraForStage,stepSize;
    //Loop once for each stage in the spectral
    // recombination process.
    for(maxSpectraForStage = 1,
                stepSize = 2*maxSpectraForStage;
                maxSpectraForStage < len;
                maxSpectraForStage = stepSize,
                stepSize = 2*maxSpectraForStage){
      double deltaAngle =
                 sign*Math.PI/maxSpectraForStage;
      //Loop once for each individual spectra
      for (int spectraCnt = 0;
                 spectraCnt < maxSpectraForStage;
                 ++spectraCnt){
        double angle = spectraCnt*deltaAngle;
        double realCorrection = Math.cos(angle);
        double imagCorrection = Math.sin(angle);

        int right = 0;
        for (int left = spectraCnt;
                   left < len;left += stepSize){
          right = left + maxSpectraForStage;
          double tempReal =
                    realCorrection*real[right]
                    - imagCorrection*imag[right];
          double tempImag =
                    realCorrection*imag[right]
                    + imagCorrection*real[right];
          real[right] = real[left]-tempReal;
          imag[right] = imag[left]-tempImag;
          real[left] += tempReal;
          imag[left] += tempImag;
        }//end for loop
      }//end for loop for individual spectra
      maxSpectraForStage = stepSize;
    }//end for loop for stages
  }//end complexToComplex method

}//end class InverseComplexToRealFFT01

Listing 17
/* File Dsp037.java
Copyright 2004, R.G.Baldwin
Revised 5/24/04

Illustrates filtering in the frequency domain.
Performs FFT on an impulse.  Eliminates all
energy between one-sixth and five-sixths of the
sampling frequency by modifying the real and
imaginary parts of the FFT output.  Then performs
inverse FFT to produce the filtered impulse.

Run with Graph03.

Tested using J2SE 1.4.2 under WinXP.
************************************************/
import java.util.*;

class Dsp037 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeOut = new double[len];

  public Dsp037(){//constructor

    //Create the raw data pulse
    timeDataIn[32] = 90;

    //Compute FFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplexFFT01.transform(
                                     timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude);

    //Apply the frequency filter eliminating all
    // energy between one-sixth and five-sixths
    // of the sampling frequency by modifying the
    // real and imaginary parts of the spectrum.
    for(int cnt = len/6;cnt < 5*len/6;cnt++){
      realSpect[cnt] = 0.0;
      imagSpect[cnt] = 0.0;
    }//end for loop

    //Recompute the magnitude based on the
    // modified real and imaginary spectra.
    for(int cnt = 0;cnt < len;cnt++){
      magnitude[cnt] =
        (Math.sqrt(
           realSpect[cnt]*realSpect[cnt]
           + imagSpect[cnt]*imagSpect[cnt])/len);
    }//end for loop

    //Compute inverse FFT of modified spectral
    // data.
    InverseComplexToRealFFT01.inverseTransform(
                                       realSpect,
                                       imagSpect,
                                       timeOut);
  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of curves to plot.  Must not
    // exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > timeDataIn.length-1){
      return 0;
    }else{
      return timeDataIn[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > realSpect.length-1){
      return 0;
    }else{
      return realSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > imagSpect.length-1){
      return 0;
    }else{
            return imagSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                    index > magnitude.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return len*magnitude[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                   index > timeOut.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 3.0*timeOut[index]/len;
    }//end else
  }//end function

}//end sample class Dsp037

Listing 18
/* File Dsp038.java
Copyright 2004, R.G.Baldwin
Revised 5/24/04

Illustrates filtering in the frequency domain.
Performs FFT on an impulse.  Modifies the
complex spectrum.  Then performs inverse FFT
to produce the filtered impulse.

Run with Graph03.

Tested using J2SE 1.4.2 under WinXP.
************************************************/
import java.util.*;

class Dsp038 implements GraphIntfc01{
  final double pi = Math.PI;

  int len = 256;

  double[] timeDataIn = new double[len];
  double[] realSpect = new double[len];
  double[] imagSpect = new double[len];
  double[] angle = new double[len];//unused
  double[] magnitude = new double[len];
  double[] timeOut = new double[len];

  public Dsp038(){//constructor

    //Create the raw data pulse
    timeDataIn[64] = 90;

    //Compute FFT of the time data and save it in
    // the output arrays.
    ForwardRealToComplexFFT01.transform(
                                     timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude);

    //Apply the frequency filter.
    for(int cnt = 0;cnt <= len/2;cnt++){
      if(cnt < 3*len/32){
        realSpect[cnt] = 0;
        imagSpect[cnt] = 0;
      }//end if

      if(cnt > 5*len/32){
        realSpect[cnt] = 0;
        imagSpect[cnt] = 0;
      }//end if

      //Fold complex spectral data
      if(cnt > 0){
        realSpect[len - cnt] = realSpect[cnt];
      }//end if
      if(cnt > 0){
        imagSpect[len - cnt] = -imagSpect[cnt];
      }//end if
    }//end for loop

    //Recompute the magnitude based on the
    // modified real and imaginary spectra.
    for(int cnt = 0;cnt < len;cnt++){
      magnitude[cnt] =
        (Math.sqrt(
           realSpect[cnt]*realSpect[cnt]
           + imagSpect[cnt]*imagSpect[cnt])/len);
    }//end for loop

    //Compute inverse FFT of modified spectral
    // data.
    InverseComplexToRealFFT01.inverseTransform(
                                       realSpect,
                                       imagSpect,
                                       timeOut);
  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of curves to plot.  Must not
    // exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > timeDataIn.length-1){
      return 0;
    }else{
      return timeDataIn[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > realSpect.length-1){
      return 0;
    }else{
      return realSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > imagSpect.length-1){
      return 0;
    }else{
            return imagSpect[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                    index > magnitude.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return len*magnitude[index];
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 ||
                   index > timeOut.length-1){
      return 0;
    }else{
      //scale for convenient viewing
      return 3.0*timeOut[index]/len;
    }//end else
  }//end function

}//end sample class Dsp038

Listing 19

 


Copyright 2004, Richard G. Baldwin.  Reproduction in whole or in
part in any form or medium without express written permission from Richard
Baldwin is prohibited.

About the author


Richard Baldwin

is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java, C#, and
XML. In addition to the many platform and/or language independent benefits
of Java and C# applications, he believes that a combination of Java, C#,
and XML will become the primary driving force in the delivery of structured
information on the Web.

Richard has participated in numerous consulting projects, and he frequently
provides onsite training at the high-tech companies located in and around
Austin, Texas.  He is the author of Baldwin’s Programming Tutorials,
which has gained a worldwide following among experienced and aspiring programmers.
He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of
practical experience in Digital Signal Processing (DSP).  His first
job after he earned his Bachelor’s degree was doing DSP in the Seismic Research
Department of Texas Instruments.  (TI is still a world leader in DSP.) 
In the following years, he applied his programming and DSP expertise to other
interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.

[email protected]

-end-

Latest Posts

Related Stories