Tuner Channel - uses new CIC prime decimation filter for decimating channel to 48k sample rate.

This commit is contained in:
dennis.sheirer
2015-04-07 01:10:10 +00:00
parent 90149fe600
commit d55d7609cb
6 changed files with 112 additions and 209 deletions

View File

@@ -114,16 +114,12 @@ public class P25Decoder extends Decoder
FrameSync.P25_PHASE1.getSync(), 64, false, mAliasList );
mNormalFramer.setListener( mMessageProcessor );
// mInvertedFramer = new P25MessageFramer(
// FrameSync.P25_PHASE1_INVERTED.getSync(), 64, true, mAliasList );
// mInvertedFramer.setListener( mMessageProcessor );
/* Setup demodulation chains based on sample type (real or complex) and
* modulation (C4FM or CQPSK) */
if( mSourceSampleType == SampleType.COMPLEX )
{
mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass(
48000, 6500, 7500, 48, WindowType.HANNING, true ), 4.0 );
48000, 7000, 7500, 60, WindowType.HAMMING, true ), 1.0 );
if( modulation == Modulation.CQPSK )
{
@@ -139,7 +135,6 @@ public class P25Decoder extends Decoder
mCQPSKDemodulator.setListener( mCQPSKSlicer );
mCQPSKSlicer.addListener( mNormalFramer );
// mCQPSKSlicer.addListener( mInvertedFramer );
}
else /* C4FM */
{
@@ -172,7 +167,6 @@ public class P25Decoder extends Decoder
mSymbolFilter.setListener( mC4FMSlicer );
mC4FMSlicer.addListener( mNormalFramer );
// mC4FMSlicer.addListener( mInvertedFramer );
}
mAudioOutput = new P25AudioOutput( resourceManager );

View File

@@ -262,12 +262,14 @@ public class ComplexCICDecimate extends ComplexFilter
mDecimation = decimation;
mCleanupFilter = new ComplexFIRFilter( FilterFactory
.getCICCleanupFilter( outputSampleRate,
stageCount,
1,
12000,
60,
WindowType.BLACKMAN ), 1.0d );
setGain();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering

View File

@@ -3,7 +3,9 @@ package dsp.filter;
import java.util.ArrayList;
import java.util.List;
import sample.Broadcaster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSample;
@@ -11,8 +13,11 @@ import sample.complex.ComplexSampleListener;
import sample.decimator.ComplexDecimator;
import dsp.filter.Window.WindowType;
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
public class ComplexPrimeCICDecimate
{
private final static Logger mLog =
LoggerFactory.getLogger( ComplexPrimeCICDecimate.class );
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
@@ -25,11 +30,11 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
public Broadcaster<ComplexSample> mBroadcaster =
new Broadcaster<ComplexSample>();
public Output mOutput;
private Output mOutput;
private Listener<ComplexSample> mListener;
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output.
@@ -46,7 +51,8 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
* @param decimation - overall decimation rate
* @param order - filter order
*/
public ComplexPrimeCICDecimate( int decimation, int order )
public ComplexPrimeCICDecimate( int decimation, int order,
int passFrequency, int attenuation, WindowType windowType )
{
assert( decimation <= 700 );
@@ -69,13 +75,12 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
mDecimatingStages.get( x - 1 ).addListener( stage );
}
}
mOutput = new Output( 48000, stageSizes.size(), decimation );
mOutput = new Output( 48000, order, decimation, passFrequency,
attenuation, windowType );
mDecimatingStages.get( mDecimatingStages.size() - 1 )
.addListener( mOutput );
mOutput.setListener( mBroadcaster );
}
public void dispose()
@@ -86,26 +91,22 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
}
mDecimatingStages.clear();
mBroadcaster.dispose();
mBroadcaster = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void addListener( Listener<ComplexSample> listener )
public void setListener( Listener<ComplexSample> listener )
{
mBroadcaster.addListener( listener );
mOutput.setListener( listener );
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener( Listener<ComplexSample> listener )
{
mBroadcaster.removeListener( listener );
mOutput.removeListener( listener );
}
/**
@@ -144,11 +145,8 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
/**
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
@Override
public void receive( ComplexBuffer sampleArray )
public void receive( float[] samples )
{
float[] samples = sampleArray.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
@@ -232,28 +230,27 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
private float mISum;
private float mQSum;
private int mSamplePointer;
private int mSamplePointer = 0;
private int mSize;
public Stage( int size )
{
mSize = size;
mISamples = new float[ size ];
mQSamples = new float[ size ];
mSize = size - 1;
mISamples = new float[ mSize ];
mQSamples = new float[ mSize ];
}
public void dispose()
{
mListener = null;
mISamples = null;
mQSamples = null;
}
public void receive( float i, float q )
{
/* Subtract the oldest sample and add back in the newest */
mISum = mISum - mISamples[ mSamplePointer ] + i;
mQSum = mQSum - mISamples[ mSamplePointer ] + q;
mQSum = mQSum - mQSamples[ mSamplePointer ] + q;
/* Overwrite the oldest sample with the newest */
mISamples[ mSamplePointer ] = i;
@@ -285,19 +282,22 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
*/
public class Output implements ComplexSampleListener
{
private int mStageCount;
private int mDecimation;
private int mOrder;
private double mGain = 1.0d;
private ComplexFilter mCleanupFilter;
public Output( int outputSampleRate, int stageCount, int decimation )
public Output( int outputSampleRate, int order, int decimation,
int passFrequency, int attenuation, WindowType windowType )
{
mStageCount = stageCount;
mOrder = order;
mDecimation = decimation;
mCleanupFilter = new ComplexFIRFilter( FilterFactory
.getCICCleanupFilter( outputSampleRate,
stageCount,
WindowType.BLACKMAN ), 1.0d );
mOrder,
passFrequency,
attenuation,
windowType ), 1.0d );
setGain();
}
@@ -359,10 +359,9 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
private void setGain()
{
/* decimation is NOT multiplied by delay since delay is 1 */
double gain = Math.pow( (double)mDecimation, (double)mStageCount );
double gain = Math.pow( (double)mDecimation, (double)mOrder );
mGain = 10.0d / gain;
mGain = 1.0d / gain;
}
}
}

