JavaSpectrum Analysis using Java, Frequency Resolution versus Data Length

Spectrum Analysis using Java, Frequency Resolution versus Data Length

Java Programming, Notes # 1483


Preface

The how and the why of spectral analysis

A previous lesson entitled
Fun with Java,
How and Why Spectral Analysis Works
explained some of the fundamentals
regarding spectral
analysis.  An understanding of that lesson is a prerequisite to
an understanding of this lesson.

Another previous lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
presented and explained several Java
programs for doing spectral analysis.  In that lesson, I used a DFT program to illustrate
several aspects of
spectral analysis that center around the sampling frequency and the Nyquist
folding frequency.

I also used and briefly explained two different plotting programs that were originally explained in
the earlier lesson entitled

Plotting Engineering and Scientific Data using Java
.

An understanding of the lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
is also a prerequisite to
an understanding of this lesson.

Frequency resolution versus data length

In this lesson I will use similar programs to explain and illustrate the manner in which spectral frequency
resolution behaves with respect to data length.

A hypothetical situation

Consider a hypothetical situation in which you are performing spectral
analysis on underwater acoustic signals in an attempt to identify enemy
submarines.

You are aware that the enemy submarine contains a device that operates
occasionally in short bursts.  You are also aware that this device contains
two rotating machines that rotate at almost but not quite the same speed.

