August 28, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Adaptive Noise Cancellation using Java

  • April 18, 2006
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Run the Program

I encourage you to copy the code for the class named Adapt08 from the section entitled Complete Program Listings.  Compile and execute the program.  Experiment with the code.  Make changes to the code, recompile, execute, and observe the results of your changes.

Modify the program parameters and the path operator and see if you can explain the results of those modifications.

See if you can create a path operator for which the adaptive process refuses to converge to a solution.

Experiment with different values for feedback Gain.  What happens if you use a large value for feedbackGain?  What happens if you use a very small value for feedbackGain?  What happens if you use a negative value for feedbackGain?  Can you explain the results that you experience?

While experimenting with the feedbackGain, you might also want to experiment with the program parameter named numberIterations.  See if you can explain the results for small, medium, and large values for this parameter.

Other classes required

In addition to the classes named Adapt08, you will need access to the following classes.  The source code for these classes can be found in the lessons indicated.

Summary

In this lesson, I showed you how to use a general-purpose LMS adaptive engine to write a Java program that illustrates the use of adaptive filtering for Noise Cancellation.

What's Next?

Adaptive filtering is commonly used for the following four scenarios:

  • System Identification
  • Inverse System Identification
  • Noise Cancellation
  • Prediction

A previous lesson entitled Adaptive Identification and Inverse Filtering using Java explained how to write Java programs to illustrate the first two scenarios in the above list.  This lesson explains how to write a Java program that illustrates the use of adaptive filtering for the third scenario: Noise Cancellation.

I plan to publish a lesson in the near future that explains and provides examples of Prediction using adaptive filtering.

References

In preparation for understanding the material in this lesson, I recommend that you study the material in the following previously-published lessons:

  • 100   Periodic Motion and Sinusoids
  • 104   Sampled Time Series
  • 108   Averaging Time Series
  • 1478 Fun with Java, How and Why Spectral Analysis Works
  • 1482 Spectrum Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT Algorithm
  • 1483 Spectrum Analysis using Java, Frequency Resolution versus Data Length
  • 1484 Spectrum Analysis using Java, Complex Spectrum and Phase Angle
  • 1485 Spectrum Analysis using Java, Forward and Inverse Transforms, Filtering in the Frequency Domain
  • 1487 Convolution and Frequency Filtering in Java
  • 1488 Convolution and Matched Filtering in Java
  • 1492 Plotting Large Quantities of Data using Java
  • 2350 Adaptive Filtering in Java, Getting Started
  • 2352 An Adaptive Whitening Filter in Java
  • 2354 A General-Purpose LMS Adaptive Engine in Java
  • 2356 An Adaptive Line Tracker in Java
  • 2358 Adaptive Identification and Inverse Filtering using Java

Complete Program Listings

A complete listings of the class discussed in this lesson is shown in Listing 14 below.