View File

@@ -34,8 +34,8 @@ import dsp.filter.Window.WindowType;
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
@@ -381,99 +381,14 @@ public class FilterFactory
return (int)( Math.round( (double)attenuation / ( 22.0d * frequency ) ) );
}
public static ComplexFilter[] getDecimationFilters( int sampleRate,
int decimatedRate,
long passFrequency,
int attenuation,
WindowType windowType )
public static ComplexPrimeCICDecimate getDecimationFilter( int sampleRate,
int decimatedRate, int order, int passFrequency, int attenuation,
WindowType windowType )
{
ComplexFilter[] filters;
if( sampleRate == 96000 && decimatedRate == 48000 )
{
filters = new ComplexFilter[ 1 ];
filters[ 0 ] = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
}
else if( sampleRate == 192000 && decimatedRate == 48000 )
{
filters = new ComplexFilter[ 2 ];
filters[ 0 ] = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
filters[ 1 ] = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
}
else
{
filters = new ComplexFilter[ 1 ];
int decimationRate = (int)( sampleRate / decimatedRate );
int decimate = (int)( sampleRate / decimatedRate );
// if( decimate < 25 )
// {
filters[ 0 ] = new ComplexCICDecimate( decimate, 1, decimatedRate );
// }
// else
// {
// filters[ 0 ] = new ComplexCICDecimate( decimate, 3, decimatedRate );
// }
// int finalStopFrequency = decimatedRate - passFrequency;
//
// int[] rates = getPolyphaseDecimationRates( sampleRate,
// decimatedRate,
// passFrequency,
// finalStopFrequency );
//
// filters = new ComplexFilter[ rates.length ];
//
// int counter = 0;
// int currentSampleRate = sampleRate;
// int currentDecimatedRate = sampleRate;
//
// while( counter < rates.length )
// {
// currentDecimatedRate /= rates[ counter ];
//
// int stopFrequency = currentDecimatedRate- passFrequency;
//
// int proposedFilterLength = getTapCount( currentSampleRate,
// passFrequency,
// stopFrequency,
// attenuation );
//
// /* check filter length is multiple of decimation rate */
// int multiplier = (int)( proposedFilterLength / rates[ counter ] );
//
// /* increase filter length to the next multiple, if needed */
// if( multiplier * rates[ counter ] < proposedFilterLength )
// {
// multiplier++;
// }
//
// double[] filter = getLowPass( currentSampleRate,
// passFrequency,
// rates[counter] * multiplier,
// windowType );
//
// filters[ counter ] =
// new ComplexPolyphaseDecimationFilter( filter, rates[ counter ] );
//
// Log.info( "FilterFactory - PFD filter [" + ( counter + 1 ) + "/" + rates.length +
// "] overall rate [" + sampleRate +
// "] decimate [" + rates[ counter ] +
// "] filter taps [" + filter.length +
// "] rate [" + currentSampleRate +
// "] pass [" + passFrequency +
// "]" );
//
// currentSampleRate /= rates[ counter ];
// counter++;
// }
}
return filters;
return new ComplexPrimeCICDecimate( decimationRate, order,
passFrequency, attenuation, windowType );
}
/**
@@ -516,10 +431,10 @@ public class FilterFactory
int stage1 = findClosest( optimalStage1, factors );
mLog.info( "Decimation rate [" + decimation +
"] stage1 optimal [" + optimalStage1 +
"] stage1 actual [" + stage1 +
"]");
// mLog.info( "Decimation rate [" + decimation +
// "] stage1 optimal [" + optimalStage1 +
// "] stage1 actual [" + stage1 +
// "]");
if( stage1 == decimation || stage1 == 1 )
{
@@ -627,11 +542,11 @@ public class FilterFactory
int retVal = (int)( 2.0d * decimation * ( numerator / denominator ) );
mLog.info( "Optimal Stage 1 Decimation - rate [" + sampleRate +
"] pass [" + passFrequency +
"] bw ratio [" + ratio +
"] optimal [" + retVal +
"]" );
// mLog.info( "Optimal Stage 1 Decimation - rate [" + sampleRate +
// "] pass [" + passFrequency +
// "] bw ratio [" + ratio +
// "] optimal [" + retVal +
// "]" );
return retVal;
}
@@ -660,15 +575,14 @@ public class FilterFactory
* @return
*/
public static double[] getCICCleanupFilter( int outputSampleRate,
int stageCount,
int order,
int passFrequency,
int attenuation,
WindowType window )
{
int pass = (int)( outputSampleRate / 4 );
int stop = pass + (int)( (double)pass * 0.25d );
int attenuation = 60; //db
int taps = getTapCount( outputSampleRate, passFrequency, passFrequency + 1500,
attenuation );
int taps = getTapCount( outputSampleRate, pass, stop, attenuation );
/* Make tap count odd */
if( taps% 2 == 0 )
{
@@ -676,7 +590,7 @@ public class FilterFactory
}
double[] frequencyResponse =
getCICResponseArray( outputSampleRate, pass, taps, stageCount );
getCICResponseArray( outputSampleRate, passFrequency, taps, order );
//Apply Inverse DFT against frequency response unity values, leaving the
//IDFT bin results in the frequency response array
@@ -707,7 +621,7 @@ public class FilterFactory
public static double[] getCICResponseArray( int sampleRate,
int frequency,
int length,
int stageCount )
int order )
{
double[] cicArray = new double[ length * 2 ];
@@ -717,14 +631,14 @@ public class FilterFactory
cicArray[ 0 ] = 1.0d;
double unityResponse = Math.pow( Math.sin( 1.0d / (double)length ) /
( 1.0d / (double)length ), stageCount );
( 1.0d / (double)length ), order );
for( int x = 1; x <= binCount; x++ )
{
/* Calculate unity response plus amplification for droop */
double compensated = 1.0d + ( unityResponse -
Math.pow( ( Math.sin( (double)x / (double)length ) /
( (double)x / (double)length ) ), stageCount ) );
( (double)x / (double)length ) ), order ) );
cicArray[ x ] = compensated;
cicArray[ length - x ] = compensated;
@@ -821,4 +735,11 @@ public class FilterFactory
return coefficients;
}
public static void main( String[] args )
{
double[] coefficients = getCICCleanupFilter( 48000, 2, 60, 24000, WindowType.BLACKMAN );
System.out.println( Arrays.toString( coefficients ) );
}
}

View File

@@ -17,6 +17,9 @@
******************************************************************************/
package dsp.nbfm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.real.RealSampleBroadcaster;
@@ -28,6 +31,9 @@ import dsp.filter.Window.WindowType;
public class FilteringNBFMDemodulator implements Listener<ComplexSample>
{
private final static Logger mLog =
LoggerFactory.getLogger( FilteringNBFMDemodulator.class );
private RealSampleBroadcaster mBroadcaster = new RealSampleBroadcaster();
private ComplexFIRFilter mIQFilter;
private FloatFIRFilter mAudioFilter;

View File

@@ -37,8 +37,7 @@ import source.tuner.FrequencyChangeEvent.Attribute;
import util.Oscillator;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import decode.p25.P25Decoder;
import dsp.filter.ComplexFilter;
import dsp.filter.ComplexPrimeCICDecimate;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
@@ -49,15 +48,15 @@ public class TunerChannelSource extends ComplexSource
private final static Logger mLog =
LoggerFactory.getLogger( TunerChannelSource.class );
private static int sCHANNEL_RATE = 48000;
private static int sCHANNEL_PASS_FREQUENCY = 12500;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private LinkedTransferQueue<ComplexBuffer> mBuffer =
new LinkedTransferQueue<ComplexBuffer>();
private Tuner mTuner;
private TunerChannel mTunerChannel;
private Oscillator mSineWaveGenerator;
private ComplexFilter[] mDecimationFilters;
private ComplexPrimeCICDecimate mDecimationFilter;
private ComplexSampleAssembler mBufferAssembler =
new ComplexSampleAssembler( 3000 );
@@ -71,7 +70,7 @@ public class TunerChannelSource extends ComplexSource
public TunerChannelSource( ThreadPoolManager threadPoolManager,
Tuner tuner,
TunerChannel tunerChannel )
throws RejectedExecutionException, SourceException
throws RejectedExecutionException, SourceException
{
super( "Tuner Channel Source" );
@@ -118,11 +117,8 @@ public class TunerChannelSource extends ComplexSource
mBuffer.clear();
mBuffer = null;
for( ComplexFilter filter: mDecimationFilters )
{
filter.dispose();
}
mDecimationFilter.dispose();
mBufferAssembler.dispose();
@@ -160,8 +156,6 @@ public class TunerChannelSource extends ComplexSource
public void removeListener( Listener<List<ComplexSample>> listener )
{
mBufferAssembler.removeListener( listener );
mDecimationFilters[ mDecimationFilters.length - 1 ]
.setListener( null );
}
@Override
@@ -184,28 +178,18 @@ public class TunerChannelSource extends ComplexSource
{
/* Set the oscillator to the new frequency */
mSineWaveGenerator.setSampleRate( sampleRate );
/* Get new decimation filters */
mDecimationFilters = FilterFactory
.getDecimationFilters( sampleRate,
sCHANNEL_RATE,
sCHANNEL_PASS_FREQUENCY,
48, //dB attenuation
WindowType.HAMMING );
/* wire the filters together */
if( mDecimationFilters.length > 1 )
{
for( int x = 1; x < mDecimationFilters.length; x++ )
{
mDecimationFilters[ x - 1 ]
.setListener( mDecimationFilters[ x ] );
}
}
/* Get new decimation filter */
mDecimationFilter = FilterFactory
.getDecimationFilter( sampleRate,
CHANNEL_RATE,
2, //Order
CHANNEL_PASS_FREQUENCY,
60, //dB attenuation
WindowType.HAMMING );
/* re-add the original output listener */
mDecimationFilters[ mDecimationFilters.length - 1 ]
.setListener( mBufferAssembler );
mDecimationFilter.setListener( mBufferAssembler );
mTunerSampleRate = sampleRate;
}
@@ -265,28 +249,25 @@ public class TunerChannelSource extends ComplexSource
* and process them, causing delays and buffer refill */
mBuffer.drainTo( sampleBuffers, 4 );
for( ComplexBuffer sampleArray: sampleBuffers )
for( ComplexBuffer buffer: sampleBuffers )
{
float[] samples = sampleArray.getSamples();
float[] samples = buffer.getSamples();
float[] translated = new float[ samples.length ];
/* Perform frequency translation */
for( int x = 0; x < samples.length; x += 2 )
{
Float left = samples[ x ];
Float right = samples[ x + 1 ];
ComplexSample multiplier =
mSineWaveGenerator.nextComplex();
if( left != null && right != null )
{
/* perform frequency translation */
ComplexSample translated = ComplexSample.multiply(
mSineWaveGenerator.nextComplex(), left, right );
/* decimate the sample rate */
if( mDecimationFilters[ 0 ] != null )
{
mDecimationFilters[ 0 ].receive( translated );
}
}
translated[ x ] = ( samples[ x ] * multiplier.inphase() ) -
( samples[ x + 1 ] * multiplier.quadrature() );
translated[ x + 1 ] = ( samples[ x + 1 ] * multiplier.inphase() ) +
( samples[ x ] * multiplier.quadrature() );
}
mDecimationFilter.receive( translated );
}
sampleBuffers.clear();