During an operating burst of the device, each of the two machines contained in the device will
emit acoustic energy that may appear as a peak in your spectral analysis
output.  (Note that I said, "may appear" and did not say, "will
appear.")
  If you can identify the two peaks, you can conclusively identify
the acoustic source as an enemy submarine.

The big question

How long must the operating bursts of this device be in
order for you to resolve the peaks and identify the enemy submarine under ideal
conditions?  That is the question that I will attempt to answer in this
lesson by teaching you about the relationship between frequency resolution and
data length.

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

Before I get into the technical details, here is a preview of the programs
and their purposes that I will present and explain in this lesson:

  • Dsp031 – Illustrates frequency resolution versus pulse length for
    pulses consisting of a truncated single sinusoid.
  • Dsp031a – Displays the pulses analyzed by Dsp031.
  • Dsp032 – Illustrates frequency resolution versus pulse length for
    pulses consisting of the sum of two truncated sinusoids with closely spaced
    frequencies.
  • Dsp032a – Displays the pulses analyzed by Dsp032.
  • Dsp033 – Illustrates frequency resolution versus pulse length for
    pulses consisting of the sum of two truncated sinusoids whose frequencies
    are barely resolvable.
  • Dsp033a – Displays the pulses analyzed by Dsp033.

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 the DFT algorithm
    for spectral analysis.
  • 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

Let’s begin by looking at the time series data that will be used as input to the first spectral
analysis experiment.  Figure 1 shows
five pulses in the time domain.  Figure 2 and Figure 3 show the result of performing a
spectral analysis on each of these pulses.

(The display in Figure 1 was produced by the program named Dsp031a,
which I will explain later.)



Figure 1 Five pulses in the time domain.

The length of the pulses

If you examine Figure 1 carefully, you will see that each pulse is twice as
long as the pulse above it.  (There is a tick mark on the horizontal
axes every twenty-five samples.)
  The bottom pulse is 400 samples long
while the top pulse is 25 samples long.

Truncated sinusoids

Each pulse consists of a cosine wave that has been truncated at a different
length.  The frequency of the
cosine wave is the same for every pulse.  As you will see when we examine
the code, the frequency of the cosine wave is 0.0625 times the sampling
frequency.  If you do the arithmetic, you will conclude that this
results in 16 samples per cycle of the cosine wave.

In all five cases, the length of the time series upon which spectral analysis
will be performed is 400 samples.  For those four cases where the length of the
pulse is less than 400 samples, the remaining samples in the time series have a
value of zero.

Will compute at 400 frequencies

When the spectral analysis is performed later, the number of individual
frequencies at which the amplitude of the spectral energy will be computed will
be equal to the total data length.  Therefore, the amplitude of the
spectral energy will be computed at the same 400 frequencies for each of the five time
series.  That makes it convenient for us to stack the spectral plots up
vertically and compare them (as in Figure 2).  This makes it easy for us to compare the
distribution of energy across the frequency spectrum for pulses of different
lengths.

Graph03 and Graph06

The plots in Figure 1 were produced using the program named Graph03
Other plots in this lesson will be produced using the program named Graph06
I explained those programs in earlier lessons, and I provided the source code
for both programs in the previous lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.  Therefore, I
won’t repeat those explanations or provide the source code for those programs in
this lesson.

The program named Dsp031a

A complete listing of the program named Dsp031a is provided in Listing
9 near the end of the lesson.

This program displays sinusoidal pulses identical to those
processed by the program named Dsp031, which will be discussed later.

Time series containing sinusoidal pulses

The program named Dsp031a creates and displays five separate time series,
each 400 samples in length. 
Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of a truncated sinusoid.  The frequency of the
sinusoid for each of the pulses is the
same.

Frequency values are specified as type double as a fraction of the sampling frequency. 
The frequency of each sinusoid is 0.0625 times
the sampling frequency.

The pulse lengths

The lengths of the five pulses are:

  • 25 samples
  • 50 samples
  • 100 samples
  • 200 samples
  • 400 samples

Will discuss in fragments

This program is very similar to programs that I explained in previous lessons
in this series, so my explanation will be very brief.  As usual, I will
discuss the program in fragments.

The beginning of the class, along with the declaration and initialization of
several variables is shown in Listing 1.  The names of the variables along
with the embedded comments should make the code self explanatory.

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

  int len = 400;//data length
  int numberPulses = 5;
  //Frequency of the sinusoids
  double freq = 0.0625;
  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain sinusoidal data
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

Listing 1

The constructor

Listing 2 shows the constructor, which creates the raw sinusoidal data and
stores that data in the array objects created in Listing 1.

(Recall that all element values in the array objects are initialized
with a value of zero.  Therefore, the code in Listing 2 only needs to
store the non-zero values in the array objects.)

 public Dsp031a(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

  }//end constructor

Listing 2

The code in the conditional clause of each of the for loops in Listing
2 controls the length of each of the sinusoidal pulses.

The interface methods

As you can see in Listing 1, the class implements the interface named
GraphIntfc01
.  I introduced this interface in the earlier lesson
entitled

Plotting Engineering and Scientific Data using Java
and also discussed it in
the previous lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.

The remaining code for the class named Dsp031a consists of the methods
necessary to satisfy the interface.  These methods are invoked by the
plotting programs named Graph03 and Graph06 to obtain and plot the
data returned by the methods.  As implemented in Dsp031a, these
interface methods return the values stored in the array objects referred to by
data1 through data5.  Thus, the values stored in those array
objects are plotted in Figure 1.

Spectral analysis results

Figure 2 shows the result of using the program named Dsp031 to perform
a spectral analysis on each of the five pulses shown in Figure 1.  These
results were plotted using the program named Graph06.  With this
plotting program, each data value is plotted as a vertical bar.  However,
in this case, the sides of each of the bars are so close together that the area
under the spectral curve appears to be solid black.

(When you run this program, you can expand the display to full screen
and see the individual vertical bars.  However, I can’t to that and
maintain the narrow publication format required for this lesson.)



Figure 2 Spectral analyses of five pulses.

Interpretation of the results

Before I get into the interpretation, I need to point out that I normalized
the data plotted in Figure 2 to cause each spectral peak to have approximately
the same value.  Otherwise, the spectral analysis result values for the
short pulses would have been too small to be visible in this plotting format.

Therefore, the fact that the area under the curve in
the top plot is greater than the area under the curve in the bottom plot doesn’t
indicate that the first pulse contains more energy than the last pulse.  It
simply means that I normalized the data for best results in plotting.

Spectrum of an ideal sinusoid

That having been said, different people will probably interpret these results
in different ways.  Let’s begin by stating that the theoretical spectrum
for a sinusoid of infinite length in the absence of noise is a single vertical
line having zero width and infinite height.

In the real world of measurements, however, there is no such thing as a
sinusoid of infinite length.  Rather, every measurement that we make must
truncate the sinusoid at some point in time.  For a theoretical signal of
infinite length, every spectral analysis that we can perform is an imperfect
estimate of the spectrum.

Two viewpoints

There are at least two ways to think of the pulses shown in Figure 1.

  1. Each pulse is a truncated section of an ideal sinusoid of infinite
    length.
  2. Each pulse is a signal having a definite planned start and stop time.

The way that you interpret the results shown in Figure 2 depends on your viewpoint
regarding the pulses.

The first viewpoint

If your viewpoint is that each pulse is a truncated section of an ideal
sinusoid of infinite length, then the width of each of the peaks (beyond zero
width)
is the result of measurement error introduced by the truncation
process.

The second viewpoint

If your viewpoint is that each pulse is a signal having a definite planned
start and stop time, then the widths and the shape of each of the peaks
describes the full range of frequency components required to physically generate
such a pulse.  This is the viewpoint that is consistent with the
hypothetical situation involving a
device on a submarine that I described earlier in this lesson.

A simplified hypothetical situation

Assume for the moment that the hypothetical device on the submarine contains
only one rotating machine and that this device is turned on and off occasionally
in short bursts.  Because of the rotating machine, when the device is
turned on, it will emit acoustic energy whose frequency matches the rotating
speed of the machine.

(In reality, it will probably also emit acoustic energy at other
frequencies as well, but we will consider it to be a very ideal machine. 
We will also assume the complete absence of any other acoustic noise in the
environment.)

Assume that you have a recording window of 400 samples, and that you are able
to record five such bursts within each of five separate recording windows. 
Further assume that the lengths of the individual bursts match the time periods
indicated by the pulses in Figure 1.

The spectrum of the bursts

If you perform spectral analysis on each of the five individual 400-sample
windows containing the bursts, and if you normalize the peak values for plotting
purposes, you should get results similar to those shown in Figure 2.

The spectral bandwidth of the signal

The frequency range over which energy is distributed is referred to as the
bandwidth of the signal.  As you can see in Figure 2, shorter pulses
require wider bandwidth.

For example, considerably more bandwidth is required of a communication
system that is required to reliably transmit a series of short truncated
sinusoids than one that is only required to reliably transmit a continuous tone
at a single frequency.

At the same time, it is very difficult to convey very much information with a
signal consisting of a continuous tone at a single frequency (other than the
fact that the tone exists).
  Communication systems designed to convey
information usually encode that information by either turning the tone on and
off or by causing it to shift among a set of previously defined frequencies. 
The tone is often referred to as the carrier and the encoding of the information
is often referred to as modulating the carrier.

Thus, you need greater bandwidth to reliably convey more information.

The relationship between pulse length and bandwidth

So far, we can draw one important conclusion from our experiment.

Shorter pulses require greater bandwidth. 

This leads to an important
question.  What is the numerical relationship between pulse length and
bandwidth?  Although we can draw the above general conclusion from Figure
2, it is hard to draw any quantitative conclusions from Figure 2.  That
brings us to the expanded plot of the spectral data shown in Figure 3.

An expanded plot of the spectral results

Figure 3 shown the left one-fourth of the spectral results from Figure 2
plotted in the same horizontal space.  In other words, Figure 3 discards
the upper three-fourths of the spectral results from Figure 2 and shows only the
lower one-fourth of the spectral results on an expanded scale.  Figure 3
also provides tick marks that make it convenient to perform measurements on the
plots.

Also, whereas Figure 2 was plotted using the program named Graph06,
Figure 3 was plotted using the program named Graph03.  Thus, Figure
3 uses a different plotting format than Figure 2



Figure 3 Expanded spectral analyses of five pulses.

Picking numeric values

The curves in Figure 3 are spread out to the point that we can pick some
approximate numeric values off the plot, and from this, we can draw a very
significant conclusion.

For purposes of our approximation, consider the bandwidth to be the distance
along the frequency axis between the points where the curves hit zero on either
side of the peak.  Using this approximation, the bandwidth indicated by the
spectral analyses in Figure 3 shows the bandwidth of each spectrum to be twice
as wide as the one below it.

Referring back to Figure 1, recall that the length of each pulse was half
that of the one below it.  The conclusion is:

The bandwidth of a
truncated sinusoidal pulse is inversely proportional to the length of the pulse.

If you reduce the length of the pulse by a factor of two, you must double the
bandwidth of a transmission system designed to reliably transmit a pulse of that
length.

This will also be an important conclusion regarding our ability to separate and identify the two spectral peaks in the burst of acoustic energy
described in our original hypothetical
situation
.

Let’s see some code

The generation of the signals and the spectral analysis for the results
presented in Figure 2 and Figure 3 were performed using the program named
Dsp031
.  A complete listing of the program is shown in Listing 10 near
the end of the lesson.

Description of the program named Dsp031

This program performs spectral analyses on five separate time
series, each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.  (The lengths of the individual pulses match
that shown in Figure 1.) 
Each pulse consists of a truncated sinusoid.  The
frequency of the sinusoid for each pulse is the
same.

All frequency values are specified as type double as a fraction of the sampling frequency.  The frequency of all five sinusoids is 0.0625 times
the sampling frequency.

The lengths of the pulses are:

  • 25 samples
  • 50 samples
  • 100 samples
  • 200 samples
  • 400 samples

If this sounds familiar, it is because the pulses are identical to those
displayed in Figure 1 and discussed under Dsp031a above.

Uses a DFT algorithm

The spectral analysis process uses a DFT algorithm and computes the amplitude
of the spectral
energy at
400 equally spaced frequ4encies between zero and the
folding frequency.

(Recall from the previous lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
that
the folding frequency is one-half the sampling frequency.)

This program
computes and displays the amplitude spectrum at frequency intervals that are
one-half of the frequency intervals for a typical FFT algorithm.

Normalize the results

The results of the spectral analysis are
multiplied by the reciprocal of the lengths of
the individual pulses to normalize all five
plots to the same peak value.  Otherwise, the
results for the short pulses would be too
small to see on the plots.

Will discuss in fragments

Once again, this program is very similar to programs explained in the
previous lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.  Therefore, this discussion will be very brief.

The code in Listing 3 declares and initializes some variables and creates the
array objects that will contain the sinusoidal pulses.

In addition, the
code in Listing 3 declares reference variables that will be used to refer to
array objects containing results of the spectral analysis process that are not used in this program.

Finally, Listing 3 declares reference variables that will be used to refer to
array objects containing the results plotted in Figure 2 and Figure 3.

Given the names of
the variables, the comments, and what you learned in the previous lesson, the
code in Listing 3 should be self explanatory.

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

  int len = 400;//data length
  //Sample that represents zero time.
  int zeroTime = 0;
  //Low and high frequency limits for the
  // spectral analysis.
  double lowF = 0.0;
  double highF = 0.5;
  int numberSpectra = 5;
  //Frequency of the sinusoids
  double freq = 0.0625;
  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain data that is
  // input to the spectral analysis process.
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];
  
  //Following arrays receive information back
  // from the spectral analysis that is not used
  // in this program.
  double[] real;
  double[] imag;
  double[] angle;
  
  //Following arrays receive the magnitude
  // spectral information back from the spectral
  // analysis process.
  double[] mag1;
  double[] mag2;
  double[] mag3;
  double[] mag4;
  double[] mag5;