/*File Adapt08.java
Copyright 2005, R.G.Baldwin

The purpose of this program is to illustrate an adaptive
noise cancellation system.

See the following URL for a description and a block 
diagram of an adaptive noise cancellation system.

http://www.owlnet.rice.edu/~ryanking/elec431/intro.html

This program requires the following classes:
Adapt08.class
AdaptEngine02.class
AdaptiveResult.class
ForwardRealToComplex01.class
PlotALot01.class
PlotALot03.class
PlotALot07.class

This program uses the adaptive engine named AdaptEngine02 
to adaptively develop a filter.  One of the inputs to the
adaptive engine is the sum of signal plus noise.  The 
other input to the adaptive engine is noise that is 
different from, but which is correlated to the noise that
is added to the signal.  The adaptive engine develops a
filter that removes the noise from the signal plus noise
data producing an output consisting of almost pure
signal.

The signal and the original noise are derived from a
random number generator.  Therefore, they are both 
essentially white.  The signal is not correlated with the 
noise.  Before scaling, the values obtained from the
random number generator are uniformly distributed from
-1.0 to +1.0. 

The white noise is processed through a convolution 
filter before adding it to the signal.  The convolution
filter is designed to simulate the effect of acoustic
energy being emitted at one point in space and being 
modified through the addition of several time-delayed and 
attenuated echoes before being received at another point 
in space.  This results in constructive and destructive 
interference such that the noise that is added to the 
signal is no longer white.  However, it is still 
correlated with the original white noise.

The program produces five graphs with three graphs in a 
row across the top of the screen and two graphs in a row 
below the top row.

The following is a brief description of each of the five 
graphs, working from left to right, top to bottom.

1.  The impulse response of the convolution filter that is
applied to the white noise before it is added to the
white signal.
2.  The amplitude and phase response of the convolution
filter that is applied to the white noise before it is
added to the white signal.
3.  Six time series that illustrate the time behavior of 
the adaptive process.
4.  The amplitude and phase response of the adaptive 
filter at the end of every 400th iteration.
5.  The impulse response of the adaptive filter at the end 
of every 400th adaptive iteration.

Graph 3 consists of multiple pages stacked on top
of one another.  Move the pages on the top of the stack to 
view the pages further down.  The pages on the top of the 
stack represent the results produced early in the adaptive 
process while those further down represent the results 
produced later in the adaptive process.  If you increase
the number of iterations described later, graphs 4 and 5
will also consist of multiple pages stacked on top of one
another.

The six time series that are plotted are, from top to
bottom in the colors indicated:
1.  (Black) Input to the adaptive filter.  This is the
white noise.
2.  (Red) Target for the adaptive process. This is the
signal plus noise.
3.  (Blue) Output from the adaptive filter.
4.  (Green) Error computed within the adaptive process.
In this case, the error trace is actually the trace that
contains the signal with the noise removed.  In other 
words, the blue trace is an estimate of the noise and the
green trace is the difference between the red trace and 
the blue trace.
5.  (Violet) The original pure signal.  This trace is 
provided for visual comparison with the green signal trace.
6.  (Turquoise) The arithmetic difference between the 
pure signal trace (violet) and the green trace containing
the adaptive process's estimate of the signal.  This 
trace is provided for an arithmetic comparison of the
pure signal trace and the adaptive estimate of the signal.
Ideally this trace will go to zero if the adaptive process
is successful in totally eliminating the noise from the
green trace.

Near the end of the run, the adaptive update process is 
disabled.  The input data is set to zero for the remainder 
of the run except that on one occasion, an impulse is 
inserted into the white noise data.  This makes it 
possible to see:

1. The impulse response of the convolution filter that
is applied to the white noise before it is added to the
white signal.
2. The impulse response of the final adaptive filter.

There is no user input to this program.  All parameters are
hard coded into the main method.  To run the program with
different parameters, modify the parameters and recompile
the program.

The program parameters are:

feedbackGain: The gain factor that is used in the feedback 
loop to adjust the coefficient values in the adaptive 
filter.

numberIterations: This is the number of iterations that the
program executes before stopping and displaying all of the 
graphic results.

filterLength: This is the number of coefficients in the 
adaptive filter.  Must be at least 26.  If you change it
to a value that is less than 26, the plot of the impulse
responses of the adaptive filter will not be properly 
aligned.

noiseScale:  The scale factor that is applied to the values
that are extracted from the random number generator and
used as white noise.

signalScale:  The scale factor that is applied to the
values that are extracted from the random number generator
and used as white signal.

pathOperator:  Areference to an array of type double[] 
containing the coefficients of the convolution filter that
is applied to the white noise before it is added to the
white signal.

Tested using J2SE 5.0 and WinXP.  J2SE 5.0 or later is 
required.
**********************************************************/
import static java.lang.Math.*;//J2SE 5.0 req

