Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -19,7 +19,7 @@ package dsp.filter;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import dsp.filter.Window.WindowType;
/**
@@ -30,7 +30,7 @@ import dsp.filter.Window.WindowType;
* Note: comb delay is fixed at 1 for this filter.
*/
public class ComplexCICDecimate extends ComplexFilter
implements Provider<ComplexSample>
implements Provider<Complex>
{
private Integrator[] mIntegrator;
private Comb[] mComb;
@@ -99,7 +99,7 @@ public class ComplexCICDecimate extends ComplexFilter
* Input method for this filter to receive complex samples.
*/
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mIntegrator[ 0 ].receive( (double)sample.left(), (double)sample.right() );
}
@@ -108,7 +108,7 @@ public class ComplexCICDecimate extends ComplexFilter
* Sets the listener to receive the output of this CIC filter
*/
@Override
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mOutput.setListener( listener );
}
@@ -117,13 +117,13 @@ public class ComplexCICDecimate extends ComplexFilter
* Removes the listener from receiving the output of this CIC filter
*/
@Override
public void removeListener( Listener<ComplexSample> listener )
public void removeListener( Listener<Complex> listener )
{
mOutput.removeListener( listener );
}
@Override
public Listener<ComplexSample> getListener()
public Listener<Complex> getListener()
{
return mOutput.getListener();
}
@@ -260,11 +260,11 @@ public class ComplexCICDecimate extends ComplexFilter
{
mStageCount = stageCount;
mDecimation = decimation;
mCleanupFilter = new ComplexFIRFilter( FilterFactory
mCleanupFilter = new ComplexFIRFilterOld( FilterFactory
.getCICCleanupFilter( outputSampleRate,
12000,
60,
WindowType.BLACKMAN ), 1.0d );
WindowType.BLACKMAN ), 1.0f );
setGain();
}
@@ -278,7 +278,7 @@ public class ComplexCICDecimate extends ComplexFilter
public void receive( double left, double right )
{
mCleanupFilter.receive(
new ComplexSample( (float)( left * mGain ),
new Complex( (float)( left * mGain ),
(float)( right * mGain ) ) );
}
@@ -294,7 +294,7 @@ public class ComplexCICDecimate extends ComplexFilter
/**
* Adds a listener to receive output samples
*/
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mCleanupFilter.setListener( listener );
}
@@ -302,12 +302,12 @@ public class ComplexCICDecimate extends ComplexFilter
/**
* Removes the listener from receiving output samples
*/
public void removeListener( Listener<ComplexSample> listener )
public void removeListener( Listener<Complex> listener )
{
mCleanupFilter.setListener( null );
}
public Listener<ComplexSample> getListener()
public Listener<Complex> getListener()
{
return mCleanupFilter.getListener();
}

View File

@@ -19,30 +19,30 @@ package dsp.filter;
import java.util.ArrayList;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ComplexFIRFilter extends ComplexFilter
public class ComplexFIRFilterOld extends ComplexFilter
{
private ArrayList<ComplexSample> mBuffer;
private ArrayList<Complex> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double[] mCoefficients;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
private double mGain;
private float mGain;
public ComplexFIRFilter( double[] coefficients, double gain )
public ComplexFIRFilterOld( float[] coefficients, float gain )
{
mCoefficients = coefficients;
mBuffer = new ArrayList<ComplexSample>();
mBuffer = new ArrayList<Complex>();
mBufferSize = mCoefficients.length;
mGain = gain;
//Fill the buffer with zero valued samples, so we don't have to check for null
for( int x = 0; x < mCoefficients.length; x++ )
{
mBuffer.add( new ComplexSample( (float)0.0, (float)0.0 ) );
mBuffer.add( new Complex( (float)0.0, (float)0.0 ) );
}
generateIndexMap( mCoefficients.length );
@@ -57,7 +57,7 @@ public class ComplexFIRFilter extends ComplexFilter
mIndexMap = null;
}
public void receive( ComplexSample newSample )
public void receive( Complex newSample )
{
//Add the new sample to the buffer
mBuffer.set( mBufferPointer, newSample );
@@ -96,7 +96,7 @@ public class ComplexFIRFilter extends ComplexFilter
//We're almost finished ... apply gain, cast the doubles to floats and
//send it on it's merry way
send( new ComplexSample( (float)( leftAccumulator * mGain ),
send( new Complex( (float)( leftAccumulator * mGain ),
(float)( rightAccumulator * mGain ) ) );
}

View File

@@ -18,13 +18,14 @@
package dsp.filter;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public abstract class ComplexFilter implements Listener<ComplexSample>
@Deprecated
public abstract class ComplexFilter implements Listener<Complex>
{
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}
@@ -34,7 +35,7 @@ public abstract class ComplexFilter implements Listener<ComplexSample>
mListener = null;
}
public Listener<ComplexSample> getListener()
public Listener<Complex> getListener()
{
return mListener;
}
@@ -44,7 +45,7 @@ public abstract class ComplexFilter implements Listener<ComplexSample>
return mListener != null;
}
protected void send( ComplexSample sample )
protected void send( Complex sample )
{
if( mListener != null )
{

View File

@@ -19,15 +19,15 @@ package dsp.filter;
import java.util.ArrayList;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ComplexHalfBandFilter extends ComplexFilter
{
private ArrayList<ComplexSample> mBuffer;
private ArrayList<Complex> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double mGain;
private double[] mCoefficients;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
@@ -49,16 +49,16 @@ public class ComplexHalfBandFilter extends ComplexFilter
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public ComplexHalfBandFilter( Filters filter, double gain )
public ComplexHalfBandFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<ComplexSample>();
mBuffer = new ArrayList<Complex>();
mBufferSize = mCoefficients.length;
//Fill the buffer with zero valued samples
for( int x = 0; x < mCoefficients.length; x++ )
{
mBuffer.add( new ComplexSample( (float)0.0, (float)0.0 ) );
mBuffer.add( new Complex( (float)0.0, (float)0.0 ) );
}
generateIndexMap( mCoefficients.length );
@@ -69,7 +69,7 @@ public class ComplexHalfBandFilter extends ComplexFilter
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public void receive( ComplexSample newSample )
public void receive( Complex newSample )
{
//Add the new sample to the buffer
mBuffer.set( mBufferPointer, newSample );
@@ -115,7 +115,7 @@ public class ComplexHalfBandFilter extends ComplexFilter
//We're almost finished ... apply gain, cast the doubles to floats and
//send it on it's merry way
send( new ComplexSample( (float)( leftAccumulator * mGain ),
send( new Complex( (float)( leftAccumulator * mGain ),
(float)( rightAccumulator * mGain ) ) );
}
}

View File

@@ -20,16 +20,16 @@ package dsp.filter;
import java.util.ArrayList;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
{
private Listener<ComplexSample> mListener;
private ArrayList<ComplexSample> mBuffer;
private Listener<Complex> mListener;
private ArrayList<Complex> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double mGain;
private double[] mCoefficients;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
@@ -51,16 +51,16 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public ComplexHalfBandNoDecimateFilter( Filters filter, double gain )
public ComplexHalfBandNoDecimateFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<ComplexSample>();
mBuffer = new ArrayList<Complex>();
mBufferSize = mCoefficients.length;
//Fill the buffer with zero valued samples
for( int x = 0; x < mCoefficients.length; x++ )
{
mBuffer.add( new ComplexSample( (float)0.0, (float)0.0 ) );
mBuffer.add( new Complex( (float)0.0, (float)0.0 ) );
}
generateIndexMap( mCoefficients.length );
@@ -71,7 +71,7 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public void receive( ComplexSample newSample )
public void receive( Complex newSample )
{
//Add the new sample to the buffer
mBuffer.set( mBufferPointer, newSample );
@@ -86,8 +86,8 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
double leftAccumulator = 0;
double rightAccumulator = 0;
float leftAccumulator = 0;
float rightAccumulator = 0;
//Start with the center tap value
leftAccumulator += mCoefficients[ mCenterCoefficient ] *
@@ -108,10 +108,10 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ).right() );
}
//We're almost finished ... apply gain, cast the doubles to floats and
//We're almost finished ... apply gain, cast the floats to floats and
//send it on it's merry way
send( new ComplexSample( (float)( leftAccumulator * mGain ),
(float)( rightAccumulator * mGain ) ) );
send( new Complex( ( leftAccumulator * mGain ),
( rightAccumulator * mGain ) ) );
}
/**
@@ -180,9 +180,9 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
public static void main( String[] args )
{
double[] c = Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.mCoefficients;
float[] c = Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.mCoefficients;
double sum = 0;
float sum = 0;
for( int x = 0; x < c.length; x++ )
{

View File

@@ -18,7 +18,7 @@
package dsp.filter;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import buffer.ComplexTappedCircularBuffer;
public class ComplexPolyphaseDecimationFilter extends ComplexFilter
@@ -68,7 +68,7 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
* Inputs a new sample into the filter at the non-decimated rate
*/
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mFilterPhases[ mPhasePointer++ ].receive( sample );
@@ -97,13 +97,13 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
for( int x = 0; x < mPhaseCount; x++ )
{
ComplexSample phaseSample = mFilterPhases[ x ].calculate();
Complex phaseSample = mFilterPhases[ x ].calculate();
left += phaseSample.left();
right += phaseSample.right();
}
send( new ComplexSample( left, right ) );
send( new Complex( left, right ) );
}
}
@@ -130,7 +130,7 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
/**
* Implements a single phase of a poly-phase filter bank
*/
public class FilterPhase implements Listener<ComplexSample>
public class FilterPhase implements Listener<Complex>
{
private int mLength;
private double[] mCoefficients;
@@ -150,7 +150,7 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mBuffer.add( sample );
}
@@ -159,7 +159,7 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
* Performs convolution by multiplying the buffer samples by their
* corresponding filter coefficients and returns the sum
*/
public ComplexSample calculate()
public Complex calculate()
{
/* Accumulators */
double left = 0.0d;
@@ -171,7 +171,7 @@ public class ComplexPolyphaseDecimationFilter extends ComplexFilter
right += ( mBuffer.get( x ).right() * mCoefficients[ x ] );
}
return new ComplexSample( (float)left, (float)right );
return new Complex( (float)left, (float)right );
}
}
}