Listing 3

The constructor

The constructor begins in Listing 4.  The code in Listing 4 is identical
to that shown earlier in Listing 2.  This code generates the five
sinusoidal pulses and stores the data representing those pulses in the arrays
referred to by data1 through data5.  So far, except for the
declaration of some extra variables, this program isn’t much different from the
program named Dsp031a discussed earlier in this lesson. 

  public Dsp031(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

Listing 4

Perform the spectral analysis

The remainder of the constructor is shown in Listing 5.  This code
invokes the transform method of the ForwardRealToComplex01 class
five times in succession to perform the spectral analysis on each of the five
pulses shown in Figure 1.

(I explained the transform method in
detail in the previous lesson entitled

Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm
.)

    mag1 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data1,real,
      imag,angle,mag1,zeroTime,lowF,highF);

    mag2 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data2,real,
      imag,angle,mag2,zeroTime,lowF,highF);

    mag3 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data3,real,
      imag,angle,mag3,zeroTime,lowF,highF);

    mag4 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data4,real,
      imag,angle,mag4,zeroTime,lowF,highF);

    mag5 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data5,real,
      imag,angle,mag5,zeroTime,lowF,highF);

  }//end constructor

Listing 5

Each time the transform method is invoked, it computes the magnitude
spectra for the incoming data and saves it in the output array.

(Note that the real, imag, and angle arrays are not used later, so they are discarded each time a new
spectral analysis is performed.)

The interface methods

The Dsp031 class also implements the interface named GraphIntfc01
The remaining code in the program consists of the methods required to satisfy
that interface.  Except for the identification of the arrays from which the
methods extract data to be returned for plotting, these methods are identical to
those defined in the earlier class named Dsp031a.  Therefore, I
won’t discuss them further.

What we have learned so far

So far, the main things that we have learned is that shorter pulses require
greater bandwidth, and the bandwidth required to
faithfully represent a truncated sinusoidal pulse is the reciprocal of the
length of the pulse.

Where do we go from here?

Now we will look at the issues involved in using spectral analysis to
separate and identify the frequencies of two closely-spaced spectral peaks for a
pulse composed of the sum of two sinusoids.  Once again, we will begin by
looking at some results and then discuss the code that produced those results.

The five pulses

The five pulses that we will be working with in this example are shown in Figure 4. 
As you can see, these pulses are a little more ugly than the pulses shown in Figure
1.  As you can also see, as was the case in Figure 1, each pulse appears to
be a shorter or longer version of the other pulses in terms of its waveform.



Figure 4 Five pulses with two sinusoids each.

Produced by Dsp032a

The plots in Figure 4 were produced by the program named Dsp032a,
which I will briefly discuss later.  (A complete listing of the program
is shown in Listing 11 near the end of the lesson.)
  This program
creates and displays pulses identical to those processed by the program named
Dsp032
, which I will also briefly discuss later.  (A complete
listing of the program named Dsp32 is presented in Listing 12.)

Five time series containing pulses

The program creates and displays five separate time series, each 400 samples in length.  Each time series contains a pulse and the pulses are different lengths. 
As before, each of the pulses shown in Figure 4 is half the length below the
pulse below it.

The sum of two sinusoids

Each pulse consists of the sum of two sinusoids at closely spaced frequencies.  The frequencies of the two sinusoids for all pulses are the same.

All frequency values are specified as type double as a fraction of the
sampling frequency.  The frequencies of the two sinusoids are equidistant
from a center frequency of 0.0625 times the sampling frequency.

(Recall that
0.0625 was the frequency of the only sinusoid contained in the pulses shown in
Figure 1 and processed by the program named Dsp031.)

The frequencies and pulse lengths

The frequency of one sinusoid is (0.0625 – 2.0/len) times the sampling frequency,
where len is the length of the time series containing the pulse. 
(The value for len is 400 samples in this program.)
  The frequency of the other sinusoid is (0.0625 +
2.0/len) times the sampling frequency.

The lengths of the five pulses are:

  • 25 samples
  • 50 samples
  • 100 samples
  • 200 samples
  • 400 samples

(Note that Figure 4 has tick marks
every 25 samples.)

The program named Dsp032a

The only new code in this program is the code in the constructor that creates
the pulses and stores them in the data arrays.  This code consists of five
separate for loops, one for each pulse.  The code for the first
for
loop, which is typical of the five, is shown in Listing 6.

  public Dsp032a(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

//... code removed for brevity

  }//end constructor

Listing 6

As you can see from Listing 6, the values that make up the pulse are produced
by adding together the values of two different cosine functions having different
frequencies.  The values for freq1 and freq2 are as described
above.

You can view the remainder of this program in Listing 11.

Spectral analysis output

The results of running the program named Dsp032 and displaying the
results with the program named Graph03 are shown in Figure 5.



Figure 5 Spectral analyses of five pulses.

Each of the peaks in the third, fourth, and fifth plots in Figure 5
corresponds to the frequency of one of the two sinusoids that were added
together to produce the pulses shown in Figure 4.

Can we answer the original question now?

The question posed in the original
hypothetical situation
was "how long must the operating bursts of this
device be in order for you to resolve the peaks and identify the enemy submarine
under ideal conditions?"

We are looking at very ideal conditions in Figure 4 and Figure 5.  In
particular, the pulses exist completely in the absence of noise.

(The
existence of wide-band noise added to the pulses in Figure 4 would cause a
change in the spectral results in Figure 5.  That change might be described as
having the appearance of grass and
weeds growing on the baseline across the entire spectrum.  The stronger the
wide-band noise, the taller would be the weeds.)

Cannot resolve two peaks for first two pulses

Clearly for the ideal condition of recording the bursts in the total absence
of noise, you cannot resolve the peaks from the top two plots in Figure 5. 
For those two pulses, the spectral peaks simply merge together to form a single
broad peak.  Therefore, for this amount of separation between the
frequencies of the two sinusoids, the lengths of the first two pulses in Figure
4 are insufficient to allow for separation and identification of the separate
peaks.

We must be in luck

We seem to have the problem bracketed. 
(Were we really lucky, or did I plan it this way?)
  Under the ideal
conditions of this experiment, the peaks are separable in the middle plot of
Figure 5.  Thus, for the amount of separation between the frequencies of
the two sinusoids, the length of the third pulse is Figure 4 is sufficient to
allow for separation and identification of the separate peaks.

A qualified answer to the question

The peaks are even better separated in the bottom two plots in Figure 5. 
For the five pulses used in this experiment and the amount of separation between
the frequencies of the two sinusoids, any pulse as long or longer than the
length of the third pulse is Figure 4 is sufficient to allow for separation and
identification of the separate peaks.

What about the effects of noise?

If you were to add a nominal amount of wide-band noise to the mix, it would
become more difficult to resolve the peaks for the bottom three plots in Figure
5 because
the peaks would be growing out of a bed of weeds.

(If you add enough wide-band noise, you couldn’t resolve the peaks
using any of the plots, because the peaks would be completely "lost in the
noise.")

What can we learn from this?

Since we have concluded that the middle pulse in Figure 4 is sufficiently
long to allow us to resolve the two peaks, let’s see what we can learn from the
parameters that describe that pulse.

Pulse length and frequency separation

To begin with, the length of the pulse is 100 samples.

What about the frequency separation of the two sinusoids?  Recall that
the frequency of one sinusoid is (0.0625 – 2.0/len) times the sampling
frequency, where len is the length of the time series containing the
pulse.  The frequency of the other sinusoid is (0.0625 + 2.0/len) times the
sampling frequency.

Thus, total separation between the two frequencies is
4/len, or 4/400.  Dividing through by 4 we see that the separation between
the two frequencies is 1/100.

Eureka, we have found it

For the third pulse, the frequency separation is the reciprocal of the length
of the pulse.  Also, the length of the third pulse is barely sufficient to
allow for separation and identification of the two peaks in the spectrum. 
Thus, the two spectral peaks are separable in the absence of noise if the
frequency separation is the reciprocal of the pulse length.

(That is too good to be a coincidence.  I must have planned
that way.)

Thus, we have reached another conclusion.

Under ideal conditions, the
two peaks in the spectrum can be resolved when the separation between the
frequencies of the two sinusoids is equal to the reciprocal of the pulse length.

The general answer

There is no single answer to the question
"how long must the operating bursts of this device be in order for you to
resolve the peaks and identify the enemy submarine under ideal conditions?"

The answer depends on the frequency separation.  The general answer is that the length of the bursts must be at least as long as
the reciprocal of the frequency separation for the two sinusoids.  If the
separation is large, the pulse length may be short.  If the
separation is small, the pulse length must be long.

The program named Dsp032

As I indicated earlier, the plots shown in Figure 5 were the result of
running the program named Dsp032 and displaying the data with the program
named Graph03.

The only thing that is new in this program is the code that generates the
five pulses and saves them in their respective data arrays.  Even that code
is not really new, because it is identical to the code shown in Listing 6. 
Therefore, I won’t discuss this program further in this lesson.

One more experiment

As you can surmise from the conclusions reached above, in order to be able to
resolve the two peaks in the spectrum, you can either keep the pulse length the
same and increase the frequency separation, or you can keep the frequency
separation the same and increase the pulse length.

Let’s examine an
example where we keep the pulse lengths the same as before and adjust the
frequency separation between the two sinusoids to make it barely possible to
resolve the peaks for each of the five pulses.

We will need to increase the
frequency separation for the first two pulses, and we can decrease the frequency
separation for the fourth and fifth pulses.  We will leave the frequency
separation the same as before for the third pulse since it already seems to have
the optimum relationship between pulse length and frequency separation.

The five pulses

The five pulses used in this experiment are shown in Figure 6.



Figure 6 Five pulses with additive sinusoids.

Unlike in the previous two cases shown in Figure 1 and Figure 4, each of
these pulses has a different shape from the others.  In other words, in the
previous two cases, each pulse simply looked like a longer or shorter version of
the other pulses.  That is not the case in this example.

(Note however that the third pulse in Figure 6 looks just like the
third pulse in Figure 4.  They were created using the same parameters. 
However, none of the other pulses in Figure 6 look like the corresponding
pulses in Figure 4, and none of the pulses in Figure 6 look like the pulses
in Figure 1.)

Spectral analysis results

Figure 7 shows the result of performing spectral analysis on the five time
series containing the pulses shown in Figure 6.



Figure 7 Spectral analyses of five pulses.

Peaks for first two pulses are now resolvable

When we examine the code, you will see that the frequency separation for the
first two pulses has been increased to the reciprocal of the pulse length in
each case. 
This results in the two peaks in the spectrum for each of the first two pulses
being resolvable in Figure 7.

Third pulse hasn’t changed

The spectrum for the third pulse shown in Figure 7 is almost identical to the
spectrum for the third pulse shown in Figure 5.  The only difference is
that I had to decrease the vertical scaling on all of the plots in Figure 5 to
keep the peak in the top plot within the bounds of the plot.

Spectral peaks for last two pulses are closer

When we examine the code, you will also see that the frequency separation for the
last two pulses has been decreased to the reciprocal of the pulse length in each
case. 
This results in the two peaks in the spectrum for each of the last two pulses
being closer than before in Figure 7.

The peaks in the bottom two plots in Figure 7 appear to be resolvable,
but we can’t be absolutely certain because they are so close together,
particularly for the last plot. 

(If you expand the Frame to full screen when you run this program, you
will see that the two peaks are resolvable, but I can’t do that and stay
within this narrow publication format.)

Expand the horizontal plotting scale

Figure 8 adjusts the plotting parameters to cause the left-most one-fourth of
the data in Figure 7 to be plotted in the full width of the Frame in
Figure 8.



Figure 8 Expanded spectral analyses of five pulses.

The peaks are barely resolvable

Figure 8 shows that the two peaks are barely resolvable for all five of the pulses
shown in Figure 6.

(There is no space between the peaks at the baseline in Figure 8, but
the plots do go almost down to the baseline half way between the two peaks.)

The program named Dsp033

The plots in Figure 7 and Figure 8 were produced by running the program named
Dsp033 and plotting the results with the program named Graph03.

A complete listing of the program named Dsp033 is shown in Listing 14
near the end of the lesson.

This program is the same as Dsp032 except that the separation between the frequencies of the two sinusoids is the reciprocal of the length of the pulse
in each case.

The program performs spectral analysis on five separate time series, each 400 samples in length.  Each time series contains a pulse and the pulses are different lengths.

Each pulse consists of the sum of two sinusoids at closely spaced frequencies.  The frequencies of the two sinusoids are equidistant from
a center frequency of 0.0625 times the sampling frequency.  The total separation between the frequencies of the two sinusoids is the reciprocal of the length of the pulse.

All frequency values are specified as type double as a fraction of the sampling frequency.

The lengths of the pulses are:

  • 25 samples
  • 50 samples
  • 100 samples
  • 200 samples
  • 400 samples

The spectral analysis

The spectral analysis computes the spectra at 400 equally spaced frequencies between zero and the folding frequency
(one-half the sampling frequency).

The results of the spectral analysis are multiplied by the reciprocal of the lengths of the individual pulses to normalize the five plots.  Otherwise, the results for the short pulses would be too small to see on the plots.

Because of the similarity of this program to the previous programs, my
discussion of the code will be very brief.

Computation of the frequencies

The code in Listing 7 shows the computation of the frequencies of the
sinusoids that will be added together to form each of the five pulses.

  //Frequencies of the sinusoids
  double freq1a = 0.0625 - 8.0/len;
  double freq2a = 0.0625 + 8.0/len;

  double freq1b = 0.0625 - 4.0/len;
  double freq2b = 0.0625 + 4.0/len;

  double freq1c = 0.0625 - 2.0/len;
  double freq2c = 0.0625 + 2.0/len;

  double freq1d = 0.0625 - 1.0/len;
  double freq2d = 0.0625 + 1.0/len;

  double freq1e = 0.0625 - 0.5/len;
  double freq2e = 0.0625 + 0.5/len;

Listing 7

Create the pulses

The code in Listing 8 uses those frequency values to create the data for the
pulses and to store that data in the arrays used to hold the pulses.

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1a)
                    + amp*Math.cos(2*pi*x*freq2a);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq1b)
                    + amp*Math.cos(2*pi*x*freq2b);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq1c)
                    + amp*Math.cos(2*pi*x*freq2c);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq1d)
                    + amp*Math.cos(2*pi*x*freq2d);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq1e)
                   + amp*Math.cos(2*pi*x*freq2e);
    }//end for loop

