mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Updates build to 0.3.0 Beta 9
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -7,6 +7,7 @@
|
||||
<w>fleetsync</w>
|
||||
<w>icecast</w>
|
||||
<w>ident</w>
|
||||
<w>nutall</w>
|
||||
<w>overflowable</w>
|
||||
<w>polyphase</w>
|
||||
<w>remez</w>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package dsp.filter;
|
||||
|
||||
@@ -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<Integer> getFactors(int value)
|
||||
{
|
||||
Set<Integer> factors = new TreeSet<Integer>();
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package dsp.filter;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<ComplexBuffer>
|
||||
|
||||
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());
|
||||
|
||||
@@ -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<Number, Number> 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<Number, Number>( 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<Number,Number> 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<Number,Number>(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<Series<Number,Number>> list = mImpulseChart.getData();
|
||||
list.add( series );
|
||||
}
|
||||
|
||||
public void setDFTTaps( float[] taps )
|
||||
{
|
||||
double smallest = 0.0;
|
||||
|
||||
final XYChart.Series<Number, Number> 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<Number, Number>( x, taps[ x ] ) );
|
||||
}
|
||||
|
||||
ObservableList<Series<Number,Number>> 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<Number,Number> 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<Number,Number>(x, taps[x]));
|
||||
}
|
||||
|
||||
ObservableList<Series<Number,Number>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>, 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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user