mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
Incrementing build to version 0.3.0 beta8
This commit is contained in:
9
.idea/libraries/commons_math3_3_6_1.xml
generated
Normal file
9
.idea/libraries/commons_math3_3_6_1.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="commons-math3-3.6.1">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/commons-math3-3.6.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
@@ -21,5 +21,6 @@
|
|||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
<orderEntry type="library" name="org.asynchttpclient:async-http-client:2.0.18" level="project" />
|
<orderEntry type="library" name="org.asynchttpclient:async-http-client:2.0.18" level="project" />
|
||||||
|
<orderEntry type="library" name="commons-math3-3.6.1" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -2,7 +2,9 @@ package dsp.filter.design;
|
|||||||
|
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
import dsp.filter.fir.FIRFilterSpecification;
|
import dsp.filter.fir.FIRFilterSpecification;
|
||||||
|
import dsp.filter.fir.remez.PolyphaseChannelizerFilterFactory;
|
||||||
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
|
import dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -51,15 +53,15 @@ public class FilterViewer extends Application
|
|||||||
// .stopBandStart(5000)
|
// .stopBandStart(5000)
|
||||||
// .build();
|
// .build();
|
||||||
|
|
||||||
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||||
.sampleRate( 24000 )
|
// .sampleRate( 24000 )
|
||||||
.stopBandCutoff( 800 )
|
// .stopBandCutoff( 800 )
|
||||||
.stopBandAmplitude( 0.0 )
|
// .stopBandAmplitude( 0.0 )
|
||||||
.stopBandRipple( 0.03 )
|
// .stopBandRipple( 0.03 )
|
||||||
.passBandStart( 1000 )
|
// .passBandStart( 1000 )
|
||||||
.passBandAmplitude( 1.0 )
|
// .passBandAmplitude( 1.0 )
|
||||||
.passBandRipple( 0.08 )
|
// .passBandRipple( 0.08 )
|
||||||
.build();
|
// .build();
|
||||||
|
|
||||||
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
|
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
|
||||||
// .sampleRate( 48000 )
|
// .sampleRate( 48000 )
|
||||||
@@ -76,20 +78,22 @@ public class FilterViewer extends Application
|
|||||||
// taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING );
|
// taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING );
|
||||||
|
|
||||||
|
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
// RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification);
|
||||||
|
//// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||||
if(designer.isValid())
|
//
|
||||||
{
|
// if(designer.isValid())
|
||||||
taps = designer.getImpulseResponse();
|
// {
|
||||||
}
|
// taps = designer.getImpulseResponse();
|
||||||
}
|
// }
|
||||||
catch(FilterDesignException fde)
|
// }
|
||||||
{
|
// catch(FilterDesignException fde)
|
||||||
mLog.error("Filter design error", fde);
|
// {
|
||||||
}
|
// mLog.error("Filter design error", fde);
|
||||||
|
// }
|
||||||
|
|
||||||
|
taps = PolyphaseChannelizerFilterFactory.getFilter(10000000, 312500, 0.21);
|
||||||
return taps;
|
return taps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,27 +28,25 @@ public class PolyphaseChannelizerFilterFactory
|
|||||||
|
|
||||||
private static final double OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE = Math.sqrt(2.0) / 2.0; //.707xxx
|
private static final double OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE = Math.sqrt(2.0) / 2.0; //.707xxx
|
||||||
|
|
||||||
public static float[] getFilter(int sampleRate, int channelBandwidth)
|
public static float[] getFilter(int sampleRate, int channelBandwidth, double alpha)
|
||||||
{
|
{
|
||||||
FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL;
|
FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL;
|
||||||
|
|
||||||
int passBandStart = 0;
|
int passBandStart = 0;
|
||||||
int gridDensity = 32;
|
int gridDensity = 50;
|
||||||
|
|
||||||
//Set pass band to stop at about 80% of channel bandwidth as starting point
|
int rolloffFrequency = (int)(alpha * (double)channelBandwidth);
|
||||||
// int passBandStop = (int)(channelBandwidth * 0.95);
|
|
||||||
int passBandStop = channelBandwidth;
|
int passBandStop = channelBandwidth - rolloffFrequency;
|
||||||
|
|
||||||
//Set the stop band to start at 40% into the adjacent channel, since we're oversampling by 2.
|
//Set the stop band to start at 40% into the adjacent channel, since we're oversampling by 2.
|
||||||
int stopBandStart = (int)(channelBandwidth * 1.40);
|
int stopBandStart = channelBandwidth + rolloffFrequency;
|
||||||
|
|
||||||
double passRipple = 0.1d;
|
double passRipple = 0.01d;
|
||||||
double stopRipple = 0.01d;
|
double stopRipple = 0.001d; //Approximately 90 dB of attenuation
|
||||||
|
|
||||||
// int order = FIRFilterSpecification.estimateBandPassOrder(sampleRate, passBandStart, passBandStop,
|
int order = FIRFilterSpecification.estimateFilterOrder( sampleRate, passBandStop, stopBandStart,
|
||||||
// passRipple, stopRipple);
|
passRipple, stopRipple );
|
||||||
int order = FIRFilterSpecification.estimateFilterOrder( sampleRate, passBandStop,
|
|
||||||
stopBandStart, passRipple, stopRipple );
|
|
||||||
|
|
||||||
// Ensure even order since we're designing a Type 1 filter
|
// Ensure even order since we're designing a Type 1 filter
|
||||||
if(order % 2 == 1)
|
if(order % 2 == 1)
|
||||||
@@ -56,66 +54,39 @@ public class PolyphaseChannelizerFilterFactory
|
|||||||
order++;
|
order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.info("Filter Order: " + order);
|
||||||
|
|
||||||
FIRFilterSpecification specification = new PolyphaseChannelizerDesigner(order, gridDensity);
|
FIRFilterSpecification specification = new PolyphaseChannelizerDesigner(order, gridDensity);
|
||||||
|
|
||||||
FIRFilterSpecification.FrequencyBand passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, 0,
|
FIRFilterSpecification.FrequencyBand passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, passBandStart,
|
||||||
passBandStop, 1.0, passRipple);
|
passBandStop, 1.0, passRipple, 1.0);
|
||||||
|
|
||||||
|
//Use the filter order as the weighting for the pass band edge frequency
|
||||||
|
double weight = order;
|
||||||
|
|
||||||
|
FIRFilterSpecification.FrequencyBand transitionBand = new FIRFilterSpecification.FrequencyBand(sampleRate,
|
||||||
|
channelBandwidth, channelBandwidth, OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE, passRipple, weight);
|
||||||
|
|
||||||
FIRFilterSpecification.FrequencyBand stopBand = new FIRFilterSpecification.FrequencyBand(sampleRate,
|
FIRFilterSpecification.FrequencyBand stopBand = new FIRFilterSpecification.FrequencyBand(sampleRate,
|
||||||
stopBandStart, (int)(sampleRate / 2), 0.0, stopRipple);
|
stopBandStart, (int)(sampleRate / 2), 0.0, stopRipple);
|
||||||
|
|
||||||
specification.addFrequencyBand(passBand);
|
specification.addFrequencyBand(passBand);
|
||||||
|
specification.addFrequencyBand(transitionBand);
|
||||||
specification.addFrequencyBand(stopBand);
|
specification.addFrequencyBand(stopBand);
|
||||||
|
|
||||||
boolean complete = false;
|
|
||||||
int stepSize = 100;
|
|
||||||
double bandEdgeFrequency = Math.cos(Math.PI * (double)channelBandwidth / (double)(sampleRate / 2));
|
double bandEdgeFrequency = Math.cos(Math.PI * (double)channelBandwidth / (double)(sampleRate / 2));
|
||||||
float[] filter = null;
|
float[] filter = null;
|
||||||
|
|
||||||
while(!complete)
|
try
|
||||||
{
|
{
|
||||||
mLog.debug("Step Size: " + stepSize);
|
RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification);
|
||||||
|
filter = designer.getImpulseResponse();
|
||||||
RemezFIRFilterDesigner2 designer = new RemezFIRFilterDesigner2(specification);
|
|
||||||
|
|
||||||
complete = true; //temporary
|
|
||||||
|
|
||||||
double bandEdgeAmplitude = designer.getFrequencyResponse(bandEdgeFrequency);
|
double bandEdgeAmplitude = designer.getFrequencyResponse(bandEdgeFrequency);
|
||||||
mLog.debug("Coefficient Amplitude at Band Edge is: " + bandEdgeAmplitude + " for frequency: " + passBandStop);
|
mLog.debug("Coefficient Amplitude at Band Edge is: " + bandEdgeAmplitude + " for frequency: " + passBandStop);
|
||||||
|
}
|
||||||
if(bandEdgeAmplitude < OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
if(stepSize <= 1)
|
mLog.error("Error designing filter", e);
|
||||||
{
|
|
||||||
complete = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
filter = designer.getImpulseResponse();
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.debug("Error getting filter response", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
passBandStop += stepSize;
|
|
||||||
stepSize /= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
passBandStop -= stepSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!complete)
|
|
||||||
{
|
|
||||||
specification.clearFrequencyBands();
|
|
||||||
|
|
||||||
passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, 0,
|
|
||||||
passBandStop, 1.0, passRipple);
|
|
||||||
specification.addFrequencyBand(passBand);
|
|
||||||
specification.addFrequencyBand(stopBand);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
@@ -125,7 +96,7 @@ public class PolyphaseChannelizerFilterFactory
|
|||||||
{
|
{
|
||||||
mLog.debug("Starting ...");
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
float[] filter = getFilter(2500000, 12500);
|
float[] filter = getFilter(200000, 12500, 0.21);
|
||||||
|
|
||||||
mLog.debug("Finished ...");
|
mLog.debug("Finished ...");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,15 +212,12 @@ public class RemezFIRFilterDesigner
|
|||||||
|
|
||||||
for( int k = 0; k < mExtremalIndices.size() - 1; k++ )
|
for( int k = 0; k < mExtremalIndices.size() - 1; k++ )
|
||||||
{
|
{
|
||||||
double cosineDelta = cosineOfFrequency -
|
double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
|
||||||
mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
|
|
||||||
|
|
||||||
//If this frequency is or is close to one of the polynomial points, use c[k] for that point
|
//If this frequency is close to one of the polynomial points, use the polynomial point for the response
|
||||||
if( Math.abs( cosineDelta ) < 1.0e-7 )
|
if( Math.abs( cosineDelta ) < 1.0e-7 )
|
||||||
{
|
{
|
||||||
numerator = mIdealFrequencyResponse[ k ];
|
return mIdealFrequencyResponse[ k ];
|
||||||
denominator = 1.0;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public class RemezFIRFilterDesigner2
|
|||||||
private static final double CONVERGENCE_THRESHOLD = 0.0001;
|
private static final double CONVERGENCE_THRESHOLD = 0.0001;
|
||||||
public static final int MAXIMUM_ITERATION_COUNT = 40;
|
public static final int MAXIMUM_ITERATION_COUNT = 40;
|
||||||
public static final double TWO_PI = 2.0 * Math.PI;
|
public static final double TWO_PI = 2.0 * Math.PI;
|
||||||
|
public static final double FREQUENCY_PROXIMITY_THRESHOLD = 1.0e-7;
|
||||||
|
|
||||||
private FIRFilterSpecification mSpecification;
|
private FIRFilterSpecification mSpecification;
|
||||||
private Grid mGrid;
|
private Grid mGrid;
|
||||||
@@ -234,25 +235,27 @@ public class RemezFIRFilterDesigner2
|
|||||||
|
|
||||||
for(int k = 0; k < mExtremalIndices.size() - 1; k++)
|
for(int k = 0; k < mExtremalIndices.size() - 1; k++)
|
||||||
{
|
{
|
||||||
BigDecimal cosineDelta = BigDecimal.valueOf(cosineOfFrequency -
|
double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)];
|
||||||
mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)]);
|
|
||||||
|
|
||||||
//If this frequency is close to one of the polynomial points, use c[k] for that point
|
//If this frequency is close to one of the polynomial points, use the point as the response value
|
||||||
if(cosineDelta.abs().compareTo(BigDecimal.valueOf(1.0e-7)) < 0)
|
if(Math.abs(cosineDelta) < FREQUENCY_PROXIMITY_THRESHOLD)
|
||||||
{
|
{
|
||||||
numerator = BigDecimal.valueOf(mIdealFrequencyResponse[k]);
|
return mIdealFrequencyResponse[k];
|
||||||
denominator = BigDecimal.ONE;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BigDecimal dkOverCosineDelta = mD[k].divide(cosineDelta, 25, RoundingMode.HALF_UP);
|
BigDecimal dkOverCosineDelta = mD[k].divide(BigDecimal.valueOf(cosineDelta), 25, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
numerator = numerator.add(dkOverCosineDelta.multiply(BigDecimal.valueOf(mIdealFrequencyResponse[k])));
|
numerator = numerator.add(dkOverCosineDelta.multiply(BigDecimal.valueOf(mIdealFrequencyResponse[k])));
|
||||||
denominator = denominator.add(dkOverCosineDelta);
|
denominator = denominator.add(dkOverCosineDelta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(denominator.doubleValue() == 0.0)
|
||||||
|
{
|
||||||
|
int a = 0;
|
||||||
|
}
|
||||||
|
|
||||||
return numerator.divide(denominator, 25, RoundingMode.HALF_UP).doubleValue();
|
return numerator.divide(denominator, 25, RoundingMode.HALF_UP).doubleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
686
src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java
Normal file
686
src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java
Normal file
@@ -0,0 +1,686 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
package dsp.filter.fir.remez;
|
||||||
|
|
||||||
|
import dsp.filter.design.FilterDesignException;
|
||||||
|
import dsp.filter.fir.FIRFilterSpecification;
|
||||||
|
import org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RemezFIRFilterDesignerWithLagrange
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(RemezFIRFilterDesignerWithLagrange.class);
|
||||||
|
|
||||||
|
private static final double CONVERGENCE_THRESHOLD = 0.0001;
|
||||||
|
public static final int MAXIMUM_ITERATION_COUNT = 40;
|
||||||
|
public static final double TWO_PI = 2.0 * Math.PI;
|
||||||
|
|
||||||
|
private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("0.00000000");
|
||||||
|
|
||||||
|
private FIRFilterSpecification mSpecification;
|
||||||
|
private Grid mGrid;
|
||||||
|
private List<Integer> mExtremalIndices;
|
||||||
|
|
||||||
|
private double[] mD;
|
||||||
|
private double[] mGridErrors;
|
||||||
|
private double[] mGridFrequencyResponse;
|
||||||
|
private double[] mIdealFrequencyResponse;
|
||||||
|
private PolynomialFunctionLagrangeForm mLagrangeInterpolator;
|
||||||
|
|
||||||
|
private double mDelta;
|
||||||
|
|
||||||
|
private boolean mConverged;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear FIR filter designer using the Parks-McClelland Remez exchange algorithm.
|
||||||
|
*
|
||||||
|
* Construct a filter specification using the builder methods in the FIRFilterSpecification
|
||||||
|
* class and construct an instance of this class with the specification. Check the isValid()
|
||||||
|
* method after construction to see if the filter design was successful. If successful, use the
|
||||||
|
* getFrequencyResponse() and getImpulseResponse() methods to access the filter taps as the
|
||||||
|
* specified length.
|
||||||
|
*
|
||||||
|
* If the filter cannot be designed from the specification the isValid() method will return
|
||||||
|
* false and any attempts to access the getFrequencyResponse() or getImpulseResponse() methods
|
||||||
|
* will throw a FilterDesignException.
|
||||||
|
*
|
||||||
|
* @param specification that defines a Type 1-4 linear phase FIR Filter
|
||||||
|
*/
|
||||||
|
public RemezFIRFilterDesignerWithLagrange(FIRFilterSpecification specification) throws FilterDesignException
|
||||||
|
{
|
||||||
|
mSpecification = specification;
|
||||||
|
|
||||||
|
design();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Designs the filter according to the specification
|
||||||
|
*/
|
||||||
|
private void design() throws FilterDesignException
|
||||||
|
{
|
||||||
|
mGrid = new Grid(mSpecification);
|
||||||
|
mExtremalIndices = getInitialExtremalIndices();
|
||||||
|
|
||||||
|
int iterationCount = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
calculateGridFrequencyResponse();
|
||||||
|
calculateGridError();
|
||||||
|
findExtremalIndices();
|
||||||
|
checkConvergence();
|
||||||
|
|
||||||
|
iterationCount++;
|
||||||
|
}
|
||||||
|
while(!mConverged && iterationCount < MAXIMUM_ITERATION_COUNT);
|
||||||
|
|
||||||
|
if(mConverged)
|
||||||
|
{
|
||||||
|
//Update frequency response using the final set of extremal indices
|
||||||
|
calculateGridFrequencyResponse();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FilterDesignException("Unable to design filter after [" + iterationCount + "] iterations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the filter was successfully created from the filter specification. Check this
|
||||||
|
* method before accessing frequency response or filter response, otherwise a FilterDesignException
|
||||||
|
* is thrown.
|
||||||
|
*
|
||||||
|
* @return true if the filter was successfully designed.
|
||||||
|
*/
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mConverged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency response of the designed filter with double precision
|
||||||
|
*
|
||||||
|
* @return half ( length / 2 ) of filter frequency response sampled at 0.0 - PI radians
|
||||||
|
* @throws FilterDesignException if the specified filter cannot be designed
|
||||||
|
*/
|
||||||
|
public double[] getFrequencyResponse() throws FilterDesignException
|
||||||
|
{
|
||||||
|
if(!mConverged)
|
||||||
|
{
|
||||||
|
throw new FilterDesignException("Can't create filter from specification - failed "
|
||||||
|
+ "to converge ");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resample the final polynomial at the desired filter length
|
||||||
|
double[] resampled = resample();
|
||||||
|
|
||||||
|
//Apply Parks/McClelland frequency response corrections according to the filter type
|
||||||
|
return correctFrequencyResponse(resampled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impulse response of the designed filter with floating point precision.
|
||||||
|
*
|
||||||
|
* @return filter impulse response
|
||||||
|
* @throws FilterDesignException if the specified filter cannot be designed
|
||||||
|
*/
|
||||||
|
public float[] getImpulseResponse() throws FilterDesignException
|
||||||
|
{
|
||||||
|
return convertToFloatArray(getImpulseResponseDoubles());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impulse response of the designed filter with double precision
|
||||||
|
*/
|
||||||
|
public double[] getImpulseResponseDoubles() throws FilterDesignException
|
||||||
|
{
|
||||||
|
double[] frequencyResponse = getFrequencyResponse();
|
||||||
|
|
||||||
|
int length = mSpecification.getFilterLength();
|
||||||
|
|
||||||
|
double[] impulseResponse = new double[length];
|
||||||
|
|
||||||
|
double M;
|
||||||
|
|
||||||
|
switch(mSpecification.getFilterType())
|
||||||
|
{
|
||||||
|
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
|
||||||
|
M = ((double)length - 1.0) / 2.0;
|
||||||
|
|
||||||
|
for(int n = 0; n < length; n++)
|
||||||
|
{
|
||||||
|
double accumulator = frequencyResponse[0];
|
||||||
|
|
||||||
|
double frequency = TWO_PI * (n - M) / length;
|
||||||
|
|
||||||
|
for(int k = 1; k <= M; k++)
|
||||||
|
{
|
||||||
|
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
|
||||||
|
}
|
||||||
|
|
||||||
|
impulseResponse[n] = accumulator / (double)length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
|
||||||
|
double offset = (double)(length - 1) / 2.0;
|
||||||
|
|
||||||
|
for(int n = 0; n < length; n++)
|
||||||
|
{
|
||||||
|
double accumulator = frequencyResponse[0];
|
||||||
|
|
||||||
|
double frequency = TWO_PI * ((double)n - offset) / (double)length;
|
||||||
|
|
||||||
|
for(int k = 1; k < frequencyResponse.length; k++)
|
||||||
|
{
|
||||||
|
accumulator += 2.0 * frequencyResponse[k] * Math.cos(frequency * (double)k);
|
||||||
|
}
|
||||||
|
|
||||||
|
impulseResponse[n] = accumulator / (double)length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return impulseResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the frequency response of this filter at the specified cosine of the frequency
|
||||||
|
*
|
||||||
|
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116a to
|
||||||
|
* determine the actual response of the specified frequency cosine using Lagrange
|
||||||
|
* interpolation of the polynomial formed at the extremal index set.
|
||||||
|
*
|
||||||
|
* @param cosineOfFrequency to calculate the frequency response
|
||||||
|
* @return actual frequency response for the specified frequency with the current set of
|
||||||
|
* extreaml indices
|
||||||
|
*/
|
||||||
|
public double getFrequencyResponse(double cosineOfFrequency)
|
||||||
|
{
|
||||||
|
if(mLagrangeInterpolator == null)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Lagrange interpolator is not setup - can't calculate frequency response");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLagrangeInterpolator.value(cosineOfFrequency);
|
||||||
|
|
||||||
|
// double numerator = 0.0;
|
||||||
|
// double denominator = 0.0;
|
||||||
|
//
|
||||||
|
// for( int k = 0; k < mExtremalIndices.size() - 1; k++ )
|
||||||
|
// {
|
||||||
|
// double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ];
|
||||||
|
//
|
||||||
|
// //If this frequency is close to one of the polynomial points, use the polynomial point for the response
|
||||||
|
// if( Math.abs( cosineDelta ) < 1.0e-7 )
|
||||||
|
// {
|
||||||
|
// return mIdealFrequencyResponse[ k ];
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// double dkOverCosineDelta = mD[ k ] / cosineDelta;
|
||||||
|
//
|
||||||
|
// numerator += dkOverCosineDelta * mIdealFrequencyResponse[ k ];
|
||||||
|
// denominator += dkOverCosineDelta;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return numerator / denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculated Delta value
|
||||||
|
*
|
||||||
|
* @return delta value
|
||||||
|
*/
|
||||||
|
public double getDelta()
|
||||||
|
{
|
||||||
|
return mDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates initial array of extremal frequency indices spaced at intervals of the grid frequency
|
||||||
|
* set for all frequencies where a frequency band weighting is specified.
|
||||||
|
*/
|
||||||
|
private List<Integer> getInitialExtremalIndices()
|
||||||
|
{
|
||||||
|
int count = mSpecification.getExtremaCount();
|
||||||
|
|
||||||
|
int density = mSpecification.getGridDensity();
|
||||||
|
|
||||||
|
List<Integer> extremalIndices = new ArrayList<>();
|
||||||
|
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
extremalIndices.add(i * density);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extremalIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the actual frequency response of a dense frequency grid using the polynomial
|
||||||
|
* represented by the extremal indices.
|
||||||
|
*/
|
||||||
|
private void calculateGridFrequencyResponse()
|
||||||
|
{
|
||||||
|
BigDecimal[] b = calculateB();
|
||||||
|
|
||||||
|
calculateDelta(b);
|
||||||
|
|
||||||
|
calculateC();
|
||||||
|
|
||||||
|
//Setup the lagrange polynomial so that we can determine the frequency response at the grid frequencies
|
||||||
|
//where mExtremalIndices are the polynomial points, the subset of cosine frequency grid identified by the
|
||||||
|
//extremal indices is the x value set and mIdealFrequencyResponse is the y axis value set
|
||||||
|
double[] xAxis = new double[mIdealFrequencyResponse.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < mExtremalIndices.size() - 1; x++)
|
||||||
|
{
|
||||||
|
int index = mExtremalIndices.get(x);
|
||||||
|
|
||||||
|
xAxis[x] = mGrid.getCosineFrequencyGrid()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
mLagrangeInterpolator = new PolynomialFunctionLagrangeForm(xAxis, mIdealFrequencyResponse);
|
||||||
|
|
||||||
|
updateGridFrequencyResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the frequency response of frequencies specified in the dense frequency grid
|
||||||
|
*/
|
||||||
|
private void updateGridFrequencyResponse()
|
||||||
|
{
|
||||||
|
mGridFrequencyResponse = new double[mGrid.getSize()];
|
||||||
|
|
||||||
|
double[] gridFrequencyCosines = mGrid.getCosineFrequencyGrid();
|
||||||
|
|
||||||
|
for(int i = 0; i < mGridFrequencyResponse.length; i++)
|
||||||
|
{
|
||||||
|
mGridFrequencyResponse[i] = getFrequencyResponse(gridFrequencyCosines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the value of b across the set of extremal indices.
|
||||||
|
*
|
||||||
|
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 115
|
||||||
|
*
|
||||||
|
* @return the array of b values of length L+2
|
||||||
|
*/
|
||||||
|
private BigDecimal[] calculateB()
|
||||||
|
{
|
||||||
|
int length = mExtremalIndices.size();
|
||||||
|
|
||||||
|
BigDecimal[] b = new BigDecimal[length];
|
||||||
|
|
||||||
|
for(int k = 0; k < length; k++)
|
||||||
|
{
|
||||||
|
b[k] = BigDecimal.ONE;
|
||||||
|
|
||||||
|
double xk = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)];
|
||||||
|
|
||||||
|
for(int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
if(i != k)
|
||||||
|
{
|
||||||
|
double xi = mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(i)];
|
||||||
|
|
||||||
|
double denominator = xk - xi;
|
||||||
|
|
||||||
|
if(Math.abs(denominator) < 0.00001)
|
||||||
|
{
|
||||||
|
denominator = 0.00001;
|
||||||
|
}
|
||||||
|
|
||||||
|
b[k] = b[k].multiply(BigDecimal.valueOf(1.0 / denominator));
|
||||||
|
// b[ k ] *= 1.0 / denominator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the value of delta that represents the maximum ripple for the current set of
|
||||||
|
* extremal indices (L+2)
|
||||||
|
*
|
||||||
|
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 114
|
||||||
|
*
|
||||||
|
* @param b calculated for the grid from equation 115
|
||||||
|
*/
|
||||||
|
private void calculateDelta(BigDecimal[] b)
|
||||||
|
{
|
||||||
|
BigDecimal numerator = BigDecimal.ZERO;
|
||||||
|
BigDecimal denominator = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
double sign = 1.0;
|
||||||
|
|
||||||
|
for(int k = 0; k < b.length; k++)
|
||||||
|
{
|
||||||
|
if(k < mExtremalIndices.size())
|
||||||
|
{
|
||||||
|
int extremalIndex = mExtremalIndices.get(k);
|
||||||
|
|
||||||
|
numerator = numerator.add(b[k].multiply(BigDecimal.valueOf(mGrid.getDesiredResponse()[extremalIndex])));
|
||||||
|
denominator = denominator.add(b[k].multiply(BigDecimal.valueOf(sign / mGrid.getWeight()[extremalIndex])));
|
||||||
|
|
||||||
|
sign = -sign;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Something went wrong -- the length of b exceeds the set of extremal indices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set the scale to the maximum scale we could see for a primitive decimal
|
||||||
|
mDelta = numerator.divide(denominator, 1023, BigDecimal.ROUND_HALF_DOWN).doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the ideal frequency response (C) of the current extremal index set. This reference
|
||||||
|
* is used to determine the level of error in the set of actual frequency responses calculated
|
||||||
|
* from this set of extremal indices to determine convergence.
|
||||||
|
*
|
||||||
|
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116b
|
||||||
|
*/
|
||||||
|
private void calculateC()
|
||||||
|
{
|
||||||
|
int length = mSpecification.getExtremaCount() - 1;
|
||||||
|
|
||||||
|
mIdealFrequencyResponse = new double[length];
|
||||||
|
|
||||||
|
double sign = 1.0;
|
||||||
|
|
||||||
|
for(int k = 0; k < length; k++)
|
||||||
|
{
|
||||||
|
if(k < mExtremalIndices.size())
|
||||||
|
{
|
||||||
|
int index = mExtremalIndices.get(k);
|
||||||
|
|
||||||
|
mIdealFrequencyResponse[k] = mGrid.getDesiredResponse()[index] -
|
||||||
|
(sign * mDelta / mGrid.getWeight()[index]);
|
||||||
|
|
||||||
|
sign = -sign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the weighted error between the specification desired frequency response and the
|
||||||
|
* actual frequency response of the current extremal index set across the frequencies in the
|
||||||
|
* dense frequency grid.
|
||||||
|
*
|
||||||
|
* Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 112
|
||||||
|
*/
|
||||||
|
public void calculateGridError()
|
||||||
|
{
|
||||||
|
int length = mGridFrequencyResponse.length;
|
||||||
|
|
||||||
|
mGridErrors = new double[length];
|
||||||
|
|
||||||
|
for(int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
mGridErrors[i] = mGrid.getWeight()[i] * (mGrid.getDesiredResponse()[i] - mGridFrequencyResponse[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the indexes of the extremal error values using the Alternation theorem
|
||||||
|
*
|
||||||
|
* @throws FilterDesignException if the full set of extremal indices can't be identified
|
||||||
|
*/
|
||||||
|
private void findExtremalIndices() throws FilterDesignException
|
||||||
|
{
|
||||||
|
mExtremalIndices.clear();
|
||||||
|
|
||||||
|
//Check for extremum at error index 0
|
||||||
|
if(((mGridErrors[0] > 0.0 && mGridErrors[0] > mGridErrors[1]) ||
|
||||||
|
(mGridErrors[0] < 0.0 && mGridErrors[0] < mGridErrors[1])) &&
|
||||||
|
isGTEDelta(mGridErrors[0]))
|
||||||
|
{
|
||||||
|
mExtremalIndices.add(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for extrema in middle error indices )
|
||||||
|
for(int x = 1; x < mGridErrors.length - 1; x++)
|
||||||
|
{
|
||||||
|
if(((mGridErrors[x] > 0.0 && (mGridErrors[x - 1] <= mGridErrors[x] && mGridErrors[x] > mGridErrors[x + 1])) ||
|
||||||
|
(mGridErrors[x] < 0.0 && (mGridErrors[x - 1] >= mGridErrors[x] && mGridErrors[x] < mGridErrors[x + 1]))) &&
|
||||||
|
isGTEDelta(mGridErrors[x]))
|
||||||
|
{
|
||||||
|
mExtremalIndices.add(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for extremum at final error index
|
||||||
|
int last = mGridErrors.length - 1;
|
||||||
|
|
||||||
|
if(((mGridErrors[last] > 0.0 && (mGridErrors[last] > mGridErrors[last - 1])) ||
|
||||||
|
(mGridErrors[last] < 0.0 && (mGridErrors[last] < mGridErrors[last - 1]))) &&
|
||||||
|
isGTEDelta(mGridErrors[last]))
|
||||||
|
{
|
||||||
|
mExtremalIndices.add(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure we have the minimum of extremals before we continue
|
||||||
|
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
|
||||||
|
{
|
||||||
|
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
|
||||||
|
+ "error set before checking for alternation theory - found:" + mExtremalIndices.size() +
|
||||||
|
" required:" + mSpecification.getExtremaCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Enforce alternation theory -- only one extremal for each transition about the zero axis
|
||||||
|
List<Integer> indicesToRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
Iterator<Integer> it = mExtremalIndices.iterator();
|
||||||
|
Integer current = it.next();
|
||||||
|
Integer next;
|
||||||
|
|
||||||
|
boolean positiveAxis = mGridErrors[current] > 0.0;
|
||||||
|
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
next = it.next();
|
||||||
|
|
||||||
|
//Check for consecutive (redundant) indices on same side of zero axis - retain largest
|
||||||
|
if(!(positiveAxis ^ (mGridErrors[next] > 0.0)))
|
||||||
|
{
|
||||||
|
if(Math.abs(mGridErrors[next]) <= Math.abs(mGridErrors[current]))
|
||||||
|
{
|
||||||
|
//Remove next error index that is less than current index and on
|
||||||
|
it.remove();
|
||||||
|
next = current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Since we can't remove the current index with the iterator, queue for later removal
|
||||||
|
indicesToRemove.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
positiveAxis = !positiveAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove redundant extremal indices that couldn't be removed via the iterator
|
||||||
|
mExtremalIndices.removeAll(indicesToRemove);
|
||||||
|
|
||||||
|
//Truncate the list to one larger than needed by removing excess tailing indices
|
||||||
|
while(mExtremalIndices.size() > mSpecification.getExtremaCount())
|
||||||
|
{
|
||||||
|
mExtremalIndices.remove(mExtremalIndices.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we have one too many indices, delete the smaller of the first or last
|
||||||
|
if(mExtremalIndices.size() > mSpecification.getExtremaCount())
|
||||||
|
{
|
||||||
|
int lastIndex = mExtremalIndices.size() - 1;
|
||||||
|
|
||||||
|
if(Math.abs(mGridErrors[mExtremalIndices.get(0)]) >
|
||||||
|
Math.abs(mGridErrors[mExtremalIndices.get(lastIndex)]))
|
||||||
|
{
|
||||||
|
mExtremalIndices.remove(lastIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mExtremalIndices.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Detect if we have too few indices
|
||||||
|
if(mExtremalIndices.size() < mSpecification.getExtremaCount())
|
||||||
|
{
|
||||||
|
throw new FilterDesignException("Couldn't find the minimum extremal frequencies in "
|
||||||
|
+ "error set - found:" + mExtremalIndices.size() + " required:" + mSpecification.getExtremaCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the absolute value of the argument is greater than or equal to the delta
|
||||||
|
* value with accuracy to 14 digits of precision. This avoids rounding errors at 15 digits or
|
||||||
|
* precision or greater.
|
||||||
|
*
|
||||||
|
* @param value to evaluate
|
||||||
|
* @return true if the absolute value is greater than or equal to the absolute delta value
|
||||||
|
*/
|
||||||
|
private boolean isGTEDelta(double value)
|
||||||
|
{
|
||||||
|
return Math.abs(value) - Math.abs(mDelta) > -1.0e-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for convergence of the frequency response of the current set of extremal indices to
|
||||||
|
* the filter specification by comparing the maximum absolute error value against the delta
|
||||||
|
* value to determine if these two values are within the convergence threshold.
|
||||||
|
*/
|
||||||
|
public void checkConvergence()
|
||||||
|
{
|
||||||
|
double maximum = Math.abs(mGridErrors[mExtremalIndices.get(0)]);
|
||||||
|
|
||||||
|
for(int i = 1; i < mExtremalIndices.size(); i++)
|
||||||
|
{
|
||||||
|
double current = Math.abs(mGridErrors[mExtremalIndices.get(i)]);
|
||||||
|
|
||||||
|
if(current > maximum)
|
||||||
|
{
|
||||||
|
maximum = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double convergence = maximum - Math.abs(mDelta);
|
||||||
|
|
||||||
|
mLog.debug("Convergence: " + convergence);
|
||||||
|
|
||||||
|
mConverged = convergence < CONVERGENCE_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coverts/casts the double array to a float array
|
||||||
|
*/
|
||||||
|
private static float[] convertToFloatArray(double[] samples)
|
||||||
|
{
|
||||||
|
float[] converted = new float[samples.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x++)
|
||||||
|
{
|
||||||
|
converted[x] = (float)samples[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resamples the designed filter at half of the filter length at evenly spaced intervals of the
|
||||||
|
* frequency spectrum from 0 to PI radians.
|
||||||
|
*
|
||||||
|
* @return frequency response of the filter sampled the desired filter length and order
|
||||||
|
*/
|
||||||
|
private double[] resample()
|
||||||
|
{
|
||||||
|
int length = mSpecification.getFilterLength();
|
||||||
|
|
||||||
|
if(length % 2 == 0)
|
||||||
|
{
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
double half = (double)length / 2.0;
|
||||||
|
|
||||||
|
double[] resampled = new double[(int)Math.ceil(half)];
|
||||||
|
|
||||||
|
for(int x = 0; x < resampled.length; x++)
|
||||||
|
{
|
||||||
|
resampled[x] = getFrequencyResponse(Math.cos(Math.PI * (double)x / half));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resampled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies Parks/McClelland filter type correction to the final frequency response array
|
||||||
|
*
|
||||||
|
* @param frequencyResponse of the resampled filter design from 0 <> ~ PI radians
|
||||||
|
* @return corrected frequency response
|
||||||
|
*/
|
||||||
|
private double[] correctFrequencyResponse(double[] frequencyResponse)
|
||||||
|
{
|
||||||
|
double filterLength = mSpecification.getFilterLength();
|
||||||
|
|
||||||
|
switch(mSpecification.getFilterType())
|
||||||
|
{
|
||||||
|
case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL:
|
||||||
|
//No correction needed
|
||||||
|
break;
|
||||||
|
case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL:
|
||||||
|
//No correction needed
|
||||||
|
break;
|
||||||
|
case TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL:
|
||||||
|
for(int x = 0; x < frequencyResponse.length; x++)
|
||||||
|
{
|
||||||
|
double frequencyRadians = Math.PI * (((double)x) / filterLength);
|
||||||
|
frequencyResponse[x] *= Math.sin(TWO_PI * frequencyRadians);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL:
|
||||||
|
for(int x = 0; x < frequencyResponse.length; x++)
|
||||||
|
{
|
||||||
|
double frequencyRadians = Math.PI * (((double)x) / filterLength);
|
||||||
|
frequencyResponse[x] *= Math.sin(Math.PI * frequencyRadians);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frequencyResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user