Java Programming, Notes # 1484
- Preface
- Preview
- Discussion and Sample Code
- Run the Program
- Summary
- What’s Next?
- Complete Program Listing
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 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.
In this lesson, I will deal with issues involving the complex spectrum, the phase angle,
and shifts in 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 a program named Dsp034.
This program will be used to illustrate the behavior of the complex spectrum and
the phase angle for several different scenarios.
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
The program named Dsp034, when run in conjunction with either
Graph03 or Graph04 computes and plots the amplitude, real,
imaginary, and phase angle spectrum for a pulse that is read from a file named
Dsp034.txt. If that file doesn’t exist in the current directory, the program uses a set of default parameters
that describe a damped sinusoidal pulse. The program also plots the pulse
itself in addition to the spectral analysis results listed above.
The order of the plotted results
When the data is plotted (see Figure 2) using the programs Graph03 or Graph06, the order of the plots from top to bottom in the display
is:
- The pulse
- The amplitude spectrum
- The real spectrum
- The imaginary spectrum
- The phase angle in degrees
Parameter file format
Each parameter value must be stored as characters on a separate line in the file named
Dsp034.txt. The required parameters and their order and type are as follows:
- Data length as type int
- Pulse length as type int
- Sample number representing zero time as type int
- Low frequency bound as type double
- High frequency bound as type double
- Sample values for the pulse as type double or int (they
are automatically converted to type double if they are provided as
type int)
The number of sample values for the pulse must match the value for the pulse length.
Format for frequency specifications
All frequency values are specified as a double representing a fractional part of the sampling frequency. For example, a value of 0.5 specifies a frequency that is half the sampling frequency.
Sample parameters for testing
Figure 1 provides a set of sample parameter values that can
be used to test the program. This sample data
describes a triangular pulse. Be careful when you create the file
containing these values. Don’t allow blank
lines at the end of the data in the file.
400 11 0 0.0 0.5 0 0 0 45 90 135 90 45 0 0 0 Figure 1 |
The plotting programs
The plotting program that is used to plot the
output data from this program requires that the
program implement GraphIntfc01.
(I explained the plotting programs and this interface in earlier
lessons.)
For example,
the plotting program named Graph03 can be used
to plot the data produced by this program. When it is used, the following
should be entered at the command-line prompt:
java Graph03 Dsp034
Spectral analysis
A static method named transform belonging to the
class named ForwardRealToComplex01 is used to
perform the actual spectral analysis.
(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. However, I skipped over that portion of the
method that computes the phase angle. I will explain that portion of
the transform method in this lesson.)
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 Dsp030 in the lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm for the use of an FFT
algorithm.).
Let’s see some results
Before getting into the technical details of the program, let’s take a look
at some results. Figure 2 shows the results produced by using the program
named Graph06 to plot the output for the program named Dsp034
using default parameters.
(The file named Dsp034.txt did not exist in the current
directory when the results shown in Figure 2 were generated.)
|
The input pulse
First consider the input pulse shown in the top plot of Figure 2. This
is the sort of pulse that you would get if you attached a mass to a spring, gave it a swift kick, and then allowed the mass to come to rest in an unimpeded
fashion.
The horizontal axis represents time moving from left to right. The values above and below the axis represent the position of the mass
over time
relative to its position at rest.
Potential energy in the spring
When the mass is at the most extreme
positions and getting ready to reverse directions, the spring is extended.
Thus, potential energy is stored in the spring. At these points in time,
there is no kinetic energy stored in the mass.
Kinetic energy in the mass
As the mass goes through the rest position heading towards the other side,
the spring is no longer extended, and there is no potential energy stored in the
spring. However, at that point in time, the mass is moving causing it to
have kinetic energy.
An exchange of energy
The behavior of the spring/mass system is to exchange that energy between
potential energy and kinetic energy until such time as energy losses due to
friction dissipate the energy. At that point in time, the entire system
comes to rest, which is the case about one-third of the way across the top plot in Figure 2.
Now that our physics lesson for the day is over, let’s get back to
programming and digital signal processing.
Capturing the position of the mass as a sampled time
series
Assume that you use an analog to digital converter to capture the position of
the mass at a set of uniformly spaced intervals in time and then you plot those samples
along a time axis. You should see something resembling the top plot in
Figure 2.
Having captured that positional information as a set of uniformly spaced
samples, you are now in a position to perform a Fourier transform on the sampled
time series.
Computing the Fourier transform
In the lesson entitled
Fun with Java,
How and Why Spectral Analysis Works, I explained that you could compute the Fourier
transform of an input time series at any given frequency F by evaluating the
first two expressions in Figure 3.
Real(F) = S(n=0,N-1)[x(n)*cos(2Pi*F*n)] Imag(F) = S(n=0,N-1)[x(n)*sin(2Pi*F*n)] ComplexAmplitude(F) = Real(F) - j*Imag(F) Figure 3 Fourier transform equations |
I also explained that the Fourier transform of the input time series at that
frequency can be viewed as a complex number having a real part and an imaginary
part as in the third expression in Figure 3.
The amplitude of the spectrum at that frequency can be determined by
computing the square root of the sum of the squares of the real and imaginary
parts at that frequency.
The Fourier transform of an input time series can be computed by performing
these calculations across a range of frequencies.
The results of performing a spectral analysis on the
pulse
The bottom four plots in Figure 2 show the results of performing a Fourier
transform on the pulse in the top plot.
(In this display format, which was produced by the program named
Graph06, each sample value is represented by a vertical bar whose height
is proportional to the value of the sample.)
These four plots show the values
of the Fourier transform output at a set of uniformly spaced frequencies ranging
from zero to 0.25 times the sampling frequency.
The amplitude spectrum
The second plot from the top in Figure 2 shows the value of the amplitude spectrum. This
is the Fourier transform output that we have been using in the previous lessons in this series.
(Those lessons ignored the complex spectrum and the phase angle.)
As you can see, the amplitude spectrum peaks at
a frequency equal to 0.0625 times the sampling frequency. The reason for
this will become clear when we examine the code that produced the pulse shown in
the first plot.
The real and imaginary parts of the transform
The real part of the transform is shown in the third plot and the imaginary
part of the transform is shown in the fourth plot. This is the first time
that I have presented the real and imaginary parts of the spectrum in this
series of lessons.
The phase angle in degrees
The phase angle in degrees is shown in the bottom plot. There are a
variety of different ways to display phase angles. This program displays
the phase angle as values that range from -180 degrees to +180 degrees.
(It is also possible to display the phase angle as ranging from 0 to
360 degrees, or any combination that equates to 360 degrees or one full
rotation. It is also possible to display the phase angle in radians
instead of degrees.)
How is the phase angle computed?
Basically, the phase angle is the angle that you get when you compute the arc
tangent of the ratio of the imaginary part to the real part of the complex spectrum
at a particular frequency. However, beyond computing the arc tangent, you
must do some additional work to take the quadrant into account.
How should we interpret the phase angle?
To begin with, you should ignore the result of phase angle computations at
those frequencies for which there is insignificant energy. It is
always possible to form a ratio of the values of the real and imaginary parts of
the complex Fourier transform at any frequency. However, if the real and
imaginary values
produced by the Fourier transform at that frequency are both very small, the
phase angle resulting from that ratio is of no practical significance. In
fact, the angle can be corrupted by arithmetic errors resulting from performing
arithmetic on very small values.
Therefore, noting the amplitude spectrum in the second plot of Figure 2,
phase angle results to the right of the fourth tick mark are probably useless.
Many combinations
The phase angle produced by performing a Fourier transform on a pulse of a
given waveform is not unique. There are an infinite number of combinations
of real and imaginary parts that can result from performing a Fourier transform
on a given waveform, depending on how you define the origin of time. This
means that there are also an infinite number of phase angle curves that can be
produced from the ratio of those real and imaginary parts. I will explain
this in more detail later using simpler pulses.
The frequency band of primary interest
In the case shown in Figure 2, the frequency band of primary interest lies approximately
between the first and the third tick marks. Most of the energy can be seen
to lie between those limits on the basis of the amplitude plot.
The phase angle curve goes from a little more than zero degrees to a little
less than 180 degrees across this frequency interval. However, it is
significant to note that the phase angle is not linear across this frequency
interval. Rather the shape of the curve is more like an elongated S
sloping to the right.
A nonlinear phase angle, so what?
What is the significance of the nonlinear phase angle? If this plot
represented the frequency response of your audio system, the existence of the
nonlinear phase angle would be bad news. In particular, it would mean that
the system would introduce phase distortion into your favorite music.
Computation of the phase angle
In an earlier lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm, I explained most of the code in the method named
transform belonging to the class named ForwardRealToComplex01.
However, I skipped over that portion of the code that computes the phase angle on
the basis of the values of the real and imaginary parts. At this time, I
am going to explain that code. For an explanation of the rest of the code
in the transform method, go back and review the lesson entitled
Spectrum
Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT
Algorithm.
The code in the transform method that computes the phase angle for a
particular frequency is shown in Listing 1. At this point in the execution
of the method, the values of the real part (real) and the
imaginary part (imag) of the Fourier transform at a particular
frequency have been computed. Those values are used to compute the phase
angle at that frequency.
What if both values are zero?
The code begins by testing to see if both the real and imaginary parts are
equal to zero. If so, attempting to form the ratio of the imaginary part
to the real part would be meaningless. In this case, the
code in Listing 1 simply sets the phase angle to a value of zero.
if(imag == 0.0 && real == 0.0){ang = 0.0;} else{ang = Math.atan(imag/real)*180.0/pi;} if(real < 0.0 && imag == 0.0){ang = 180.0;} else if(real < 0.0 && imag == -0.0){ ang = -180.0;} else if(real < 0.0 && imag > 0.0){ ang += 180.0;} else if(real < 0.0 && imag < 0.0){ ang += -180.0;} angleOut[i] = ang; Listing 1 |
The atan method of the Math class
If both the real and imaginary parts are not zero, then the ratio of the
imaginary value to the real value is formed and passed as a parameter to the
atan method of the Math class.
The atan method returns the angle in radians whose tangent matches the
value received as a parameter. The angle that is returned is in the range
from -pi/2 to +pi/2 (-90 degrees to +90 degrees). The code in
Listing 1 multiplies that angle in radians by 180.0/pi to convert the angle from
radians to degrees.
Correction required for the quadrant
Although we have an intermediate answer at this point, we’re still not
finished. There is more work to do. The atan method simply uses
the sign of its incoming parameter to decide whether to report the angle as
positive or negative, and it only covers angles in two quadrants (-90 degrees
to + 90 degrees). We know
that the angle can actually be in any one of four quadrants (-180 degrees to
+180 degrees).
The first and third quadrants
For example, a positive ratio can result from a positive imaginary value and
a positive real value, or from a negative imaginary value and a negative real
value. Both of these would be reported by the atan method as being
between 0 and 90 degrees when in fact, the negative imaginary value and the
negative real value means that the angle is actually between -90 degrees and
-180 degrees.
The second and fourth quadrants
Similarly, a negative ratio can result from a negative imaginary value and a
positive real value or from a positive imaginary value and a negative real
value. Both of these would be reported by the atan method as being
between 0 and -90 degrees when in fact, the positive imaginary value and the
negative real value means that the angle is actually between 90 degrees and 180
degrees.
An exercise for the reader
I will leave it as an exercise for the reader to work through the remaining
code in Listing 1 to see how this code determines the proper quadrant and
adjusts the angle appropriately, all the while maintaining the angle between
-180 degrees and +180 degrees.
Will discuss Dsp034 in fragments
I will discuss the program named Dsp034 in fragments. A complete
listing of the program is provided in Listing 6 near the end of the lesson.
Due to the similarity of this program to programs explained in previous
lessons in this series, this discussion will be rather brief. Following a
discussion of the code, I will provide and explain some more spectral analysis results obtained by
running the program with parameters read from the file named Dsp034.txt.
The beginning of the Dsp034 class
The beginning of the class, along with the declaration of several variables
is shown in Listing 2.
(Note that the class implements the interface named GraphIntfc01.)
The variable names and the embedded comments should
make the purpose of these variables self explanatory. If not, you will see
how they are used later in the program.
class Dsp034 implements GraphIntfc01{ final double pi = Math.PI;//for simplification int dataLen;;//data length int pulseLen;//length of the pulse int zeroTime;//sample that represents zero time //Low and high frequency limits for the // spectral analysis. double lowF; double highF; double[] pulse;//data describing the pulse //Following array stores input data for the // spectral analysis process. double[] data; //Following arrays receive information back // from the spectral analysis process double[] real; double[] imag; double[] angle; double[] mag; Listing 2 |
The constructor
The constructor begins in Listing 3. The code in the constructor either
invokes the getParameters method to get the operating parameters from the
file named Dsp034.txt, or creates a set of operating parameters on the
fly if that file does not exist in the current directory. In either case,
many of the variables declared in Listing 2 are populated as a result of that
action.
public Dsp034(){//constructor //Get the parameters from a file named // Dsp034.txt. Create and use default // parameters describing a damped sinusoidal // pulse if the file doesn't exist in the // current directory. if(new File("Dsp034.txt").exists()){ getParameters(); }else{ //Create default parameters dataLen = 400;//data length pulseLen = 100;//pulse length //Sample that represents zero time. zeroTime = 0; //Low and high frequency limits for the // spectral analysis. lowF = 0.0; highF = 0.5;//half the sampling frequency pulse = new double[pulseLen]; double scale = 240.0; for(int cnt = 0;cnt < pulseLen;cnt++){ scale = 0.94*scale;//damping scale factor pulse[cnt] = scale*Math.sin(2*pi*cnt*0.0625); }//end for loop //End default parameters }//end else Listing 3 |
The damped sinusoid pulse
For the case where the file named Dsp034.txt doesn’t exist in the
current directory, the code in the else clause in Listing 3 establishes
default operating parameters, and creates the damped sinusoid pulse shown in the
top plot of Figure 2.
Store the pulse in the time series
At this point in the process, the array referred to by the reference variable
named pulse contains
a set of samples that constitutes a pulse. The code in Listing 4 creates a
data array containing the data upon which spectral analysis will be performed
and stores the pulse in that array.
(All elements in the data array other than those elements occupied by
values of the pulse have a value of zero.)
data = new double[dataLen]; for(int cnt = 0;cnt < pulse.length;cnt++){ data[cnt] = pulse[cnt]; }//end for loop Listing 4 |
Print the parameters and the pulse
Following this, code in the constructor prints the parameters and the
values of the samples that constitute the pulse. You can view that code in
Listing 6 near the end of the lesson.
Perform the spectral analysis
Finally, the code in Listing 5 creates the array objects that will receive
the results of the spectral analysis, and invokes the transform method of
the ForwardRealToComplex01 class to perform the spectral analysis.
mag = new double[dataLen]; real = new double[dataLen]; imag = new double[dataLen]; angle = new double[dataLen]; ForwardRealToComplex01.transform(data,real, imag,angle,mag,zeroTime,lowF,highF); }//end constructor Listing 5 |
Listing 5 also signals the end of the constructor. At this point, the
object has been instantiated and it’s array objects have been populated with the
input and output data from the spectral analysis process. This data is
ready to be handed over to the plotting program to be plotted, as shown in
Figure 2.
The getParameters method
This method, invoked in Listing 3, reads parameter and pulse data from the file named Dsp034.txt,
and deposits that data into the variables and the pulse array declared in
Listing 2.
The code in the getParameters method is straightforward, so I won’t bore you with an
explanation. You can view the method in Listing 6 near the end of the
lesson.
The interface methods
As pointed out earlier, the Dsp034 class implements the GraphIntfc01 interface.
As such, the class must define the six methods declared in that interface. These methods
are invoked by the plotting program to obtain the data that is to be plotted.
You have seen implementations of these methods in several earlier lessons, so
there is nothing new here. Consequently, I won’t discuss the interface
methods. You can view the methods in Listing 6 near the end of the
lesson.
The one thing that you might want to pay attention to in these methods is the scaling that is
applied to the data before it is returned. This is an attempt to
cause all of the curves to plot reasonably well within a value range of -180 to
+180. This range is dictated by the fact that this is the range of values
for the phase angle data.
Now that you understand the inner workings of the program, let’s look at some
more examples, this time getting the input data from the file named
Dsp034.txt.
The simplest pulse of all, an impulse
The simplest pulse that you can create is a single non-zero valued sample among a bunch of zero-valued samples.
This simple pulse, commonly called
an impulse in digital signal processing (DSP), is an extremely important type of
signal. It is used for a variety of purposes in testing both digital and
analog signal processing systems.
Let’s examine the result of performing spectral analysis on an impulse.
The parameters used for this experiment are shown in Figure 4.
Parameters read from file Data length: 400 Pulse length: 11 Sample for zero time: 0 Lower frequency bound: 0.0 Upper frequency bound: 0.5 Pulse Values 180.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Figure 4 |
The format of Figure 4
The data that you see in Figure 4 is the screen output from the method named
getParameters. To orient you with this format, the second item in
Figure 4 indicates that the entire length of the data upon which spectral
analysis was performed
was 400 samples. All 400 samples had a value of zero except for the
values of the sample in the pulse that occurred at the beginning. In this
program, setting
the total data length to 400 causes the spectral analysis to be performed at 400
individual frequencies.
(By now, you may be suspecting that I have a particular affinity for a
data length of 400 samples. If so, you are correct. This is not
a technical affinity. Rather, it is a visual one. These figures
are formatted such that the plotted data occupies an area of the screen
containing approximately 400 pixels. By matching the plotted points to
the positions of the pixels, it is possible to avoid, or at least minimize,
the distortion that can occur when attempting to map from sample points to
pixel locations when there is a mismatch between the two.To see the result of such mapping problems, repeat the experiment
shown in Figure 2 and use your mouse to stretch the Frame horizontally by a
very small amount. Depending on how much you stretch the Frame, you
should see vertical lines disappear, vertical lines that are too close
together, or a combination of the two. This is another manifestation
of the impact of sampling that I don’t have the time to get into at this
point.)
The length of the pulse
As you can see in Figure 4, the length of the pulse for this experiment was
11 samples, all but one of which had a value of zero.
(In this case, I could have made the pulse length 1
but for simplicity, I will keep it at 11 for several different experiments.)
Defining the origin of time
As you may have discovered by playing video games, we can do things with a computer that we can’t do in the real world.
For example, the Fourier transform program allows me to specify which sample I
regard as representing zero time. Samples to the left of that sample
represent negative time (history) and samples to the right of that one
represent positive time (the future).
In this case, I specified that the first sample
(sample number 0) represents zero time. As you will see later, this
has a significant impact on the distribution of energy between the real and
imaginary parts of the transform results, and as such, has a significant impact
on the phase angle.
Computational frequency range
Because I am using a DFT algorithm (instead of an FFT algorithm) I can
compute the Fourier transform across a range of frequencies of my choosing.
As shown in Figure 4, I chose to compute the transform across the range of frequencies
from zero to one-half the sampling frequency, known as the Nyquist folding
frequency. Thus, the spectral analysis was performed at 400 individual
frequencies between these two limits.
The values that make up the pulse
This is followed in Figure 4 by the values of the samples that make up the
pulse. Note that the first sample has a value of 180 while the other ten
samples all have a value of 0.
The plotted results of spectral analysis
I used the program named Graph03 to plot the output from this program,
and the results are shown in Figure 5.
(Note that rather than plotting each sample value as a vertical bar,
Graph03 plots each sample as a dot and connects the dots with straight line
segments.)
|
Where is that impulse?
If you strain your eyes and you know exactly where to look, you can barely see a single
black spike with an amplitude
of 180 at the far left of the top plot. If you can’t see it, you will
simply have to trust me when I tell you that it is there.
The amplitude spectrum
Now take a look at the amplitude spectrum in the second plot from the
top. What you should see is a straight black line extending from zero to
the folding frequency on the right. This is because such an impulse
(theoretically) contains an equal distribution of energy at every frequency from
zero to infinity.
(In reality, there is no such thing as a perfect impulse, so there is
no such thing as infinite bandwidth. However, the bandwidth of a
practical impulse is very wide and the amplitude spectrum is very flat.)
That is one of the things that make the impulse so useful as an input signal
that is used for
various testing purposes in both the analog world and the digital world.
The real spectrum
Now look at the real spectrum in the second plot from the top. As
you can see, it looks exactly like the amplitude spectrum. This is because
the impulse appears at zero time. We will change this in the next
experiment so that you can see the impact of a time delay on the complex
spectrum.
The imaginary spectrum
Moving on down the page, the imaginary part of the spectrum is a flat line
with a value of zero across the entire frequency range. Once again, this
is because the impulse appears at zero time.
The phase angle
Because the imaginary value is zero everywhere, the ratio of the imaginary
value to the real value is also zero everywhere. Thus, the phase angle is
also zero at all frequencies within the range.
Introduce a time delay
Now we are going to introduce a one-sample time delay in the location of the
impulse relative to the time origin. We will keep the zero time reference at the
first sample and cause the impulse to appear as the second sample in the
eleven-sample sequence.
The new parameters are shown in Figure 6. The only change is the move
of the impulse from the first sample in the eleven-sample pulse to the second
sample in the eleven-sample pulse.
Parameters read from file Data length: 400 Pulse length: 11 Sample for zero time: 0 Lower frequency bound: 0.0 Upper frequency bound: 0.5 Pulse Values 0.0 180.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Figure 6 |
The spectral analysis output
The result of performing the spectral analysis on this new time series is
shown in Figure 7.
|
Once again, if you strain your eyes, you can see the impulse on the left end
of the top plot. It has been shifted one sample to the right relative to
that shown in Figure 5.
The amplitude spectrum
The amplitude spectrum in the second plot looks exactly like it looked in
Figure 5. That
is as it should be. The spectral content of a pulse is determined by its
waveform, not by its location in time. Simply moving the impulse by one
sample into the future doesn’t change its spectral content.
The real and imaginary parts of the spectrum
However, moving the impulse one sample into the future (a time delay) did change the values
of the real and imaginary parts of the complex spectrum. As you can see
from the third plot in Figure 7, the real part of the spectrum is no longer a replica of the
amplitude spectrum. Also the imaginary part of the spectrum in the fourth
plot is no longer zero across the frequency range from zero to the folding
frequency.
Real and imaginary values are intrinsically linked
However, the real and imaginary parts cannot change in arbitrary ways
relative to one another.
Recall that the amplitude spectrum at each individual frequency is the square
root of the sum of the squares of the real and imaginary parts. In order
for the amplitude spectrum to stay the same, changes to the real part of the
spectrum must be accompanied by changes to the imaginary part that will maintain
that relationship.
The phase angle spectrum
Finally, the phase angle shown in the bottom plot is no longer zero.
Rather it is a straight line with a value of zero at zero frequency and a value
of 180 degrees at the folding frequency.
An important conclusion
Shifting an impulse forward
or backward in time introduces a phase shift that is linear with frequency.
Shifting the pulse forward in time introduces a linear phase shift with a
positive slope. Shifting the pulse backwards in time introduces a linear
phase shift with a negative slope. In both cases, the amount of slope
depends on the amount of time shift.
The converse is also true
A shift in time introduces a linear phase shift. Conversely,
introducing a linear phase shift causes a shift in time.
A more acceptable form of phase distortion
Once again, consider your audio system. If your audio system introduces a
phase shift across the frequency band of interest, you would probably like for
that phase shift to be linear with frequency. That will simply cause the music to
be delayed in time. In other words, all frequency components in the music
will be delayed an equal amount of time.
On the other hand, if the phase shift is not linear with frequency, some
frequencies will delayed more than other frequencies. This
sometimes results in noticeable phase distortion in your music.
Introduce a large time delay
Now let’s move the impulse to the center of the eleven-sample pulse and
observe the result. The new parameters are shown in Figure 8.
Parameters read from file Data length: 400 Pulse length: 11 Sample for zero time: 0 Lower frequency bound: 0.0 Upper frequency bound: 0.5 Pulse Values 0.0 0.0 0.0 0.0 0.0 180.0 0.0 0.0 0.0 0.0 0.0 Figure 8 |
The output from the spectral analysis
Figure 9 shows the result of performing a spectral analysis on the time
series containing this new time-delayed impulse.
|
Finally, you can see the impulse on the left side of the top plot in Figure 9
without straining your eyes too much.
As we would expect, the amplitude spectrum hasn’t changed.
Although it wasn’t apparent in Figure 7, Figure 9 shows that the real part of
the spectrum takes on the shape of a cosine wave, while the imaginary part of the
spectrum takes on the shape of a sine wave as a result of the time delay of the
impulse.
The phase shift is still linear across frequency as would be expected, but
the slope is now five times greater than the slope of the phase shift in Figure
7.
(Note that the time delay is five times greater in Figure 9.
Note also that the plot of the phase angle wraps around from +180 degrees to
-180 degrees each time the phase angle reaches +180 degrees. This
produces the sawtooth effect shown in the bottom plot in Figure 9.)
A boxcar pulse
If an impulse is the simplest kind of pulse to generate digitally, a
boxcar pulse is probably the next simplest. A boxcar pulse is one
where several adjacent samples have the same non-zero value. Let’s examine
the case for an eleven-sample boxcar pulse. The new parameters for this
case are shown in Figure 10.
Parameters read from file Data length: 400 Pulse length: 11 Sample for zero time: 0 Lower frequency bound: 0.0 Upper frequency bound: 0.5 Pulse Values 40.0 40.0 40.0 40.0 40.0 40.0 40.0 40.0 40.0 40.0 40.0 Figure 10 |
The spectral analysis output for the boxcar pulse
The spectral analysis output for the eleven-sample boxcar pulse is shown in
Figure 11.
|
The pulse itself is relatively easy to see on the leftmost end of the top
plot.
The amplitude spectrum
The amplitude spectrum is no longer flat. Rather it has a peak
at zero frequency and goes to zero between frequency sample 72 and frequency
sample 73.
Without
getting into the technical details, I will simply tell you that the location of
the point where it goes to zero is related to the reciprocal of the pulse width.
If that sounds familiar, it is because we encountered similar situations
involving bandwidth in the lesson entitled
Spectrum
Analysis using Java, Frequency Resolution versus Data Length.
In fact, the shape of
the amplitude spectrum is a familiar (sin x)/x curve with the negative lobes
flipped up and turned into positive lobes instead.
The phase angle is still linear with frequency although it now shows some
discontinuities at those frequencies where the amplitude spectrum touches zero.
The magic of digital
When working with digital time series, it is not only possible to
physically shift pulses forward or backward in time, it is also possible to leave
the pulses where they are and redefine the underlying time base. For the
next experiment, I will leave everything else the same and redefine the location
of the origin of time. I will place the time origin at the middle of the boxcar pulse.
The new parameters for this experiment are shown in Figure 12. Note
that the only significant difference between Figure 12 and Figure 10 is the
redefinition of the sample that represents zero time. I redefined the
time origin from sample 0 to sample 5. This causes the boxcar pulse to be
centered on zero time. Five of the samples in the boxcar pulse occur in negative time.
One sample occurs exactly at zero time. The other five samples occur in
positive time.
Parameters read from file Data length: 400 Pulse length: 11 Sample for zero time: 5 Lower frequency bound: 0.0 Upper frequency bound: 0.5 Pulse Values 40.0 40.0 40.0 40.0 40.001 40.0 40.0 40.0 40.0 40.0 40.0 Figure 12 |
(I did make one other change. This change was to add a tiny spike to one of
the samples near the center of the pulse. This creates a tiny amount
of wide-band energy and tends to stabilize the computation of the phase
angle. It prevents the imaginary part of the spectrum from switching
back and forth between very small positive and negative values due to
arithmetic errors.)
The spectral analysis output
The output from the spectral analysis is shown in Figure 13. The
magnitude spectrum hasn’t changed. The real part of the spectrum has
changed significantly. It is now a true (sin x)/x curve with both positive
and negative lobes.
|
The imaginary part of the spectrum is zero or nearly zero at every frequency.
(It would be zero in the absence of arithmetic errors.)
The phase angle is zero across the entire main energy lobe of the spectrum.
It is -180 degrees in those frequency areas where the real part of the spectrum
is negative, and is zero in those frequency areas where the real part of the
spectrum is positive. There is no linear phase shift because the boxcar
pulse is centered on the time origin.
Probably more than you ever wanted to know
And that is probably more than you ever wanted to know about the complex
spectrum, phase angles, and time shifts. I will stop writing and leave it at
that.
Run the Program
I encourage you to copy, compile, and run the program provided in this
lesson. Experiment with it, making changes and observing the results of your
changes.
Create more complex experiments. For example, you could create pulses
of different lengths with complex shapes and examine the complex spectra and
phase angles for those pulses.
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. Pay particular attention to the phase
angle across the frequency band containing most of the energy.
Most of all enjoy yourself and learn something in the process.
Summary
The default pulse for the Dsp034 program is a damped sinusoid.
This is a pulse whose shape is commonly found in mechanical and electronic
systems in the real world. The phase angle in the complex spectrum for a pulse of this shape
is nonlinear. Among other things, nonlinear phase angles introduce phase
distortion into audio systems.
The simplest pulse of all is a single impulse. A pulse of this type has
an infinite bandwidth (theoretically) and a linear phase angle. The slope of the phase
angle depends on the location of the pulse relative to the time origin.
Shifting a pulse in time introduces a linear phase angle to the complex
spectrum. Conversely, introducing a linear phase angle to the complex
spectrum causes a pulse to be shifted in time.
What’s Next?
The next lesson in this series will introduce the inverse Fourier transform
and will explain the reversible nature of the Fourier transform.
Complete Program Listing
A complete listing of the program discussed in this lesson follows.
/* File Dsp034.java Copyright 2004, R.G.Baldwin Rev 5/21/04 Computes and plots the amplitude, real, imag, and phase angle spectrum of a pulse that is read from a file named Dsp034.txt. If that file doesn't exist in the current directory, the program uses a set of default parameters describing a damped sinusoidal pulse. The program also plots the pulse. When the data is plotted using the programs Graph03 or Graph06, the order of the plots from top to bottom in the display are: The pulse The amplitude spectrum The real spectrum The imaginary spectrum The phase angle in degrees Each parameter value must be stored as characters on a separate line in the file named Dsp034.txt. The required parameters are as follows: Data length as type int Pulse length as type int Sample number representing zero time as type int Low frequency bound as type double High frequency bound as type double Sample values for the pulse as type double The number of sample values must match the value for the pulse length. All frequency values are specified as a double representing a fractional part of the sampling frequency. For example, a value of 0.5 specifies a frequency that is half the sampling frequency. Here is a set of sample parameter values that can be used to test the program. This sample data describes a triangular pulse. Don't allow blank lines at the end of the data in the file. 400 11 0 0.0 0.5 0 0 0 45 90 135 90 45 0 0 0 The plotting program that is used to plot the output data from this program requires that the program implement GraphIntfc01. For example, the plotting program named Graph03 can be used to plot the data produced by this program. When it is used, the usage information is: java Graph03 Dsp034 A static method named transform belonging to the class named ForwardRealToComplex01 is used to perform the actual spectral analysis. The method named transform does not implement an FFT algorithm. Rather, it is more general than, but much slower than an FFT algorithm. (See the program named Dsp030 for the use of an FFT algorithm.) Tested using SDK 1.4.2 under WinXP. ************************************************/ import java.util.*; import java.io.*; class Dsp034 implements GraphIntfc01{ final double pi = Math.PI;//for simplification int dataLen;;//data length int pulseLen;//length of the pulse int zeroTime;//sample that represents zero time //Low and high frequency limits for the // spectral analysis. double lowF; double highF; double[] pulse;//data describing the pulse //Following array stores input data for the // spectral analysis process. double[] data; //Following arrays receive information back // from the spectral analysis process double[] real; double[] imag; double[] angle; double[] mag; public Dsp034(){//constructor //Get the parameters from a file named // Dsp034.txt. Create and use default // parameters describing a damped sinusoidal // pulse if the file doesn't exist in the // current directory. if(new File("Dsp034.txt").exists()){ getParameters(); }else{ //Create default parameters dataLen = 400;//data length pulseLen = 100;//pulse length //Sample that represents zero time. zeroTime = 0; //Low and high frequency limits for the // spectral analysis. lowF = 0.0; highF = 0.5;//half the sampling frequency pulse = new double[pulseLen]; double scale = 240.0; for(int cnt = 0;cnt < pulseLen;cnt++){ scale = 0.94*scale;//damping scale factor pulse[cnt] = scale*Math.sin(2*pi*cnt*0.0625); }//end for loop //End default parameters }//end else //Create the data array and deposit the pulse // in it. data = new double[dataLen]; for(int cnt = 0;cnt < pulse.length;cnt++){ data[cnt] = pulse[cnt]; }//end for loop //Print parameter values. System.out.println( "Data length: " + dataLen); System.out.println( "Pulse length: " + pulseLen); System.out.println( "Sample for zero time: " + zeroTime); System.out.println( "Lower frequency bound: " + lowF); System.out.println( "Upper frequency bound: " + highF); System.out.println("Pulse Values"); for(int cnt = 0;cnt < pulseLen;cnt++){ System.out.println(pulse[cnt]); }//end for loop //Create array objects to receive the results // and perform the spectral analysis. mag = new double[dataLen]; real = new double[dataLen]; imag = new double[dataLen]; angle = new double[dataLen]; ForwardRealToComplex01.transform(data,real, imag,angle,mag,zeroTime,lowF,highF); }//end constructor //-------------------------------------------// //This method gets processing parameters from // a file named Dsp034.txt and stores those // parameters in instance variables belonging // to the object of type Dsp034. void getParameters(){ int cnt = 0; //Temporary holding area for strings. Allow // space for a few blank lines at the end // of the data in the file. String[] data = new String[20]; try{ //Open an input stream. BufferedReader inData = new BufferedReader(new FileReader( "Dsp034.txt")); //Read and save the strings from each of // the lines in the file. Be careful to // avoid having blank lines at the end, // which may cause an ArrayIndexOutOfBounds // exception to be thrown. while((data[cnt] = inData.readLine()) != null){ cnt++; }//end while inData.close(); }catch(IOException e){} //Move the parameter values from the // temporary holding array into the instance // variables, converting from characters to // numeric values in the process. cnt = 0; dataLen = (int)Double.parseDouble(data[cnt++]); pulseLen = (int)Double.parseDouble(data[cnt++]); zeroTime = (int)Double.parseDouble(data[cnt++]); lowF = Double.parseDouble(data[cnt++]); highF = Double.parseDouble(data[cnt++]); //Create a new array object for the pulse // and populate it from the file file data. pulse = new double[pulseLen]; for(int pCnt = 0;pCnt < pulseLen;pCnt++){ pulse[pCnt] = Double.parseDouble( data[cnt++]); }//end for loop System.out.println( "Parameters read from file"); }//end getParameters //-------------------------------------------// //The following six methods are required by the // interface named GraphIntfc01. The plotting // program pulls the data values to be plotted // by invoking these methods. public int getNmbr(){ //Return number of functions to process. // Must not exceed 5. return 5; }//end getNmbr //-------------------------------------------// //Provide the input data for plotting. public double f1(double x){ int index = (int)Math.round(x); if(index < 0 || index > data.length-1){ return 0; }else{ return data[index]; }//end else }//end function //-------------------------------------------// //Provide the amplitude spectral data for // plotting. Attempt to scale it so that it // will plot well in the range 0 to 180. public double f2(double x){ int index = (int)Math.round(x); if(index < 0 || index > mag.length-1){ return 0; }else{ return (4*dataLen/pulseLen)*mag[index]; }//end else }//end function //-------------------------------------------// //Provide the real spectral data for // plotting. Attempt to scale it so that it // will plot well in the range -180 to 180. public double f3(double x){ int index = (int)Math.round(x); if(index < 0 || index > real.length-1){ return 0; }else{ //Scale for convenient display return (4*dataLen/pulseLen)*real[index]; }//end else }//end function //-------------------------------------------// //Provide the imaginary spectral data for // plotting. Attempt to scale it so that it // will plot well in the range -180 to 180. public double f4(double x){ int index = (int)Math.round(x); if(index < 0 || index > imag.length-1){ return 0; }else{ //Scale for convenient display return (4*dataLen/pulseLen)*imag[index]; }//end else }//end function //-------------------------------------------// //Provide the phase angle data for plotting. // The angle ranges from -180 degrees to +180 // degrees. This is thing that drives the // attempt to cause the other curves to plot // well in the range -180 to +180. public double f5(double x){ int index = (int)Math.round(x); if(index < 0 || index > angle.length-1){ return 0; }else{ return angle[index]; }//end else }//end function //-------------------------------------------// }//end class Dsp034 Listing 6 |
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.
-end-