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());