From 7d5371db4c3aeef86edb0dbade690c01c0b600d3 Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 20 Mar 2017 06:12:15 -0400 Subject: [PATCH] Updates build to 0.3.0 Beta 9 --- .idea/dictionaries/denny.xml | 1 + src/dsp/filter/FilterFactory.java | 344 +++++----- src/dsp/filter/Window.java | 602 ++++++++++++------ .../filter/channelizer/ChannelizerViewer.java | 47 +- .../channelizer/PolyphaseChannelizer2.java | 11 +- src/dsp/filter/design/FilterView.java | 273 ++++---- src/dsp/filter/design/FilterViewer.java | 44 +- src/module/decode/p25/P25_LSMDecoder.java | 2 +- .../audio/DemodulatedAudioFilterModule.java | 2 +- src/spectrum/DFTProcessor.java | 2 +- 10 files changed, 771 insertions(+), 557 deletions(-) diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 223a1c26..015bac87 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -7,6 +7,7 @@ fleetsync icecast ident + nutall overflowable polyphase remez diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java index 15b673f2..6486a758 100644 --- a/src/dsp/filter/FilterFactory.java +++ b/src/dsp/filter/FilterFactory.java @@ -1,24 +1,20 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 Dennis Sheirer + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer * - * Root Raised Cosine filter designer: - * Copyright 2002,2007,2008,2012,2013 Free Software Foundation, Inc. - * http://gnuradio.org/redmine/projects/gnuradio/repository/changes/gr-filter - * /lib/firdes.cc?rev=435b1d166f0c7092bbd5e1f788e75dbb6ade3a4b + * 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 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. * - * 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 * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see ******************************************************************************/ package dsp.filter; @@ -31,14 +27,12 @@ import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; import java.util.Set; import java.util.TreeSet; public class FilterFactory { - private final static Logger mLog = - LoggerFactory.getLogger(FilterFactory.class); + private final static Logger mLog = LoggerFactory.getLogger(FilterFactory.class); /** * Generates coefficients for a unity-gain, windowed low-pass filter @@ -49,43 +43,49 @@ public class FilterFactory * @param window - to apply against the coefficients * @return */ - public static float[] getSinc(int sampleRate, - long frequency, - int length, - WindowType window) + public static float[] getSinc(int sampleRate, long frequency, int length, WindowType window) { - //Ensure we have an odd length - assert (length % 2 == 0); + int evenLength = length % 2 == 0 ? length : length + 1; //Get unity response array (one element longer to align with IDFT size) - float[] frequencyResponse = getUnityResponseArray(sampleRate, frequency, length + 1); + float[] frequencyResponse = getUnityResponseArray(sampleRate, frequency, evenLength); //Apply Inverse DFT against frequency response unity values, leaving the //IDFT bin results in the frequency response array - FloatFFT_1D idft = new FloatFFT_1D(length + 1); - idft.realInverseFull(frequencyResponse, false); + FloatFFT_1D idft = new FloatFFT_1D(evenLength); + idft.realInverseFull(frequencyResponse, true); //Transfer the IDFT results to the return array float[] coefficients = new float[length]; - int middleCoefficient = (int) (length / 2); + int middleCoefficient = (int)(length / 2); - //Bin 0 of the idft is our center coefficient - coefficients[middleCoefficient] = frequencyResponse[0]; - - //The remaining idft bins from 1 to (middle - 1) are the mirror image - //coefficients - for(int x = 1; x < middleCoefficient; x++) + if(length % 2 == 0) //Even length { - coefficients[middleCoefficient + x] = frequencyResponse[2 * x]; - coefficients[middleCoefficient - x] = frequencyResponse[2 * x]; + //The remaining idft bins from 1 to (middle - 1) are the mirror image + //coefficients + for(int x = 0; x < middleCoefficient; x++) + { + coefficients[middleCoefficient + x] = frequencyResponse[2 * x]; + coefficients[middleCoefficient - x] = frequencyResponse[2 * x]; + } + } + else + { + //Bin 0 of the idft is our center coefficient + coefficients[middleCoefficient] = frequencyResponse[0]; + + //The remaining idft bins from 1 to (middle - 1) are the mirror image + //coefficients + for(int x = 1; x < middleCoefficient; x++) + { + coefficients[middleCoefficient + x] = frequencyResponse[2 * x]; + coefficients[middleCoefficient - x] = frequencyResponse[2 * x]; + } } //Apply the window against the coefficients coefficients = Window.apply(window, coefficients); - //Normalize to unity (1) gain - coefficients = normalize(coefficients); - return coefficients; } @@ -96,7 +96,7 @@ public class FilterFactory * @param coefficients * @return */ - private static float[] normalize(float[] coefficients) + public static float[] normalize(float[] coefficients) { float accumulator = 0; @@ -129,17 +129,15 @@ public class FilterFactory * @param length * @return */ - public static float[] getUnityResponseArray(int sampleRate, - long frequency, - int length) + public static float[] getUnityResponseArray(int sampleRate, long frequency, int length) { float[] unityArray = new float[length * 2]; - int binCount = (int) ((Math.round( - (float) frequency / (float) sampleRate * (float) length))); if(length % 2 == 0) //even length { + int binCount = (int)((Math.round((float)frequency / (float)sampleRate * (float)length))); + for(int x = 0; x < binCount; x++) { unityArray[x] = 1.0f; @@ -148,9 +146,11 @@ public class FilterFactory } else //odd length { + int binCount = (int)((Math.round((float)frequency / (float)sampleRate * (float)length + 0.5))); + unityArray[0] = 1.0f; - for(int x = 1; x <= binCount; x++) + for(int x = 1; x < binCount; x++) { unityArray[x] = 1.0f; unityArray[length - x] = 1.0f; @@ -160,25 +160,6 @@ public class FilterFactory return unityArray; } - public static float[] getSine(float sampleRate, float frequency, int length) - { - float[] retVal = new float[length]; - - float radianFrequency = (float) (2.0d * Math.PI * (frequency / sampleRate)); - - int middle = (int) (length / 2); - - for(int x = 0; x < middle; x++) - { - float val = (float) Math.sin(radianFrequency * x); - - retVal[middle + x] = val; - retVal[middle - x] = -val; - } - - return retVal; - } - /** * Applies a repeating sequence of 1, -1 to the coefficients to invert * the frequency response of the filter. Used in converting a low-pass @@ -212,25 +193,9 @@ public class FilterFactory * @param windowType - window to apply against the generated coefficients * @return */ - public static float[] getLowPass(int sampleRate, - long cutoff, - int filterLength, - WindowType windowType) + public static float[] getLowPass(int sampleRate, long cutoff, int filterLength, WindowType windowType) { - if(filterLength % 2 == 0) //even length - { - float[] values = getSinc(sampleRate, cutoff, filterLength + 2, windowType); - - //throw away the 0 index and the last index - return Arrays.copyOfRange(values, 1, values.length - 2); - } - else - { - float[] values = getSinc(sampleRate, cutoff, filterLength + 1, windowType); - - //throw away the 0 index - return Arrays.copyOfRange(values, 1, values.length); - } + return getSinc(sampleRate, cutoff, filterLength, windowType); } /** @@ -247,26 +212,17 @@ public class FilterFactory * - passFrequency < stopFrequency * - stopFrequency <= sampleRate/2 */ - public static float[] getLowPass(int sampleRate, - int passFrequency, - int stopFrequency, - int attenuation, - WindowType windowType, - boolean forceOddLength) + public static float[] getLowPass(int sampleRate, int passFrequency, int stopFrequency, int attenuation, + WindowType windowType, boolean forceOddLength) { if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2)) { - throw new IllegalArgumentException("FilterFactory - low pass " - + "filter pass frequency [" + passFrequency + "] must be " - + "less than the stop frequency [" + stopFrequency + "] " - + "and must be less than half [" + (int) (sampleRate / 2) + - "] of the sample rate [" + sampleRate + "]"); + throw new IllegalArgumentException("FilterFactory - low pass filter pass frequency [" + passFrequency + + "] must be less than the stop frequency [" + stopFrequency + "] and must be less than half [" + + (int)(sampleRate / 2) + "] of the sample rate [" + sampleRate + "]"); } - int tapCount = getTapCount(sampleRate, - passFrequency, - stopFrequency, - attenuation); + int tapCount = getTapCount(sampleRate, passFrequency, stopFrequency, attenuation); if(forceOddLength) { @@ -300,10 +256,7 @@ public class FilterFactory //inversion, its at the correct frequency long convertedCutoff = sampleRate / 2 - cutoff; - return invert(getSinc(sampleRate, - convertedCutoff, - filterLength, - windowType)); + return invert(getSinc(sampleRate, convertedCutoff, filterLength, windowType)); } public static float[] getHighPass(int sampleRate, @@ -378,16 +331,16 @@ public class FilterFactory long stop, int attenuation) { - double frequency = ((double) stop - (double) pass) / (double) sampleRate; + double frequency = ((double)stop - (double)pass) / (double)sampleRate; - return (int) (Math.round((double) attenuation / (22.0d * frequency))); + return (int)(Math.round((double)attenuation / (22.0d * frequency))); } public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order, int passFrequency, int attenuation, WindowType windowType) { - int decimationRate = (int) (sampleRate / decimatedRate); + int decimationRate = (int)(sampleRate / decimatedRate); return new ComplexPrimeCICDecimate(decimationRate, order, passFrequency, attenuation, windowType); @@ -415,7 +368,7 @@ public class FilterFactory + "integer multiple of sample rate"); } - int decimation = (int) (sampleRate / decimatedRate); + int decimation = (int)(sampleRate / decimatedRate); //Decimation rates below 20 will use single stage polyphase filter if(decimation < 20) @@ -448,7 +401,7 @@ public class FilterFactory { rates = new int[2]; - int stage2 = (int) (decimation / stage1); + int stage2 = (int)(decimation / stage1); if(stage1 > stage2) { @@ -499,11 +452,11 @@ public class FilterFactory private static Set getFactors(int value) { Set factors = new TreeSet(); - + /* Brute force */ for(int x = 1; x <= value; x++) { - int remainder = (int) (value / x); + int remainder = (int)(value / x); if(remainder * x == value) { @@ -530,25 +483,15 @@ public class FilterFactory * @return optimum integer decimation rate for the first stage decimation * filter */ - public static int getOptimalStageOneRate(int sampleRate, - int decimation, - long passFrequency, - long stopFrequency) + public static int getOptimalStageOneRate(int sampleRate, int decimation, long passFrequency, long stopFrequency) { double ratio = getBandwidthRatio(passFrequency, stopFrequency); - double numerator = 1.0d - ( - Math.sqrt((double) decimation * ratio / (2.0d - ratio))); + double numerator = 1.0d - (Math.sqrt((double)decimation * ratio / (2.0d - ratio))); double denominator = 2.0d - (ratio * (decimation + 1.0d)); - int retVal = (int) (2.0d * decimation * (numerator / denominator)); - -// mLog.info( "Optimal Stage 1 Decimation - rate [" + sampleRate + -// "] pass [" + passFrequency + -// "] bw ratio [" + ratio + -// "] optimal [" + retVal + -// "]" ); + int retVal = (int)(2.0d * decimation * (numerator / denominator)); return retVal; } @@ -564,8 +507,8 @@ public class FilterFactory { assert (passFrequency < stopFrequency); - return ((double) (stopFrequency - passFrequency) / - (double) stopFrequency); + return ((double)(stopFrequency - passFrequency) / + (double)stopFrequency); } /** @@ -576,14 +519,11 @@ public class FilterFactory * @param outputSampleRate * @return */ - public static float[] getCICCleanupFilter(int outputSampleRate, - int passFrequency, - int attenuation, - WindowType window) + public static float[] getCICCleanupFilter(int outputSampleRate, int passFrequency, int attenuation, WindowType window) { int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500, attenuation); - + /* Make tap count odd */ if(taps % 2 == 0) { @@ -600,7 +540,7 @@ public class FilterFactory //Transfer the IDFT results to the odd length return array float[] coefficients = new float[taps]; - int middleCoefficient = (int) (taps / 2); + int middleCoefficient = (int)(taps / 2); //Bin 0 of the idft is our center coefficient coefficients[middleCoefficient] = frequencyResponse[0]; @@ -621,26 +561,22 @@ public class FilterFactory return coefficients; } - public static float[] getCICResponseArray(int sampleRate, - int frequency, - int length) + public static float[] getCICResponseArray(int sampleRate, int frequency, int length) { float[] cicArray = new float[length * 2]; - int binCount = (int) ((Math.round( - (double) frequency / (double) sampleRate * 2.0d * (double) length))); + int binCount = (int)((Math.round( + (double)frequency / (double)sampleRate * 2.0d * (double)length))); cicArray[0] = 1.0f; - float unityResponse = (float) (Math.sin(1.0d / (double) length) / - (1.0d / (double) length)); + float unityResponse = (float)(Math.sin(1.0d / (double)length) / + (1.0d / (double)length)); for(int x = 1; x <= binCount; x++) { - /* Calculate unity response plus amplification for droop */ - float compensated = 1.0f + (unityResponse - - (float) (Math.sin((double) x / (double) length) / - ((double) x / (double) length))); + /* Calculate unity response plus amplification for droop */ + float compensated = 1.0f + (unityResponse - (float)(Math.sin((double)x / (double)length) / ((double)x / (double)length))); cicArray[x] = compensated; cicArray[length - x] = compensated; @@ -672,7 +608,6 @@ public class FilterFactory * @param samplesPerSymbol - number of samples per symbol * @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size * @param alpha - roll-off factor. - * * @return - filter coefficients */ public static float[] getRootRaisedCosine(int samplesPerSymbol, int symbolCount, float alpha) @@ -685,10 +620,10 @@ public class FilterFactory for(int x = 0; x < taps; x++) { - float index = (float) x - ((float) taps / 2.0f); + float index = (float)x - ((float)taps / 2.0f); - float x1 = (float) Math.PI * index / (float) samplesPerSymbol; - float x2 = 4.0f * alpha * index / (float) samplesPerSymbol; + float x1 = (float)Math.PI * index / (float)samplesPerSymbol; + float x2 = 4.0f * alpha * index / (float)samplesPerSymbol; float x3 = x2 * x2 - 1.0f; float numerator, denominator; @@ -697,17 +632,15 @@ public class FilterFactory { if(x != taps / 2) { - numerator = (float) Math.cos((1.0 + alpha) * x1) + - (float) Math.sin((1.0f - alpha) * x1) / - (4.0f * alpha * index / (float) samplesPerSymbol); + numerator = (float)Math.cos((1.0 + alpha) * x1) + + (float)Math.sin((1.0f - alpha) * x1) / (4.0f * alpha * index / (float)samplesPerSymbol); } else { - numerator = (float) Math.cos((1.0f + alpha) * x1) + - (1.0f - alpha) * (float) Math.PI / (4.0f * alpha); + numerator = (float)Math.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)Math.PI / (4.0f * alpha); } - denominator = x3 * (float) Math.PI; + denominator = x3 * (float)Math.PI; } else { @@ -720,14 +653,11 @@ public class FilterFactory x3 = (1.0f - alpha) * x1; x2 = (1.0f + alpha) * x1; - numerator = (float) (Math.sin(x2) * (1.0f + alpha) * Math.PI - - Math.cos(x3) * ((1.0 - alpha) * Math.PI * - (double) samplesPerSymbol) / (4 * alpha * index) + - Math.sin(x3) * (double) samplesPerSymbol * - (double) samplesPerSymbol / (4.0 * alpha * index * index)); + numerator = (float)(Math.sin(x2) * (1.0f + alpha) * Math.PI - Math.cos(x3) * ((1.0 - alpha) * Math.PI * + (double)samplesPerSymbol) / (4.0 * alpha * index) + Math.sin(x3) * (double)samplesPerSymbol * + (double)samplesPerSymbol / (4.0 * alpha * index * index)); - denominator = (float) (-32.0 * Math.PI * alpha * alpha * index / - (double) samplesPerSymbol); + denominator = (float)(-32.0 * Math.PI * alpha * alpha * index / (double)samplesPerSymbol); } coefficients[x] = 4.0f * alpha * numerator / denominator; @@ -764,4 +694,100 @@ public class FilterFactory return null; } + + /** + * Evaluates the filter to determine the magnitude response (dB) at the specified frequency. + * + * @param filter to evaluate + * @param frequency to calculate (0 <> 0.5) + * @return magnitude frequency response in decibels + */ + public static double evaluate(float[] filter, double frequency) + { + double real = 0.0; + double imag = 0.0; + + for(int x = 0; x < filter.length; x++) + { + real += filter[x] * Math.cos(Math.PI * frequency * (double)x); + imag += filter[x] * Math.sin(Math.PI * frequency * (double)x); + } + + return decibel(real, imag); + } + + public static double decibel(double real, double imag) + { + return (float)(10.0 * Math.log10(Math.pow(real, 2.0) + Math.pow(imag, 2.0))); + } + + public static float[] getChannelizer(int channelBandwidth, int channels, int tapsPerChannel, WindowType windowType) + { + int sampleRate = channelBandwidth * channels * 2; + int filterLength = channels * tapsPerChannel; + + double objective = -6.0; + int cutoff = channelBandwidth; + int increment = (int)((double)channelBandwidth * .1); + + float[] taps = null; + double response = 0.0; + + while(increment > 1) + { + taps = FilterFactory.getLowPass(sampleRate, cutoff, filterLength, windowType); + + double responseToCompare = FilterFactory.evaluate(taps, (double)channelBandwidth / (double)sampleRate ); + + mLog.debug("Comparing: " + responseToCompare + " Previous:" + response + " Cutoff:" + cutoff + " Increment:" + increment); + + double a = Math.abs(objective - responseToCompare); + double b = Math.abs(objective - response); + + if(a == b) + { + cutoff -= increment; + } + else if(a < b) + { + response = responseToCompare; + cutoff -= increment; + } + else + { + //reset the cutoff + if(cutoff < channelBandwidth) + { + cutoff += increment; + } + + increment /= 2; + + if(increment == 1) + { + taps = FilterFactory.getLowPass(sampleRate, cutoff, filterLength, windowType); + } + } + } + + if(response < objective) + { + mLog.debug("Checking ..."); + float[] tapsToCompare = FilterFactory.getLowPass(sampleRate, cutoff + 1, filterLength, windowType); + double responseToCompare = FilterFactory.evaluate(tapsToCompare, (double)channelBandwidth / (double)sampleRate ); + + if(Math.abs(objective - responseToCompare) < Math.abs(objective - response)) + { + mLog.debug("Using Cutoff:" + cutoff + 1); + taps = tapsToCompare; + } + else + { + mLog.debug("Staying at cutoff: " + cutoff + " And Response at 12500 of:" + response); + } + } + + + return taps; + } } diff --git a/src/dsp/filter/Window.java b/src/dsp/filter/Window.java index 72e846b4..b3d00845 100644 --- a/src/dsp/filter/Window.java +++ b/src/dsp/filter/Window.java @@ -1,234 +1,456 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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 + * 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.Arrays; /** - * Window creator - * - * Blackman, Hamming and Hanning windows - * based on windowing methods described at: - * http://www.mstarlabs.com/dsp/goertzel/goertzel.html - * and - * Understanding Digital Signal Processing 3e, Lyons, p.91 - * and - * Digital Filters for Everyone, Allred, p. 132 - * + * Window factory and corresponding utility methods. */ public class Window { - public static double[] getWindow( WindowType type, int length ) + private final static Logger mLog = LoggerFactory.getLogger(Window.class); + + private static final double TWO_PI = Math.PI * 2.0d; + private static final double FOUR_PI = Math.PI * 4.0d; + private static final double SIX_PI = Math.PI * 6.0d; + private static final double EIGHT_PI = Math.PI * 8.0d; + + /** + * Creates a window of the specified type and length. + * + * @param type of window + * @param length of window + * @return window of the specified type and length + */ + public static double[] getWindow(WindowType type, int length) { - switch( type ) - { - case BLACKMAN: - return getBlackmanWindow( length ); - case COSINE: - return getCosineWindow( length ); - case HAMMING: - return getHammingWindow( length ); - case HANNING: - return getHanningWindow( length ); - case NONE: - default: - return getRectangularWindow( length ); - } + switch(type) + { + case BLACKMAN: + return getBlackman(length); + case BLACKMAN_HARRIS_4: + return getBlackmanHarris4(length); + case BLACKMAN_HARRIS_7: + return getBlackmanHarris7(length); + case BLACKMAN_NUTALL: + return getBlackmanNutall(length); + case COSINE: + return getCosine(length); + case FLAT_TOP: + return getFlatTop(length); + case HAMMING: + return getHamming(length); + case HANN: + return getHann(length); + case NUTALL: + return getNutall(length); + case NONE: + default: + return getRectangular(length); + } } - public static double[] getRectangularWindow( int length ) + /** + * Creates an all-pass or rectangular window. + * + * @param length of the window + * @return rectangular window + */ + public static double[] getRectangular(int length) { - double[] coefficients = new double[ length ]; + double[] coefficients = new double[length]; - Arrays.fill( coefficients, 1.0d ); - - return coefficients; - } - - public static double[] getCosineWindow( int length ) - { - double[] coefficients = new double[ length ]; + Arrays.fill(coefficients, 1.0d); - - if( length % 2 == 0 ) //Even length - { - int half = (int)( ( length - 1 ) / 2 ); - - for( int x = -half; x < length / 2 + 1; x++ ) - { - coefficients[ x + half ] = Math.cos( - ( (double)x * Math.PI ) / ( (double)length + 1.0d ) ); - } - } - else //Odd length - { - int half = (int) length / 2; - - for( int x = -half; x < half + 1; x++ ) - { - coefficients[ x + half ] = Math.cos( - ( (double)x * Math.PI ) / ( (double)length + 1.0d ) ); - } - } - - return coefficients; + return coefficients; } - public static double[] getBlackmanWindow( int length ) + /** + * Creates a cosine window of the specified length. + * + * @param length of the window + * @return cosine window + */ + public static double[] getCosine(int length) { - double[] coefficients = new double[ length ]; - - for( int x = 0; x < length; x++ ) - { - coefficients[ x ] = .426591D - - ( .496561D * Math.cos( ( Math.PI * 2.0D * (double)x ) / (double)( length - 1 ) ) + - ( .076848D * Math.cos( ( Math.PI * 4.0D * (double)x ) / (double)( length - 1 ) ) ) ); - } + double[] coefficients = new double[length]; - return coefficients; - } - - public static double[] getHammingWindow( int length ) - { - double[] coefficients = new double[ length ]; - - if( length % 2 == 0 ) //Even length - { - for( int x = 0; x < length; x++ ) - { - coefficients[ x ] = .54D - ( .46D * - Math.cos( ( Math.PI * ( 2.0D * (double)x + 1.0d ) ) / (double)( length - 1 ) ) ); - } - } - else //Odd length - { - for( int x = 0; x < length; x++ ) - { - coefficients[ x ] = .54D - ( .46D * - Math.cos( ( 2.0D * Math.PI * (double)x ) / (double)( length - 1 ) ) ); - } - } + if(length % 2 == 0) //Even length + { + int half = (int)((length - 1) / 2); - return coefficients; + for(int x = -half; x < length / 2 + 1; x++) + { + coefficients[x + half] = Math.cos( + ((double)x * Math.PI) / ((double)length + 1.0d)); + } + } + else //Odd length + { + int half = (int)length / 2; + + for(int x = -half; x < half + 1; x++) + { + coefficients[x + half] = Math.cos( + ((double)x * Math.PI) / ((double)length + 1.0d)); + } + } + + return coefficients; } - public static double[] getHanningWindow( int length ) + /** + * Creates a 3-term Blackman window. + * + * @param length of the window + * @return Blackman window + */ + public static double[] getBlackman(int length) { - double[] coefficients = new double[ length ]; + double[] coefficients = new double[length]; - if( length % 2 == 0 ) //Even length - { - for( int x = 0; x < length; x++ ) - { - coefficients[ x ] = .5D - ( .5D * - Math.cos( ( Math.PI * ( 2.0D * (double)x + 1 ) ) / (double)( length - 1 ) ) ); - } - } - else //Odd length - { - for( int x = 0; x < length; x++ ) - { - coefficients[ x ] = .5D - ( .5D * - Math.cos( ( 2.0D * Math.PI * (double)x ) / (double)( length - 1 ) ) ); - } - } + double denominator = length - 1; + + double a0 = 0.426590713672; + double a1 = 0.496560619089; + double a2 = 0.0768486672399; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * Math.cos((TWO_PI * (double)x) / denominator) + + (a2 * Math.cos((FOUR_PI * (double)x) / denominator))); + } + + return coefficients; + } + + /** + * Creates a 4-term Nutall window + * + * @param length of the window + * @return Nutall window + */ + public static double[] getNutall(int length) + { + double[] coefficients = new double[length]; + + double denominator = length - 1; + double a0 = 0.355768; + double a1 = 0.487396; + double a2 = 0.144232; + double a3 = 0.012604; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * Math.cos(TWO_PI * (double)x / denominator)) + + (a2 * Math.cos(FOUR_PI * (double)x / denominator)) - + (a3 * Math.cos(SIX_PI * (double)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Nutall window + * + * @param length of the window + * @return Blackman-Nutall window + */ + public static double[] getBlackmanNutall(int length) + { + double[] coefficients = new double[length]; + + double denominator = length - 1; + double a0 = 0.3635819; + double a1 = 0.4891775; + double a2 = 0.1365995; + double a3 = 0.0106411; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * Math.cos(TWO_PI * (double)x / denominator)) + + (a2 * Math.cos(FOUR_PI * (double)x / denominator)) - + (a3 * Math.cos(SIX_PI * (double)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static double[] getBlackmanHarris4(int length) + { + double[] coefficients = new double[length]; + + double denominator = length - 1; + double a0 = 0.35875; + double a1 = 0.48829; + double a2 = 0.14128; + double a3 = 0.01168; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * Math.cos(TWO_PI * (double)x / denominator)) + + (a2 * Math.cos(FOUR_PI * (double)x / denominator)) - + (a3 * Math.cos(SIX_PI * (double)x / denominator)); + } + + return coefficients; + } + + /** + * Creates a 7-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static double[] getBlackmanHarris7(int length) + { + double[] coefficients = new double[length]; + + double denominator = length - 1; + + double a0 = 0.27105140069342; + double a1 = 0.43329793923448; + double a2 = 0.21812299954311; + double a3 = 0.06592544638803; + double a4 = 0.01081174209837; + double a5 = 0.00077658482522; + double a6 = 0.00001388721735; - return coefficients; + for(int x = 0; x < length; x++) + { + double w = TWO_PI * (double)x / denominator; + + coefficients[x] = a0 - + (a1 * Math.cos(w)) + + (a2 * Math.cos(2.0 * w)) - + (a3 * Math.cos(3.0 * w)) + + (a4 * Math.cos(4.0 * w)) - + (a5 * Math.cos(5.0 * w)) + + (a6 * Math.cos(6.0 * w)); + } + + return coefficients; + } + + /** + * Creates a Hamming window + * + * @param length of the window + * @return Hamming window + */ + public static double[] getHamming(int length) + { + double[] coefficients = new double[length]; + + if(length % 2 == 0) //Even length + { + for(int x = 0; x < length; x++) + { + coefficients[x] = .54D - (.46D * + Math.cos((Math.PI * (2.0D * (double)x + 1.0d)) / (double)(length - 1))); + } + } + else //Odd length + { + for(int x = 0; x < length; x++) + { + coefficients[x] = .54D - (.46D * + Math.cos((2.0D * Math.PI * (double)x) / (double)(length - 1))); + } + } + + return coefficients; + } + + /** + * Creates a Hann window + * + * @param length of the window + * @return Hann window + */ + public static double[] getHann(int length) + { + double[] coefficients = new double[length]; + + if(length % 2 == 0) //Even length + { + for(int x = 0; x < length; x++) + { + coefficients[x] = .5D - (.5D * + Math.cos((Math.PI * (2.0D * (double)x + 1)) / (double)(length - 1))); + } + } + else //Odd length + { + for(int x = 0; x < length; x++) + { + coefficients[x] = .5D - (.5D * + Math.cos((2.0D * Math.PI * (double)x) / (double)(length - 1))); + } + } + + + return coefficients; + } + + /** + * Creates a 4-term Blackman-Harris window + * + * @param length of the window + * @return Blackman-Harris window + */ + public static double[] getFlatTop(int length) + { + double[] coefficients = new double[length]; + + double denominator = length - 1; + + double a0 = 0.215578948; + double a1 = 0.41663158; + double a2 = 0.277263158; + double a3 = 0.083578947; + double a4 = 0.006947368; + + for(int x = 0; x < length; x++) + { + coefficients[x] = a0 - + (a1 * Math.cos(TWO_PI * (double)x / denominator)) + + (a2 * Math.cos(FOUR_PI * (double)x / denominator)) - + (a3 * Math.cos(SIX_PI * (double)x / denominator)) + + (a4 * Math.cos(EIGHT_PI * (double)x / denominator)); + } + + return coefficients; + } + + + private static double getKaiserBeta(double attenuation) + { + if(attenuation > 50.0) + { + return 0.1102 * (attenuation - 8.7); + } + else if(attenuation >= 21.0) + { + return (0.5842 * Math.pow(attenuation - 21.0, 0.4)) + (0.07886 * (attenuation - 21.0)); + } + else + { + return 0.0; + } } /** * Apply the window against an array of float-type samples */ - public static float[] apply( double[] coefficients, float[] samples ) + public static float[] apply(double[] coefficients, float[] samples) { - for( int x = 0; x < coefficients.length; x++ ) - { - samples[ x ] = (float)( samples[ x ] * coefficients[ x ] ); - } - - return samples; - } - - public static float[] apply( WindowType type, float[] samples ) - { - double[] coefficients = getWindow( type, samples.length ); - - return apply( coefficients, samples ); - } - - /** - * Apply the window against an array of float-type samples - */ - public static double[] apply( double[] coefficients, double[] samples ) - { - for( int x = 0; x < coefficients.length; x++ ) - { - samples[ x ] = samples[ x ] * coefficients[ x ]; - } - - return samples; + for(int x = 0; x < coefficients.length; x++) + { + samples[x] = (float)(samples[x] * coefficients[x]); + } + + return samples; } - public static double[] apply( WindowType type, double[] samples ) + /** + * Apply the window type against the float array of samples + * + * @param type of window to use + * @param samples to be windowed + * @return windowed samples + */ + public static float[] apply(WindowType type, float[] samples) { - double[] coefficients = getWindow( type, samples.length ); - - return apply( coefficients, samples ); + double[] coefficients = getWindow(type, samples.length); + + return apply(coefficients, samples); } - - - + + /** + * Apply the window against an array of double-type samples + */ + public static double[] apply(double[] coefficients, double[] samples) + { + for(int x = 0; x < coefficients.length; x++) + { + samples[x] = samples[x] * coefficients[x]; + } + + return samples; + } + + /** + * Apply the window type against the double array of samples + * + * @param type of window to use + * @param samples to be windowed + * @return windowed samples + */ + public static double[] apply(WindowType type, double[] samples) + { + double[] coefficients = getWindow(type, samples.length); + + return apply(coefficients, samples); + } + /** * Window types */ public enum WindowType { - BLACKMAN, COSINE, HAMMING, HANNING, NONE; - } - - /** - * Utility to log the double arrays - */ - public static String arrayToString( double[] array, boolean breaks ) - { - StringBuilder sb = new StringBuilder(); - for( int x = 0; x < array.length; x++ ) - { - sb.append( x + ":" + array[ x ] ); + BLACKMAN("Blackman"), + BLACKMAN_HARRIS_4("Blackman-Harris 4"), + BLACKMAN_HARRIS_7("Blackman-Harris 7"), + BLACKMAN_NUTALL("Blackman-Nutall"), + COSINE("Cosine"), + FLAT_TOP("Flat Top"), + HAMMING("Hamming"), + HANN("Hann"), + NUTALL("Nutall"), + NONE("None"); - if( breaks ) - { - sb.append( "\n" ); - } - else - { - if( x % 8 == 7 ) - { - sb.append( "\n" ); - } - else - { - sb.append( "\t" ); - } - } - } - - return sb.toString(); - } + private String mLabel; + + WindowType(String label) + { + mLabel = label; + } + + public String toString() + { + return mLabel; + } + } } diff --git a/src/dsp/filter/channelizer/ChannelizerViewer.java b/src/dsp/filter/channelizer/ChannelizerViewer.java index 9ad53ca1..65392538 100644 --- a/src/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/dsp/filter/channelizer/ChannelizerViewer.java @@ -18,7 +18,8 @@ ******************************************************************************/ package dsp.filter.channelizer; -import dsp.filter.FilterFactory; +import dsp.filter.*; +import dsp.filter.Window; import dsp.filter.design.FilterDesignException; import dsp.filter.fir.FIRFilterSpecification; import dsp.filter.fir.remez.RemezFIRFilterDesigner; @@ -184,48 +185,12 @@ public class ChannelizerViewer extends JFrame private float[] getFilter() { - int symbolRate = 4800; - int samplesPerSymbol = 2; - int symbolCount = 14; - - //Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol -// float alpha = ((float)CHANNEL_BANDWIDTH / (float)(symbolRate * samplesPerSymbol)) - 1.0f; - - float alpha = 0.8f; - -// float[] taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol * mChannelCount, symbolCount, alpha); - float[] taps = null; - - FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder() - .sampleRate(CHANNEL_BANDWIDTH * 2 * mChannelCount) - .gridDensity(16) - .passBandAmplitude(1.0) - .passBandCutoff(11900) - .passBandRipple(0.01) - .stopBandAmplitude(0.0) - .stopBandRipple(0.001) - .stopBandStart(13100) - .build(); - - try - { - RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); - - if(designer.isValid()) - { - taps = designer.getImpulseResponse(); - } - } - catch(FilterDesignException fde) - { - mLog.error("Filter design error", fde); - } - + float[] taps = FilterFactory.getChannelizer(CHANNEL_BANDWIDTH, mChannelCount, 16, Window.WindowType.BLACKMAN_HARRIS_7); StringBuilder sb = new StringBuilder(); sb.append("\nPolyphase Channelizer\n"); sb.append("Sample Rate:" + mSampleRate + " Channels:" + mChannelCount + " Channel Rate:" + CHANNEL_BANDWIDTH + "\n"); - sb.append("Alpha: " + alpha + " Tap Count:" + taps.length + "\n"); + sb.append("Tap Count:" + taps.length + "\n"); sb.append("Channel:" + mToneFrequency); mLog.debug(sb.toString()); @@ -256,7 +221,7 @@ public class ChannelizerViewer extends JFrame { setLayout(new MigLayout("insets 0 0 0 0", "fill", "fill")); - int channelsPerRow = 16; + int channelsPerRow = 20; for(int x = 0; x < mChannelCount; x++) { @@ -346,7 +311,7 @@ public class ChannelizerViewer extends JFrame public static void main(String[] args) { - int channelCount = 4; + int channelCount = 10; final ChannelizerViewer frame = new ChannelizerViewer(channelCount); diff --git a/src/dsp/filter/channelizer/PolyphaseChannelizer2.java b/src/dsp/filter/channelizer/PolyphaseChannelizer2.java index a95a8e7e..5fd700c2 100644 --- a/src/dsp/filter/channelizer/PolyphaseChannelizer2.java +++ b/src/dsp/filter/channelizer/PolyphaseChannelizer2.java @@ -19,6 +19,7 @@ package dsp.filter.channelizer; import dsp.filter.FilterFactory; +import dsp.filter.Window; import dsp.filter.fir.real.RealFIRFilter; import dsp.mixer.Oscillator; import org.jtransforms.fft.FloatFFT_1D; @@ -167,22 +168,18 @@ public class PolyphaseChannelizer2 implements Listener int symbolRate = 4800; int samplesPerSymbol = 2; - int symbolCount = 4; - int channels = 10; + int channels = 800; int channelBandwidth = 12500; int sampleRate = channels * channelBandwidth; - //Alpha is the residual channel bandwidth left over from the symbol rate and samples per symbol - float alpha = ((float)channelBandwidth / (float)(symbolRate * samplesPerSymbol)) - 1.0f; - - float[] taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol * channels, symbolCount, alpha); + float[] taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 ); int channelCenter = (int)(12500 * 1.5); StringBuilder sb = new StringBuilder(); sb.append("\nPolyphase Channelizer\n"); sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n"); - sb.append("Alpha: " + alpha + " Tap Count:" + taps.length + "\n"); + sb.append("Tap Count:" + taps.length + "\n"); sb.append("Channel:" + channelCenter); mLog.debug(sb.toString()); diff --git a/src/dsp/filter/design/FilterView.java b/src/dsp/filter/design/FilterView.java index 12191462..bfec7971 100644 --- a/src/dsp/filter/design/FilterView.java +++ b/src/dsp/filter/design/FilterView.java @@ -1,13 +1,5 @@ package dsp.filter.design; -import org.jtransforms.fft.FloatFFT_1D; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dsp.filter.FilterFactory; -import dsp.filter.Window.WindowType; -import dsp.filter.fir.FIRFilterSpecification; -import dsp.filter.fir.remez.RemezFIRFilterDesigner; import gui.control.CurveFittedAreaChart; import javafx.collections.ObservableList; import javafx.scene.chart.NumberAxis; @@ -16,156 +8,157 @@ import javafx.scene.chart.XYChart.Series; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; +import org.jtransforms.fft.FloatFFT_1D; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FilterView extends BorderPane { - private final static Logger mLog = LoggerFactory.getLogger( FilterView.class ); + private final static Logger mLog = LoggerFactory.getLogger(FilterView.class); - private CurveFittedAreaChart mImpulseChart; - private NumberAxis mImpulseXAxis; - private NumberAxis mImpulseYAxis; - private CurveFittedAreaChart mDFTChart; - private NumberAxis mDFTXAxis; - private NumberAxis mDFTYAxis; - private static final int FFT_SIZE = 4096; - private FloatFFT_1D mFFT = new FloatFFT_1D( FFT_SIZE ); - - public FilterView( float[] taps ) - { - init(); + private CurveFittedAreaChart mImpulseChart; + private NumberAxis mImpulseXAxis; + private NumberAxis mImpulseYAxis; + private CurveFittedAreaChart mDFTChart; + private NumberAxis mDFTXAxis; + private NumberAxis mDFTYAxis; + private static final int FFT_SIZE = 4096; + private FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE); - if( taps != null ) - { - setImpulseTaps( taps ); + public FilterView(float[] taps) + { + init(); - float[] dft = new float[ FFT_SIZE ]; - System.arraycopy( taps, 0, dft, 0, taps.length ); - - mFFT.realForward( dft ); - - setDFTTaps( convertDFTToDecibel( dft, taps.length ) ); - } - } - - private void init() - { - HBox hbox = new HBox(); - - mImpulseXAxis = new NumberAxis( 0.0, 0.5, 1.0 ); - mImpulseYAxis = new NumberAxis( -0.2, 1.2, .1 ); - - mImpulseChart = new CurveFittedAreaChart( mImpulseXAxis, mImpulseYAxis ); - mImpulseChart.setLegendVisible( false ); - mImpulseChart.setHorizontalGridLinesVisible( false ); - mImpulseChart.setVerticalGridLinesVisible(false); + if(taps != null) + { + setImpulseTaps(taps); + + float[] dft = new float[FFT_SIZE]; + System.arraycopy(taps, 0, dft, 0, taps.length); + + mFFT.realForward(dft); + + setDFTTaps(convertDFTToDecibel(dft, taps.length)); + } + } + + private void init() + { + HBox hbox = new HBox(); + + mImpulseXAxis = new NumberAxis(0.0, 0.5, 1.0); + mImpulseYAxis = new NumberAxis(-0.2, 1.2, .1); + + mImpulseChart = new CurveFittedAreaChart(mImpulseXAxis, mImpulseYAxis); + mImpulseChart.setLegendVisible(false); + mImpulseChart.setHorizontalGridLinesVisible(false); + mImpulseChart.setVerticalGridLinesVisible(false); mImpulseChart.setAlternativeColumnFillVisible(false); mImpulseChart.setAlternativeRowFillVisible(false); - - String css = FilterView.class.getResource( "FilterView.css" ).toExternalForm(); - mImpulseChart.getStylesheets().add( css ); - hbox.getChildren().add( mImpulseChart ); - HBox.setHgrow( mImpulseChart, Priority.ALWAYS ); - - mDFTXAxis = new NumberAxis( 0.0, FFT_SIZE / 2, FFT_SIZE / 8 ); - mDFTYAxis = new NumberAxis( -200.0, 10.0, 10.0 ); - - mDFTChart = new CurveFittedAreaChart( mDFTXAxis, mDFTYAxis ); - mDFTChart.setLegendVisible( false ); - mDFTChart.setHorizontalGridLinesVisible( false ); - mDFTChart.setVerticalGridLinesVisible(false); + String css = FilterView.class.getResource("FilterView.css").toExternalForm(); + mImpulseChart.getStylesheets().add(css); + + hbox.getChildren().add(mImpulseChart); + HBox.setHgrow(mImpulseChart, Priority.ALWAYS); + + mDFTXAxis = new NumberAxis(0.0, FFT_SIZE / 2, FFT_SIZE / 8); + mDFTYAxis = new NumberAxis(-200.0, 50.0, 10.0); + + mDFTChart = new CurveFittedAreaChart(mDFTXAxis, mDFTYAxis); + mDFTChart.setLegendVisible(false); + mDFTChart.setHorizontalGridLinesVisible(false); + mDFTChart.setVerticalGridLinesVisible(false); mDFTChart.setAlternativeColumnFillVisible(false); mDFTChart.setAlternativeRowFillVisible(false); - mDFTChart.getStylesheets().add( css ); - - hbox.getChildren().add( mDFTChart ); - HBox.setHgrow( mDFTChart, Priority.ALWAYS ); + mDFTChart.getStylesheets().add(css); - setCenter( hbox ); - } - - public void setImpulseTaps( double[] taps ) - { - float[] convertedTaps = new float[ taps.length ]; - - for( int x = 0; x < taps.length; x++ ) - { - convertedTaps[ x ] = (float)taps[ x ]; - } - - setImpulseTaps( convertedTaps ); - } - - public void setImpulseTaps( float[] taps ) - { - double smallest = 0.0; - double largest = 0.0; - - final XYChart.Series series = new XYChart.Series<>(); - - for( int x = 0; x < taps.length; x++ ) + hbox.getChildren().add(mDFTChart); + HBox.setHgrow(mDFTChart, Priority.ALWAYS); + + setCenter(hbox); + } + + public void setImpulseTaps(double[] taps) + { + float[] convertedTaps = new float[taps.length]; + + 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 ] ) ); + convertedTaps[x] = (float)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 ); + + setImpulseTaps(convertedTaps); + } + + public void setImpulseTaps(float[] taps) + { + double smallest = 0.0; + double largest = 0.0; + + final XYChart.Series 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 ) + list.add(series); + } + + public void setDFTTaps(float[] taps) { - float[] decibels = new float[ dft.length / 2 ]; + double smallest = 0.0; - 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 ] ) ) ); - } + final XYChart.Series series = new XYChart.Series<>(); - return decibels; + 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] = 10.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 index 2ea1f536..8a6a8f91 100644 --- a/src/dsp/filter/design/FilterViewer.java +++ b/src/dsp/filter/design/FilterViewer.java @@ -6,12 +6,15 @@ import dsp.filter.fir.FIRFilterSpecification; import dsp.filter.fir.remez.PolyphaseChannelizerFilterFactory; import dsp.filter.fir.remez.RemezFIRFilterDesigner; import dsp.filter.fir.remez.RemezFIRFilterDesignerWithLagrange; +import filter.Filter; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; + public class FilterViewer extends Application { private final static Logger mLog = LoggerFactory.getLogger(FilterViewer.class); @@ -85,27 +88,34 @@ public class FilterViewer extends Application float[] taps = null; -// taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING ); +// int tapCount = FilterFactory.getTapCount(10000000, 12500, 18750, 80); +// taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 ); - -// try -// { -// RemezFIRFilterDesignerWithLagrange designer = new RemezFIRFilterDesignerWithLagrange(specification); -//// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); +// int channels = 10; +// int channelRate = 12500; +// int cutoff = 8000; +// int sampleRate = channelRate * channels; +// int filterLength = channels * 7; // -// if(designer.isValid()) -// { -// taps = designer.getImpulseResponse(); -// } -// } -// catch(FilterDesignException fde) -// { -// mLog.error("Filter design error", fde); -// } +// taps = FilterFactory.getLowPass(sampleRate, cutoff, filterLength, Window.WindowType.BLACKMAN_HARRIS_7); +// +// double response = FilterFactory.evaluate(taps, (double)channelRate / (double)sampleRate ); +// +// mLog.debug("Taps:" + taps.length + " With response at [" + channelRate + "]: " + response); -// taps = PolyphaseChannelizerFilterFactory.getFilter(2000000, 12500, 0.3); + int channelBandwidth = 12500; + int channels = 4; + int tapsPerChannel = 20; - taps = FilterFactory.getSinc(10000000, 12500, 450, Window.WindowType.HANNING); + taps = FilterFactory.getChannelizer(channelBandwidth, channels, tapsPerChannel, Window.WindowType.BLACKMAN_HARRIS_4); + + double sampleRate = channelBandwidth * channels * 2; + + for(int x = 0; x < 25000; x += 100) + { + double toEval = (double)x / sampleRate; + mLog.debug("Response at [" + x + "] is:" + FilterFactory.evaluate(taps, toEval)); + } return taps; } diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java index 011ff83d..4f86d2c8 100644 --- a/src/module/decode/p25/P25_LSMDecoder.java +++ b/src/module/decode/p25/P25_LSMDecoder.java @@ -69,7 +69,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener super( aliasList ); mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass( - 48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f ); + 48000, 7250, 8000, 60, WindowType.HANN, true ), 1.0f ); mBasebandFilter.setListener( mStreamConverter ); diff --git a/src/module/demodulate/audio/DemodulatedAudioFilterModule.java b/src/module/demodulate/audio/DemodulatedAudioFilterModule.java index 4efb3d8b..a28ff3fd 100644 --- a/src/module/demodulate/audio/DemodulatedAudioFilterModule.java +++ b/src/module/demodulate/audio/DemodulatedAudioFilterModule.java @@ -53,7 +53,7 @@ public class DemodulatedAudioFilterModule extends Module implements IUnFilteredR //TODO: change this to a band pass filter mBandPassFilter = new RealFIRFilter_RB_RB(FilterFactory.getLowPass(48000, pass, stop, 60, - WindowType.HANNING, true), 1.0f); + WindowType.HANN, true), 1.0f); mDCFilter.setListener(mBandPassFilter); } diff --git a/src/spectrum/DFTProcessor.java b/src/spectrum/DFTProcessor.java index 795c3f2b..0f9e066c 100644 --- a/src/spectrum/DFTProcessor.java +++ b/src/spectrum/DFTProcessor.java @@ -70,7 +70,7 @@ public class DFTProcessor implements Listener, IFrequencyChangePr /* The Cosine and Hanning windows seem to offer the best spectral display * with minimal bin leakage/smearing */ - private WindowType mWindowType = Window.WindowType.HANNING; + private WindowType mWindowType = WindowType.BLACKMAN_HARRIS_7; private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize());