From e2fe06638ba3237903ec11ed2c03a455f0cde960 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 5 Mar 2017 05:55:04 -0500 Subject: [PATCH] Incrementing build to version 0.3.0 beta8 --- .idea/libraries/commons_math3_3_6_1.xml | 9 + sdrtrunk.iml | 1 + src/dsp/filter/design/FilterViewer.java | 48 +- .../filter/fir/FIRFilterSpecification.java | 1990 ++++++++--------- .../PolyphaseChannelizerFilterFactory.java | 89 +- .../fir/remez/RemezFIRFilterDesigner.java | 9 +- .../fir/remez/RemezFIRFilterDesigner2.java | 19 +- .../RemezFIRFilterDesignerWithLagrange.java | 686 ++++++ 8 files changed, 1664 insertions(+), 1187 deletions(-) create mode 100644 .idea/libraries/commons_math3_3_6_1.xml create mode 100644 src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java diff --git a/.idea/libraries/commons_math3_3_6_1.xml b/.idea/libraries/commons_math3_3_6_1.xml new file mode 100644 index 00000000..b692344c --- /dev/null +++ b/.idea/libraries/commons_math3_3_6_1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sdrtrunk.iml b/sdrtrunk.iml index 04cc0636..0621246d 100644 --- a/sdrtrunk.iml +++ b/sdrtrunk.iml @@ -21,5 +21,6 @@ + \ No newline at end of file diff --git a/src/dsp/filter/design/FilterViewer.java b/src/dsp/filter/design/FilterViewer.java index 348645b3..9bdf1ac0 100644 --- a/src/dsp/filter/design/FilterViewer.java +++ b/src/dsp/filter/design/FilterViewer.java @@ -2,7 +2,9 @@ package dsp.filter.design; import dsp.filter.FilterFactory; import dsp.filter.fir.FIRFilterSpecification; +import dsp.filter.fir.remez.PolyphaseChannelizerFilterFactory; import dsp.filter.fir.remez.RemezFIRFilterDesigner; +import dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -51,15 +53,15 @@ public class FilterViewer extends Application // .stopBandStart(5000) // .build(); - FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() - .sampleRate( 24000 ) - .stopBandCutoff( 800 ) - .stopBandAmplitude( 0.0 ) - .stopBandRipple( 0.03 ) - .passBandStart( 1000 ) - .passBandAmplitude( 1.0 ) - .passBandRipple( 0.08 ) - .build(); +// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() +// .sampleRate( 24000 ) +// .stopBandCutoff( 800 ) +// .stopBandAmplitude( 0.0 ) +// .stopBandRipple( 0.03 ) +// .passBandStart( 1000 ) +// .passBandAmplitude( 1.0 ) +// .passBandRipple( 0.08 ) +// .build(); // FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() // .sampleRate( 48000 ) @@ -76,20 +78,22 @@ public class FilterViewer extends Application // taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING ); - try - { - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - if(designer.isValid()) - { - taps = designer.getImpulseResponse(); - } - } - catch(FilterDesignException fde) - { - mLog.error("Filter design error", fde); - } +// try +// { +// RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification); +//// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); +// +// if(designer.isValid()) +// { +// taps = designer.getImpulseResponse(); +// } +// } +// catch(FilterDesignException fde) +// { +// mLog.error("Filter design error", fde); +// } + taps = PolyphaseChannelizerFilterFactory.getFilter(10000000, 312500, 0.21); return taps; } } diff --git a/src/dsp/filter/fir/FIRFilterSpecification.java b/src/dsp/filter/fir/FIRFilterSpecification.java index ab599f89..da4aa40a 100644 --- a/src/dsp/filter/fir/FIRFilterSpecification.java +++ b/src/dsp/filter/fir/FIRFilterSpecification.java @@ -18,879 +18,677 @@ ******************************************************************************/ package dsp.filter.fir; +import dsp.filter.fir.remez.FIRLinearPhaseFilterType; + import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import dsp.filter.fir.remez.FIRLinearPhaseFilterType; - public class FIRFilterSpecification { - private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat( "0.00000" ); + private static final DecimalFormat DECIMAL_FORMATTER = new DecimalFormat("0.00000"); - private FIRLinearPhaseFilterType mRemezFilterType; - private int mOrder; - private int mGridDensity = 16; - private List mFrequencyBands = new ArrayList<>(); + private FIRLinearPhaseFilterType mRemezFilterType; + private int mOrder; + private int mGridDensity = 16; + private List mFrequencyBands = new ArrayList<>(); - /** - * Constructs a Remez filter specification. Use one of the filter builders to construct a Remez - * filter specification. - * - * @param type of filter - * @param order of the filter - * @param gridDensity to use when calculating the coefficients - */ - protected FIRFilterSpecification( FIRLinearPhaseFilterType type, int order, int gridDensity ) - { - mRemezFilterType = type; - mOrder = order; - mGridDensity = gridDensity; - } + /** + * Constructs a Remez filter specification. Use one of the filter builders to construct a Remez + * filter specification. + * + * @param type of filter + * @param order of the filter + * @param gridDensity to use when calculating the coefficients + */ + protected FIRFilterSpecification(FIRLinearPhaseFilterType type, int order, int gridDensity) + { + mRemezFilterType = type; + mOrder = order; + mGridDensity = gridDensity; + } - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( "Type: " ); - sb.append( mRemezFilterType.name() ); - sb.append( " Order: " ); - sb.append( mOrder ); - sb.append( " Extrema: " ); - sb.append( getExtremaCount() ); - sb.append( " Grid Density: " ); - sb.append( mGridDensity ); - sb.append( " Grid Size: " ); - sb.append( getGridSize() ); + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("Type: "); + sb.append(mRemezFilterType.name()); + sb.append(" Order: "); + sb.append(mOrder); + sb.append(" Extrema: "); + sb.append(getExtremaCount()); + sb.append(" Grid Density: "); + sb.append(mGridDensity); + sb.append(" Grid Size: "); + sb.append(getGridSize()); - for ( FrequencyBand band : mFrequencyBands ) - { - sb.append( "\n" ); - sb.append( band.toString() ); - } + for(FrequencyBand band : mFrequencyBands) + { + sb.append("\n"); + sb.append(band.toString()); + } - return sb.toString(); - } + return sb.toString(); + } - public void addFrequencyBand( FrequencyBand band ) - { - mFrequencyBands.add( band ); + public void addFrequencyBand(FrequencyBand band) + { + mFrequencyBands.add(band); - updateGridSize(); - } + updateGridSize(); + } - public void addFrequencyBands( Collection bands ) - { - mFrequencyBands.addAll( bands ); + public void addFrequencyBands(Collection bands) + { + mFrequencyBands.addAll(bands); - updateGridSize(); - } + updateGridSize(); + } /** * Removes all frequency bands from this filter specification */ - public void clearFrequencyBands() + public void clearFrequencyBands() { mFrequencyBands.clear(); } - public List getFrequencyBands() - { - return mFrequencyBands; - } - - /** - * Returns the total bandwidth of the defined frequency bands in the specification. - * - * @return total bandwidth -- should be less than or equal to 0.5 - */ - public double getTotalBandwidth() - { - double bandwidth = 0.0; - - for ( FrequencyBand band : mFrequencyBands ) - { - bandwidth += band.getBandWidth(); - } - - return bandwidth; - } - - /** - * Creates a Low-Pass FIR filter specification builder that allows you to define the filter - * parameters and create a filter specification. - */ - public static LowPassBuilder lowPassBuilder() - { - return new LowPassBuilder(); - } - - /** - * Creates a High-Pass FIR filter specification builder that allows you to define the filter - * parameters and create a filter specification. - */ - public static HighPassBuilder highPassBuilder() - { - return new HighPassBuilder(); - } - - /** - * Creates a Band-Pass FIR filter specification builder that allows you to define the filter - * parameters and create a filter specification. - */ - public static BandPassBuilder bandPassBuilder() - { - return new BandPassBuilder(); - } - - public FIRLinearPhaseFilterType getFilterType() - { - return mRemezFilterType; - } - - public boolean isSymmetrical() - { - return mRemezFilterType == FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL || - mRemezFilterType == FIRLinearPhaseFilterType.TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL; - } - - public boolean isAntiSymmetrical() - { - return mRemezFilterType == FIRLinearPhaseFilterType.TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL || - mRemezFilterType == FIRLinearPhaseFilterType.TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL; - } - - public int getOrder() - { - return mOrder; - } - - public boolean isEvenOrder() - { - return mOrder % 2 == 0; - } - - public boolean isOddOrder() - { - return mOrder % 2 == 1; - } - - public int getFilterLength() - { - return mOrder + 1; - } - - public int getBandCount() - { - return mFrequencyBands.size(); - } - - /** - * Grid density setting. Default value is 16. - */ - public int getGridDensity() - { - return mGridDensity; - } - - /** - * Sets the grid density setting. Default value is 16. - */ - public void setGridDensity( int density ) - { - mGridDensity = density; - - updateGridSize(); - } - - /** - * Maximum number of alternations/extrema (L + 2 or M + 2) - */ - public int getExtremaCount() - { - return getHalfFilterOrder() + 2; - } - - /** - * Half of the filter order. - * - * This value is referred to as L or M, depending on the source. - */ - public int getHalfFilterOrder() - { - switch ( mRemezFilterType ) - { - case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL: - return ( mOrder ) / 2; - case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL: - return ( mOrder - 1 ) / 2; - case TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL: - return ( mOrder - 2 ) / 2; - case TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL: - return ( mOrder - 1 ) / 2; - default: - return 0; - } - } - - /** - * Determines the max band ripple from across the frequency bands in this specification. - */ - public double getMaxBandAmplitude() - { - double maxRippleAmplitude = 0.0; - - for ( FrequencyBand band : mFrequencyBands ) - { - double bandRippleAmplitude = band.getRippleAmplitude(); - - if ( bandRippleAmplitude > maxRippleAmplitude ) - { - maxRippleAmplitude = bandRippleAmplitude; - } - } - - return maxRippleAmplitude; - } - - public int getNominalGridSize() - { - return ( getExtremaCount() - 1 ) * getGridDensity() + 1; - } - - public int getGridSize() - { - int gridSize = 0; - - for ( FrequencyBand band : mFrequencyBands ) - { - gridSize += band.getGridSize(); - } - - return gridSize; - } - - private void updateGridSize() - { - if ( !mFrequencyBands.isEmpty() ) - { - int gridSize = getNominalGridSize(); - - double totalBandwidth = getTotalBandwidth(); - - for ( FrequencyBand band : mFrequencyBands ) - { - band.setGridSize( gridSize, totalBandwidth ); - } - } - } - - // /** - // * Determines the grid size needed to represent the percentage of weighted - // frequency bands - // * over the entire frequency band. - // */ - // public int getGridSize() - // { - // int gridSize = 0; - // - // int maxGridSize = getMaxGridSize(); - // - // //Add the proportional index counts from each weighted band - // for( FrequencyBand band: mFrequencyBands ) - // { - // gridSize += band.getBandGridSize( maxGridSize ); - // } - // - // if( getSymmetry() == Symmetry.NEGATIVE ) - // { - // gridSize--; - // } - // - // return gridSize; - // } - - /** - * Dense frequency spacing for extrema count * grid density over a frequency range of 0.0 - 1.0 - */ - public double getGridFrequencyInterval() - { - return getTotalBandwidth() / (double) ( ( getGridSize() - mFrequencyBands.size() ) ); - } - - // /** - // * ''' FIR order estimator (lowpass, highpass, bandpass, mulitiband). - // * - // * (n, fo, ao, w) = remezord (f, a, dev) (n, fo, ao, w) = remezord (f, a, - // * dev, fs) - // * - // * (n, fo, ao, w) = remezord (f, a, dev) finds the approximate order, - // * normalized frequency band edges, frequency band amplitudes, and weights - // * that meet input specifications f, a, and dev, to use with the remez - // * command. - // * - // * f is a sequence of frequency band edges (between 0 and Fs/2, where Fs - // is - // * the sampling frequency), and a is a sequence specifying the desired - // * amplitude on the bands defined by f. The length of f is twice the - // length - // * of a, minus 2. The desired function is piecewise constant. - // * - // * dev is a sequence the same size as a that specifies the maximum - // allowable - // * deviation or ripples between the frequency response and the desired - // * amplitude of the output filter, for each band. - // * - // * Use remez with the resulting order n, frequency sequence fo, amplitude - // * response sequence ao, and weights w to design the filter b which - // * approximately meets the specifications given by remezord input - // parameters - // * f, a, and dev: - // * - // * b = remez (n, fo, ao, w) - // * - // * (n, fo, ao, w) = remezord (f, a, dev, Fs) specifies a sampling - // frequency - // * Fs. - // * - // * Fs defaults to 2 Hz, implying a Nyquist frequency of 1 Hz. You can - // * therefore specify band edges scaled to a particular applications - // sampling - // * frequency. - // * - // * In some cases remezord underestimates the order n. If the filter does - // not - // * meet the specifications, try a higher order such as n+1 or n+2. ''' - // * - // * @param fcuts - // * @param mags - // * @param devs - // * @param fsamp - // */ - // // TODO: rename to calculateFilterOrder() - // public static FIRFilterSpecification remezOrd( int[] fcutoffs, - // double[] mags, double[] devs, int fsamp ) - // { - // // def remezord (fcuts, mags, devs, fsamp = 2): - // - // double[] fcuts = new double[ fcutoffs.length ]; - // - // for ( int x = 0; x < fcuts.length; x++ ) - // { - // fcuts[ x ] = (double) fcutoffs[ x ] / (double) fsamp; - // } - // - // int nf = fcuts.length; - // int nm = mags.length; - // int nd = devs.length; - // int nbands = nm; - // - // if ( mags.length != devs.length ) - // { - // throw new IllegalArgumentException( - // "Length of magnitudes and deviations must be equal" ); - // } - // - // if ( fcuts.length != ( 2 * ( mags.length - 1 ) ) ) - // { - // throw new IllegalArgumentException( - // "Length of frequency band edges must be 2 * mags length - 2" ); - // } - // - // for ( int x = 0; x < mags.length; x++ ) - // { - // if ( mags[ x ] != 0 ) - // { - // devs[ x ] = devs[ x ] / mags[ x ]; - // } - // } - // - // // Separate the passband and stopband edges - // double[] f1 = new double[ fcuts.length / 2 ]; - // double[] f2 = new double[ fcuts.length / 2 ]; - // - // for ( int x = 0; x < fcuts.length / 2; x++ ) - // { - // f1[ x ] = fcuts[ 2 * x ]; - // f2[ x ] = fcuts[ 2 * x + 1 ]; - // } - // - // int n = 0; - // - // double min_delta = 2.0; - // - // for ( int x = 0; x < f1.length; x++ ) - // { - // if ( f2[ x ] - f1[ x ] < min_delta ) - // { - // n = x; - // min_delta = f2[ x ] - f1[ x ]; - // } - // } - // - // double l; - // - // if ( nbands == 2 ) - // { - // // lowpass or highpass case (use formula) - // l = lporder( f1[ n ], f2[ n ], devs[ 0 ], devs[ 1 ] ); - // } else - // { - // // bandpass or multipass case - // // try different lowpasses and take the worst one that - // // goes through the BP specs - // l = 0; - // - // for ( int x = 1; x < nbands - 1; x++ ) - // { - // double l1 = lporder( f1[ x - 1 ], f2[ x - 1 ], devs[ x ], - // devs[ x - 1 ] ); - // double l2 = lporder( f1[ x ], f2[ x ], devs[ x ], - // devs[ x + 1 ] ); - // l = Math.max( l1, l2 ); - // } - // } - // - // n = (int) ( Math.ceil( l ) - 1 ); // need order, not length for remez - // - // // // cook up remez compatible result - // double[] ff = new double[ fcuts.length + 2 ]; - // ff[ 0 ] = 0.0; - // ff[ ff.length - 1 ] = 1.0; - // - // System.arraycopy( fcuts, 0, ff, 1, fcuts.length ); - // - // for ( int x = 1; x < ff.length - 1; x++ ) - // { - // ff[ x ] *= 2; - // } - // - // double[] aa = new double[ mags.length * 2 ]; - // - // for ( int x = 0; x < mags.length; x++ ) - // { - // aa[ 2 * x ] = mags[ x ]; - // aa[ 2 * x + 1 ] = mags[ x ]; - // } - // - // double max_dev = 0; - // - // for ( double dev : devs ) - // { - // if ( dev > max_dev ) - // { - // max_dev = dev; - // } - // } - // - // double[] wts = new double[ devs.length ]; - // - // for ( int x = 0; x < devs.length; x++ ) - // { - // wts[ x ] = max_dev / devs[ x ]; - // } - // - // return null; - // // return new RemezFilterSpecification( RemezFilterType.BANDPASS, n, ff, - // // aa, wts, 16); - // } - - /** - * Low-pass FIR filter specification builder - */ - public static class LowPassBuilder - { - private int mSampleRate; - private int mOrder; - private int mGridDensity = 16; - private int mPassBandEndFrequency; - private int mStopBandStartFrequency; - private double mPassBandRipple; - private double mStopBandRipple; - private double mPassBandAmplitude = 1.0; - private double mStopBandAmplitude = 0.0; - - public LowPassBuilder() - { - } - - public LowPassBuilder sampleRate( int sampleRateHz ) - { - mSampleRate = sampleRateHz; - return this; - } - - /** - * Sets the filter order. If the order is not specified, it will be calculated from the - * other parameters. - */ - public LowPassBuilder order( int order ) - { - mOrder = order; - return this; - } - - public LowPassBuilder gridDensity( int density ) - { - mGridDensity = density; - return this; - } - - public LowPassBuilder passBandCutoff( int frequencyHz ) - { - mPassBandEndFrequency = frequencyHz; - return this; - } - - public LowPassBuilder stopBandStart( int frequencyHz ) - { - mStopBandStartFrequency = frequencyHz; - return this; - } - - public LowPassBuilder passBandRipple( double rippleDb ) - { - mPassBandRipple = rippleDb; - return this; - } - - public LowPassBuilder stopBandRipple( double rippleDb ) - { - mStopBandRipple = rippleDb; - return this; - } - - public LowPassBuilder passBandAmplitude( double amplitude ) - { - mPassBandAmplitude = amplitude; - return this; - } - - public LowPassBuilder stopBandAmplitude( double amplitude ) - { - mStopBandAmplitude = amplitude; - return this; - } - - public FIRFilterSpecification build() - { - if ( mOrder < 6 ) - { - mOrder = estimateFilterOrder( mSampleRate, mPassBandEndFrequency, - mStopBandStartFrequency, mPassBandRipple, mStopBandRipple ); - } - - FIRLinearPhaseFilterType type = ( mOrder % 2 == 0 - ? FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL - : FIRLinearPhaseFilterType.TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL ); - - FIRFilterSpecification spec = new FIRFilterSpecification( type, mOrder, mGridDensity ); - - FrequencyBand passBand = new FrequencyBand( mSampleRate, 0, mPassBandEndFrequency, - mPassBandAmplitude, mPassBandRipple ); - spec.addFrequencyBand( passBand ); - - FrequencyBand stopBand = new FrequencyBand( mSampleRate, mStopBandStartFrequency, - mSampleRate / 2, mStopBandAmplitude, mStopBandRipple ); - spec.addFrequencyBand( stopBand ); - - return spec; - } - } - - /** - * Remez high-pass FIR filter specification builder - */ - public static class HighPassBuilder - { - private int mSampleRate; - private int mOrder = -1; - private int mGridDensity = 16; - private int mPassBandStartFrequency; - private int mStopBandEndFrequency; - private double mPassBandRipple; - private double mStopBandRipple; - private double mPassBandAmplitude = 1.0; - private double mStopBandAmplitude = 0.0; - - public HighPassBuilder() - { - } - - public HighPassBuilder sampleRate( int sampleRateHz ) - { - mSampleRate = sampleRateHz; - return this; - } - - /** - * Sets the filter order. If the order is not specified, it will be calculated from the - * other parameters. - */ - public HighPassBuilder order( int order ) - { - mOrder = order; - return this; - } - - public HighPassBuilder gridDensity( int density ) - { - mGridDensity = density; - return this; - } - - public HighPassBuilder passBandStart( int frequencyHz ) - { - mPassBandStartFrequency = frequencyHz; - return this; - } - - public HighPassBuilder stopBandCutoff( int frequencyHz ) - { - mStopBandEndFrequency = frequencyHz; - return this; - } - - public HighPassBuilder passBandRipple( double rippleDb ) - { - mPassBandRipple = rippleDb; - return this; - } - - public HighPassBuilder stopBandRipple( double rippleDb ) - { - mStopBandRipple = rippleDb; - return this; - } - - public HighPassBuilder passBandAmplitude( double amplitude ) - { - mPassBandAmplitude = amplitude; - return this; - } - - public HighPassBuilder stopBandAmplitude( double amplitude ) - { - mStopBandAmplitude = amplitude; - return this; - } - - public FIRFilterSpecification build() - { - if ( mOrder < 6 ) - { - mOrder = estimateFilterOrder( mSampleRate, mStopBandEndFrequency, - mPassBandStartFrequency, mPassBandRipple, mStopBandRipple ); - } - - // High pass is a Type 1 filter -- ensure order is even - if ( mOrder % 2 == 1 ) - { - mOrder++; - } - - FIRFilterSpecification spec = new FIRFilterSpecification( - FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL, mOrder, - mGridDensity ); - - FrequencyBand stopBand = new FrequencyBand( mSampleRate, 0, mStopBandEndFrequency, - mStopBandAmplitude, mStopBandRipple ); - spec.addFrequencyBand( stopBand ); - - FrequencyBand passBand = new FrequencyBand( mSampleRate, mPassBandStartFrequency, - mSampleRate / 2, mPassBandAmplitude, mPassBandRipple ); - spec.addFrequencyBand( passBand ); - - return spec; - } - } - - /** - * Band-pass or band-stop FIR filter specification builder - */ - public static class BandPassBuilder - { - private int mOrder; - private int mGridDensity = 16; - private int mSampleRate; - private int mStopFrequency1; - private int mPassFrequencyBegin; - private int mPassFrequencyEnd; - private int mStopFrequency2; - private double mStopRipple = 0.01; - private double mPassRipple = 0.01; - private double mStopAmplitude = 0.0; - private double mPassAmplitude = 1.0; - - public BandPassBuilder() - { - } - - /** - * Sets the filter order. If the order is not specified, it will be calculated from the - * other parameters. If you specify and odd-length order, it will be corrected (+1) to make - * it an even order number. - */ - public BandPassBuilder order( int order ) - { - mOrder = order; - return this; - } - - /** - * Sets the frequency grid density. Default is 16. - */ - public BandPassBuilder gridDensity( int density ) - { - mGridDensity = density; - return this; - } - - /** - * Sets the filter sample rate in hertz - */ - public BandPassBuilder sampleRate( int sampleRate ) - { - mSampleRate = sampleRate; - return this; - } - - /** - * Specifies the ending frequency of the first stop band - */ - public BandPassBuilder stopFrequency1( int stopFrequency ) - { - mStopFrequency1 = stopFrequency; - return this; - } - - /** - * Specifies the beginning frequency of the second stop band - */ - public BandPassBuilder stopFrequency2( int stopFrequency ) - { - mStopFrequency2 = stopFrequency; - return this; - } - - /** - * Specifies the pass band beginning frequency - */ - public BandPassBuilder passFrequencyBegin( int passFrequency ) - { - mPassFrequencyBegin = passFrequency; - return this; - } - - /** - * Specifies the pass band ending frequency - */ - public BandPassBuilder passFrequencyEnd( int passFrequency ) - { - mPassFrequencyEnd = passFrequency; - return this; - } - - /** - * Specifies the ripple in dB for both stop bands - */ - public BandPassBuilder stopRipple( double rippleDb ) - { - mStopRipple = rippleDb; - return this; - } - - /** - * Specifies the ripple in dB for the pass band - */ - public BandPassBuilder passRipple( double rippleDb ) - { - mPassRipple = rippleDb; - return this; - } - - /** - * Specifies the amplitude of the stop bands. Default is 0.0 - */ - public BandPassBuilder stopAmplitude( double amplitude ) - { - mStopAmplitude = amplitude; - return this; - } - - /** - * Specifies the amplitude of the pass band. Default is 1.0 - */ - public BandPassBuilder passAmplitude( double amplitude ) - { - mPassAmplitude = amplitude; - return this; - } - - /** - * Validates the frequency bands to test for overlap and completeness. - */ - private void validateFrequencyBands() - { - if ( mStopFrequency1 >= mPassFrequencyBegin ) - { - throw new IllegalArgumentException( "Stop band 1 ending frequency [" - + mStopFrequency1 + "] must be less than the pass band start frequency [" - + mPassFrequencyBegin + "]" ); - } - - if ( mPassFrequencyBegin >= mPassFrequencyEnd ) - { - throw new IllegalArgumentException( "Pass band begin frequency [" - + mPassFrequencyBegin + "] must be less than the pass band end frequency [" - + mPassFrequencyEnd + "]" ); - } - - if ( mPassFrequencyEnd >= mStopFrequency2 ) - { - throw new IllegalArgumentException( "Pass band end frequency [" + mPassFrequencyEnd - + "] must be less than stop band 2 beginning frequency [" + mStopFrequency2 - + "]" ); - } - - if ( mStopFrequency2 >= ( mSampleRate / 2 ) ) - { - throw new IllegalArgumentException( "Stop band 2 beginning frequency [" - + mStopFrequency2 + "] must be less than half of the sample rate [" - + ( mSampleRate / 2 ) + "]" ); - } - } - - public FIRFilterSpecification build() - { - validateFrequencyBands(); - - FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL; - - if ( mOrder < 10 ) - { - mOrder = estimateBandPassOrder( mSampleRate, mPassFrequencyBegin, mPassFrequencyEnd, - mPassRipple, mStopRipple ); - } - - // Ensure even order since we're designing a Type 1 filter - if ( mOrder % 2 == 1 ) - { - mOrder++; - } - - FIRFilterSpecification spec = new FIRFilterSpecification( type, mOrder, mGridDensity ); - - spec.addFrequencyBand( new FrequencyBand( mSampleRate, 0, mStopFrequency1, - mStopAmplitude, mStopRipple ) ); - spec.addFrequencyBand( new FrequencyBand( mSampleRate, mPassFrequencyBegin, - mPassFrequencyEnd, mPassAmplitude, mPassRipple ) ); - spec.addFrequencyBand( new FrequencyBand( mSampleRate, mStopFrequency2, - (int) ( mSampleRate / 2 ), mStopAmplitude, mStopRipple ) ); - - return spec; - } - } + public List getFrequencyBands() + { + return mFrequencyBands; + } + + /** + * Returns the total bandwidth of the defined frequency bands in the specification. + * + * @return total bandwidth -- should be less than or equal to 0.5 + */ + public double getTotalBandwidth() + { + double bandwidth = 0.0; + + for(FrequencyBand band : mFrequencyBands) + { + bandwidth += band.getBandWidth(); + } + + return bandwidth; + } + + /** + * Creates a Low-Pass FIR filter specification builder that allows you to define the filter + * parameters and create a filter specification. + */ + public static LowPassBuilder lowPassBuilder() + { + return new LowPassBuilder(); + } + + /** + * Creates a High-Pass FIR filter specification builder that allows you to define the filter + * parameters and create a filter specification. + */ + public static HighPassBuilder highPassBuilder() + { + return new HighPassBuilder(); + } + + /** + * Creates a Band-Pass FIR filter specification builder that allows you to define the filter + * parameters and create a filter specification. + */ + public static BandPassBuilder bandPassBuilder() + { + return new BandPassBuilder(); + } + + public FIRLinearPhaseFilterType getFilterType() + { + return mRemezFilterType; + } + + public boolean isSymmetrical() + { + return mRemezFilterType == FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL || + mRemezFilterType == FIRLinearPhaseFilterType.TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL; + } + + public boolean isAntiSymmetrical() + { + return mRemezFilterType == FIRLinearPhaseFilterType.TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL || + mRemezFilterType == FIRLinearPhaseFilterType.TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL; + } + + public int getOrder() + { + return mOrder; + } + + public boolean isEvenOrder() + { + return mOrder % 2 == 0; + } + + public boolean isOddOrder() + { + return mOrder % 2 == 1; + } + + public int getFilterLength() + { + return mOrder + 1; + } + + public int getBandCount() + { + return mFrequencyBands.size(); + } + + /** + * Grid density setting. Default value is 16. + */ + public int getGridDensity() + { + return mGridDensity; + } + + /** + * Sets the grid density setting. Default value is 16. + */ + public void setGridDensity(int density) + { + mGridDensity = density; + + updateGridSize(); + } + + /** + * Maximum number of alternations/extrema (L + 2 or M + 2) + */ + public int getExtremaCount() + { + return getHalfFilterOrder() + 2; + } + + /** + * Half of the filter order. + * + * This value is referred to as L or M, depending on the source. + */ + public int getHalfFilterOrder() + { + switch(mRemezFilterType) + { + case TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL: + return (mOrder) / 2; + case TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL: + return (mOrder - 1) / 2; + case TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL: + return (mOrder - 2) / 2; + case TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL: + return (mOrder - 1) / 2; + default: + return 0; + } + } + + /** + * Determines the max band ripple from across the frequency bands in this specification. + */ + public double getMaxBandAmplitude() + { + double maxRippleAmplitude = 0.0; + + for(FrequencyBand band : mFrequencyBands) + { + double bandRippleAmplitude = band.getRippleAmplitude(); + + if(bandRippleAmplitude > maxRippleAmplitude) + { + maxRippleAmplitude = bandRippleAmplitude; + } + } + + return maxRippleAmplitude; + } + + public int getNominalGridSize() + { + return (getExtremaCount() - 1) * getGridDensity() + 1; + } + + public int getGridSize() + { + int gridSize = 0; + + for(FrequencyBand band : mFrequencyBands) + { + gridSize += band.getGridSize(); + } + + return gridSize; + } + + private void updateGridSize() + { + if(!mFrequencyBands.isEmpty()) + { + int gridSize = getNominalGridSize(); + + double totalBandwidth = getTotalBandwidth(); + + for(FrequencyBand band : mFrequencyBands) + { + band.setGridSize(gridSize, totalBandwidth); + } + } + } + + /** + * Dense frequency spacing for extrema count * grid density over a frequency range of 0.0 - 1.0 + */ + public double getGridFrequencyInterval() + { + return getTotalBandwidth() / (double)((getGridSize() - mFrequencyBands.size())); + } + + /** + * Low-pass FIR filter specification builder + */ + public static class LowPassBuilder + { + private int mSampleRate; + private int mOrder; + private int mGridDensity = 16; + private int mPassBandEndFrequency; + private int mStopBandStartFrequency; + private double mPassBandRipple; + private double mStopBandRipple; + private double mPassBandAmplitude = 1.0; + private double mStopBandAmplitude = 0.0; + + public LowPassBuilder() + { + } + + public LowPassBuilder sampleRate(int sampleRateHz) + { + mSampleRate = sampleRateHz; + return this; + } + + /** + * Sets the filter order. If the order is not specified, it will be calculated from the + * other parameters. + */ + public LowPassBuilder order(int order) + { + mOrder = order; + return this; + } + + public LowPassBuilder gridDensity(int density) + { + mGridDensity = density; + return this; + } + + public LowPassBuilder passBandCutoff(int frequencyHz) + { + mPassBandEndFrequency = frequencyHz; + return this; + } + + public LowPassBuilder stopBandStart(int frequencyHz) + { + mStopBandStartFrequency = frequencyHz; + return this; + } + + public LowPassBuilder passBandRipple(double rippleDb) + { + mPassBandRipple = rippleDb; + return this; + } + + public LowPassBuilder stopBandRipple(double rippleDb) + { + mStopBandRipple = rippleDb; + return this; + } + + public LowPassBuilder passBandAmplitude(double amplitude) + { + mPassBandAmplitude = amplitude; + return this; + } + + public LowPassBuilder stopBandAmplitude(double amplitude) + { + mStopBandAmplitude = amplitude; + return this; + } + + public FIRFilterSpecification build() + { + if(mOrder < 6) + { + mOrder = estimateFilterOrder(mSampleRate, mPassBandEndFrequency, + mStopBandStartFrequency, mPassBandRipple, mStopBandRipple); + } + + FIRLinearPhaseFilterType type = (mOrder % 2 == 0 + ? FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL + : FIRLinearPhaseFilterType.TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL); + + FIRFilterSpecification spec = new FIRFilterSpecification(type, mOrder, mGridDensity); + + FrequencyBand passBand = new FrequencyBand(mSampleRate, 0, mPassBandEndFrequency, + mPassBandAmplitude, mPassBandRipple); + spec.addFrequencyBand(passBand); + + FrequencyBand stopBand = new FrequencyBand(mSampleRate, mStopBandStartFrequency, + mSampleRate / 2, mStopBandAmplitude, mStopBandRipple); + spec.addFrequencyBand(stopBand); + + return spec; + } + } + + /** + * Remez high-pass FIR filter specification builder + */ + public static class HighPassBuilder + { + private int mSampleRate; + private int mOrder = -1; + private int mGridDensity = 16; + private int mPassBandStartFrequency; + private int mStopBandEndFrequency; + private double mPassBandRipple; + private double mStopBandRipple; + private double mPassBandAmplitude = 1.0; + private double mStopBandAmplitude = 0.0; + + public HighPassBuilder() + { + } + + public HighPassBuilder sampleRate(int sampleRateHz) + { + mSampleRate = sampleRateHz; + return this; + } + + /** + * Sets the filter order. If the order is not specified, it will be calculated from the + * other parameters. + */ + public HighPassBuilder order(int order) + { + mOrder = order; + return this; + } + + public HighPassBuilder gridDensity(int density) + { + mGridDensity = density; + return this; + } + + public HighPassBuilder passBandStart(int frequencyHz) + { + mPassBandStartFrequency = frequencyHz; + return this; + } + + public HighPassBuilder stopBandCutoff(int frequencyHz) + { + mStopBandEndFrequency = frequencyHz; + return this; + } + + public HighPassBuilder passBandRipple(double rippleDb) + { + mPassBandRipple = rippleDb; + return this; + } + + public HighPassBuilder stopBandRipple(double rippleDb) + { + mStopBandRipple = rippleDb; + return this; + } + + public HighPassBuilder passBandAmplitude(double amplitude) + { + mPassBandAmplitude = amplitude; + return this; + } + + public HighPassBuilder stopBandAmplitude(double amplitude) + { + mStopBandAmplitude = amplitude; + return this; + } + + public FIRFilterSpecification build() + { + if(mOrder < 6) + { + mOrder = estimateFilterOrder(mSampleRate, mStopBandEndFrequency, + mPassBandStartFrequency, mPassBandRipple, mStopBandRipple); + } + + // High pass is a Type 1 filter -- ensure order is even + if(mOrder % 2 == 1) + { + mOrder++; + } + + FIRFilterSpecification spec = new FIRFilterSpecification( + FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL, mOrder, + mGridDensity); + + FrequencyBand stopBand = new FrequencyBand(mSampleRate, 0, mStopBandEndFrequency, + mStopBandAmplitude, mStopBandRipple); + spec.addFrequencyBand(stopBand); + + FrequencyBand passBand = new FrequencyBand(mSampleRate, mPassBandStartFrequency, + mSampleRate / 2, mPassBandAmplitude, mPassBandRipple); + spec.addFrequencyBand(passBand); + + return spec; + } + } + + /** + * Band-pass or band-stop FIR filter specification builder + */ + public static class BandPassBuilder + { + private int mOrder; + private int mGridDensity = 16; + private int mSampleRate; + private int mStopFrequency1; + private int mPassFrequencyBegin; + private int mPassFrequencyEnd; + private int mStopFrequency2; + private double mStopRipple = 0.01; + private double mPassRipple = 0.01; + private double mStopAmplitude = 0.0; + private double mPassAmplitude = 1.0; + + public BandPassBuilder() + { + } + + /** + * Sets the filter order. If the order is not specified, it will be calculated from the + * other parameters. If you specify and odd-length order, it will be corrected (+1) to make + * it an even order number. + */ + public BandPassBuilder order(int order) + { + mOrder = order; + return this; + } + + /** + * Sets the frequency grid density. Default is 16. + */ + public BandPassBuilder gridDensity(int density) + { + mGridDensity = density; + return this; + } + + /** + * Sets the filter sample rate in hertz + */ + public BandPassBuilder sampleRate(int sampleRate) + { + mSampleRate = sampleRate; + return this; + } + + /** + * Specifies the ending frequency of the first stop band + */ + public BandPassBuilder stopFrequency1(int stopFrequency) + { + mStopFrequency1 = stopFrequency; + return this; + } + + /** + * Specifies the beginning frequency of the second stop band + */ + public BandPassBuilder stopFrequency2(int stopFrequency) + { + mStopFrequency2 = stopFrequency; + return this; + } + + /** + * Specifies the pass band beginning frequency + */ + public BandPassBuilder passFrequencyBegin(int passFrequency) + { + mPassFrequencyBegin = passFrequency; + return this; + } + + /** + * Specifies the pass band ending frequency + */ + public BandPassBuilder passFrequencyEnd(int passFrequency) + { + mPassFrequencyEnd = passFrequency; + return this; + } + + /** + * Specifies the ripple in dB for both stop bands + */ + public BandPassBuilder stopRipple(double rippleDb) + { + mStopRipple = rippleDb; + return this; + } + + /** + * Specifies the ripple in dB for the pass band + */ + public BandPassBuilder passRipple(double rippleDb) + { + mPassRipple = rippleDb; + return this; + } + + /** + * Specifies the amplitude of the stop bands. Default is 0.0 + */ + public BandPassBuilder stopAmplitude(double amplitude) + { + mStopAmplitude = amplitude; + return this; + } + + /** + * Specifies the amplitude of the pass band. Default is 1.0 + */ + public BandPassBuilder passAmplitude(double amplitude) + { + mPassAmplitude = amplitude; + return this; + } + + /** + * Validates the frequency bands to test for overlap and completeness. + */ + private void validateFrequencyBands() + { + if(mStopFrequency1 >= mPassFrequencyBegin) + { + throw new IllegalArgumentException("Stop band 1 ending frequency [" + + mStopFrequency1 + "] must be less than the pass band start frequency [" + + mPassFrequencyBegin + "]"); + } + + if(mPassFrequencyBegin >= mPassFrequencyEnd) + { + throw new IllegalArgumentException("Pass band begin frequency [" + + mPassFrequencyBegin + "] must be less than the pass band end frequency [" + + mPassFrequencyEnd + "]"); + } + + if(mPassFrequencyEnd >= mStopFrequency2) + { + throw new IllegalArgumentException("Pass band end frequency [" + mPassFrequencyEnd + + "] must be less than stop band 2 beginning frequency [" + mStopFrequency2 + + "]"); + } + + if(mStopFrequency2 >= (mSampleRate / 2)) + { + throw new IllegalArgumentException("Stop band 2 beginning frequency [" + + mStopFrequency2 + "] must be less than half of the sample rate [" + + (mSampleRate / 2) + "]"); + } + } + + public FIRFilterSpecification build() + { + validateFrequencyBands(); + + FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL; + + if(mOrder < 10) + { + mOrder = estimateBandPassOrder(mSampleRate, mPassFrequencyBegin, mPassFrequencyEnd, + mPassRipple, mStopRipple); + } + + // Ensure even order since we're designing a Type 1 filter + if(mOrder % 2 == 1) + { + mOrder++; + } + + FIRFilterSpecification spec = new FIRFilterSpecification(type, mOrder, mGridDensity); + + spec.addFrequencyBand(new FrequencyBand(mSampleRate, 0, mStopFrequency1, + mStopAmplitude, mStopRipple)); + spec.addFrequencyBand(new FrequencyBand(mSampleRate, mPassFrequencyBegin, + mPassFrequencyEnd, mPassAmplitude, mPassRipple)); + spec.addFrequencyBand(new FrequencyBand(mSampleRate, mStopFrequency2, + (int)(mSampleRate / 2), mStopAmplitude, mStopRipple)); + + return spec; + } + } /** * Builder for creating polyphase channelizer low-pass prototype filter. @@ -916,7 +714,7 @@ public class FIRFilterSpecification /** * Sets the filter sample rate in hertz */ - public PolyphaseChannelizerBuilder sampleRate(int sampleRate ) + public PolyphaseChannelizerBuilder sampleRate(int sampleRate) { mSampleRate = sampleRate; return this; @@ -936,7 +734,7 @@ public class FIRFilterSpecification * other parameters. If you specify and odd-length order, it will be corrected (+1) to make * it an even order number. */ - public PolyphaseChannelizerBuilder order(int order ) + public PolyphaseChannelizerBuilder order(int order) { mOrder = order; return this; @@ -945,7 +743,7 @@ public class FIRFilterSpecification /** * Sets the frequency grid density. Default is 16. */ - public PolyphaseChannelizerBuilder gridDensity(int density ) + public PolyphaseChannelizerBuilder gridDensity(int density) { mGridDensity = density; return this; @@ -954,7 +752,7 @@ public class FIRFilterSpecification /** * Specifies the ripple in dB for both stop bands */ - public PolyphaseChannelizerBuilder stopRipple(double rippleDb ) + public PolyphaseChannelizerBuilder stopRipple(double rippleDb) { mStopRipple = rippleDb; return this; @@ -963,7 +761,7 @@ public class FIRFilterSpecification /** * Specifies the ripple in dB for the pass band */ - public PolyphaseChannelizerBuilder passRipple(double rippleDb ) + public PolyphaseChannelizerBuilder passRipple(double rippleDb) { mPassRipple = rippleDb; return this; @@ -972,7 +770,7 @@ public class FIRFilterSpecification /** * Specifies the amplitude of the stop bands. Default is 0.0 */ - public PolyphaseChannelizerBuilder stopAmplitude(double amplitude ) + public PolyphaseChannelizerBuilder stopAmplitude(double amplitude) { mStopAmplitude = amplitude; return this; @@ -981,7 +779,7 @@ public class FIRFilterSpecification /** * Specifies the amplitude of the pass band. Default is 1.0 */ - public PolyphaseChannelizerBuilder passAmplitude(double amplitude ) + public PolyphaseChannelizerBuilder passAmplitude(double amplitude) { mPassAmplitude = amplitude; return this; @@ -991,19 +789,19 @@ public class FIRFilterSpecification { FIRLinearPhaseFilterType type = FIRLinearPhaseFilterType.TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL; - if ( mOrder < 10 ) + if(mOrder < 10) { - mOrder = estimateBandPassOrder( mSampleRate, mPassFrequencyBegin, mPassFrequencyEnd, - mPassRipple, mStopRipple ); + mOrder = estimateBandPassOrder(mSampleRate, mPassFrequencyBegin, mPassFrequencyEnd, + mPassRipple, mStopRipple); } // Ensure even order since we're designing a Type 1 filter - if ( mOrder % 2 == 1 ) + if(mOrder % 2 == 1) { mOrder++; } - FIRFilterSpecification spec = new FIRFilterSpecification( type, mOrder, mGridDensity ); + FIRFilterSpecification spec = new FIRFilterSpecification(type, mOrder, mGridDensity); //Set pass band to stop at about 80% of channel bandwidth as starting point int passBandStop = (int)(mChannelBandwidth * .80); @@ -1023,265 +821,273 @@ public class FIRFilterSpecification } - // /** - // * Converts the set of band amplitude values into a set of weights - // * normalized to the largest band amplitude value - // */ - // private static double[] getBandWeights( double... amplitudes ) - // { - // double maximum = 0.0; - // - // for ( double amplitude : amplitudes ) - // { - // if ( amplitude > maximum ) - // { - // maximum = amplitude; - // } - // } - // - // double[] weights = new double[ amplitudes.length ]; - // - // for ( int x = 0; x < amplitudes.length; x++ ) - // { - // weights[ x ] = maximum / amplitudes[ x ]; - // } - // - // return weights; - // } + // * Converts the set of band amplitude values into a set of weights + // * normalized to the largest band amplitude value + // */ + // private static double[] getBandWeights( double... amplitudes ) + // { + // double maximum = 0.0; + // + // for ( double amplitude : amplitudes ) + // { + // if ( amplitude > maximum ) + // { + // maximum = amplitude; + // } + // } + // + // double[] weights = new double[ amplitudes.length ]; + // + // for ( int x = 0; x < amplitudes.length; x++ ) + // { + // weights[ x ] = maximum / amplitudes[ x ]; + // } + // + // return weights; + // } - /** - * Calculates filter order from filter length - */ - private static int getFilterOrder( double length ) - { - return (int) ( Math.ceil( length ) - 1 ); - } + /** + * Calculates filter order from filter length + */ + private static int getFilterOrder(double length) + { + return (int)(Math.ceil(length) - 1); + } - /** - * Estimates filter length for a two-band (high or low pass) FIR filter. - * - * Note: this estimator doesn't work well if the transition frequency is near 0 or sampleRate/2 - * - * From Herrmann et al (1973), Practical design rules for optimum finite impulse response - * filters. Bell System Technical J., 52, 769-99 - * - * @param sampleRate in hertz - * @param frequency1 in hertz - * @param frequency2 in hertz - * @param passBandRipple pass band ripple in dB - * @param stopBandRipple stop band ripple in dB - * - * @return estimated filter length - */ - public static int estimateFilterOrder( int sampleRate, int frequency1, int frequency2, - double passBandRipple, double stopBandRipple ) - { - double df = Math.abs( frequency2 - frequency1 ) / (double) sampleRate; + /** + * Estimates filter length for a two-band (high or low pass) FIR filter. + * + * Note: this estimator doesn't work well if the transition frequency is near 0 or sampleRate/2 + * + * From Herrmann et al (1973), Practical design rules for optimum finite impulse response + * filters. Bell System Technical J., 52, 769-99 + * + * @param sampleRate in hertz + * @param frequency1 in hertz + * @param frequency2 in hertz + * @param passBandRipple pass band ripple in dB + * @param stopBandRipple stop band ripple in dB + * @return estimated filter length + */ + public static int estimateFilterOrder(int sampleRate, int frequency1, int frequency2, + double passBandRipple, double stopBandRipple) + { + double df = Math.abs(frequency2 - frequency1) / (double)sampleRate; - double ddp = Math - .log10( stopBandRipple <= passBandRipple ? passBandRipple : stopBandRipple ); - double dds = Math - .log10( stopBandRipple <= passBandRipple ? stopBandRipple : passBandRipple ); + double ddp = Math + .log10(stopBandRipple <= passBandRipple ? passBandRipple : stopBandRipple); + double dds = Math + .log10(stopBandRipple <= passBandRipple ? stopBandRipple : passBandRipple); - double a1 = 5.309e-3; - double a2 = 7.114e-2; - double a3 = -4.761e-1; - double a4 = -2.66e-3; - double a5 = -5.941e-1; - double a6 = -4.278e-1; + double a1 = 5.309e-3; + double a2 = 7.114e-2; + double a3 = -4.761e-1; + double a4 = -2.66e-3; + double a5 = -5.941e-1; + double a6 = -4.278e-1; - double b1 = 11.01217; - double b2 = 0.5124401; + double b1 = 11.01217; + double b2 = 0.5124401; - double t1 = a1 * ddp * ddp; - double t2 = a2 * ddp; - double t3 = a4 * ddp * ddp; - double t4 = a5 * ddp; + double t1 = a1 * ddp * ddp; + double t2 = a2 * ddp; + double t3 = a4 * ddp * ddp; + double t4 = a5 * ddp; - double dinf = ( ( t1 + t2 + a3 ) * dds ) + ( t3 + t4 + a6 ); - double ff = b1 + b2 * ( ddp - dds ); - double n = dinf / df - ff * df + 1.0; + double dinf = ((t1 + t2 + a3) * dds) + (t3 + t4 + a6); + double ff = b1 + b2 * (ddp - dds); + double n = dinf / df - ff * df + 1.0; - return (int) Math.ceil( n ); - } + return (int)Math.ceil(n); + } - /** - * Estimates band pass filter length; - * - * @param passBandStart frequency normalized to sample rate - * @param passBandEnd frequency normalized to sample rate - * @param passBandRippleDb - * @param stopBandRippleDb - */ - public static int estimateBandPassOrder( int sampleRate, int passBandStart, int passBandEnd, - double passBandRippleDb, double stopBandRippleDb ) - { - double df = (double) Math.abs( passBandEnd - passBandStart ) / (double) sampleRate; - double ddp = (double) Math.log10( passBandRippleDb ); - double dds = (double) Math.log10( stopBandRippleDb ); + /** + * Estimates band pass filter length; + * + * @param passBandStart frequency normalized to sample rate + * @param passBandEnd frequency normalized to sample rate + * @param passBandRippleDb + * @param stopBandRippleDb + */ + public static int estimateBandPassOrder(int sampleRate, int passBandStart, int passBandEnd, + double passBandRippleDb, double stopBandRippleDb) + { + double df = (double)Math.abs(passBandEnd - passBandStart) / (double)sampleRate; + double ddp = (double)Math.log10(passBandRippleDb); + double dds = (double)Math.log10(stopBandRippleDb); - double a1 = 0.01201; - double a2 = 0.09664; - double a3 = -0.51325; - double a4 = 0.00203; - double a5 = -0.57054; - double a6 = -0.44314; + double a1 = 0.01201; + double a2 = 0.09664; + double a3 = -0.51325; + double a4 = 0.00203; + double a5 = -0.57054; + double a6 = -0.44314; - double t1 = a1 * ddp * ddp; - double t2 = a2 * ddp; - double t3 = a4 * ddp * ddp; - double t4 = a5 * ddp; + double t1 = a1 * ddp * ddp; + double t2 = a2 * ddp; + double t3 = a4 * ddp * ddp; + double t4 = a5 * ddp; - double cinf = dds * ( t1 + t2 + a3 ) + t3 + t4 + a6; - double ginf = -14.6f * (double) Math.log10( passBandRippleDb / stopBandRippleDb ) - 16.9; - double n = cinf / df + ginf * df + 1.0; + double cinf = dds * (t1 + t2 + a3) + t3 + t4 + a6; + double ginf = -14.6f * (double)Math.log10(passBandRippleDb / stopBandRippleDb) - 16.9; + double n = cinf / df + ginf * df + 1.0; - return (int) Math.ceil( n ); - } + return (int)Math.ceil(n); + } - /** - * Frequency band - */ - public static class FrequencyBand - { - private int mGridSize; - private double mStart; - private double mEnd; - private double mAmplitude; - private double mRippleDB; + /** + * Frequency band + */ + public static class FrequencyBand + { + private int mGridSize; + private double mStart; + private double mEnd; + private double mAmplitude; + private double mRippleDB; + private Double mWeight; - /** - * Constructs a frequency band where the start and end frequencies are normalized to a - * sample rate of 1. - * - * @param start edge of the frequency band (0.0 - 1.0) - * @param end edge of the frequency band (0.0 - 1.0) - * @param amplitude of the frequency band on the unity scale (0.0 - 1.0) - * @param ripple of the frequency band specified as db ripple - */ - public FrequencyBand( double start, double end, double amplitude, double ripple ) - { - assert ( 0.0 <= mStart ); - assert ( mStart < mEnd ); - assert ( mEnd <= 1.0 ); - assert ( 0.0 <= amplitude && amplitude <= 1.0 ); - assert ( 0.0 <= ripple ); + /** + * Constructs a frequency band where the start and end frequencies are normalized to a + * sample rate of 1. + * + * @param start edge of the frequency band (0.0 - 1.0) + * @param end edge of the frequency band (0.0 - 1.0) + * @param amplitude of the frequency band on the unity scale (0.0 - 1.0) + * @param ripple of the frequency band specified as db ripple + */ + public FrequencyBand(double start, double end, double amplitude, double ripple) + { + assert (0.0 <= mStart); + assert (mStart <= mEnd); + assert (mEnd <= 1.0); + assert (0.0 <= amplitude && amplitude <= 1.0); + assert (0.0 <= ripple); - mStart = start; - mEnd = end; - mAmplitude = amplitude; - mRippleDB = ripple; - } + mStart = start; + mEnd = end; + mAmplitude = amplitude; + mRippleDB = ripple; + } - /** - * Constructs a frequency band for the sample rate and start and end frequencies. - * - * @param sampleRate in hertz - * @param start edge of the frequency band in hertz - * @param end edge of the frequency band in hertz - * @param amplitude of the frequency band relative to unity (0.0 - 1.0) - * @param ripple of the frequency band specified as db ripple - */ - public FrequencyBand( int sampleRate, int start, int end, double amplitude, double ripple ) - { - this( (double) start / (double) sampleRate, (double) end / (double) sampleRate, - amplitude, ripple ); - } + /** + * Constructs a frequency band for the sample rate and start and end frequencies. + * + * @param sampleRate in hertz + * @param start edge of the frequency band in hertz + * @param end edge of the frequency band in hertz + * @param amplitude of the frequency band relative to unity (0.0 - 1.0) + * @param ripple of the frequency band specified as db ripple + */ + public FrequencyBand(int sampleRate, int start, int end, double amplitude, double ripple) + { + this((double)start / (double)sampleRate, (double)end / (double)sampleRate, + amplitude, ripple); + } - public int getGridSize() - { - return mGridSize; - } + public FrequencyBand(int sampleRate, int start, int end, double amplitude, double ripple, double weight) + { + this(sampleRate, start, end, amplitude, ripple); + mWeight = weight; + } - public void setGridSize( int totalGridSize, double totalBandwidth ) - { - mGridSize = (int) Math - .ceil( (double) totalGridSize * ( getBandWidth() / totalBandwidth ) ); - } + public int getGridSize() + { + return mGridSize; + } - public String toString() - { - StringBuilder sb = new StringBuilder(); + public void setGridSize(int totalGridSize, double totalBandwidth) + { + mGridSize = Math.max(1, (int)Math.ceil((double)totalGridSize * (getBandWidth() / totalBandwidth))); + } - sb.append( "Band Start: " ); - sb.append( mStart ); - sb.append( " End:" ); - sb.append( mEnd ); - sb.append( " Amplitude: " ); - sb.append( mAmplitude ); - sb.append( " Ripple dB: " ); - sb.append( mRippleDB ); - sb.append( " Ripple Amplitude: " ); - sb.append( DECIMAL_FORMATTER.format( getRippleAmplitude() ) ); - sb.append( " Grid Size: " ); - sb.append( mGridSize ); + public String toString() + { + StringBuilder sb = new StringBuilder(); - return sb.toString(); - } + sb.append("Band Start: "); + sb.append(mStart); + sb.append(" End:"); + sb.append(mEnd); + sb.append(" Amplitude: "); + sb.append(mAmplitude); + sb.append(" Ripple dB: "); + sb.append(mRippleDB); + sb.append(" Ripple Amplitude: "); + sb.append(DECIMAL_FORMATTER.format(getRippleAmplitude())); + sb.append(" Grid Size: "); + sb.append(mGridSize); - /** - * Start frequency edge for this band normalized to 1 Hz. - */ - public double getStart() - { - return mStart; - } + return sb.toString(); + } - /** - * End frequency edge for this band normalized to 1 Hz. - */ - public double getEnd() - { - return mEnd; - } + /** + * Start frequency edge for this band normalized to 1 Hz. + */ + public double getStart() + { + return mStart; + } - /** - * Desired amplitude response for this frequency band - * - * @return amplitude (0.0 - 1.0) - */ - public double getAmplitude() - { - return mAmplitude; - } + /** + * End frequency edge for this band normalized to 1 Hz. + */ + public double getEnd() + { + return mEnd; + } - /** - * Frequency band ripple in decibels - */ - public double getRippleDB() - { - return mRippleDB; - } + /** + * Desired amplitude response for this frequency band + * + * @return amplitude (0.0 - 1.0) + */ + public double getAmplitude() + { + return mAmplitude; + } - /** - * Frequency band ripple as an amplitude value - */ - public double getRippleAmplitude() - { - return ( Math.pow( 10.0, ( mRippleDB / 20 ) ) - 1 ) - / ( Math.pow( 10.0, ( mRippleDB / 20 ) ) + 1 ); - } + /** + * Frequency band ripple in decibels + */ + public double getRippleDB() + { + return mRippleDB; + } - /** - * Calculates the weighting of this frequency band's ripple relative to the max ripple for a - * filter specification. - * - * @param maxRippleAmplitude for the filter specification - * - * @return weight of this frequency band - */ - public double getWeight( double maxRippleAmplitude ) - { - return 1.0 / ( getRippleAmplitude() / maxRippleAmplitude ); - } + /** + * Frequency band ripple as an amplitude value + */ + public double getRippleAmplitude() + { + return (Math.pow(10.0, (mRippleDB / 20)) - 1) + / (Math.pow(10.0, (mRippleDB / 20)) + 1); + } - /** - * Double-sided bandwidth of this frequency band - */ - public double getBandWidth() - { - return mEnd - mStart; - } - } + /** + * Calculates the weighting of this frequency band's ripple relative to the max ripple for a + * filter specification. + * + * @param maxRippleAmplitude for the filter specification + * @return weight of this frequency band + */ + public double getWeight(double maxRippleAmplitude) + { + if(mWeight == null) + { + return 1.0 / (getRippleAmplitude() / maxRippleAmplitude); + } + + return mWeight; + } + + /** + * Double-sided bandwidth of this frequency band + */ + public double getBandWidth() + { + return mEnd - mStart; + } + } } diff --git a/src/dsp/filter/fir/remez/PolyphaseChannelizerFilterFactory.java b/src/dsp/filter/fir/remez/PolyphaseChannelizerFilterFactory.java index 04417fa3..7e21f6fa 100644 --- a/src/dsp/filter/fir/remez/PolyphaseChannelizerFilterFactory.java +++ b/src/dsp/filter/fir/remez/PolyphaseChannelizerFilterFactory.java @@ -28,27 +28,25 @@ public class PolyphaseChannelizerFilterFactory 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; int passBandStart = 0; - int gridDensity = 32; + int gridDensity = 50; - //Set pass band to stop at about 80% of channel bandwidth as starting point -// int passBandStop = (int)(channelBandwidth * 0.95); - int passBandStop = channelBandwidth; + int rolloffFrequency = (int)(alpha * (double)channelBandwidth); + + int passBandStop = channelBandwidth - rolloffFrequency; //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 stopRipple = 0.01d; + double passRipple = 0.01d; + double stopRipple = 0.001d; //Approximately 90 dB of attenuation -// int order = FIRFilterSpecification.estimateBandPassOrder(sampleRate, passBandStart, passBandStop, -// passRipple, stopRipple); - int order = FIRFilterSpecification.estimateFilterOrder( sampleRate, passBandStop, - stopBandStart, passRipple, stopRipple ); + int order = FIRFilterSpecification.estimateFilterOrder( sampleRate, passBandStop, stopBandStart, + passRipple, stopRipple ); // Ensure even order since we're designing a Type 1 filter if(order % 2 == 1) @@ -56,66 +54,39 @@ public class PolyphaseChannelizerFilterFactory order++; } + mLog.info("Filter Order: " + order); + FIRFilterSpecification specification = new PolyphaseChannelizerDesigner(order, gridDensity); - FIRFilterSpecification.FrequencyBand passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, 0, - passBandStop, 1.0, passRipple); + FIRFilterSpecification.FrequencyBand passBand = new FIRFilterSpecification.FrequencyBand(sampleRate, passBandStart, + 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, stopBandStart, (int)(sampleRate / 2), 0.0, stopRipple); specification.addFrequencyBand(passBand); + specification.addFrequencyBand(transitionBand); specification.addFrequencyBand(stopBand); - boolean complete = false; - int stepSize = 100; double bandEdgeFrequency = Math.cos(Math.PI * (double)channelBandwidth / (double)(sampleRate / 2)); float[] filter = null; - while(!complete) + try { - mLog.debug("Step Size: " + stepSize); - - RemezFIRFilterDesigner2 designer = new RemezFIRFilterDesigner2(specification); - - complete = true; //temporary - + RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification); + filter = designer.getImpulseResponse(); double bandEdgeAmplitude = designer.getFrequencyResponse(bandEdgeFrequency); mLog.debug("Coefficient Amplitude at Band Edge is: " + bandEdgeAmplitude + " for frequency: " + passBandStop); - - if(bandEdgeAmplitude < OBJECTIVE_BAND_EDGE_COEFFICIENT_AMPLITUDE) - { - if(stepSize <= 1) - { - 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); - } + } + catch(Exception e) + { + mLog.error("Error designing filter", e); } return filter; @@ -125,7 +96,7 @@ public class PolyphaseChannelizerFilterFactory { mLog.debug("Starting ..."); - float[] filter = getFilter(2500000, 12500); + float[] filter = getFilter(200000, 12500, 0.21); mLog.debug("Finished ..."); } diff --git a/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java index e5ca196b..dba1fd76 100644 --- a/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java +++ b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java @@ -212,15 +212,12 @@ public class RemezFIRFilterDesigner for( int k = 0; k < mExtremalIndices.size() - 1; k++ ) { - double cosineDelta = cosineOfFrequency - - mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ]; + double cosineDelta = cosineOfFrequency - 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 ) { - numerator = mIdealFrequencyResponse[ k ]; - denominator = 1.0; - break; + return mIdealFrequencyResponse[ k ]; } else { diff --git a/src/dsp/filter/fir/remez/RemezFIRFilterDesigner2.java b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner2.java index 2890d939..521b4f65 100644 --- a/src/dsp/filter/fir/remez/RemezFIRFilterDesigner2.java +++ b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner2.java @@ -36,6 +36,7 @@ public class RemezFIRFilterDesigner2 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; + public static final double FREQUENCY_PROXIMITY_THRESHOLD = 1.0e-7; private FIRFilterSpecification mSpecification; private Grid mGrid; @@ -234,25 +235,27 @@ public class RemezFIRFilterDesigner2 for(int k = 0; k < mExtremalIndices.size() - 1; k++) { - BigDecimal cosineDelta = BigDecimal.valueOf(cosineOfFrequency - - mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)]); + double cosineDelta = cosineOfFrequency - mGrid.getCosineFrequencyGrid()[mExtremalIndices.get(k)]; - //If this frequency is close to one of the polynomial points, use c[k] for that point - if(cosineDelta.abs().compareTo(BigDecimal.valueOf(1.0e-7)) < 0) + //If this frequency is close to one of the polynomial points, use the point as the response value + if(Math.abs(cosineDelta) < FREQUENCY_PROXIMITY_THRESHOLD) { - numerator = BigDecimal.valueOf(mIdealFrequencyResponse[k]); - denominator = BigDecimal.ONE; - break; + return mIdealFrequencyResponse[k]; } 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]))); denominator = denominator.add(dkOverCosineDelta); } } + if(denominator.doubleValue() == 0.0) + { + int a = 0; + } + return numerator.divide(denominator, 25, RoundingMode.HALF_UP).doubleValue(); } diff --git a/src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java b/src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java new file mode 100644 index 00000000..d3fee4cf --- /dev/null +++ b/src/dsp/filter/fir/remez/RemezFIRFilterDesignerWithLagrange.java @@ -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 + * + ******************************************************************************/ +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 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 getInitialExtremalIndices() + { + int count = mSpecification.getExtremaCount(); + + int density = mSpecification.getGridDensity(); + + List 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 indicesToRemove = new ArrayList<>(); + + Iterator 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; + } +}