diff --git a/src/dsp/filter/cic/RealPrimeCICDecimate.java b/src/dsp/filter/cic/RealPrimeCICDecimate.java new file mode 100644 index 00000000..8e359cea --- /dev/null +++ b/src/dsp/filter/cic/RealPrimeCICDecimate.java @@ -0,0 +1,414 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2015 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.cic; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dsp.filter.FilterFactory; +import dsp.filter.Filters; +import dsp.filter.Window.WindowType; +import dsp.filter.fir.real.RealFIRFilter_RB_RB; +import dsp.filter.halfband.real.HalfBandFilter_RB_RB; +import sample.Listener; +import sample.decimator.RealDecimator; +import sample.real.RealBuffer; +import sample.real.RealSampleListener; +import sample.real.RealToRealBufferAssembler; + +public class RealPrimeCICDecimate +{ + private final static Logger mLog = + LoggerFactory.getLogger( RealPrimeCICDecimate.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, + 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359, + 367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463, + 467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593, + 599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 }; + + private ArrayList mDecimatingStages = + new ArrayList(); + + private DecimatingStage mFirstDecimatingStage; + + private Output mOutput; + + private int mOutputBufferSize = 2048; + + /** + * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and + * decimated float sample output. + * + * Implements the CIC filter described in Understanding Digital Signal + * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple + * decimating stages each with a prime factor decimation rate. Multiple + * stages are cascaded to achieve the overall decimation rate. + * + * This filter supports a maximum decimation rate of 700. This filter can + * be adapted to higher decimation rates by adding additional prime factors + * to the PRIMES array. + * + * @param decimation - overall decimation rate + * @param order - filter order + */ + public RealPrimeCICDecimate( int decimation, int order, int passFrequency, + int attenuation, WindowType windowType ) + { + this( decimation, order, passFrequency, attenuation, windowType, 2048 ); + } + + public RealPrimeCICDecimate( int decimation, int order, int passFrequency, + int attenuation, WindowType windowType, int outputBufferSize ) + { + assert( decimation <= 700 ); + + mOutputBufferSize = outputBufferSize; + + List stageSizes = getPrimeFactors( decimation ); + + for( int x = 0; x < stageSizes.size(); x++ ) + { + DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order ); + + mDecimatingStages.add( stage ); + + if( x == 0 ) + { + /* Reference to first stage -- will receive all samples */ + mFirstDecimatingStage = stage; + } + else + { + /* Wire the current stage to the previous stage */ + mDecimatingStages.get( x - 1 ).setListener( stage ); + } + } + + mOutput = new Output( 48000, passFrequency, attenuation, windowType, + outputBufferSize ); + + mDecimatingStages.get( mDecimatingStages.size() - 1 ) + .setListener( mOutput ); + } + + public void dispose() + { + for( DecimatingStage stage: mDecimatingStages ) + { + stage.dispose(); + } + + mDecimatingStages.clear(); + mDecimatingStages = null; + mFirstDecimatingStage = null; + + mOutput.dispose(); + mOutput = null; + } + + /** + * Adds a listener to receive the output of this CIC decimation filter + */ + public void setListener( Listener listener ) + { + mOutput.setListener( listener ); + } + + /** + * Removes listener from output of this CIC decimation filter + */ + public void removeListener() + { + mOutput.removeListener(); + } + + /** + * Calculates the prime factors of the decimation rate up to a maximum + * decimation rate of 700. If you wish to have decimation rates higher + * than 700, then add additional prime factors to the PRIMES array. + * + * @param decimation - integral decimation rate + * @return - ordered list (smallest to largest) of prime factors + */ + public static List getPrimeFactors( int decimation ) + { + ArrayList stages = new ArrayList(); + + int pointer = 0; + + while( decimation > 0 && pointer < PRIMES.length ) + { + int prime = PRIMES[ pointer ]; + + if( decimation % prime == 0 ) + { + stages.add( prime ); + + decimation /= prime; + } + else + { + pointer++; + } + } + + return stages; + } + + /** + * Primary input method for receiving real buffers. + */ + public void receive( RealBuffer buffer ) + { + if( mFirstDecimatingStage != null ) + { + for( float sample: buffer.getSamples() ) + { + mFirstDecimatingStage.receive( sample ); + } + } + } + + /** + * Decimating stage combines multiple CIC stages with a decimator. The + * number of stages is indicated by the order value and the size indicates + * the decimation rate of this stage. + */ + public class DecimatingStage implements RealSampleListener + { + private ArrayList mStages = new ArrayList(); + private Stage mFirstStage; + private RealDecimator mDecimator; + + public DecimatingStage( int size, int order ) + { + for( int x = 0; x < order; x++ ) + { + Stage stage; + + if( size == 2 ) + { + stage = new TwoStage(); + } + else + { + stage = new Stage( size ); + } + + mStages.add( stage ); + + if( x == 0 ) + { + mFirstStage = stage; + } + else + { + mStages.get( x - 1 ).setListener( stage ); + } + } + + mDecimator = new RealDecimator( size ); + + mStages.get( mStages.size() - 1 ).setListener( mDecimator ); + } + + public void dispose() + { + for( Stage stage: mStages ) + { + stage.dispose(); + } + + mStages.clear(); + mDecimator.dispose(); + mDecimator = null; + mFirstStage = null; + mStages = null; + } + + @Override + public void receive( float sample ) + { + mFirstStage.receive( sample ); + } + + public void setListener( RealSampleListener listener ) + { + mDecimator.setListener( listener ); + } + } + + /** + * Single non-decimating CIC stage component. Uses a circular buffer and a + * running average internally to implement the stage so that stage size has + * essentially no impact on the computational requirements of the stage + */ + public class Stage implements RealSampleListener + { + protected RealSampleListener mListener; + + private float[] mSamples; + + protected float mSum; + + private int mSamplePointer = 0; + private int mSize; + + protected float mGain; + + public Stage() + { + } + + public Stage( int size ) + { + mSize = size - 1; + + mSamples = new float[ mSize ]; + + mGain = 1.0f / (float)size; + } + + public void dispose() + { + mListener = null; + } + + public void receive( float sample ) + { + /* Subtract the oldest sample and add back in the newest */ + mSum = mSum - mSamples[ mSamplePointer ] + sample; + + /* Overwrite the oldest sample with the newest */ + mSamples[ mSamplePointer ] = sample; + + mSamplePointer++; + + if( mSamplePointer >= mSize ) + { + mSamplePointer = 0; + } + + if( mListener != null ) + { + mListener.receive( mSum * mGain ); + } + } + + public void setListener( RealSampleListener listener ) + { + mListener = listener; + } + } + + /** + * Size 2 stage that removes the unnecessary circular buffer management for + * a two-stage. + */ + public class TwoStage extends Stage + { + public TwoStage() + { + super(); + + mGain = 0.5f; + } + + public void receive( float sample ) + { + float sum = mSum + sample; + + mSum = sample; + + if( mListener != null ) + { + mListener.receive( sum * mGain ); + } + } + } + + /** + * Output adapter - applies gain correction, applies cleanup filter, casts + * double-valued samples into float-valued complex sample and sends the + * result to the registered listener. + */ + public class Output implements RealSampleListener + { + /* Decimated output buffers will contain 1024 samples */ + private RealToRealBufferAssembler mAssembler; + + private RealFIRFilter_RB_RB mCleanupFilter; + private HalfBandFilter_RB_RB mHalfBandFilter = new HalfBandFilter_RB_RB( + Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false ); + + public Output( int outputSampleRate, int passFrequency, int attenuation, + WindowType windowType, int outputBufferSize ) + { + mCleanupFilter = new RealFIRFilter_RB_RB( FilterFactory + .getCICCleanupFilter( outputSampleRate, + passFrequency, + attenuation, + windowType ), 0.4f ); + + mAssembler = new RealToRealBufferAssembler( outputBufferSize ); + mAssembler.setListener( mCleanupFilter ); + mCleanupFilter.setListener( mHalfBandFilter ); + + } + + public void dispose() + { + mAssembler.dispose(); + mCleanupFilter.dispose(); + mHalfBandFilter.dispose(); + } + + /** + * Receiver method for the output adapter to receive a filtered, + * decimated sample, apply gain correction, apply cleanup filtering + * and output the sample values as a complex sample. + */ + @Override + public void receive( float sample ) + { + mAssembler.receive( sample * 32.0f ); + } + + /** + * Adds a listener to receive output samples + */ + public void setListener( Listener listener ) + { + mHalfBandFilter.setListener( listener ); + } + + /** + * Removes the listener from receiving output samples + */ + public void removeListener() + { + mHalfBandFilter.removeListener(); + } + } +} diff --git a/src/dsp/fsk/LTRFSKDecoder.java b/src/dsp/fsk/LTRFSKDecoder.java index d7745bf0..118ea13f 100644 --- a/src/dsp/fsk/LTRFSKDecoder.java +++ b/src/dsp/fsk/LTRFSKDecoder.java @@ -17,29 +17,33 @@ ******************************************************************************/ package dsp.fsk; -import instrument.Instrumentable; -import instrument.tap.Tap; -import instrument.tap.TapGroup; -import instrument.tap.stream.BinaryTap; -import instrument.tap.stream.FloatTap; -import instrument.tap.stream.SymbolEventTap; - import java.util.ArrayList; import java.util.List; -import sample.Listener; -import sample.real.RealBuffer; import dsp.filter.Filters; import dsp.filter.FloatHalfBandFilter; import dsp.filter.FloatHalfBandNoDecimateFilter; import dsp.filter.LTRPulseShapingFilter; import dsp.filter.SquaringFilter; +import dsp.filter.Window.WindowType; +import dsp.filter.cic.RealPrimeCICDecimate; import dsp.filter.dc.IIRSinglePoleDCRemovalFilter; import dsp.symbol.Slicer; import dsp.symbol.Slicer.Output; +import instrument.Instrumentable; +import instrument.tap.Tap; +import instrument.tap.TapGroup; +import instrument.tap.stream.BinaryTap; +import instrument.tap.stream.FloatBufferTap; +import instrument.tap.stream.FloatTap; +import instrument.tap.stream.SymbolEventTap; +import sample.Listener; +import sample.real.RealBuffer; public class LTRFSKDecoder implements Listener, Instrumentable { + private RealPrimeCICDecimate mCICDecimator; + private FloatHalfBandFilter mHBFilter1; private FloatHalfBandFilter mHBFilter2; private FloatHalfBandFilter mHBFilter3; @@ -58,6 +62,7 @@ public class LTRFSKDecoder implements Listener, Instrumentable private final String TAP_F4_F5 = "LTR FSK Demod Filter4 >< Filter5"; private final String TAP_F5_F6 = "LTR FSK Demod Filter5 >< Filter6"; private final String TAP_F6_DT = "LTR FSK Demod Filter6 >< DC Filter"; + private final String TAP_CIC = "LTR FSK Demod CIC Decimator"; private final String TAP_DT_SF1 = "LTR FSK Demod DC Filter >< Squaring Filter"; private final String TAP_SF1_PS = "LTR FSK Demod Squaring Filter >< Pulse Shaper"; private final String TAP_PS_SLICER = "LTR FSK Demod Pulse Shaper >< Slicer"; @@ -69,6 +74,8 @@ public class LTRFSKDecoder implements Listener, Instrumentable */ public LTRFSKDecoder() { + mCICDecimator = new RealPrimeCICDecimate( 32, 1, 375, 60, WindowType.HAMMING, 2 ); + mHBFilter1 = new FloatHalfBandFilter( Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f ); @@ -112,6 +119,8 @@ public class LTRFSKDecoder implements Listener, Instrumentable { mHBFilter1.receive( sample ); } + + mCICDecimator.receive(buffer); } public void addListener( Listener listener ) @@ -139,6 +148,7 @@ public class LTRFSKDecoder implements Listener, Instrumentable group.add( new FloatTap( TAP_F4_F5, 14, 0.125f ) ); group.add( new FloatTap( TAP_F5_F6, 15, 0.0625f ) ); group.add( new FloatTap( TAP_F6_DT, 31, 0.0625f ) ); + group.add( new FloatBufferTap( TAP_CIC, 31, 0.0625f ) ); group.add( new FloatTap( TAP_DT_SF1, 31, 0.0625f ) ); group.add( new BinaryTap( TAP_SF1_PS, 33, 0.0625f ) ); group.add( new BinaryTap( TAP_PS_SLICER, 33, 0.0625f ) ); @@ -155,6 +165,9 @@ public class LTRFSKDecoder implements Listener, Instrumentable { switch( tap.getName() ) { + case TAP_CIC: + mCICDecimator.setListener( (FloatBufferTap)tap ); + break; case TAP_F1_F2: mHBFilter1.setListener( (FloatTap)tap ); ((FloatTap)tap).setListener( mHBFilter2 ); @@ -202,6 +215,9 @@ public class LTRFSKDecoder implements Listener, Instrumentable { switch( tap.getName() ) { + case TAP_CIC: + mCICDecimator.removeListener(); + break; case TAP_F1_F2: mHBFilter1.setListener( mHBFilter2 ); break; diff --git a/src/instrument/tap/stream/FloatBufferTap.java b/src/instrument/tap/stream/FloatBufferTap.java index 55bbfd93..5b1508f5 100644 --- a/src/instrument/tap/stream/FloatBufferTap.java +++ b/src/instrument/tap/stream/FloatBufferTap.java @@ -1,11 +1,17 @@ package instrument.tap.stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import sample.Listener; import sample.real.RealBuffer; public class FloatBufferTap extends FloatTap implements Listener { - private Listener mListener; + private final static Logger mLog = + LoggerFactory.getLogger( FloatBufferTap.class ); + + private Listener mRealBufferListener; public FloatBufferTap( String name, int delay, float sampleRateRatio ) { @@ -15,24 +21,26 @@ public class FloatBufferTap extends FloatTap implements Listener @Override public void receive( RealBuffer buffer ) { - if( mListener != null ) + mLog.debug("Got a buffer!"); + if( mRealBufferListener != null ) { - mListener.receive( buffer ); + mRealBufferListener.receive( buffer ); } for( float sample: buffer.getSamples() ) { - receive( sample ); + mLog.debug("Sending a sample to the tap"); + super.receive( sample ); } } public void setListener( Listener listener ) { - mListener = listener; + mRealBufferListener = listener; } public void removeListener( Listener listener ) { - mListener = null; + mRealBufferListener = null; } } diff --git a/src/instrument/tap/stream/FloatTap.java b/src/instrument/tap/stream/FloatTap.java index 1beebfa5..1d94daff 100644 --- a/src/instrument/tap/stream/FloatTap.java +++ b/src/instrument/tap/stream/FloatTap.java @@ -23,7 +23,7 @@ import sample.real.RealSampleListener; public class FloatTap extends StreamTap implements RealSampleListener { - private RealSampleListener mListener; + private RealSampleListener mFloatTapListener; public FloatTap( String name, int delay, @@ -35,9 +35,9 @@ public class FloatTap extends StreamTap implements RealSampleListener @Override public void receive( float sample ) { - if( mListener != null ) + if( mFloatTapListener != null ) { - mListener.receive( sample ); + mFloatTapListener.receive( sample ); } for( TapListener listener: mListeners ) @@ -48,11 +48,11 @@ public class FloatTap extends StreamTap implements RealSampleListener public void setListener( RealSampleListener listener ) { - mListener = listener; + mFloatTapListener = listener; } public void removeListener( RealSampleListener listener ) { - mListener = null; + mFloatTapListener = null; } } diff --git a/src/sample/decimator/RealDecimator.java b/src/sample/decimator/RealDecimator.java index 3a01249f..d8d34e31 100644 --- a/src/sample/decimator/RealDecimator.java +++ b/src/sample/decimator/RealDecimator.java @@ -1,11 +1,11 @@ package sample.decimator; -import sample.complex.ComplexSampleListener; +import sample.real.RealSampleListener; -public class RealDecimator implements ComplexSampleListener +public class RealDecimator implements RealSampleListener { - private ComplexSampleListener mListener; + private RealSampleListener mListener; private int mCounter = 0; private int mDecimationRate; @@ -36,13 +36,13 @@ public class RealDecimator implements ComplexSampleListener * to the registered listener */ @Override - public void receive( float i, float q ) + public void receive( float sample ) { mCounter++; if( mCounter >= mDecimationRate ) { - mListener.receive( i, q ); + mListener.receive( sample ); mCounter = 0; } @@ -51,7 +51,7 @@ public class RealDecimator implements ComplexSampleListener /** * Sets the decimated output listener */ - public void setListener( ComplexSampleListener listener ) + public void setListener( RealSampleListener listener ) { mListener = listener; }