Listing 8

Other than the code shown in Listing 7 and Listing 8, the program named
Dsp033
is the same as the programs that were previously explained, and I
won’t discuss it further.

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, you could create pulses
containing three or more sinusoids at closely spaced frequencies, and you could
cause the amplitudes of the sinusoids to be different.  See what it takes
to cause the peaks in the spectra of those pulses to be separable and
identifiable.

If you really want to get fancy, you could create a pulse consisting of a
sinusoid whose frequency changes with time from the beginning to the end of the
pulse.  (A pulse of this type is often referred to as a frequency
modulated sweep signal.)
  See what you can conclude from doing spectral
analysis on a pulse of this type.

Try using the random number generator of the Math class to add some
random noise to every value in the 400-sample time series.  See what this
does to your spectral analysis results.

Move the center frequency up and down the frequency axis.  See if you
can explain what happens as the center frequency approaches zero and as the
center frequency approaches the folding frequency.

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

Summary

This program provides the code for three spectral analysis experiments of
increasing complexity.

Bandwidth versus pulse length

The first experiment performs spectral analyses on five simple pulses consisting of
truncated sinusoids.  This experiment shows:

  • Shorter pulses require greater bandwidth.
  • The bandwidth of a
    truncated sinusoidal pulse is inversely proportional to the length of the pulse.

Peak resolution versus pulse length and frequency
separation

The second experiment performs spectral analyses on five more complex pulses consisting of the
sum of two truncated sinusoids having closely spaced frequencies.  The
purpose is to determine the required length of the pulse in order to use
spectral analysis to resolve spectral peaks attributable to the two sinusoids. 
The experiment shows that the peaks are barely resolvable when the length of the pulse
is the reciprocal of the frequency separation between the two sinusoids.

Five pulses with barely resolvable spectral peaks

The third experiment also performs spectral analyses on five pulses consisting of
the sum of two truncated sinusoids having closely spaced frequencies.  In
this case, the frequency separation for each pulse is the reciprocal of the
length of the pulse.  The results of the spectral analysis reinforce the
conclusions drawn in the second experiment.

What’s Next?

So far, the lessons in this series have ignored the complex nature of the
results of spectral analysis.  The complex results have been converted into
real results by computing the square root of the sum of the squares of the real
and imaginary parts.

The next lesson in the series will meet the issue of complex spectral results
head on and will explain the concept of phase angle.  In addition, the lesson
will explain the behavior of the phase angle with respect to time shifts in
the input time series.

Complete Program Listings

Complete listings of all the programs discussed in this lesson are provided in
this section.