View File

@@ -27,10 +27,12 @@ import java.util.Set;
import java.util.TreeSet;
import org.jtransforms.fft.DoubleFFT_1D;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate;
public class FilterFactory
{
@@ -45,24 +47,24 @@ public class FilterFactory
* @param window - to apply against the coefficients
* @return
*/
public static double[] 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 );
//Get unity response array (one element longer to align with IDFT size)
double[] frequencyResponse = getUnityResponseArray( sampleRate, frequency, length + 1 );
float[] frequencyResponse = getUnityResponseArray( sampleRate, frequency, length + 1 );
//Apply Inverse DFT against frequency response unity values, leaving the
//IDFT bin results in the frequency response array
DoubleFFT_1D idft = new DoubleFFT_1D( length + 1 );
FloatFFT_1D idft = new FloatFFT_1D( length + 1 );
idft.realInverseFull( frequencyResponse, false );
//Transfer the IDFT results to the return array
double[] coefficients = new double[ length ];
float[] coefficients = new float[ length ];
int middleCoefficient = (int)( length / 2 );
//Bin 0 of the idft is our center coefficient
@@ -91,9 +93,9 @@ public class FilterFactory
* @param coefficients
* @return
*/
private static double[] normalize( double[] coefficients )
private static float[] normalize( float[] coefficients )
{
double accumulator = 0;
float accumulator = 0;
for( int x = 0; x < coefficients.length; x++ )
{
@@ -124,51 +126,48 @@ public class FilterFactory
* @param length
* @return
*/
public static double[] getUnityResponseArray( int sampleRate,
long frequency,
int length )
public static float[] getUnityResponseArray( int sampleRate,
long frequency,
int length )
{
double[] unityArray = new double[ length * 2 ];
float[] unityArray = new float[ length * 2 ];
int binCount = (int)( ( Math.round(
(double)frequency / (double)sampleRate * (double)length ) ) );
(float)frequency / (float)sampleRate * (float)length ) ) );
// if( length % 2 == 0 ) //even length
// {
// for( int x = 0; x < binCount; x++ )
// {
// unityArray[ x ] = 1.0d;
// unityArray[ length - 1 - x ] = 1.0d;
// }
// }
// else //odd length
// {
unityArray[ 0 ] = 1.0d;
if( length % 2 == 0 ) //even length
{
for( int x = 0; x < binCount; x++ )
{
unityArray[ x ] = 1.0f;
unityArray[ length - 1 - x ] = 1.0f;
}
}
else //odd length
{
unityArray[ 0 ] = 1.0f;
for( int x = 1; x <= binCount; x++ )
{
unityArray[ x ] = 1.0d;
unityArray[ length - x ] = 1.0d;
unityArray[ x ] = 1.0f;
unityArray[ length - x ] = 1.0f;
}
// }
}
return unityArray;
}
public static double[] getSine( double sampleRate, double frequency, int length )
public static float[] getSine( float sampleRate, float frequency, int length )
{
//Ensure we have an odd length
assert( length % 2 == 0 );
float[] retVal = new float[ length ];
double[] retVal = new double[ length ];
double radianFrequency = 2.0D * Math.PI * ( frequency / sampleRate );
float radianFrequency = (float)( 2.0d * Math.PI * ( frequency / sampleRate ) );
int middle = (int)( length / 2 );
for( int x = 0; x < middle; x++ )
{
double val = Math.sin( radianFrequency * x );
float val = (float)Math.sin( radianFrequency * x );
retVal[ middle + x ] = val;
retVal[ middle - x ] = -val;
@@ -190,7 +189,7 @@ public class FilterFactory
* ...
* Index length - 1: same
*/
public static double[] invert( double[] coefficients )
public static float[] invert( float[] coefficients )
{
for( int x = 1; x < coefficients.length; x += 2 )
{
@@ -210,21 +209,21 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static double[] 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
{
double[] values = getSinc( sampleRate, cutoff, filterLength + 2, windowType );
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
{
double[] values = getSinc( sampleRate, cutoff, filterLength + 1, windowType );
float[] values = getSinc( sampleRate, cutoff, filterLength + 1, windowType );
//throw away the 0 index
return Arrays.copyOfRange( values, 1, values.length );
@@ -245,12 +244,12 @@ public class FilterFactory
* - passFrequency < stopFrequency
* - stopFrequency <= sampleRate/2
*/
public static double[] 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 ) )
{
@@ -288,10 +287,10 @@ public class FilterFactory
* @param windowType - window to apply against the generated coefficients
* @return
*/
public static double[] getHighPass( int sampleRate,
long cutoff,
int filterLength,
WindowType windowType )
public static float[] getHighPass( int sampleRate,
long cutoff,
int filterLength,
WindowType windowType )
{
//Convert the high frequency cutoff to its low frequency cutoff
//equivalent, so that when we generate the low pass filter, prior to
@@ -304,12 +303,12 @@ public class FilterFactory
windowType ) );
}
public static double[] getHighPass( int sampleRate,
long stopFrequency,
long passFrequency,
int attenuation,
WindowType windowType,
boolean forceOddLength )
public static float[] getHighPass( int sampleRate,
long stopFrequency,
long passFrequency,
int attenuation,
WindowType windowType,
boolean forceOddLength )
{
/* reverse the stop and pass frequency to get the low pass variant */
int tapCount = getTapCount( sampleRate,
@@ -331,7 +330,7 @@ public class FilterFactory
/**
* Utility to log the arrays of doubles with line breaks
*/
public static String arrayToString( double[] array, boolean breaks )
public static String arrayToString( float[] array, boolean breaks )
{
StringBuilder sb = new StringBuilder();
for( int x = 0; x < array.length; x++ )
@@ -574,10 +573,10 @@ public class FilterFactory
* @param stageCount
* @return
*/
public static double[] 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 );
@@ -588,16 +587,16 @@ public class FilterFactory
taps ++;
}
double[] frequencyResponse =
float[] frequencyResponse =
getCICResponseArray( outputSampleRate, passFrequency, taps );
//Apply Inverse DFT against frequency response unity values, leaving the
//IDFT bin results in the frequency response array
DoubleFFT_1D idft = new DoubleFFT_1D( taps );
FloatFFT_1D idft = new FloatFFT_1D( taps );
idft.realInverseFull( frequencyResponse, false );
//Transfer the IDFT results to the odd length return array
double[] coefficients = new double[ taps ];
float[] coefficients = new float[ taps ];
int middleCoefficient = (int)( taps / 2 );
//Bin 0 of the idft is our center coefficient
@@ -612,32 +611,32 @@ public class FilterFactory
}
//Apply the window against the coefficients
coefficients = Window.apply( window, coefficients );
// coefficients = Window.apply( window, coefficients );
normalize( coefficients );
return coefficients;
}
public static double[] getCICResponseArray( int sampleRate,
public static float[] getCICResponseArray( int sampleRate,
int frequency,
int length )
{
double[] cicArray = new double[ length * 2 ];
float[] cicArray = new float[ length * 2 ];
int binCount = (int)( ( Math.round(
(double)frequency / (double)sampleRate * (double)length ) ) );
(double)frequency / (double)sampleRate * 2.0d * (double)length ) ) );
cicArray[ 0 ] = 1.0d;
cicArray[ 0 ] = 1.0f;
double unityResponse = 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 */
double compensated = 1.0d + ( unityResponse -
( Math.sin( (double)x / (double)length ) /
float compensated = 1.0f + ( unityResponse -
(float)( Math.sin( (double)x / (double)length ) /
( (double)x / (double)length ) ) );
cicArray[ x ] = compensated;
@@ -666,64 +665,64 @@ public class FilterFactory
*
* @return - filter coefficients
*/
public static double[] getRootRaisedCosine( int samplesPerSymbol,
int symbols,
double alpha )
public static float[] getRootRaisedCosine( int samplesPerSymbol,
int symbols,
float alpha )
{
int taps = samplesPerSymbol * symbols + 1;
double scale = 0;
float scale = 0;
double[] coefficients = new double[ taps ];
float[] coefficients = new float[ taps ];
for( int x = 0; x < taps; x++ )
{
double index = x - ( taps / 2 );
float index = (float)x - ( (float)taps / 2.0f );
double x1 = Math.PI * index / (double)samplesPerSymbol;
double x2 = 4.0 * alpha * index / (double)samplesPerSymbol;
double x3 = x2 * x2 - 1;
float x1 = (float)Math.PI * index / (float)samplesPerSymbol;
float x2 = 4.0f * alpha * index / (float)samplesPerSymbol;
float x3 = x2 * x2 - 1.0f;
double numerator, denominator;
float numerator, denominator;
if( Math.abs( x3 ) >= 0.000001 )
{
if( x != taps / 2 )
{
numerator = Math.cos( ( 1.0 + alpha ) * x1 ) +
Math.sin( ( 1.0 - alpha ) * x1 ) /
( 4.0 * alpha * index / (double)samplesPerSymbol );
numerator = (float)Math.cos( ( 1.0 + alpha ) * x1 ) +
(float)Math.sin( ( 1.0f - alpha ) * x1 ) /
( 4.0f * alpha * index / (float)samplesPerSymbol );
}
else
{
numerator = Math.cos( ( 1.0 + alpha ) * x1 ) +
( 1.0 - alpha ) * Math.PI / ( 4.0 * alpha );
numerator = (float)Math.cos( ( 1.0f + alpha ) * x1 ) +
( 1.0f - alpha ) * (float)Math.PI / ( 4.0f * alpha );
}
denominator = x3 * Math.PI;
denominator = x3 * (float)Math.PI;
}
else
{
if( alpha == 1.0 )
if( alpha == 1.0f )
{
coefficients[ x ] = -1.0;
coefficients[ x ] = -1.0f;
continue;
}
x3 = ( 1.0 - alpha ) * x1;
x2 = ( 1.0 + alpha ) * x1;
x3 = ( 1.0f - alpha ) * x1;
x2 = ( 1.0f + alpha ) * x1;
numerator = ( Math.sin( x2 ) * ( 1.0 + alpha ) * Math.PI -
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 ) );
denominator = -32.0 * Math.PI * alpha * alpha * index /
(double)samplesPerSymbol;
denominator = (float)( -32.0 * Math.PI * alpha * alpha * index /
(double)samplesPerSymbol );
}
coefficients[ x ] = 4.0 * alpha * numerator / denominator;
coefficients[ x ] = 4.0f * alpha * numerator / denominator;
scale += coefficients[ x ];
}
@@ -738,13 +737,4 @@ public class FilterFactory
return coefficients;
}
public static void main( String[] args )
{
double[] c = FilterFactory.getRootRaisedCosine( 10, 4, 0.2 );
mLog.debug( "C:" + Arrays.toString( c ) );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,13 +27,13 @@ public class FloatFIRFilter implements RealSampleListener
private ArrayList<Float> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double[] mCoefficients;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
private double mGain;
private float mGain;
public FloatFIRFilter( double[] coefficients, double gain )
public FloatFIRFilter( float[] coefficients, float gain )
{
mCoefficients = coefficients;
mBuffer = new ArrayList<Float>();
@@ -86,7 +86,7 @@ public class FloatFIRFilter implements RealSampleListener
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
double accumulator = 0.0d;
float accumulator = 0.0f;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
@@ -103,7 +103,7 @@ public class FloatFIRFilter implements RealSampleListener
//We're almost finished ... apply gain, cast the doubles to shorts and
//return the value
return (float)( accumulator * mGain );
return accumulator * mGain;
}
/**

View File

@@ -27,8 +27,8 @@ public class FloatHalfBandFilter implements RealSampleListener
private ArrayList<Float> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double mGain;
private double[] mCoefficients;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
@@ -50,7 +50,7 @@ public class FloatHalfBandFilter implements RealSampleListener
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public FloatHalfBandFilter( Filters filter, double gain )
public FloatHalfBandFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<Float>();
@@ -97,7 +97,7 @@ public class FloatHalfBandFilter implements RealSampleListener
{
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
double accumulator = 0;
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
@@ -112,7 +112,7 @@ public class FloatHalfBandFilter implements RealSampleListener
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
}
//We're almost finished ... apply gain, cast the doubles to shorts and
//We're almost finished ... apply gain, cast the floats to shorts and
//send it on it's merry way
if( mListener != null )
{

View File

@@ -27,8 +27,8 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
private ArrayList<Float> mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private double mGain;
private double[] mCoefficients;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
@@ -48,7 +48,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public FloatHalfBandNoDecimateFilter( Filters filter, double gain )
public FloatHalfBandNoDecimateFilter( Filters filter, float gain )
{
mCoefficients = filter.getCoefficients();
mBuffer = new ArrayList<Float>();
@@ -88,7 +88,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
//Convolution - multiply filter coefficients by the circular buffer
//samples to calculate a new filtered value
double accumulator = 0;
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
@@ -103,7 +103,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) );
}
//We're almost finished ... apply gain, cast the doubles to floats and
//We're almost finished ... apply gain, cast the floats to floats and
//send it on it's merry way
if( mListener != null )
{

View File

@@ -1,22 +1,38 @@
package dsp.filter;
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package dsp.filter.cic;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener;
import sample.complex.ComplexToComplexBufferAssembler;
import sample.decimator.ComplexDecimator;
import dsp.filter.FilterFactory;
import dsp.filter.Filters;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
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,
@@ -31,9 +47,7 @@ public class ComplexPrimeCICDecimate
private DecimatingStage mFirstDecimatingStage;
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.
@@ -55,6 +69,7 @@ public class ComplexPrimeCICDecimate
{
assert( decimation <= 700 );
List<Integer> stageSizes = getPrimeFactors( decimation );
for( int x = 0; x < stageSizes.size(); x++ )
@@ -89,12 +104,17 @@ public class ComplexPrimeCICDecimate
}
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<ComplexSample> listener )
public void setListener( Listener<ComplexBuffer> listener )
{
mOutput.setListener( listener );
}
@@ -102,9 +122,9 @@ public class ComplexPrimeCICDecimate
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener( Listener<ComplexSample> listener )
public void removeListener()
{
mOutput.removeListener( listener );
mOutput.removeListener();
}
/**
@@ -143,14 +163,17 @@ public class ComplexPrimeCICDecimate
/**
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
public void receive( float[] samples )
public void receive( ComplexBuffer buffer )
{
for( int x = 0; x < samples.length; x += 2 )
{
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
}
samples = null;
if( mFirstDecimatingStage != null )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
}
}
}
/**
@@ -326,22 +349,33 @@ public class ComplexPrimeCICDecimate
*/
public class Output implements ComplexSampleListener
{
private ComplexFilter mCleanupFilter;
private ComplexHalfBandNoDecimateFilter mHalfBandFilter =
new ComplexHalfBandNoDecimateFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 0.4 );
/* Decimated output buffers will contain 1024 samples */
private ComplexToComplexBufferAssembler mAssembler =
new ComplexToComplexBufferAssembler( 2048 );
private ComplexFIRFilter_CB_CB mCleanupFilter;
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
public Output( int outputSampleRate, int passFrequency, int attenuation,
WindowType windowType )
{
mCleanupFilter = new ComplexFIRFilter( FilterFactory
mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory
.getCICCleanupFilter( outputSampleRate,
passFrequency,
attenuation,
windowType ), 0.4d );
windowType ), 0.4f );
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,
@@ -349,15 +383,15 @@ public class ComplexPrimeCICDecimate
* and output the sample values as a complex sample.
*/
@Override
public void receive( float left, float right )
public void receive( float inphase, float quadrature )
{
mCleanupFilter.receive( new ComplexSample( left, right ) );
mAssembler.receive( inphase, quadrature );
}
/**
* Adds a listener to receive output samples
*/
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<ComplexBuffer> listener )
{
mHalfBandFilter.setListener( listener );
}
@@ -365,14 +399,9 @@ public class ComplexPrimeCICDecimate
/**
* Removes the listener from receiving output samples
*/
public void removeListener( Listener<ComplexSample> listener )
public void removeListener()
{
mHalfBandFilter.setListener( null );
mHalfBandFilter.removeListener();
}
public Listener<ComplexSample> getListener()
{
return mHalfBandFilter.getListener();
}
}
}

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package dsp.filter.dc;
import sample.Listener;
import sample.real.RealBuffer;
public class AveragingDCRemovalFilter_RB extends DCRemovalFilter_RB
implements Listener<RealBuffer>
{
private float mAverage;
private float mRatio;
public AveragingDCRemovalFilter_RB( float ratio )
{
mRatio = ratio;
}
public void dispose()
{
mListener = null;
}
public void reset()
{
mAverage = 0.0f;
}
public float filter( float sample )
{
mAverage += mRatio * ( sample - mAverage );
return sample - mAverage;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
mListener.receive( buffer );
}
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,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 <http://www.gnu.org/licenses/>
******************************************************************************/
package dsp.filter.dc;
public abstract class DCRemovalFilter
{
private float mAverage;
private float mRatio;
public DCRemovalFilter( float ratio )
{
mRatio = ratio;
}
public abstract void dispose();
public void reset()
{
mAverage = 0.0f;
}
public float filter( float sample )
{
mAverage += mRatio * ( sample - mAverage );
return sample - mAverage;
}
}

View File

@@ -15,22 +15,21 @@
* 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;
package dsp.filter.dc;
import sample.Listener;
import sample.real.RealSampleListener;
/**
* Implements the real-time general DC removal algorithm described on page
* 762, figure 13-62d, in Understanding Digital Signal Processing 3e, Lyons.
*/
public class DCRemovalFilter implements RealSampleListener
public class DCRemovalFilterLyons implements RealSampleListener
{
private double mAlpha;
private float mAlpha;
private float mPrevious;
private RealSampleListener mListener;
public DCRemovalFilter( double alpha )
public DCRemovalFilterLyons( float alpha )
{
mAlpha = alpha;
}
@@ -43,7 +42,7 @@ public class DCRemovalFilter implements RealSampleListener
@Override
public void receive( float sample )
{
send( (float)( sample + ( mPrevious * mAlpha ) - mPrevious ) );
send( sample + ( mPrevious * mAlpha ) - mPrevious );
mPrevious = sample;
}

View File

@@ -0,0 +1,31 @@
package dsp.filter.dc;
import sample.Listener;
import sample.real.RealBuffer;
public abstract class DCRemovalFilter_RB implements Listener<RealBuffer>
{
protected Listener<RealBuffer> mListener;
public DCRemovalFilter_RB()
{
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
public void dispose()
{
mListener = null;
}
public abstract void reset();
}

View File

@@ -15,43 +15,41 @@
* 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;
package dsp.filter.dc;
import sample.Listener;
import sample.real.RealSampleListener;
public class DCRemovalFilter2 implements RealSampleListener
public class DCRemovalFilter_R_R extends DCRemovalFilter implements RealSampleListener
{
private double mAverage;
private double mRatio;
private RealSampleListener mListener;
RealSampleListener mListener;
public DCRemovalFilter2( double ratio )
public DCRemovalFilter_R_R( float ratio )
{
mRatio = ratio;
super( ratio );
}
@Override
public void dispose()
{
mListener = null;
}
@Override
public void receive( float sample )
{
mAverage += mRatio * ( (double)sample - mAverage );
send( (float)( sample - mAverage ) );
}
/**
* Sends the filtered sample to all registered listeners
*/
private void send( float sample )
{
if( mListener != null )
{
mListener.receive( sample );
mListener.receive( filter( sample ) );
}
}
}
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,8 +1,8 @@
package dsp.filter;
package dsp.filter.dc;
import sample.real.RealSampleListener;
public class DCRemovalFilter3 implements RealSampleListener
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
{
private float mAlpha;
private float mPreviousInput = 0.0f;
@@ -19,7 +19,7 @@ public class DCRemovalFilter3 implements RealSampleListener
* @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
* the cutoff frequency is to DC.
*/
public DCRemovalFilter3( float alpha )
public IIRSinglePoleDCRemovalFilter( float alpha )
{
mAlpha = alpha;
}

View File

@@ -0,0 +1,58 @@
package dsp.filter.dc;
import sample.real.RealBuffer;
public class IIRSinglePoleDCRemovalFilter_RB extends DCRemovalFilter_RB
{
private float mAlpha;
private float mPreviousInput = 0.0f;
private float mPreviousOutput = 0.0f;
/**
* IIR single-pole DC removal filter, as described by J M de Freitas in
* 29Jan2007 paper at:
*
* http://www.mathworks.com/matlabcentral/fileexchange/downloads/72134/download
*
* @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
* the cutoff frequency is to DC.
*/
public IIRSinglePoleDCRemovalFilter_RB( float alpha )
{
mAlpha = alpha;
}
@Override
public void receive( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
if( mListener != null )
{
mListener.receive( buffer );
}
}
public float filter( float sample )
{
float currentOutput = ( sample - mPreviousInput ) +
( mAlpha * mPreviousOutput );
mPreviousInput = sample;
mPreviousOutput = currentOutput;
return currentOutput;
}
@Override
public void reset()
{
mPreviousInput = 0.0f;
mPreviousOutput = 0.0f;
}
}

View File

@@ -4,9 +4,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class CMAEqualizer implements Listener<ComplexSample>
public class CMAEqualizer implements Listener<Complex>
{
private final static Logger mLog = LoggerFactory.getLogger( CMAEqualizer.class );
@@ -14,14 +14,14 @@ public class CMAEqualizer implements Listener<ComplexSample>
private float mModulus;
private float mMu;
private ComplexSample mError = new ComplexSample( 0.0f, 0.0f );
private Complex mError = new Complex( 0.0f, 0.0f );
private ComplexSample[] mTaps = new ComplexSample[ TAP_COUNT ];
private ComplexSample[] mBuffer = new ComplexSample[ TAP_COUNT ];
private Complex[] mTaps = new Complex[ TAP_COUNT ];
private Complex[] mBuffer = new Complex[ TAP_COUNT ];
private int mBufferPointer = 0;
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
public CMAEqualizer( float modulus, float mu )
{
@@ -31,21 +31,21 @@ public class CMAEqualizer implements Listener<ComplexSample>
/* Set first tap to 1,0 and all other taps to 0,0 as a starting point.
* This means that the output is solely determined by the last sample
* entering the filter, initially. */
mTaps[ 0 ] = new ComplexSample( 1.0f, 0.0f );
mTaps[ 0 ] = new Complex( 1.0f, 0.0f );
for( int x = 1; x < TAP_COUNT; x++ )
{
mTaps[ x ] = new ComplexSample( 0.0f, 0.0f );
mBuffer[ x ] = new ComplexSample( 0.0f, 0.0f );
mTaps[ x ] = new Complex( 0.0f, 0.0f );
mBuffer[ x ] = new Complex( 0.0f, 0.0f );
}
}
private void updateTaps( ComplexSample sample )
private void updateTaps( Complex sample )
{
/* Calculate the error. The ideal sample magnitude is unity (1.0).
* When the sample is not at unity, the error vector points in the
* opposite direction with a magnitude equal to 1 - sample magnitude.*/
mError = ComplexSample.multiply( sample, sample.norm() - mModulus );
mError = Complex.multiply( sample, sample.norm() - mModulus );
/* Ensure we don't exceed unity */
mError.clip( 1.0f );
@@ -54,14 +54,14 @@ public class CMAEqualizer implements Listener<ComplexSample>
{
// tap -= mu * conj(sample) * error
mTaps[ x ] = ComplexSample.subtract( mTaps[ x ],
ComplexSample.multiply( ComplexSample.multiply(
mTaps[ x ] = Complex.subtract( mTaps[ x ],
Complex.multiply( Complex.multiply(
mBuffer[ x ].conjugate(), mError ), mMu ) );
}
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mBuffer[ mBufferPointer ] = sample;
@@ -69,7 +69,7 @@ public class CMAEqualizer implements Listener<ComplexSample>
mBufferPointer = mBufferPointer % TAP_COUNT;
ComplexSample equalized = filter();
Complex equalized = filter();
updateTaps( equalized );
@@ -81,13 +81,13 @@ public class CMAEqualizer implements Listener<ComplexSample>
mLog.debug( "Tap 0:" + mTaps[ 0 ].toString() + " error:" + mError.toString() );
}
private ComplexSample filter()
private Complex filter()
{
ComplexSample sum = new ComplexSample( 0.0f, 0.0f );
Complex sum = new Complex( 0.0f, 0.0f );
for( int x = 0; x < TAP_COUNT; x++ )
{
sum = ComplexSample.add( sum, ComplexSample.multiply(
sum = Complex.add( sum, Complex.multiply(
mBuffer[ ( x + mBufferPointer ) % TAP_COUNT ],
mTaps[ TAP_COUNT - x - 1 ] ) );
}
@@ -95,7 +95,7 @@ public class CMAEqualizer implements Listener<ComplexSample>
return sum;
}
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}

View File

@@ -0,0 +1,6 @@
package dsp.filter.fir;
public abstract class FIRFilter
{
public abstract void dispose();
}

View File

@@ -0,0 +1,54 @@
package dsp.filter.fir.complex;
import sample.complex.Complex;
import dsp.filter.fir.FIRFilter;
import dsp.filter.fir.real.RealFIRFilter;
public class ComplexFIRFilter extends FIRFilter
{
private RealFIRFilter mIFilter;
private RealFIRFilter mQFilter;
/**
* Complex FIR Filter for processing complex sample pairs. Wraps two real
* FIR filters for processing each of the inphase and quadrature samples.
*
* @param coefficients - filter taps
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
*/
public ComplexFIRFilter( float[] coefficients, float gain )
{
mIFilter = new RealFIRFilter( coefficients, gain );
mQFilter = new RealFIRFilter( coefficients, gain );
}
public float[] getCoefficients()
{
return mIFilter.getCoefficients();
}
public float filterInphase( float sample )
{
return mIFilter.filter( sample );
}
public float filterQuadrature( float sample )
{
return mQFilter.filter( sample );
}
public Complex filter( Complex sample )
{
float i = filterInphase( sample.inphase() );
float q = filterQuadrature( sample.quadrature() );
return new Complex( i, q );
}
@Override
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
}
}

View File

@@ -0,0 +1,54 @@
package dsp.filter.fir.complex;
import sample.Listener;
import sample.complex.ComplexBuffer;
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter
implements Listener<ComplexBuffer>
{
private Listener<ComplexBuffer> mListener;
/**
* Complex FIR Filter for processing complex buffers. Wraps two real FIR
* filters for processing each of the inphase and quadrature samples.
*
* @param coefficients - odd length symmetric filter taps
* @param gain - gain value to apply to each of the filtered samples
*/
public ComplexFIRFilter_CB_CB( float[] coefficients, float gain )
{
super( coefficients, gain );
}
public void dispose()
{
mListener = null;
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
samples[ x ] = filterInphase( samples[ x ] );
samples[ x + 1 ] = filterQuadrature( samples[ x + 1 ] );
}
mListener.receive( buffer );
}
}
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -0,0 +1,105 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package dsp.filter.fir.real;
import dsp.filter.fir.FIRFilter;
public class RealFIRFilter extends FIRFilter
{
private float[] mBuffer;
private int mBufferSize = 1;
private int mBufferPointer = 0;
private int[][] mIndexMap;
private float[] mCoefficients;
private float mGain;
/**
* Float sample FIR filter base class.
*
* @param coefficients - filter coefficients
* @param gain value to apply to the filtered output - use 1.0f for no gain
*/
public RealFIRFilter( float[] coefficients, float gain )
{
mCoefficients = coefficients;
mGain = gain;
mBufferSize = mCoefficients.length;
mBuffer = new float[ mBufferSize ];
mBufferPointer = mBufferSize - 1;
generateIndexMap( mBufferSize );
}
public float[] getCoefficients()
{
return mCoefficients;
}
@Override
public void dispose()
{
mCoefficients = null;
mIndexMap = null;
mBuffer = null;
}
public float filter( float sample )
{
mBuffer[ mBufferPointer ] = sample;
float accumulator = 0.0f;
for( int x = 0; x < mBufferSize; x++ )
{
accumulator += mCoefficients[ x ] *
mBuffer[ mIndexMap[ mBufferPointer][ x ] ];
}
mBufferPointer--;
if( mBufferPointer < 0 )
{
mBufferPointer += mBufferSize;
}
/* Apply gain and return the filtered value */
return accumulator * mGain;
}
/**
* Generates a circular buffer index map to support lookup of the translated
* index based on the current buffer pointer and the desired sample index.
*/
private void generateIndexMap( int size )
{
mIndexMap = new int[ size ][ size ];
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
int z = x + y;
mIndexMap[ x ][ y ] = z < size ? z : z - size;
}
}
}
}

View File

@@ -0,0 +1,67 @@
package dsp.filter.fir.real;
import sample.Listener;
import sample.real.RealBuffer;
public class RealFIRFilter_RB_RB extends RealFIRFilter
implements Listener<RealBuffer>
{
private Listener<RealBuffer> mListener;
/**
* Real Buffer Float FIR filter with streaming float buffer provider and
* listener interfaces.
*
* @param coefficients
* @param gain
*/
public RealFIRFilter_RB_RB( float[] coefficients, float gain )
{
super( coefficients, gain );
}
@Override
public void dispose()
{
super.dispose();
mListener = null;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( filter( buffer ) );
}
}
/**
* Filters the buffer samples
*
* @param buffer
* @return
*/
public RealBuffer filter( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return buffer;
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -0,0 +1,56 @@
package dsp.filter.fir.real;
import sample.real.RealSampleListener;
public class RealFIRFilter_R_R extends RealFIRFilter implements RealSampleListener
{
private RealSampleListener mListener;
/**
* Float FIR filter with streaming float provider and listener interfaces.
*
* @param coefficients
* @param gain
*/
public RealFIRFilter_R_R( float[] coefficients, float gain )
{
super( coefficients, gain );
}
@Override
public void dispose()
{
super.dispose();
mListener = null;
}
@Override
public void receive( float sample )
{
if( mListener != null )
{
mListener.receive( filter( sample ) );
}
}
public float[] filter( float[] samples )
{
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return samples;
}
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -0,0 +1,117 @@
package dsp.filter.halfband.complex;
import sample.Listener;
import sample.complex.ComplexBuffer;
import dsp.filter.halfband.real.HalfBandFilter;
public class HalfBandFilter_CB_CB implements Listener<ComplexBuffer>
{
private HalfBandFilter mIFilter;
private HalfBandFilter mQFilter;
private boolean mDecimate;
private boolean mDecimateFlag;
private Listener<ComplexBuffer> mListener;
/**
*
* @param coefficients
* @param gain
* @param decimate
*/
public HalfBandFilter_CB_CB( float[] coefficients, float gain, boolean decimate )
{
mIFilter = new HalfBandFilter( coefficients, gain );
mQFilter = new HalfBandFilter( coefficients, gain );
mDecimate = decimate;
}
public void dispose()
{
mIFilter.dispose();
mQFilter.dispose();
mListener = null;
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
if( mDecimate )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] decimated;
if( half % 2 == 0 || mDecimateFlag )
{
decimated = new float[ half ];
}
else
{
/* If inbound buffer size is odd-length, then we have to
* adjust when the first operation is non-decimation, since
* that will produce an outbound buffer 1 sample larger */
decimated = new float[ half + 1 ];
}
int decimatedPointer = 0;
for( int x = 0; x < samples.length; x += 2 )
{
/* Insert the sample but don't filter */
if( mDecimateFlag )
{
mIFilter.insert( samples[ x ] );
mQFilter.insert( samples[ x + 1 ] );
}
else
{
decimated[ decimatedPointer++ ] = mIFilter.filter( samples[ x ] );
decimated[ decimatedPointer++ ] = mQFilter.filter( samples[ x + 1 ] );
}
/* Toggle the decimation flag for every sample */
mDecimateFlag = !mDecimateFlag;
}
if( mListener != null )
{
mListener.receive( new ComplexBuffer( decimated ) );
}
}
else
{
/* Non Decimate - filter each of the values and return the buffer */
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
samples[ x ] = mIFilter.filter( samples[ x ] );
samples[ x + 1 ] = mQFilter.filter( samples[ x + 1 ] );
}
if( mListener != null )
{
mListener.receive( buffer );
}
}
}
}
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -0,0 +1,160 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,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 <http://www.gnu.org/licenses/>
******************************************************************************/
package dsp.filter.halfband.real;
public class HalfBandFilter
{
private float[] mBuffer;
private int mBufferSize = 1; //Temporary initial value
private int mBufferPointer = 0;
private float mGain;
private float[] mCoefficients;
private int[][] mIndexMap;
private int mCenterCoefficient;
private int mCenterCoefficientMapIndex;
/**
* Half-Band filter for processing real float samples.
*
* Takes advantage of the 0-valued FIR half-band coefficents inherent
* in the half-band filter and does not calculate those coefficients.
*
* This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample.
*
* @param filter - filter coefficients
* @param gain - gain multiplier. Use 1.0 for unity/no gain
*/
public HalfBandFilter( float[] coefficients, float gain )
{
mCoefficients = coefficients;
mGain = gain;
mBufferSize = mCoefficients.length;
mBuffer = new float[ mBufferSize ];
generateIndexMap( mCoefficients.length );
}
public void dispose()
{
}
/**
* Inserts the sample into the buffer without calculating a filtered value
*/
public void insert( float sample )
{
mBuffer[ mBufferPointer ] = sample;
mBufferPointer++;
mBufferPointer = mBufferPointer % mBufferSize;
}
/**
* Calculate the filtered value by applying the coefficients against
* the complex samples in mBuffer
*/
public float filter( float sample )
{
insert( sample );
float accumulator = 0;
//Start with the center tap value
accumulator += mCoefficients[ mCenterCoefficient ] *
mBuffer[ mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ];
//For the remaining coefficients, add the symmetric samples, oldest and newest
//first, then multiply by the single coefficient
for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 )
{
accumulator += mCoefficients[ x ] *
( mBuffer[ mIndexMap[ mBufferPointer ][ x ] ] +
mBuffer[ mIndexMap[ mBufferPointer ][ x + 1 ] ] );
}
return accumulator * mGain;
}
/**
* Creates an n X (n + 1 / 2) index map enabling quick access to the
* circular buffer samples.
*
* As the buffer shifts right with each subsequent sample, we have to move
* the index pointers with it, for efficient access of the samples.
*
* The first array index value in the index map corresponds to the current
* buffer pointer location.
*
* The second array index value points to the samples that should be
* multiplied by the coefficients as follows:
*
* 0 = center tap sample, to be multiplied by center coefficient
*
* 0 = sample( 1 )
* 1 = sample( size - 1 )
*
* Indexes 0 and 1 will be multiplied by coefficient( 0 ).
*
* Subsequent indexes 3, 4, etc, point to the oldest and newest samples that
* correspond to the matching ( 3 ) coefficient index.
*
* @param odd-sized number of filter taps (ie coefficients) and buffer
*/
private void generateIndexMap( int size )
{
//Ensure we have an odd size
assert( size % 2 == 1 );
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
//Set center tap index for coefficients array
mCenterCoefficient = ( size - 1 ) / 2;
mCenterCoefficientMapIndex = mCenterCoefficient + 1;
mIndexMap = new int[ size ][ mapWidth ];
//Setup the first row, buffer pointer index 0, as a starting point
for( int x = 0; x < mapWidth - 2; x += 2 )
{
mIndexMap[ 0 ][ x ] = x;
mIndexMap[ 0 ][ x + 1 ] = size - 1 - x;
}
//Place center tap index in last element
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
//For each subsequent row, increment the previous row's value by 1,
//subtracting size as needed, to keep the values between 0 and size - 1
for( int x = 1; x < size; x++ )
{
for( int y = 0; y < mapWidth; y++ )
{
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
if( mIndexMap[ x ][ y ] >= size )
{
mIndexMap[ x ][ y ] -= size;
}
}
}
}
}

View File

@@ -0,0 +1,101 @@
package dsp.filter.halfband.real;
import sample.Listener;
import sample.real.RealBuffer;
public class HalfBandFilter_RB_RB extends HalfBandFilter implements Listener<RealBuffer>
{
private boolean mDecimate;
private boolean mDecimateFlag;
private Listener<RealBuffer> mListener;
/**
*
* @param coefficients
* @param gain
* @param decimate
*/
public HalfBandFilter_RB_RB( float[] coefficients, float gain, boolean decimate )
{
super( coefficients, gain );
mDecimate = decimate;
}
public void dispose()
{
super.dispose();
mListener = null;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
if( mDecimate )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] decimated;
if( half % 2 == 0 || mDecimateFlag )
{
decimated = new float[ half ];
}
else
{
/* If inbound buffer size is odd-length, then we have to
* adjust when the first operation is non-decimation, since
* that will produce an outbound buffer 1 sample larger */
decimated = new float[ half + 1 ];
}
int decimatedPointer = 0;
for( float sample: samples )
{
/* Insert the sample but don't filter */
if( mDecimateFlag )
{
insert( sample );
}
else
{
decimated[ decimatedPointer++ ] = filter( sample );
}
/* Toggle the decimation flag for every sample */
mDecimateFlag = !mDecimateFlag;
}
mListener.receive( new RealBuffer( decimated ) );
}
else
{
/* Non Decimate - filter each of the values and return the buffer */
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
mListener.receive( buffer );
}
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,61 +0,0 @@
package dsp.filter.interpolator;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.ComplexSample;
public class QPSKInterpolator extends Interpolator
{
private final static Logger mLog = LoggerFactory.getLogger( QPSKInterpolator.class );
private float mGain;
public QPSKInterpolator( float gain )
{
mGain = gain;
}
/**
* Calculates an interpolated value from the 8 samples beginning at the
* offset index. Uses the mu value to determine which of 128 filter kernels
* to use in order to closely approximate the ideal frequency response for
* the value of mu, ranging from 0.0 to 1.0, indicating the interpolated
* position in the sample set ranging from offset to offset + 7.
*
* @param samples - sample array of length at least offset + 7
*
* @param mu - interpolated sample position between 0 and 1.0
*
* @return - interpolated sample value
*/
public ComplexSample filter( ComplexSample[] samples, int offset, float mu )
{
/* Ensure we have enough samples in the array */
assert( samples.length >= offset + 7 );
/* Identify the filter bank that corresponds to mu */
int index = (int)( NSTEPS * mu );
float realAccumulator = ( TAPS[ index ][ 7 ] * samples[ offset ].real() );
realAccumulator += ( TAPS[ index ][ 6 ] * samples[ offset + 1 ].real() );
realAccumulator += ( TAPS[ index ][ 5 ] * samples[ offset + 2 ].real() );
realAccumulator += ( TAPS[ index ][ 4 ] * samples[ offset + 3 ].real() );
realAccumulator += ( TAPS[ index ][ 3 ] * samples[ offset + 4 ].real() );
realAccumulator += ( TAPS[ index ][ 2 ] * samples[ offset + 5 ].real() );
realAccumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ].real() );
realAccumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ].real() );
float imaginaryAccumulator = ( TAPS[ index ][ 7 ] * samples[ offset ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 6 ] * samples[ offset + 1 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 5 ] * samples[ offset + 2 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 4 ] * samples[ offset + 3 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 3 ] * samples[ offset + 4 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 2 ] * samples[ offset + 5 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ].imaginary() );
imaginaryAccumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ].imaginary() );
return new ComplexSample( realAccumulator * mGain, imaginaryAccumulator * mGain );
}
}