class Adapt08{
  public static void main(String[] args){
    //Default parameter values
    double feedbackGain = 0.0001;
    int numberIterations = 2001;
    int filterLength = 26;//Must be >= 26 for plotting.
    double noiseScale = 10;
    double signalScale = 10;
    
    //Define the path impulse response as a simulation of
    // an acoustic signal with time-delayed echoes.
    double[] pathOperator = {0.0,
                             0.0,
                             0.0,
                             1.0,
                             0.0,
                             0.64,
                             0.0,
                             0.0,
                             0.32768,
                             0.0,
                             0.0,
                             0.0,
                             0.1342176,
                             0.0,
                             0.0,
                             0.0,
                             0.0,
                             0.0439803,
                             0.0};

    //Instantiate a new object of the Adapt08 class
    // and invoke the method named process on that object.
    new Adapt08().process(feedbackGain,
                          numberIterations,
                          filterLength,
                          noiseScale,
                          signalScale,
                          pathOperator);
  }//end main
  //-----------------------------------------------------//
  
  //This is the primary adaptive processing and plotting
  // method for the program.
  void process(double feedbackGain,
               int numberIterations,
               int filterLength,
               double noiseScale,
               double signalScale,
               double[] pathOperator){

    //The following array will be populated with the
    // adaptive filter for display purposes.
    double[] filter = null;

    //Display the pathOperator
    //First instantiate a plotting object.
    PlotALot01 pathOperatorObj = new PlotALot01("Path",
               (pathOperator.length * 4) + 8,148,70,4,0,0);

    //Feed the data to the plotting object.
    for(int cnt = 0;cnt < pathOperator.length;cnt++){
      pathOperatorObj.feedData(40*pathOperator[cnt]);
    }//end for loop
    
    //Cause the graph to be displayed on the computer
    // screen in the upper left corner.
    pathOperatorObj.plotData(0,0);
    
    //Now compute and plot the frequency response of the
    // path
    
    //Instantiate a plotting object for two channels of
    // frequency response data.  One channel is for
    // the amplitude and the other channel is the phase.
    PlotALot03 pathFreqPlotObj = 
                   new PlotALot03("Path",264,148,35,2,0,0);
                   
    //Compute the frequency response and feed the results
    // to the plotting object.
    displayFreqResponse(pathOperator,pathFreqPlotObj,
                                                    128,0);
                       
    //Cause the frequency response data stored in the
    // plotting object to be displayed on the screen in
    // the top row of images.
    pathFreqPlotObj.plotData(112,0);
    
    //Instantiate an object to handle the adaptive behavior
    // of the program.
    AdaptEngine02 adapter = new AdaptEngine02(
                                filterLength,feedbackGain);

    //Instantiate an array object that will be used as a
    // delay line for the white noise data.
    double[] rawData = new double[pathOperator.length];
    
    //Instantiate a plotting object for six channels of
    // time-series data.
    PlotALot07 timePlotObj = 
                   new PlotALot07("Time",468,198,25,2,0,0);

    //Instantiate a plotting object for two channels of
    // filter frequency response data.  One channel is for
    // the amplitude and the other channel is for the
    // phase.
    PlotALot03 freqPlotObj = 
                   new PlotALot03("Freq",264,487,35,2,0,0);

    //Instantiate a plotting object to display the filter
    // impulse response at specific time intervals during
    // the adaptive process.
    PlotALot01 filterPlotObj = new PlotALot01("Filter",
                     (filterLength * 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 whiteNoise = 0;
    double whiteSignal = 0;
    boolean adaptOn;

    //Perform the specified number of iterations.
    for(int cnt = 0;cnt < numberIterations;cnt++){
      
      //The following variable is used to control whether
      // or not the adapt method of the adaptive engine
      // updates the filter coefficients when it is called.
      // The filters are updated when this variable is
      // true and are not updated when this variable is
      // false.
      adaptOn = true;
      
      //Get and scale the next sample of white noise and
      // white signal data from a random number generator.
      // Before scaling by noiseScale and signalScale, the
      // values are uniformly distributed from -1.0 to 1.0.
      whiteNoise = noiseScale*(2*(Math.random() - 0.5));
      whiteSignal = signalScale*(2*(Math.random() - 0.5));
      
      //Set white noise and white signal values near the
      // end of the run to zero and turn adaptation off.
      // Insert an impulse near the end of the whiteNoise
      // data that will produce impulse responses for the
      // path and for the adaptive filter.
      //Set values to zero.
      if(cnt > (numberIterations - 5*filterLength)){
        whiteNoise = 0;
        whiteSignal = 0;
        adaptOn = false;
      }//end if
      //Now insert an impulse at one specific location in
      // the whiteNoise data.
      if(cnt == numberIterations - 3*filterLength){
        whiteNoise = 2 * noiseScale;
      }//end if

      //Insert the white noise data into the delay line
      // that will be used to convolve the white noise
      // with the path operator.
      flowLine(rawData,whiteNoise);
    
      //Apply the path operator to the white noise data.
      double pathNoise = 
                   reverseDotProduct(rawData,pathOperator);

      //Declare a variable that will be populated with the
      // results returned by the adapt method of the
      // adaptive engine.
      AdaptiveResult result = null;
      
      //Establish the appropriate input values for an
      // adaptive noise cancellation filter and perform the
      // adaptive update.
      input = whiteNoise;
      target = pathNoise + whiteSignal;
      result = adapter.adapt(input,target,adaptOn);

      //Get and save adaptive results for plotting and
      // spectral analysis
      output = result.output;
      err = result.err;
      filter = result.filterArray;
    
      //Feed the time series data to the plotting object.
      timePlotObj.feedData(input,target,output,-err,
                            whiteSignal,err + whiteSignal);
    
      //Compute and plot summary results at the end of
      // every 400th iteration.
      if(cnt%400 == 0){
        displayFreqResponse(filter,freqPlotObj,
                                    128,filter.length - 1);

        //Display the filter impulse response coefficient
        // values.  The adaptive engine returns the filter
        // with the time axis reversed relative to the
        // conventional display of an impulse response.
        // Therefore, it is necessary to display it in
        // reverse order.
        for(int ctr = 0;ctr < filter.length;ctr++){
          filterPlotObj.feedData(
                       40*filter[filter.length - 1 - ctr]);
        }//end for loop
      }//End display of frequency response and filter
    }//End for loop,
    
    //Cause the data stored in the plotting objects to be
    // plotted.
    timePlotObj.plotData(376,0);//Top of screen
    freqPlotObj.plotData(0,148);//Left side of screen
    filterPlotObj.plotData(265,148);

  }//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);

    //Note that the conversion to decibels has been
    // disabled.  You can re-enable the conversion by
    // removing the comment indicators.
/*    
    //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
*/
    //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 20 times the peak value
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = 20*magnitude[cnt]/peak;
    }//end for loop

    //Now feed the normalized data to the plotting
    // object.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      plot.feedData(magnitude[cnt],angle[cnt]/20);
    }//end for loop
    
  }//end displayFreqResponse
  //-----------------------------------------------------//
  
  //This method receives two arrays and treats each array
  // as a vector. The two arrays must have the same length.
  // The program reverses the order of one of the vectors
  // and returns the vector dot product of the two vectors.
  double reverseDotProduct(double[] v1,double[] v2){
    if(v1.length != v2.length){
      System.out.println("reverseDotProduct");
      System.out.println("Vectors must be same length.");
      System.out.println("Terminating program");
      System.exit(0);
    }//end if
    
    double result = 0;
    
    for(int cnt = 0;cnt < v1.length;cnt++){
      result += v1[cnt] * v2[v1.length - cnt - 1];
    }//end for loop

    return result;
  }//end reverseDotProduct
  //-----------------------------------------------------//
}//end class Adapt08

Listing 14


Copyright 2006, 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.

Baldwin@DickBaldwin.com





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel