# A General-Purpose LMS Adaptive Engine in Java, Page 2

## Complete Program Listings

Complete listings of the programs discussed in this lesson are shown in the listings below.
/*File AdaptEngine01.java Copyright 2005, R.G.Baldwin General purpose LMS adaptive algorithm. An object of this class is a general purpose adaptive engine that implements the classical LMS adaptive algorithm. The adaptive algorithm is implemented by the instance method belonging to the object named adapt. Each time the adapt method is called, it receives one sample from each of two different time series. One time series is considered to be the data that is to be filtered. The other time series is considered to be a target. The purpose of the adapt method is to adaptively create a convolution filter which, when applied to the data time series, will transform it into the target time series. Each time the method is called, it performs a dot product between the current version of the filter and the contents of a delay line in which historical data samples have been saved. The result of that dot product is compared with the target sample to produce an error value. The error value is produced by subtracting the value of the target sample from the result of the dot product. The error value is then used in a classical LMS adaptive algorithm to adjust the filter coefficients. The objective is to produce a set of filter coefficients that will drive the error to zero over time. This adaptive engine can be used as the solution to a variety of different signal processing problems, depending on the selection of time series that are provided as data and target. The constructor for the class receives two parameters: filterLength feedbackGain The filter length is used to construct two arrays. One array is used later to contain the filter coefficients. The other array is used later as a tapped delay line to contain the historical data samples and to precess them by one element each time the method is called. The feedback gain is used in the LMS adaptive algorithm to compute the new filter coefficients. Tested using J2SE 5.0 and WinXP. **********************************************************/ class AdaptEngine01{ double[] filterArray;//filter coefficients stored here double[] dataArray;//historical data is stored here double feedbackGain;//used in LMS adaptive algorithm //Constructor public AdaptEngine01(int filterLength, double feedbackGain){ //Construct the two arrays and save the feedback gain. filterArray = new double[filterLength]; dataArray = new double[filterLength]; this.feedbackGain = feedbackGain; }//end constructor //-----------------------------------------------------// //This method implements a classical LMS adaptive // algorithm to create and to apply a convolution filter. // The filter output, the error, and a reference to the // array containing the filter coefficients are // encapsulated in an object of type AdaptiveResult and // returned to the calling method. AdaptiveResult adapt(double rawData,double target){ //Insert the incoming data value into the data delay // line. flowLine(dataArray,rawData); //Apply the current filter coefficients to the data. double output = dotProduct(filterArray,dataArray); //Compute the error. double err = output - target; //Use the error to update the filter coefficients. for(int ctr = 0;ctr < filterArray.length;ctr++){ filterArray[ctr] -= err*dataArray[ctr]*feedbackGain; }//end for loop. //Construct and return an object containing the filter // output, the error, and a reference to the array // object containing the current filter coefficients. return new AdaptiveResult(filterArray,output,err); }//end adapt //-----------------------------------------------------// //This method simulates a tapped delay line. It receives // a reference to an array and a value. It discards the // value at index 0 of the array, moves all the other // values by one element toward 0, and inserts the new // value at the top of the array. void flowLine(double[] line,double val){ for(int cnt = 0;cnt < (line.length - 1);cnt++){ line[cnt] = line[cnt+1]; }//end for loop line[line.length - 1] = val; }//end flowLine //-----------------------------------------------------// //This method receives two arrays and treats the first N // elements in each of the two arrays as a pair of // vectors. It computes and returns the vector dot // product of the two vectors. If the length of one // array is greater than the length of the other array, // it considers the number of dimensions of the vectors // to be equal to the length of the smaller array. double dotProduct(double[] v1,double[] v2){ double result = 0; if((v1.length) <= (v2.length)){ for(int cnt = 0;cnt < v1.length;cnt++){ result += v1[cnt]*v2[cnt]; }//emd for loop return result; }else{ for(int cnt = 0;cnt < v2.length;cnt++){ result += v1[cnt]*v2[cnt]; }//emd for loop return result; }//end else }//end dotProduct //-----------------------------------------------------// }//end class AdaptEngine01 //=======================================================// //This class is used to encapsulate the adaptive results // into an object for return to the calling method. class AdaptiveResult{ public double[] filterArray; public double output; public double err; //Constructor public AdaptiveResult(double[] filterArray, double output, double err){ this.filterArray = filterArray; this.output = output; this.err = err; }//end constructor }//end class AdaptiveResult //=======================================================// |

/*File Adapt06.java Copyright 2005, R.G.Baldwin The purpose of this program is to illustrate the use of the general-purpose LMS adaptive engine named AdaptEngine01. This is the simplest program that I was able to devise to illustrate the use of the adaptive engine. This program adaptively designs a convolution filter transforms a cosine function into a sine function having the same amplitude and frequency. There are no input parameters. Just compile and run. The following time series are plotted in color showing the convergence of the adaptive algorithm: black: input to the filter - the cosine function red: output from the filter blue: adaptive target - a sine function green: error The filter has two coefficients, each of which is initialized to a value of 0. The final values of the two filter coefficients are listed on the command-line screen. For the sinusoidal frequencies used by the program, when the program is allowed to run for 1000 iterations, the values for the two coefficients converge to: 1.4142064344006575 -0.9999927187203118 You might recognize that is the square root of two and minus 1. For other frequencies, the coefficients converge to different values. This program requires the following classes in addition to Adapt06: Adapt06.class AdaptEngine01.class AdaptiveResult.class PlotALot05.class Tested using J2SE 5.0 and WinXP. J2SE 5.0 or later is required. **********************************************************/ import static java.lang.Math.*;//J2SE 5.0 req class Adapt06{ public static void main(String[] args){ double feedbackGain = 0.0002; int numberIterations = 1000; int filterLength = 2; //Instantiate an object of the class and execute the // adaptive algorithm using the specified feedbackGain. new Adapt06().process(feedbackGain, numberIterations, filterLength); }//end main //-----------------------------------------------------// void process(double feedbackGain, int numberIterations, int filterLength){ //Instantiate object of the adaptive engine to provide //adaptive behavior for the program. AdaptEngine01 adapter = new AdaptEngine01(filterLength,feedbackGain); //Instantiate a plotting object for four data channels. // This object will be used to plot the time series // data. PlotALot05 plotObj = new PlotALot05( "Time Series",460,155,25,5,4,4); //Declare and initialize working variables. double output = 0; double err = 0; double target = 0; double input = 0; double dataScale = 20;//Default data scale //Do the iterative adaptive process AdaptiveResult result = null; for(int cnt = 0;cnt < numberIterations;cnt++){ //Generate the data to be filtered and the adaptive // target on the fly. input = dataScale * cos(2*cnt*PI/8); target = dataScale * sin(2*cnt*PI/8); //Execute the adaptive behavior. result = adapter.adapt(input,target); //Get the results of the adaptive behavior for // plotting. err = result.err; output = result.output; //Feed the time series data to the plotting object. plotObj.feedData(input,output,target,err); }//end for loop //Cause the data to be plotted. plotObj.plotData(); //List the values of the filter coefficients. System.out.println("nFinal filter coefficients:"); double[] filter = result.filterArray; for(int cnt = 0;cnt < filter.length;cnt++){ System.out.println(filter[cnt]); }//end for loop }//end process method //-----------------------------------------------------// }//end class Adapt06 |

/*File Adapt01a.java.java Copyright 2005, R.G.Baldwin This is an upgrade to the program named Adapt01. The purpose of this upgrade is to use the adaptive engine named AdaptEngine01 to provide the adaptive behavior of the program and to clearly deliniate the code that controls the adaptive behavior from the code that simply handles the data and displays the results. As was the case with the earlier program named Adapt01, this program illustrates one aspect of time-adaptive signal processing. However, this version of the program has been greatly simplified by eliminating all but one of the command-line parameters. Two versions of the same sampled time series, one delayed relative to the other, are presented to the an adaptive algorithm. The objective is to adaptively create a convolution filter that will delay the first time series to cause it to match up in time with the second time series. This results in a convolution filter having a flat amplitude response and a linear phase response. The user provides the following information as a command line parameter: timeDelay - This value causes the second time series to be delayed relative to the first time series by from 0 to 8 samples. Negative values and values greater than 8 cause the program to terminate. The following time series are plotted in color showing the convergence of the adaptive algorithm: black: input to the filter red: output from the filter blue: adaptive target green: error In addition, the frequency response of the filter at the end of every tenth iteration is computed and displayed when the adaptive process terminates. Both the amplitude and the phase response of the filter are computed and plotted. Also, the convolution filter is plotted as a time series on the same iterations that the frequency response is computed. Thus, the shape of the convolution filter can be compared with the frequency response of the filter. The filter is initialized with values of 0 for all coefficients. The ideal solution is a single coefficient value of 1 at a location in the filter that matches the time delay between the two time series along with a linear phase response that matches the location of the single filter coefficient. Tested using J2SE 5.0 and WinXP. J2SE 5.0 or later is required. **********************************************************/ import static java.lang.Math.*;//J2SE 5.0 req class Adapt01a{ public static void main(String[] args){ //Default value for time delay input parameter. int timeDelay = 7; //Fixed values. double feedbackGain = 0.001; int numberIterations = 100; //Deal with command-line parameter. if(args.length != 1){ System.out.println("Usage: java Adapt01a " + "timeDelay"); }else{//Command line param was provided. //Convert String to int timeDelay = Integer.parseInt(args[0]); System.out.println("timeDelay: " + timeDelay); }//end else if((timeDelay > 8) || (timeDelay < 0)){ System.out.println( "Time delay must be >= 0 and <= 8"); System.out.println("Terminating"); System.exit(0); }//end if //Instantiate an object of the class and execute the // adaptive algorithm using the specified feedbackGain // and other parameters. new Adapt01a().process(timeDelay, feedbackGain, numberIterations); }//end main //-----------------------------------------------------// void process(int timeDelay, double feedbackGain, int numberIterations){ int filterLength = 9; //Instantiate object of the adaptive engine to provide //adaptive behavior for the program. AdaptEngine01 adapter = new AdaptEngine01(filterLength,feedbackGain); //The filter array is created and maintained within the // adaptive engine object. A reference to the array is // returned by the adapt method. double[] filter; //Create array object that will be used as delay line. double[] rawData = new double[9]; //Instantiate a plotting object for four data channels. // This object will be used to plot the time series // data. PlotALot05 plotObj = new PlotALot05( "Time Series",398,250,25,5,4,4); //Instantiate a plotting object for two channels of // filter frequency response data. One channel is used // to plot the amplitude response in db and the other // channel is used to plot the phase on a scale that // extends from -180 degrees to +180 degrees. PlotALot03 freqPlotObj = new PlotALot03("Freq",264,487,20,2,0,0); //Instantiate a plotting object to display the filter // as a short time series at intervals during the // adaptive process. //Note that the minimum allowable width for a Frame is // 112 pixels under WinXP. Therefore, the following // display doesn't synchronize properly for filter // lengths less than 25 coefficients. However, the // code that feeds the filter data to the plotting // object later in the program extends the length of // the filter to cause it to synchronize and to plot // one set of filter coefficients on each axis. PlotALot01 filterPlotObj = new PlotALot01( "Filter",(filterLength * 4) + 8,487,40,4,0,0); //Declare and initialize working variables. double output = 0; double err = 0; double target = 0; double input = 0; double dataScale = 20;//Default data scale //Do the iterative adaptive process for(int cnt = 0;cnt < numberIterations;cnt++){ //Add new input data to the delay line containing the // raw input data. Raw data values are uniformly // distributed from -10.0 to +10.0. flowLine(rawData,dataScale*(random() - 0.5)); //Get the raw input data from the end of the delay // line. input = rawData[8]; //Get the target data from one of nine available taps // on the delay line. target = rawData[8 - timeDelay]; //Execute the adaptive behavior. AdaptiveResult result = adapter.adapt(input,target); //Get the results of the adaptive behavior for // plotting and frequency response analysis. filter = result.filterArray; err = result.err; output = result.output; //Feed the time series data to the plotting object. plotObj.feedData(input,output,target,err); //Compute and plot the frequency response and plot // the filter as a time series every 10 iterations. if(cnt%10 == 0){ displayFreqResponse(filter, freqPlotObj, 128, filter.length - 1); //Plot the filter coefficient values. Scale the // coefficient values by 30 to make them compatible // with the plotting software. for(int ctr = 0;ctr < filter.length;ctr++){ filterPlotObj.feedData(30*filter[ctr]); }//end for loop //Extend the filter with a value of 2.5 for // plotting purposes only to cause it to // synchronize with one filter being plotted on // each axis. if(filter.length <= 26){ for(int count = 0;count < (26 - filter.length); count++){ filterPlotObj.feedData(2.5); }//end for loop }//end if }//end if on cnt%10 }//end for loop //Cause all the data to be plotted in the // screen locations specified. plotObj.plotData(); filterPlotObj.plotData(0,201); freqPlotObj.plotData(112,201); }//end process //-----------------------------------------------------// //This method simulates a tapped delay line. // It receives a reference to an array and // a value. It discards the value at // index 0 of the array, moves all the other // values by one element toward 0, and // inserts the new value at the top of the // array. void flowLine(double[] line,double val){ for(int cnt = 0;cnt < (line.length - 1); cnt++){ line[cnt] = line[cnt+1]; }//end for loop line[line.length - 1] = val; }//end flowLine //-----------------------------------------------------// //This method receives a reference to a double // array containing a convolution filter // along with a reference to a plotting object // capable of plotting two channels of data. // It also receives a value specifying the // number of frequencies at which a DFT is // to be performed on the filter, along with // the sample number that represents the zero // time location in the filter. The method // uses this information to perform a DFT on // the filter from zero to the folding // frequency. It feeds the amplitude spectrum // and the phase spectrum to the plotting // object for plotting. void displayFreqResponse(double[] filter, PlotALot03 plot, int len, int zeroTime){ //Create the arrays required by the Fourier // Transform. double[] timeDataIn = new double[len]; double[] realSpect = new double[len]; double[] imagSpect = new double[len]; double[] angle = new double[len]; double[] magnitude = new double[len]; //Copy the filter into the timeDataIn array. System.arraycopy(filter,0,timeDataIn,0, filter.length); //Compute DFT of the filter from zero to the // folding frequency and save it in the // output arrays. ForwardRealToComplex01.transform(timeDataIn, realSpect, imagSpect, angle, magnitude, zeroTime, 0.0, 0.5); //Plot the magnitude data. Convert to // normalized decibels before plotting. //Eliminate or change all values that are // incompatible with log10 method. for(int cnt = 0;cnt < magnitude.length; cnt++){ if((magnitude[cnt] == Double.NaN) || (magnitude[cnt] <= 0)){ magnitude[cnt] = 0.0000001; }else if(magnitude[cnt] == Double.POSITIVE_INFINITY){ magnitude[cnt] = 9999999999.0; }//end else if }//end for loop //Now convert magnitude data to log base 10 for(int cnt = 0;cnt < magnitude.length; cnt++){ magnitude[cnt] = log10(magnitude[cnt]); }//end for loop //Note that from this point forward, all // references to magnitude are referring to // log base 10 data, which can be thought of // as scaled decibels. //Find the absolute peak value double peak = -9999999999.0; for(int cnt = 0;cnt < magnitude.length; cnt++){ if(peak < abs(magnitude[cnt])){ peak = abs(magnitude[cnt]); }//end if }//end for loop //Normalize to 50 times the peak value and // shift up the screen by 50 units to make // the values compatible with the plotting // program. Recall that adding a constant to // log values is equivalent to scaling the // original data. for(int cnt = 0;cnt < magnitude.length; cnt++){ magnitude[cnt] = 50*magnitude[cnt]/peak + 50; }//end for loop //Now feed the normalized decibel data to the // plotting object. The angle data rangers // from -180 to +180. Scale it down by a // factor of 20 to make it compatible with // the plotting format being used. for(int cnt = 0;cnt < magnitude.length; cnt++){ plot.feedData( magnitude[cnt],angle[cnt]/20); }//end for loop }//end displayFreqResponse //-------------------------------------------// }//end class Adapt01a |

/*File Adapt02a.java.java Copyright 2005, R.G.Baldwin This program is an update of the program named Adapt01. The purpose of the update is to delegate the adaptive behavior of the program to an object of the class named AdaptEngine02, clearly deliniating between that portion of the program that provides adaptive behavior and that portion that simply does data handling and displays results. This program has been simplified relative to the program named Adapt02 by eliminating all command-line parameters. This program illustrates one aspect of time-adaptive signal processing. This program implements a time-adaptive whitening filter using a predictive approach. The program input is a time series consisting of a wide band signal plus two sinusoidal noise functions. The program adaptively creates a filter that attempts to eliminate the sinusoidal noise while preserving the wide band signal. The following time series are displayed when the program runs: -err: This is the negative of the output from the whitening filter. Ideally this time series contains the wide band signal with the sinusoidal noise having been removed. signal: The raw wideband signal consisting of samples taken from a random noise generator. sineNoise: The raw noise consisting of the sum of two sinusoidal functions. input: The sum of the signal plus the sinusoidal noise. output: The output produced by applying the prediction filter to the input signal plus noise. target: The target signal that is used to control the adaptive process. This is the next sample beyond the samples that are processed by the prediction filter. In other words, the prediction filter attempts to predict this value. Thus, the adaptive process attempts to cause the output from the prediction filter to match the next sample in the incoming signal plus noise. This is an attempt to predict a future value based solely on the current and past values. Although not required by the adaptive process, the frequency response of the whitening filter is computed and displayed once every 100 iterations. Ideally the amplitude response is flat with very narrow notches at the frequencies of the interfering sinusoidal noise components. Both the amplitude and phase response are displayed. This makes it possible to see the notches develop in the frequency response of the whitening filter as it converges. It also makes it possible to see how the phase behaves at the notches. The individual whitening filters on which the frequency response is computed are also displayed as time series. USAGE: Just compile and run the program. There are no user inputs. The program uses fixed values for the following: feedbackGain: The gain factor that is used in the feedback loop to adjust the coefficient values in the prediction/whitening filter. (A whitening filter is a prediction filter with a -1 appended to its end.) numberIterations: The is the number of iterations that the program executes before stopping and displaying all of the graphic results. pFilterLength: This is the number of coefficients in the prediction filter. signalScale: A scale factor that is applied to the wide band signel provided by the random noise generator. The random noise generator produces uniformaly distributed values ranging from -0.5 to +0.5. noiseScale: A scale factor that is applied to each of the sinusoidal noise functions before they are added to the signal. The raw sinusoids vary from -1.0 to +1.0. Tested using J2SE 5.0 and WinXP. J2SE 5.0 or later is required. **********************************************************/ import static java.lang.Math.*;//J2SE 5.0 req class Adapt02a{ public static void main(String[] args){ //Default parameter values double feedbackGain = 0.00001; int numberIterations = 600; int pFilterLength = 26; double signalScale = 20; double noiseScale = 20; //Instantiate a new object of the Adapt02a class // and invoke the method named process on that object. new Adapt02a().process(feedbackGain, numberIterations, pFilterLength, signalScale, noiseScale); }//end main //-----------------------------------------------------// //This is the primary adaptive processing and plotting // method for the program. void process(double feedbackGain, int numberIterations, int pFilterLength, double signalScale, double noiseScale){ double[] pFilter; //Instantiate an object to handle the adaptive behavior // of the program. AdaptEngine01 adapter = new AdaptEngine01( pFilterLength,feedbackGain); //Create the initial whiteningFilter and initialize it. double[] whiteningFilter = new double[pFilterLength + 1]; //Set the final value in the whitening filter to -1. whiteningFilter[whiteningFilter.length - 1] = -1; //Create an array to serve as a two-sample delay line // for the raw data. The data value at zero index will // be the data is to be filtered. The data value at // index value 1 will be the predictive target. double[] rawData = new double[2]; //Instantiate a plotting object for six channels of // time-serie data. PlotALot07 timePlotObj = new PlotALot07("Time",468,200,25,10,4,4); //Instantiate a plotting object for two channels of // filter frequency response data. One channel is for // the amplitude and the other channel is the phase. PlotALot03 freqPlotObj = new PlotALot03("Freq",264,487,35,2,0,0); //Instantiate a plotting object to display the // whitening filter at specific time intervals during // the adaptive process. PlotALot01 filterPlotObj = new PlotALot01("Filter", (whiteningFilter.length * 4) + 8,487,70,4,0,0); //Declare and initialize working variables. double output = 0; double err = 0; double target = 0; double input = 0; double signal = 0; double sineNoise = 0; //Perform the specified number of iterations. for(int cnt = 0;cnt < numberIterations;cnt++){ //Get the next sample of wideband signal. signal = signalScale*(Math.random() - 0.5); //Get the next sample of sinusoidal noise consisting // of two sinusoids at different frequencies. sineNoise = noiseScale*(Math.sin(2*cnt*PI/8) + Math.sin(2*cnt*PI/3)); //Insert the signal plus noise into the raw data // delay line. flowLine(rawData,signal + sineNoise); //Execute the adaptive behavior. AdaptiveResult result = adapter.adapt(rawData[0], rawData[1]); //Get and save adaptive results for plotting and // spectral analysis output = result.output; err = result.err; pFilter = result.filterArray; //Feed the time series data to the plotting object. timePlotObj.feedData(-err,signal,sineNoise, rawData[0],output,rawData[1]); //Compute and plot the frequency response and plot // the whitening filter every 100 iterations. if(cnt%100 == 0){ //Create a whitening filter from the data in the // prediction filter by copying the prediction // filter into the bottom elements of the whitening // filter. Recall that the last element in the // whitening filter already has a value of -1. System.arraycopy(pFilter, 0, whiteningFilter, 0, pFilter.length); displayFreqResponse(whiteningFilter,freqPlotObj, 128,whiteningFilter.length - 1); //Display the whitening filter coefficient values. for(int ctr = 0;ctr < whiteningFilter.length; ctr++){ filterPlotObj.feedData(40*whiteningFilter[ctr]); }//end for loop }//End display of frequency response and whitening // filter }//End for loop, //Cause all the data to be plotted. timePlotObj.plotData(); freqPlotObj.plotData(0,201); filterPlotObj.plotData(265,201); }//end process method //-----------------------------------------------------// //This method simulates a tapped delay line. It receives // a reference to an array and a value. It discards the // value at index 0 of the array, moves all the other // values by one element toward 0, and inserts the new // value at the top of the array. void flowLine(double[] line,double val){ for(int cnt = 0;cnt < (line.length - 1);cnt++){ line[cnt] = line[cnt+1]; }//end for loop line[line.length - 1] = val; }//end flowLine //-----------------------------------------------------// void displayFreqResponse( double[] filter,PlotALot03 plot,int len,int zeroTime){ //Create the arrays required by the Fourier Transform. double[] timeDataIn = new double[len]; double[] realSpect = new double[len]; double[] imagSpect = new double[len]; double[] angle = new double[len]; double[] magnitude = new double[len]; //Copy the filter into the timeDataIn array System.arraycopy(filter,0,timeDataIn,0,filter.length); //Compute DFT of the filter from zero to the folding // frequency and save it in the output arrays. ForwardRealToComplex01.transform(timeDataIn, realSpect, imagSpect, angle, magnitude, zeroTime, 0.0, 0.5); //Display the magnitude data. Convert to normalized // decibels first. //Eliminate or change any values that are incompatible // with log10 method. for(int cnt = 0;cnt < magnitude.length;cnt++){ if((magnitude[cnt] == Double.NaN) || (magnitude[cnt] <= 0)){ //Replace the magnitude by a very small positive // value. magnitude[cnt] = 0.0000001; }else if(magnitude[cnt] == Double.POSITIVE_INFINITY){ //Replace the magnitude by a very large positive // value. magnitude[cnt] = 9999999999.0; }//end else if }//end for loop //Now convert magnitude data to log base 10 for(int cnt = 0;cnt < magnitude.length;cnt++){ magnitude[cnt] = log10(magnitude[cnt]); }//end for loop //Note that from this point forward, all references to // magnitude are referring to log base 10 data, which // can be thought of as scaled decibels. //Find the absolute peak value. Begin with a negative // peak value with a large magnitude and replace it // with the largest magnitude value. double peak = -9999999999.0; for(int cnt = 0;cnt < magnitude.length;cnt++){ if(peak < abs(magnitude[cnt])){ peak = abs(magnitude[cnt]); }//end if }//end for loop //Normalize to 50 times the peak value and shift up the // page by 50 units to make the values compatible with // the plotting program. Recall that adding a // constant to log values is equivalent to scaling the // original data. for(int cnt = 0;cnt < magnitude.length;cnt++){ magnitude[cnt] = 50*magnitude[cnt]/peak + 50; }//end for loop //Now feed the normalized decibel data to the plotting // system. for(int cnt = 0;cnt < magnitude.length;cnt++){ plot.feedData(magnitude[cnt],angle[cnt]/20); }//end for loop }//end displayFreqResponse //-----------------------------------------------------// }//end class Adapt02a |

Copyright 2005, 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 have 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.*

Page 2 of 2