/* File Dsp031a.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Displays sinusoidal pulses identical to those
processed by Dsp031.

Creates and displays five separate time series,
each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of a truncated sinusoid.  The
frequency of the sinusoid for all pulses is the
same.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The frequency of all sinusoids is 0.0625 times
the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

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

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

  int len = 400;//data length
  int numberPulses = 5;
  //Frequency of the sinusoids
  double freq = 0.0625;
  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain sinusoidal data
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  public Dsp031a(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data1.length-1){
      return 0;
    }else{
      //Scale the amplitude of the pulses to make
      // them compatible with the default
      // plotting amplitude of 100.0.
      return data1[index]*90.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data2.length-1){
      return 0;
    }else{
      return data2[index]*90.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data3.length-1){
      return 0;
    }else{
      return data3[index]*90.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data4.length-1){
      return 0;
    }else{
      return data4[index]*90.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data5.length-1){
      return 0;
    }else{
      return data5[index]*90.0/amp;
    }//end else
  }//end function

}//end sample class Dsp031a

Listing 9

 

/* File Dsp031.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Performs spectral analysis on five separate time
series, each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of a truncated sinusoid.  The
frequency of the sinusoid for all pulses is the
same.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The frequency of all sinusoids is 0.0625 times
the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

The spectral analyis computes the spectra at
400 equally spaced points between zero and the
folding frequency (one-half the sampling
frequency).

The results of the spectral analysis are
multiplied by the reciprocal of the lengths of
the individual pulses to normalize all five
plots to the same peak value.  Otherwise, the
results for the short pulses would be too
small to see on the plots.

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

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

  int len = 400;//data length
  //Sample that represents zero time.
  int zeroTime = 0;
  //Low and high frequency limits for the
  // spectral analysis.
  double lowF = 0.0;
  double highF = 0.5;
  int numberSpectra = 5;
  //Frequency of the sinusoids
  double freq = 0.0625;
  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain data that is
  // input to the spectral analysis process.
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  //Following arrays receive information back
  // from the spectral analysis that is not used
  // in this program.
  double[] real;
  double[] imag;
  double[] angle;

  //Following arrays receive the magnitude
  // spectral information back from the spectral
  // analysis process.
  double[] mag1;
  double[] mag2;
  double[] mag3;
  double[] mag4;
  double[] mag5;

  public Dsp031(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq);
    }//end for loop


    //Compute magnitude spectra of the raw data
    // and save it in output arrays.  Note that
    // the real, imag, and angle arrays are not
    // used later, so they are discarded each
    // time a new spectral analysis is performed.
    mag1 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data1,real,
      imag,angle,mag1,zeroTime,lowF,highF);

    mag2 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data2,real,
      imag,angle,mag2,zeroTime,lowF,highF);

    mag3 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data3,real,
      imag,angle,mag3,zeroTime,lowF,highF);

    mag4 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data4,real,
      imag,angle,mag4,zeroTime,lowF,highF);

    mag5 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data5,real,
      imag,angle,mag5,zeroTime,lowF,highF);

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag1.length-1){
      return 0;
    }else{
      //Scale the magnitude data by the
      // reciprocal of the length of the sinusoid
      // to normalize the five plots to the same
      // peak value.
      return mag1[index]*16.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag2.length-1){
      return 0;
    }else{
      return mag2[index]*8.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag3.length-1){
      return 0;
    }else{
      return mag3[index]*4.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag4.length-1){
      return 0;
    }else{
      return mag4[index]*2.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag5.length-1){
      return 0;
    }else{
      return mag5[index]*1.0;
    }//end else
  }//end function

}//end sample class Dsp031

Listing 10

 

/* File Dsp032a.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Displays sinusoidal pulses identical to those
processed by Dsp032.

Creates and displays five separate time series,
each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of the sum of two sinusoids
at closely spaced frequencies.  The frequencies
of the two sinusoids for all pulses are the same.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The frequencies of the two sinusoids are
equidistant from 0.0625 times the sampling
frequency.

The frequency of one sinusoid is
(0.0625 - 2.0/len) times the sampling frequency.

The frequency of the other sinusoid is
(0.0625 + 2.0/len) times the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

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

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

  int len = 400;//data length
  int numberPulses = 5;
  //Frequencies of the sinusoids
  double freq1 = 0.0625 - 2.0/len;
  double freq2 = 0.0625 + 2.0/len;

  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain sinusoidal data
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  public Dsp032a(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data1.length-1){
      return 0;
    }else{
      //Scale the amplitude of the pulses to make
      // them compatible with the default
      // plotting amplitude of 100.0.
      return data1[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data2.length-1){
      return 0;
    }else{
      return data2[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data3.length-1){
      return 0;
    }else{
      return data3[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data4.length-1){
      return 0;
    }else{
      return data4[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data5.length-1){
      return 0;
    }else{
      return data5[index]*40.0/amp;
    }//end else
  }//end function

}//end sample class Dsp032a

Listing 11

 

/* File Dsp032.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Performs spectral analysis on five separate time
series, each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of the sum of two sinusoids
at closely spaced frequencies.  The frequencies
of the two sinusoids for all pulses are the same.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The frequencies of the two sinusoids are
equidistant from 0.0625 times the sampling
frequency.

The frequency of one sinusoid is
(0.0625 - 2.0/len) times the sampling frequency.

The frequency of the other sinusoid is
(0.0625 + 2.0/len) times the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

The spectral analyis computes the spectra at
400 equally spaced points between zero and the
folding frequency (one-half the sampling
frequency).

The results of the spectral analysis are
multiplied by the reciprocal of the lengths of
the individual pulses to normalize the five
plots.  Otherwise, the results for the short
pulses would be too small to see on the plots.

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

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

  int len = 400;//data length
  //Sample that represents zero time.
  int zeroTime = 0;
  //Low and high frequency limits for the
  // spectral analysis.
  double lowF = 0.0;
  double highF = 0.5;
  int numberSpectra = 5;
  //Frequencies of the sinusoids
  double freq1 = 0.0625 - 2.0/len;
  double freq2 = 0.0625 + 2.0/len;

  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain data that is
  // input to the spectral analysis process.
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  //Following arrays receive information back
  // from the spectral analysis that is not used
  // in this program.
  double[] real;
  double[] imag;
  double[] angle;

  //Following arrays receive the magnitude
  // spectral information back from the spectral
  // analysis process.
  double[] mag1;
  double[] mag2;
  double[] mag3;
  double[] mag4;
  double[] mag5;

  public Dsp032(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq1)
                    + amp*Math.cos(2*pi*x*freq2);
    }//end for loop


    //Compute magnitude spectra of the raw data
    // and save it in output arrays.  Note that
    // the real, imag, and angle arrays are not
    // used later, so they are discarded each
    // time a new spectral analysis is performed.
    mag1 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data1,real,
      imag,angle,mag1,zeroTime,lowF,highF);

    mag2 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data2,real,
      imag,angle,mag2,zeroTime,lowF,highF);

    mag3 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data3,real,
      imag,angle,mag3,zeroTime,lowF,highF);

    mag4 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data4,real,
      imag,angle,mag4,zeroTime,lowF,highF);

    mag5 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data5,real,
      imag,angle,mag5,zeroTime,lowF,highF);

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag1.length-1){
      return 0;
    }else{
      //Scale the magnitude data by the
      // reciprocal of the length of the sinusoid
      // to normalize the five plots to the same
      // peak value.
      return mag1[index]*16.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag2.length-1){
      return 0;
    }else{
      return mag2[index]*8.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag3.length-1){
      return 0;
    }else{
      return mag3[index]*4.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag4.length-1){
      return 0;
    }else{
      return mag4[index]*2.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag5.length-1){
      return 0;
    }else{
      return mag5[index]*1.0;
    }//end else
  }//end function

}//end sample class Dsp032

Listing 12

 

/* File Dsp033a.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Displays sinusoidal pulses identical to those
processed by Dsp033.

Creates and displays five separate time series,
each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of the sum of two sinusoids
at closely spaced frequencies.  The frequencies
of the two sinusoids are equidistant from 0.0625
times the sampling frequency.  The total
separation between the frequencies of the two
sinusoids is the reciprocal of the length of the
pulse.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

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

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

  int len = 400;//data length
  int numberPulses = 5;
  //Frequencies of the sinusoids
  double freq1a = 0.0625 - 8.0/len;
  double freq2a = 0.0625 + 8.0/len;

  double freq1b = 0.0625 - 4.0/len;
  double freq2b = 0.0625 + 4.0/len;

  double freq1c = 0.0625 - 2.0/len;
  double freq2c = 0.0625 + 2.0/len;

  double freq1d = 0.0625 - 1.0/len;
  double freq2d = 0.0625 + 1.0/len;

  double freq1e = 0.0625 - 0.5/len;
  double freq2e = 0.0625 + 0.5/len;

  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain sinusoidal data
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  public Dsp033a(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1a)
                    + amp*Math.cos(2*pi*x*freq2a);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq1b)
                    + amp*Math.cos(2*pi*x*freq2b);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq1c)
                    + amp*Math.cos(2*pi*x*freq2c);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq1d)
                    + amp*Math.cos(2*pi*x*freq2d);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq1e)
                   + amp*Math.cos(2*pi*x*freq2e);
    }//end for loop

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data1.length-1){
      return 0;
    }else{
      //Scale the amplitude of the pulses to make
      // them compatible with the default
      // plotting amplitude of 100.0.
      return data1[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data2.length-1){
      return 0;
    }else{
      return data2[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data3.length-1){
      return 0;
    }else{
      return data3[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data4.length-1){
      return 0;
    }else{
      return data4[index]*40.0/amp;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > data5.length-1){
      return 0;
    }else{
      return data5[index]*40.0/amp;
    }//end else
  }//end function

}//end sample class Dsp033a

Listing 13

 

/* File Dsp033.java
Copyright 2004, R.G.Baldwin
Revised 5/17/2004

Same as Dsp032 except that the separation between
the frequencies of the two sinusoids is the
reciprocal of the length of the pulse.

Performs spectral analysis on five separate time
series, each 400 samples in length.

Each time series contains a pulse and the pulses
are different lengths.

Each pulse consists of the sum of two sinusoids
at closely spaced frequencies.  The frequencies
of the two sinusoids are equidistant from 0.0625
times the sampling frequency.  The total
separation between the frequencies of the two
sinusoids is the reciprocal of the length of the
pulse.

All frequency values are specified as type
double as a fraction of the sampling frequency.

The lengths of the pulses are:

25 samples
50 samples
100 samples
200 samples
400 samples

The spectral analyis computes the spectra at
400 equally spaced points between zero and the
folding frequency (one-half the sampling
frequency).

The results of the spectral analysis are
multiplied by the reciprocal of the lengths of
the individual pulses to normalize the five
plots.  Otherwise, the results for the short
pulses would be too small to see on the plots.

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

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

  int len = 400;//data length
  //Sample that represents zero time.
  int zeroTime = 0;
  //Low and high frequency limits for the
  // spectral analysis.
  double lowF = 0.0;
  double highF = 0.5;
  int numberSpectra = 5;
  //Frequencies of the sinusoids
  double freq1a = 0.0625 - 8.0/len;
  double freq2a = 0.0625 + 8.0/len;

  double freq1b = 0.0625 - 4.0/len;
  double freq2b = 0.0625 + 4.0/len;

  double freq1c = 0.0625 - 2.0/len;
  double freq2c = 0.0625 + 2.0/len;

  double freq1d = 0.0625 - 1.0/len;
  double freq2d = 0.0625 + 1.0/len;

  double freq1e = 0.0625 - 0.5/len;
  double freq2e = 0.0625 + 0.5/len;


  //Amplitude of the sinusoids
  double amp = 160;

  //Following arrays will contain data that is
  // input to the spectral analysis process.
  double[] data1 = new double[len];
  double[] data2 = new double[len];
  double[] data3 = new double[len];
  double[] data4 = new double[len];
  double[] data5 = new double[len];

  //Following arrays receive information back
  // from the spectral analysis that is not used
  // in this program.
  double[] real;
  double[] imag;
  double[] angle;

  //Following arrays receive the magnitude
  // spectral information back from the spectral
  // analysis process.
  double[] mag1;
  double[] mag2;
  double[] mag3;
  double[] mag4;
  double[] mag5;

  public Dsp033(){//constructor

    //Create the raw data
    for(int x = 0;x < len/16;x++){
      data1[x] = amp*Math.cos(2*pi*x*freq1a)
                    + amp*Math.cos(2*pi*x*freq2a);
    }//end for loop

    for(int x = 0;x < len/8;x++){
      data2[x] = amp*Math.cos(2*pi*x*freq1b)
                    + amp*Math.cos(2*pi*x*freq2b);
    }//end for loop

    for(int x = 0;x < len/4;x++){
      data3[x] = amp*Math.cos(2*pi*x*freq1c)
                    + amp*Math.cos(2*pi*x*freq2c);
    }//end for loop

    for(int x = 0;x < len/2;x++){
      data4[x] = amp*Math.cos(2*pi*x*freq1d)
                    + amp*Math.cos(2*pi*x*freq2d);
    }//end for loop

    for(int x = 0;x < len;x++){
      data5[x] = amp*Math.cos(2*pi*x*freq1e)
                   + amp*Math.cos(2*pi*x*freq2e);
    }//end for loop


    //Compute magnitude spectra of the raw data
    // and save it in output arrays.  Note that
    // the real, imag, and angle arrays are not
    // used later, so they are discarded each
    // time a new spectral analysis is performed.
    mag1 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data1,real,
      imag,angle,mag1,zeroTime,lowF,highF);

    mag2 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data2,real,
      imag,angle,mag2,zeroTime,lowF,highF);

    mag3 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data3,real,
      imag,angle,mag3,zeroTime,lowF,highF);

    mag4 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data4,real,
      imag,angle,mag4,zeroTime,lowF,highF);

    mag5 = new double[len];
    real = new double[len];
    imag = new double[len];
    angle = new double[len];
    ForwardRealToComplex01.transform(data5,real,
      imag,angle,mag5,zeroTime,lowF,highF);

  }//end constructor

  //-------------------------------------------//
  //The following six methods are required by the
  // interface named GraphIntfc01.
  public int getNmbr(){
    //Return number of functions to process.
    // Must not exceed 5.
    return 5;
  }//end getNmbr
  //-------------------------------------------//
  public double f1(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag1.length-1){
      return 0;
    }else{
      //Scale the magnitude data by the
      // reciprocal of the length of the sinusoid
      // to normalize the five plots to the same
      // peak value.
      return mag1[index]*16.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f2(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag2.length-1){
      return 0;
    }else{
      return mag2[index]*8.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f3(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag3.length-1){
      return 0;
    }else{
      return mag3[index]*4.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f4(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag4.length-1){
      return 0;
    }else{
      return mag4[index]*2.0;
    }//end else
  }//end function
  //-------------------------------------------//
  public double f5(double x){
    int index = (int)Math.round(x);
    if(index < 0 || index > mag5.length-1){
      return 0;
    }else{
      return mag5[index]*1.0;
    }//end else
  }//end function

}//end sample class Dsp033

Listing 14

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.

Baldwin@DickBaldwin.com

-end-

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories