From 129dba869ff82157d0e555e034f575a1d8dea32c Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 29 May 2016 08:54:07 -0400 Subject: [PATCH] Adds Remez filter designer for low, high and band-pass filters. Support for Hilbert and Differentiator FIR filter designers may be added in the future. Adds developer filter viewer tool written in JavaFX to visualize the frequency and impulse response of FIR filters. This application is not intended to be a user application and is specifically for the developer to analyze filters and filter specifications that will be used in the code. NOTE: Java 8 JDK is now required to build this project for version 0.3.x and forward. The JavaFX visualizer application and future user interfaces will use the Java 8 version of JavaFX --- .classpath | 2 +- .project | 6 + images/Curve-fitted-background.png | Bin 0 -> 24754 bytes images/Curve-fitted-chart-background.png | Bin 0 -> 91654 bytes images/Curve-fitted-graph-gridlines.png | Bin 0 -> 152 bytes .../filter/design/FilterDesignException.java | 16 + src/dsp/filter/design/FilterView.css | 39 + src/dsp/filter/design/FilterView.java | 171 +++ src/dsp/filter/design/FilterViewer.java | 96 ++ .../filter/fir/FIRFilterSpecification.java | 1131 +++++++++++++++++ .../fir/remez/FIRLinearPhaseFilterType.java | 56 + src/dsp/filter/fir/remez/Grid.java | 112 ++ .../fir/remez/RemezFIRFilterDesigner.java | 675 ++++++++++ src/gui/control/CurveFittedAreaChart.java | 207 +++ 14 files changed, 2510 insertions(+), 1 deletion(-) create mode 100644 images/Curve-fitted-background.png create mode 100644 images/Curve-fitted-chart-background.png create mode 100644 images/Curve-fitted-graph-gridlines.png create mode 100644 src/dsp/filter/design/FilterDesignException.java create mode 100644 src/dsp/filter/design/FilterView.css create mode 100644 src/dsp/filter/design/FilterView.java create mode 100644 src/dsp/filter/design/FilterViewer.java create mode 100644 src/dsp/filter/fir/FIRFilterSpecification.java create mode 100644 src/dsp/filter/fir/remez/FIRLinearPhaseFilterType.java create mode 100644 src/dsp/filter/fir/remez/Grid.java create mode 100644 src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java create mode 100644 src/gui/control/CurveFittedAreaChart.java diff --git a/.classpath b/.classpath index 2217d1f1..512a74ba 100644 --- a/.classpath +++ b/.classpath @@ -1,7 +1,6 @@ - @@ -30,5 +29,6 @@ + diff --git a/.project b/.project index f65c4170..7accc076 100644 --- a/.project +++ b/.project @@ -5,6 +5,11 @@ + + org.eclipse.xtext.ui.shared.xtextBuilder + + + org.eclipse.jdt.core.javabuilder @@ -13,5 +18,6 @@ org.eclipse.jdt.core.javanature + org.eclipse.xtext.ui.shared.xtextNature diff --git a/images/Curve-fitted-background.png b/images/Curve-fitted-background.png new file mode 100644 index 0000000000000000000000000000000000000000..2085805889de522bd0a4ea9a4ce39cf7ea96172b GIT binary patch literal 24754 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 N002ovPDHLkV1kQ-l~Vu! literal 0 HcmV?d00001 diff --git a/images/Curve-fitted-graph-gridlines.png b/images/Curve-fitted-graph-gridlines.png new file mode 100644 index 0000000000000000000000000000000000000000..648c4f9a78ac3145afa953d7ddcd6c5cd3f384ca GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!foD^C~4kcwMLfByfs*Jl=G series = new XYChart.Series<>(); + + for( int x = 0; x < taps.length; x++ ) + { + if( taps[ x ] < smallest ) + { + smallest = taps[ x ]; + } + + if( taps[ x ] > largest ) + { + largest = taps[ x ]; + } + + series.getData().add( new XYChart.Data( x, taps[ x ] ) ); + } + + mImpulseXAxis.setLowerBound( 0.0 ); + mImpulseXAxis.setUpperBound( taps.length - 1 ); + mImpulseXAxis.setTickUnit( 1.0 ); + + double padding = Math.abs( largest - smallest ) * .1; + + mImpulseYAxis.setLowerBound( smallest - padding ); + mImpulseYAxis.setUpperBound( largest + padding ); + mImpulseYAxis.setTickUnit( padding ); + + ObservableList> list = mImpulseChart.getData(); + list.add( series ); + } + + public void setDFTTaps( float[] taps ) + { + double smallest = 0.0; + + final XYChart.Series series = new XYChart.Series<>(); + + for( int x = 0; x < taps.length; x++ ) + { + if( taps[ x ] < smallest ) + { + smallest = taps[ x ]; + } + + series.getData().add( new XYChart.Data( x, taps[ x ] ) ); + } + + ObservableList> list = mDFTChart.getData(); + list.add( series ); + } + + public float[] convertDFTToDecibel( float[] dft, int tapLength ) + { + float[] decibels = new float[ dft.length / 2 ]; + + int index = 0; + + for( int x = 0; x < decibels.length; x ++ ) + { + index = x * 2; + + decibels[ x ] = 20.0f * (float)Math.log10( ( ( dft[ index ] * dft[ index ] ) + + ( dft[ index + 1 ] * dft[ index + 1 ] ) ) ); + } + + return decibels; + } +} diff --git a/src/dsp/filter/design/FilterViewer.java b/src/dsp/filter/design/FilterViewer.java new file mode 100644 index 00000000..7dac65b7 --- /dev/null +++ b/src/dsp/filter/design/FilterViewer.java @@ -0,0 +1,96 @@ +package dsp.filter.design; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dsp.filter.fir.FIRFilterSpecification; +import dsp.filter.fir.remez.RemezFIRFilterDesigner; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class FilterViewer extends Application +{ + private final static Logger mLog = LoggerFactory.getLogger( FilterViewer.class ); + + /** + * Developer tool to visualize filter designs + */ + public FilterViewer() + { + } + + @Override + public void start( Stage primaryStage ) throws Exception + { + Scene scene = new Scene( new FilterView( getFilter() ) ); + + primaryStage.setTitle( "Filter Viewer" ); + primaryStage.setScene( scene ); + primaryStage.show(); + } + + public static void main( String[] args ) + { + launch( args ); + } + + /** + * Provides the filter to visualize. Modify this method to visualize your filter design. + */ + private float[] getFilter() + { + FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() + .sampleRate( 10000 ) + .gridDensity( 16 ) + .passBandCutoff( 1300 ) + .passBandAmplitude( 1.0 ) + .passBandRipple( 0.01 ) + .stopBandStart( 2675 ) + .stopBandAmplitude( 0.0 ) + .stopBandRipple( 0.001 ) + .build(); + +// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() +// .sampleRate( 8000 ) +// .gridDensity( 16 ) +// .stopBandCutoff( 2000 ) +// .stopBandAmplitude( 0.0 ) +// .stopBandRipple( 0.003 ) +// .passBandStart( 3000 ) +// .passBandAmplitude( 1.0 ) +// .passBandRipple( 0.001 ) +// .build(); + +// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() +// .sampleRate( 8000 ) +// .order( 164 ) +// .stopFrequency1( 1100 ) +// .passFrequencyBegin( 1200 ) +// .passFrequencyEnd( 2400 ) +// .stopFrequency2( 2500 ) +// .stopRipple( 0.01 ) +// .passRipple( 0.01 ) +// .build(); + + float[] taps = null; + +// 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 ); + } + + return taps; + } +} diff --git a/src/dsp/filter/fir/FIRFilterSpecification.java b/src/dsp/filter/fir/FIRFilterSpecification.java new file mode 100644 index 00000000..b03e82ec --- /dev/null +++ b/src/dsp/filter/fir/FIRFilterSpecification.java @@ -0,0 +1,1131 @@ +package dsp.filter.fir; + +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 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 order of the filter + * @param frequencyBands containing the start and stop values for each frequency band normalized + * to the sample rate where each frequency value is in the range (0.0 <> 0.5) + * @param amplitudes of each frequency band + * @param weights of the band ripples normalized to the largest ripple value + */ + private 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() ); + + for ( FrequencyBand band : mFrequencyBands ) + { + sb.append( "\n" ); + sb.append( band.toString() ); + } + + return sb.toString(); + } + + public void addFrequencyBand( FrequencyBand band ) + { + mFrequencyBands.add( band ); + + updateGridSize(); + } + + public void addFrequencyBands( Collection bands ) + { + mFrequencyBands.addAll( bands ); + + updateGridSize(); + } + + 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; + } + } + + // /** + // * 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 ); + } + + /** + * 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 + */ + private 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 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 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; + + 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 ); + + 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 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 ); + } + + /** + * Frequency band + */ + public static class FrequencyBand + { + private int mGridSize; + private double mStart; + private double mEnd; + private double mAmplitude; + private double mRippleDB; + + /** + * 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; + } + + /** + * 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 void setGridSize( int totalGridSize, double totalBandwidth ) + { + mGridSize = (int) Math + .ceil( (double) totalGridSize * ( getBandWidth() / totalBandwidth ) ); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + 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 ); + + return sb.toString(); + } + + /** + * Start frequency edge for this band normalized to 1 Hz. + */ + public double getStart() + { + return mStart; + } + + /** + * End frequency edge for this band normalized to 1 Hz. + */ + public double getEnd() + { + return mEnd; + } + + /** + * Desired amplitude response for this frequency band + * + * @return amplitude (0.0 - 1.0) + */ + public double getAmplitude() + { + return mAmplitude; + } + + /** + * Frequency band ripple in decibels + */ + public double getRippleDB() + { + return mRippleDB; + } + + /** + * 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 ); + } + + /** + * 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 ); + } + + /** + * Double-sided bandwidth of this frequency band + */ + public double getBandWidth() + { + return mEnd - mStart; + } + } +} diff --git a/src/dsp/filter/fir/remez/FIRLinearPhaseFilterType.java b/src/dsp/filter/fir/remez/FIRLinearPhaseFilterType.java new file mode 100644 index 00000000..aef00ec7 --- /dev/null +++ b/src/dsp/filter/fir/remez/FIRLinearPhaseFilterType.java @@ -0,0 +1,56 @@ +package dsp.filter.fir.remez; + +public enum FIRLinearPhaseFilterType +{ + /** + * TYPE 1 + * Length: ODD + * Order: EVEN + * Symmetry: SYMMETRICAL + * + * Valid for: LOW PASS, HIGH PASS + */ + TYPE_1_ODD_LENGTH_EVEN_ORDER_SYMMETRICAL, + + /** + * TYPE 2 + * Length: EVEN + * Order: ODD + * Symmetry: SYMMETRICAL + * Valid for: LOW PASS + * + * Notes: + * -Frequency response at FS/2 (Pi) must be 0 -- not suitable for High Pass. + */ + TYPE_2_EVEN_LENGTH_ODD_ORDER_SYMMETRICAL, + + /** + * TYPE 3 + * Length: ODD + * Order: EVEN + * Symmetry: ANTI-SYMMETRICAL + * Valid for: + * + * Notes: + * -Introduces a 90 degree phase shift such that h[N-n] = -h[n] for all n + * -Frequency response at 0 and FS/2 (Pi) must be 0 -- not suitable for Low or High Pass. + */ + TYPE_3_ODD_LENGTH_EVEN_ORDER_ANTI_SYMMETRICAL, + + /** + * TYPE 4 + * Length: EVEN + * Order: ODD + * Symmetry: ANTI-SYMMETRICAL + * Valid for: HIGH PASS + * + * Notes: + * -Introduces a 90 degree phase shift such that h[N-n] = -h[n] for all n + * -Frequency response at 0 must be zero -- not suitable for Low Pass + */ + TYPE_4_EVEN_LENGTH_ODD_ORDER_ANTI_SYMMETRICAL, + + BANDPASS, + DIFFERENTIATOR, + HILBERT; +} diff --git a/src/dsp/filter/fir/remez/Grid.java b/src/dsp/filter/fir/remez/Grid.java new file mode 100644 index 00000000..bca44b40 --- /dev/null +++ b/src/dsp/filter/fir/remez/Grid.java @@ -0,0 +1,112 @@ +package dsp.filter.fir.remez; + +import dsp.filter.fir.FIRFilterSpecification; +import dsp.filter.fir.FIRFilterSpecification.FrequencyBand; + +public class Grid +{ + public static final double TWO_PI = 2.0 * Math.PI; + + private double[] mFrequencyGrid; + private double[] mCosineOfFrequencyGrid; + private double[] mDesiredResponse; + private double[] mWeight; + + /** + * Creates a dense frequency grid from the filter specification for those frequency bands + * where the weighted value is non-zero. This grid is appropriate for use in the Remez + * exchange algorithm. + */ + public Grid( FIRFilterSpecification specification ) + { + create( specification ); + } + + private void create( FIRFilterSpecification specification ) + { + int gridSize = specification.getGridSize(); + + mFrequencyGrid = new double[ gridSize ]; + mCosineOfFrequencyGrid = new double[ gridSize ]; + mDesiredResponse = new double[ gridSize ]; + mWeight = new double[ gridSize ]; + + double gridFrequencyInterval = specification.getGridFrequencyInterval(); + + double bandZeroEdge = specification.getFrequencyBands().get( 0 ).getStart(); + + // For differentiator, hilbert, symmetry is odd and Grid[0] = max(delf, bands[0]) + double grid0 = specification.isAntiSymmetrical() && + ( gridFrequencyInterval > bandZeroEdge ) ? gridFrequencyInterval : bandZeroEdge; + + int j = 0; + + double maxRipple = specification.getMaxBandAmplitude(); + + for( int x = 0; x < specification.getFrequencyBands().size(); x++ ) + { + FrequencyBand band = specification.getFrequencyBands().get( x ); + + double lowFrequency = ( x == 0 ? grid0 : band.getStart() ); + + for( int i = 0; i < band.getGridSize(); i++ ) + { + mDesiredResponse[ j ] = band.getAmplitude(); + + //Set the weighting function equally across this band's dense grids + mWeight[ j ] = band.getWeight( maxRipple ); + + //Frequency grid is linear taper from start frequency to stop frequency in + //gridFrequencyInterval increments + mFrequencyGrid[ j ] = lowFrequency; + + mCosineOfFrequencyGrid[ j ] = Math.cos( TWO_PI * mFrequencyGrid[ j ] ); + + lowFrequency += gridFrequencyInterval; + + j++; + } + + int lastIndex = j - 1; + + mFrequencyGrid[ lastIndex ] = band.getEnd(); + } + } + + /** + * Size of the dense frequency grid + */ + public int getSize() + { + return mFrequencyGrid.length; + } + + /** + * The dense frequency grid + */ + public double[] getFrequencyGrid() + { + return mFrequencyGrid; + } + + public double[] getCosineFrequencyGrid() + { + return mCosineOfFrequencyGrid; + } + + /** + * Desired frequency response + */ + public double[] getDesiredResponse() + { + return mDesiredResponse; + } + + /** + * Weighting function + */ + public double[] getWeight() + { + return mWeight; + } +} diff --git a/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java new file mode 100644 index 00000000..e5ca196b --- /dev/null +++ b/src/dsp/filter/fir/remez/RemezFIRFilterDesigner.java @@ -0,0 +1,675 @@ +package dsp.filter.fir.remez; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dsp.filter.design.FilterDesignException; +import dsp.filter.fir.FIRFilterSpecification; + +public class RemezFIRFilterDesigner +{ + private final static Logger mLog = LoggerFactory.getLogger( RemezFIRFilterDesigner.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 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 RemezFIRFilterDesigner( FIRFilterSpecification specification ) + { + mSpecification = specification; + + design(); + } + + /** + * Designs the filter according to the specification + */ + private void design() + { + mGrid = new Grid( mSpecification ); + mExtremalIndices = getInitialExtremalIndices(); + + int iterationCount = 0; + + try + { + do + { + calculateGridFrequencyResponse(); + calculateGridError(); + findExtremalIndices(); + checkConvergence(); + + iterationCount++; + } + while( !mConverged && iterationCount < MAXIMUM_ITERATION_COUNT ); + } + catch( FilterDesignException fde ) + { + mLog.error( "Filter design error - couldn't find extremal indices at count [" + + iterationCount + "] try changing filter order up/down from [" + + mSpecification.getOrder() + "]" ); + + mConverged = false; + } + + if( mConverged ) + { + //Update frequency response using the final set of extremal indices + calculateGridFrequencyResponse(); + } + } + + /** + * 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 + */ + private double getFrequencyResponse( double 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 or is close to one of the polynomial points, use c[k] for that point + if( Math.abs( cosineDelta ) < 1.0e-7 ) + { + numerator = mIdealFrequencyResponse[ k ]; + denominator = 1.0; + break; + } + 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() + { + double[] b = calculateB(); + + calculateDelta( b ); + + calculateC(); + + calculateD( b ); + + 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 double[] calculateB() + { + int length = mExtremalIndices.size(); + + double[] b = new double[ length ]; + + for( int k = 0; k < length; k++ ) + { + b[ k ] = 1.0; + + 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 ] *= 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( double[] b ) + { + double numerator = 0.0; + double denominator = 0.0; + + double sign = 1.0; + + for( int k = 0; k < b.length; k++ ) + { + if( k < mExtremalIndices.size() ) + { + int extremalIndex = mExtremalIndices.get( k ); + + numerator += ( b[ k ] * mGrid.getDesiredResponse()[ extremalIndex ] ); + denominator += b[ k ] * sign / mGrid.getWeight()[ extremalIndex ]; + sign = -sign; + } + else + { + mLog.error( "Something went wrong -- the length of b exceeds the set of extremal indices" ); + } + } + + mDelta = numerator / denominator; + } + + /** + * 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 set of D values for the current extremal index set. + * + * Implements Oppenheim/Schafer Discrete Time Signal Processing, 3e, 2016, equation 116c + */ + private void calculateD( double[] b ) + { + int length = mExtremalIndices.size() - 1; + + mD = new double[ length ]; + + for( int k = 0; k < length; k++ ) + { + //Note: extremalCosines array is one index longer than d array, so we use length as + //a pointer to the final (L+2) extremalCosine index + mD[k] = b[k] * ( mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( k ) ] - + mGrid.getCosineFrequencyGrid()[ mExtremalIndices.get( length ) ] ); + } + } + + /** + * 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" ); + } + + //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" ); + } + } + + /** + * 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 ); + + 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; + } +} diff --git a/src/gui/control/CurveFittedAreaChart.java b/src/gui/control/CurveFittedAreaChart.java new file mode 100644 index 00000000..98ca9068 --- /dev/null +++ b/src/gui/control/CurveFittedAreaChart.java @@ -0,0 +1,207 @@ +package gui.control; + +import javafx.collections.ObservableList; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.chart.AreaChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.shape.ClosePath; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.util.Pair; + +public class CurveFittedAreaChart extends AreaChart +{ + + public CurveFittedAreaChart( NumberAxis xAxis, NumberAxis yAxis ) + { + super( xAxis, yAxis ); + } + + @Override + protected void layoutPlotChildren() + { + super.layoutPlotChildren(); + for ( int seriesIndex = 0; seriesIndex < getDataSize(); seriesIndex++ ) + { + final XYChart.Series series = getData() + .get( seriesIndex ); + final Path seriesLine = (Path) ( (Group) series.getNode() ) + .getChildren().get( 1 ); + final Path fillPath = (Path) ( (Group) series.getNode() ) + .getChildren().get( 0 ); + smooth( seriesLine.getElements(), fillPath.getElements() ); + } + } + + private int getDataSize() + { + final ObservableList> data = getData(); + return ( data != null ) ? data.size() : 0; + } + + private static void smooth( ObservableList strokeElements, + ObservableList fillElements ) + { + // as we do not have direct access to the data, first recreate the list + // of all the data points we have + final Point2D[] dataPoints = new Point2D[ strokeElements.size() ]; + for ( int i = 0; i < strokeElements.size(); i++ ) + { + final PathElement element = strokeElements.get( i ); + if ( element instanceof MoveTo ) + { + final MoveTo move = (MoveTo) element; + dataPoints[ i ] = new Point2D( move.getX(), move.getY() ); + } else if ( element instanceof LineTo ) + { + final LineTo line = (LineTo) element; + final double x = line.getX(), y = line.getY(); + dataPoints[ i ] = new Point2D( x, y ); + } + } + // next we need to know the zero Y value + final double zeroY = ( (MoveTo) fillElements.get( 0 ) ).getY(); + // now clear and rebuild elements + strokeElements.clear(); + fillElements.clear(); + Pair result = calcCurveControlPoints( + dataPoints ); + Point2D[] firstControlPoints = result.getKey(); + Point2D[] secondControlPoints = result.getValue(); + // start both paths + strokeElements.add( + new MoveTo( dataPoints[ 0 ].getX(), dataPoints[ 0 ].getY() ) ); + fillElements.add( new MoveTo( dataPoints[ 0 ].getX(), zeroY ) ); + fillElements.add( + new LineTo( dataPoints[ 0 ].getX(), dataPoints[ 0 ].getY() ) ); + // add curves + for ( int i = 1; i < dataPoints.length; i++ ) + { + final int ci = i - 1; + strokeElements + .add( new CubicCurveTo( firstControlPoints[ ci ].getX(), + firstControlPoints[ ci ].getY(), + secondControlPoints[ ci ].getX(), + secondControlPoints[ ci ].getY(), + dataPoints[ i ].getX(), dataPoints[ i ].getY() ) ); + fillElements.add( new CubicCurveTo( firstControlPoints[ ci ].getX(), + firstControlPoints[ ci ].getY(), + secondControlPoints[ ci ].getX(), + secondControlPoints[ ci ].getY(), dataPoints[ i ].getX(), + dataPoints[ i ].getY() ) ); + } + // end the paths + fillElements.add( new LineTo( + dataPoints[ dataPoints.length - 1 ].getX(), zeroY ) ); + fillElements.add( new ClosePath() ); + } + + /** + * Calculate open-ended Bezier Spline Control Points. + */ + public static Pair calcCurveControlPoints( + Point2D[] dataPoints ) + { + Point2D[] firstControlPoints; + Point2D[] secondControlPoints; + int n = dataPoints.length - 1; + if ( n == 1 ) + { // Special case: Bezier curve should be a straight line. + firstControlPoints = new Point2D[ 1 ]; + // 3P1 = 2P0 + P3 + firstControlPoints[ 0 ] = new Point2D( + ( 2 * dataPoints[ 0 ].getX() + dataPoints[ 1 ].getX() ) / 3, + ( 2 * dataPoints[ 0 ].getY() + dataPoints[ 1 ].getY() ) + / 3 ); + + secondControlPoints = new Point2D[ 1 ]; + // P2 = 2P1 – P0 + secondControlPoints[ 0 ] = new Point2D( + 2 * firstControlPoints[ 0 ].getX() - dataPoints[ 0 ].getX(), + 2 * firstControlPoints[ 0 ].getY() + - dataPoints[ 0 ].getY() ); + return new Pair( firstControlPoints, + secondControlPoints ); + } + + // Calculate first Bezier control points + // Right hand side vector + double[] rhs = new double[ n ]; + + // Set right hand side X values + for ( int i = 1; i < n - 1; ++i ) + { + rhs[ i ] = 4 * dataPoints[ i ].getX() + + 2 * dataPoints[ i + 1 ].getX(); + } + rhs[ 0 ] = dataPoints[ 0 ].getX() + 2 * dataPoints[ 1 ].getX(); + rhs[ n - 1 ] = ( 8 * dataPoints[ n - 1 ].getX() + + dataPoints[ n ].getX() ) / 2.0; + // Get first control points X-values + double[] x = GetFirstControlPoints( rhs ); + + // Set right hand side Y values + for ( int i = 1; i < n - 1; ++i ) + { + rhs[ i ] = 4 * dataPoints[ i ].getY() + + 2 * dataPoints[ i + 1 ].getY(); + } + rhs[ 0 ] = dataPoints[ 0 ].getY() + 2 * dataPoints[ 1 ].getY(); + rhs[ n - 1 ] = ( 8 * dataPoints[ n - 1 ].getY() + + dataPoints[ n ].getY() ) / 2.0; + // Get first control points Y-values + double[] y = GetFirstControlPoints( rhs ); + + // Fill output arrays. + firstControlPoints = new Point2D[ n ]; + secondControlPoints = new Point2D[ n ]; + for ( int i = 0; i < n; ++i ) + { + // First control point + firstControlPoints[ i ] = new Point2D( x[ i ], y[ i ] ); + // Second control point + if ( i < n - 1 ) + { + secondControlPoints[ i ] = new Point2D( + 2 * dataPoints[ i + 1 ].getX() - x[ i + 1 ], + 2 * dataPoints[ i + 1 ].getY() - y[ i + 1 ] ); + } else + { + secondControlPoints[ i ] = new Point2D( + ( dataPoints[ n ].getX() + x[ n - 1 ] ) / 2, + ( dataPoints[ n ].getY() + y[ n - 1 ] ) / 2 ); + } + } + return new Pair( firstControlPoints, + secondControlPoints ); + } + + /** + * Solves a tridiagonal system for one of coordinates (x or y) of first + * Bezier control points. + */ + private static double[] GetFirstControlPoints( double[] rhs ) + { + int n = rhs.length; + double[] x = new double[ n ]; // Solution vector. + double[] tmp = new double[ n ]; // Temp workspace. + double b = 2.0; + x[ 0 ] = rhs[ 0 ] / b; + for ( int i = 1; i < n; i++ ) + {// Decomposition and forward substitution. + tmp[ i ] = 1 / b; + b = ( i < n - 1 ? 4.0 : 3.5 ) - tmp[ i ]; + x[ i ] = ( rhs[ i ] - x[ i - 1 ] ) / b; + } + for ( int i = 1; i < n; i++ ) + { + x[ n - i - 1 ] -= tmp[ n - i ] * x[ n - i ]; // Backsubstitution. + } + return x; + } +} \ No newline at end of file