View File

@@ -0,0 +1,58 @@
package dsp.filter.interpolator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.complex.Complex;
public class RealInterpolator extends Interpolator
{
private final static Logger mLog = LoggerFactory.getLogger( RealInterpolator.class );
private float mGain;
public RealInterpolator( float gain )
{
mGain = gain;
}
/**
* Calculates an interpolated value from the 8 samples beginning at the
* offset index. Uses the mu value to determine which of 128 filter kernels
* to use in order to closely approximate the ideal frequency response for
* the value of mu, ranging from 0.0 to 1.0, indicating the interpolated
* position in the sample set ranging from offset to offset + 7.
*
* @param samples - sample array of length at least offset + 7
*
* @param mu - interpolated sample position between 0 and 1.0
*
* @return - interpolated sample value
*/
public float filter( float[] samples, int offset, float mu )
{
/* Ensure we have enough samples in the array */
assert( samples.length >= offset + 7 );
/* Identify the filter bank that corresponds to mu */
int index = (int)( NSTEPS * mu );
float accumulator = ( TAPS[ index ][ 7 ] * samples[ offset ] );
accumulator += ( TAPS[ index ][ 6 ] * samples[ offset + 1 ] );
accumulator += ( TAPS[ index ][ 5 ] * samples[ offset + 2 ] );
accumulator += ( TAPS[ index ][ 4 ] * samples[ offset + 3 ] );
accumulator += ( TAPS[ index ][ 3 ] * samples[ offset + 4 ] );
accumulator += ( TAPS[ index ][ 2 ] * samples[ offset + 5 ] );
accumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ] );
accumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ] );
return accumulator * mGain;
}
public Complex filter( float[] iSamples, float[] qSamples, int offset, float mu )
{
float i = filter( iSamples, offset, mu );
float q = filter( qSamples, offset, mu );
return new Complex( i, q );
}
}