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

@@ -1,45 +1,22 @@
package dsp.am;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
public class AMDemodulator implements Listener<ComplexSample>, RealSampleProvider
public abstract class AMDemodulator
{
private RealSampleListener mListener;
public AMDemodulator()
private float mGain;
public AMDemodulator( float gain )
{
mGain = gain;
}
public float demodulate( float inphase, float quadrature )
{
return (float)Math.sqrt( ( inphase * inphase ) +
( quadrature * quadrature ) ) * mGain;
}
@Override
public void receive( ComplexSample sample )
{
float demodulated = (float)Math.sqrt( ( sample.real() *
sample.real() ) +
( sample.imaginary() *
sample.imaginary() ) );
if( mListener != null )
{
mListener.receive( demodulated * 500.0f );
}
}
/**
* Adds a listener to receive demodulated samples
*/
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
/**
* Removes a listener from receiving demodulated samples
*/
public void removeListener( RealSampleListener listener )
{
mListener = null;
}
public void setGain( float gain )
{
mGain = gain;
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* 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.am;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.real.RealBuffer;
public class AMDemodulator_CB extends AMDemodulator implements Listener<ComplexBuffer>
{
private Listener<RealBuffer> mListener;
public AMDemodulator_CB( float gain )
{
super( gain );
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] demodulated = new float[ half ];
for( int x = 0; x < half; x++ )
{
int index = x * 2;
demodulated[ x ] = demodulate( samples[ index ], samples[ index + 1 ] );
}
mListener.receive( new RealBuffer( demodulated ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
public void dispose()
{
mListener = null;
}
}

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

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* 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.fm;
public abstract class FMDemodulator
{
private float mPreviousI = 0.0f;
private float mPreviousQ = 0.0f;
protected float mGain;
public FMDemodulator( float gain )
{
mGain = gain;
}
public float demodulate( float currentI, float currentQ )
{
/**
* Multiply the current sample against the complex conjugate of the
* previous sample to derive the phase delta between the two samples
*
* Negating the previous sample quadrature produces the conjugate
*/
double inphase = ( currentI * mPreviousI ) - ( currentQ * -mPreviousQ );
double quadrature = ( currentQ * mPreviousI ) + ( currentI * -mPreviousQ );
double angle = 0.0f;
//Check for divide by zero
if( inphase != 0 )
{
/**
* Use the arc-tangent of quadrature divided by inphase to
* get the phase angle (+/-) which was directly manipulated by the
* original message waveform during the modulation. This value now
* serves as the instantaneous amplitude of the demodulated signal
*/
double denominator = 1.0d / inphase;
angle = Math.atan( (double)quadrature * denominator );
}
/**
* Store the current sample to use during the next iteration
*/
mPreviousI = currentI;
mPreviousQ = currentQ;
return (float)( angle * mGain );
}
public abstract void dispose();
public void reset()
{
mPreviousI = 0.0f;
mPreviousQ = 0.0f;
}
public void setGain( float gain )
{
mGain = gain;
}
}

View File

@@ -0,0 +1,81 @@
/*******************************************************************************
* 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.fm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.wave.ComplexWaveRecorder;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.real.RateCounter_RB;
import sample.real.RealBuffer;
public class FMDemodulator_CB extends FMDemodulator implements Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( FMDemodulator_CB.class );
private Listener<RealBuffer> mListener;
private RateCounter_RB mRateCounter = new RateCounter_RB( "FM DEMOD OUTPUT" );
public FMDemodulator_CB( float gain )
{
super( gain );
}
@Override
public void receive( ComplexBuffer buffer )
{
if( mListener != null )
{
float[] samples = buffer.getSamples();
int half = samples.length / 2;
float[] demodulated = new float[ half ];
for( int x = 0; x < half; x++ )
{
int index = x * 2;
demodulated[ x ] = demodulate( samples[ index ],
samples[ index + 1 ] );
}
mListener.receive( new RealBuffer( demodulated ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
@Override
public void dispose()
{
mListener = null;
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* 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
@@ -15,16 +15,17 @@
* 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.nbfm;
package dsp.fm;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.real.RealSampleListener;
public class FMDiscriminator implements Listener<ComplexSample>
@Deprecated
public class FMDiscriminator implements Listener<Complex>
{
private RealSampleListener mListener;
private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f );
private Complex mPreviousSample = new Complex( 0.0f, 0.0f );
private double mGain;
/**
@@ -51,7 +52,7 @@ public class FMDiscriminator implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample currentSample )
public void receive( Complex currentSample )
{
/**
* Multiply the current sample against the complex conjugate of the

View File

@@ -15,34 +15,35 @@
* 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.nbfm;
package dsp.fm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.real.RealSampleBroadcaster;
import sample.real.RealSampleListener;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.ComplexFIRFilterOld;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
public class FilteringNBFMDemodulator implements Listener<ComplexSample>
@Deprecated
public class FilteringNBFMDemodulator implements Listener<Complex>
{
private final static Logger mLog =
LoggerFactory.getLogger( FilteringNBFMDemodulator.class );
private RealSampleBroadcaster mBroadcaster = new RealSampleBroadcaster();
private ComplexFIRFilter mIQFilter;
private ComplexFIRFilterOld mIQFilter;
private FloatFIRFilter mAudioFilter;
private FMDiscriminator mDiscriminator;
public FilteringNBFMDemodulator( double[] iqFilter, double iqGain,
double[] audioFilter, double audioGain )
public FilteringNBFMDemodulator( float[] iqFilter, float iqGain,
float[] audioFilter, float audioGain )
{
mIQFilter = new ComplexFIRFilter( iqFilter, iqGain );
mIQFilter = new ComplexFIRFilterOld( iqFilter, iqGain );
mAudioFilter = new FloatFIRFilter( audioFilter, audioGain );
@@ -62,7 +63,7 @@ public class FilteringNBFMDemodulator implements Listener<ComplexSample>
public FilteringNBFMDemodulator()
{
this( FilterFactory.getLowPass( 48000, 5000, 7000, 48, WindowType.HAMMING, true ),
1.0002,
1.0002f,
FilterFactory.getLowPass( 48000, 3000, 6000, 48, WindowType.HAMMING, true ),
2 );
}
@@ -72,7 +73,7 @@ public class FilteringNBFMDemodulator implements Listener<ComplexSample>
* processed
*/
@Override
public void receive( ComplexSample quadratureSample )
public void receive( Complex quadratureSample )
{
mIQFilter.receive( quadratureSample );
}

View File

@@ -15,20 +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.nbfm;
package dsp.fm;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.real.RealSampleBroadcaster;
import sample.real.RealSampleListener;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.ComplexFIRFilterOld;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
public class NBFMDemodulator implements Listener<ComplexSample>
@Deprecated
public class NBFMDemodulator implements Listener<Complex>
{
private RealSampleBroadcaster mBroadcaster = new RealSampleBroadcaster();
private ComplexFIRFilter mIQFilter;
private ComplexFIRFilterOld mIQFilter;
private FMDiscriminator mDiscriminator;
/**
@@ -38,12 +39,12 @@ public class NBFMDemodulator implements Listener<ComplexSample>
public NBFMDemodulator()
{
this( FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ),
1.0002, true );
1.0002f, true );
}
public NBFMDemodulator( double[] iqFilter, double iqGain, boolean afc )
public NBFMDemodulator( float[] iqFilter, float iqGain, boolean afc )
{
mIQFilter = new ComplexFIRFilter( iqFilter, iqGain );
mIQFilter = new ComplexFIRFilterOld( iqFilter, iqGain );
mDiscriminator = new FMDiscriminator( 1 );
@@ -66,7 +67,7 @@ public class NBFMDemodulator implements Listener<ComplexSample>
* processed
*/
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mIQFilter.receive( sample );
}

View File

@@ -26,7 +26,7 @@ import java.util.BitSet;
import java.util.List;
import sample.Listener;
import sample.real.RealSampleListener;
import sample.real.RealBuffer;
import buffer.BooleanAveragingBuffer;
import dsp.symbol.SymbolEvent;
import dsp.symbol.SymbolEvent.Shift;
@@ -51,7 +51,7 @@ import dsp.symbol.SymbolEvent.Shift;
* Implements instrumentable interface, so that slice events can be received
* externally to analyze decoder performance.
*/
public class FSK2Decoder implements Instrumentable, RealSampleListener
public class FSK2Decoder implements Instrumentable, Listener<RealBuffer>
{
public enum Output{ NORMAL, INVERTED };
@@ -95,23 +95,26 @@ public class FSK2Decoder implements Instrumentable, RealSampleListener
* Primary sample input
*/
@Override
public void receive( float floatSample )
public void receive( RealBuffer buffer )
{
/* Square the sample. Greater than zero is a 1 (true) and less than
* zero is a 0 (false) */
boolean bitSample = ( floatSample >= 0.0f );
for( float sample: buffer.getSamples() )
{
/* Square the sample. Greater than zero is a 1 (true) and less than
* zero is a 0 (false) */
boolean bitSample = ( sample >= 0.0f );
/* Feed the delay buffer and fetch the one-baud delayed sample */
boolean delayedBitSample = mDelayBuffer.get( bitSample );
/* Feed the delay buffer and fetch the one-baud delayed sample */
boolean delayedBitSample = mDelayBuffer.get( bitSample );
/* Correlation: xor current bit with delayed bit */
boolean softBit = bitSample ^ delayedBitSample;
/* Correlation: xor current bit with delayed bit */
boolean softBit = bitSample ^ delayedBitSample;
/* Low pass filter to smooth the correlated values */
boolean filteredSoftBit = mLowPassFilter.getAverage( softBit );
/* Low pass filter to smooth the correlated values */
boolean filteredSoftBit = mLowPassFilter.getAverage( softBit );
/* Send the filtered correlated bit to the slicer */
mSlicer.receive( filteredSoftBit );
/* Send the filtered correlated bit to the slicer */
mSlicer.receive( filteredSoftBit );
}
}
/**

View File

@@ -27,17 +27,17 @@ import java.util.ArrayList;
import java.util.List;
import sample.Listener;
import sample.real.RealSampleListener;
import dsp.filter.DCRemovalFilter3;
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.dc.IIRSinglePoleDCRemovalFilter;
import dsp.symbol.Slicer;
import dsp.symbol.Slicer.Output;
public class LTRFSKDecoder implements RealSampleListener, Instrumentable
public class LTRFSKDecoder implements Listener<RealBuffer>, Instrumentable
{
private FloatHalfBandFilter mHBFilter1;
private FloatHalfBandFilter mHBFilter2;
@@ -45,7 +45,7 @@ public class LTRFSKDecoder implements RealSampleListener, Instrumentable
private FloatHalfBandFilter mHBFilter4;
private FloatHalfBandFilter mHBFilter5;
private FloatHalfBandNoDecimateFilter mHBFilter6;
private DCRemovalFilter3 mDCFilter;
private IIRSinglePoleDCRemovalFilter mDCFilter;
private SquaringFilter mSquaringFilter;
private LTRPulseShapingFilter mPulseShaper;
private Slicer mSlicer;
@@ -69,29 +69,29 @@ public class LTRFSKDecoder implements RealSampleListener, Instrumentable
public LTRFSKDecoder()
{
mHBFilter1 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter2 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter1.setListener( mHBFilter2 );
mHBFilter3 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter2.setListener( mHBFilter3 );
mHBFilter4 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter3.setListener( mHBFilter4 );
mHBFilter5 = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter4.setListener( mHBFilter5 );
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002f );
mHBFilter5.setListener( mHBFilter6 );
mDCFilter = new DCRemovalFilter3( 0.9946f );
mDCFilter = new IIRSinglePoleDCRemovalFilter( 0.9946f );
mHBFilter6.setListener( mDCFilter );
mSquaringFilter = new SquaringFilter();
@@ -105,9 +105,12 @@ public class LTRFSKDecoder implements RealSampleListener, Instrumentable
}
@Override
public void receive( float sample )
public void receive( RealBuffer buffer )
{
mHBFilter1.receive( sample );
for( float sample: buffer.getSamples() )
{
mHBFilter1.receive( sample );
}
}
public void addListener( Listener<Boolean> listener )

View File

@@ -2,9 +2,6 @@ package dsp.gain;
import java.util.concurrent.atomic.AtomicBoolean;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
import buffer.DoubleCircularBuffer;
import buffer.FloatCircularBuffer;
@@ -61,8 +58,7 @@ import buffer.FloatCircularBuffer;
* those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of Moe Wheatley.
******************************************************************************/
public class RealAutomaticGainControl implements RealSampleListener,
RealSampleProvider
public class AutomaticGainControl
{
private static final double SAMPLE_RATE = 48000;
@@ -139,38 +135,10 @@ public class RealAutomaticGainControl implements RealSampleListener,
private DoubleCircularBuffer mMagnitudeBuffer =
new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) );
private RealSampleListener mListener;
public RealAutomaticGainControl()
public AutomaticGainControl()
{
}
@Override
public void receive( float currentSample )
{
float processed = process( currentSample );
if( mListener != null )
{
mListener.receive( processed );
}
}
/**
* Applies AGC to a buffer of real (float) samples
*/
public RealBuffer process( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = process( samples[ x ] );
}
return new RealBuffer( samples );
}
public float process( float currentSample )
{
float delayedSample = mDelayBuffer.get( currentSample );
@@ -233,9 +201,7 @@ public class RealAutomaticGainControl implements RealSampleListener,
}
delayedSample = (float)( delayedSample * gain );
return delayedSample;
return (float)( delayedSample * gain );
}
/**
@@ -254,16 +220,4 @@ public class RealAutomaticGainControl implements RealSampleListener,
{
return mAGCEnabled.get();
}
@Override
public void setListener( RealSampleListener listener )
{
mListener = listener;
}
@Override
public void removeListener( RealSampleListener listener )
{
mListener = null;
}
}

View File

@@ -0,0 +1,53 @@
package dsp.gain;
import sample.Listener;
import sample.real.RealBuffer;
public class AutomaticGainControl_RB extends AutomaticGainControl
implements Listener<RealBuffer>
{
private Listener<RealBuffer> mListener;
public AutomaticGainControl_RB()
{
}
public void dispose()
{
mListener = null;
}
/**
* Applies AGC to a buffer of real (float) samples
*/
public RealBuffer process( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = process( samples[ x ] );
}
return buffer;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( process( buffer ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import buffer.ComplexCircularBuffer;
import buffer.DoubleCircularBuffer;
@@ -61,8 +61,8 @@ import buffer.DoubleCircularBuffer;
* those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of Moe Wheatley.
******************************************************************************/
public class ComplexAutomaticGainControl implements Listener<ComplexSample>,
Provider<ComplexSample>
public class ComplexAutomaticGainControl implements Listener<Complex>,
Provider<Complex>
{
private static final double SAMPLE_RATE = 48000;
@@ -138,16 +138,16 @@ public class ComplexAutomaticGainControl implements Listener<ComplexSample>,
private DoubleCircularBuffer mMagnitudeBuffer =
new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) );
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
public ComplexAutomaticGainControl()
{
}
@Override
public void receive( ComplexSample currentSample )
public void receive( Complex currentSample )
{
ComplexSample delayedSample = mDelayBuffer.get( currentSample );
Complex delayedSample = mDelayBuffer.get( currentSample );
double gain = MANUAL_AGC_GAIN;
@@ -235,13 +235,13 @@ public class ComplexAutomaticGainControl implements Listener<ComplexSample>,
}
@Override
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
public void removeListener( Listener<Complex> listener )
{
mListener = null;
}

View File

@@ -4,8 +4,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import buffer.FloatCircularBuffer;
/*******************************************************************************
@@ -31,8 +31,7 @@ import buffer.FloatCircularBuffer;
* feedforward_agc_cc_impl.cc
*
******************************************************************************/
public class ComplexFeedForwardGainControl
implements Listener<ComplexSample>, Provider<ComplexSample>
public class ComplexFeedForwardGainControl implements ComplexSampleListener
{
private final static Logger mLog =
LoggerFactory.getLogger( ComplexFeedForwardGainControl.class );
@@ -40,7 +39,7 @@ public class ComplexFeedForwardGainControl
public static final float OBJECTIVE_ENVELOPE = 1.0f;
public static final float MINIMUM_ENVELOPE = 0.0001f;
private Listener<ComplexSample> mListener;
private ComplexSampleListener mListener;
private FloatCircularBuffer mEnvelopeHistory;
@@ -65,9 +64,9 @@ public class ComplexFeedForwardGainControl
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
float envelope = sample.envelope();
float envelope = Complex.envelope( inphase, quadrature );
if( envelope > mMaxEnvelope )
{
@@ -99,9 +98,7 @@ public class ComplexFeedForwardGainControl
/* Apply current gain value to the sample and send to the listener */
if( mListener != null )
{
sample.multiply( mGain );
mListener.receive( sample );
mListener.receive( inphase *= mGain, quadrature *= mGain );
}
}
@@ -110,15 +107,8 @@ public class ComplexFeedForwardGainControl
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
}
@Override
public void setListener( Listener<ComplexSample> listener )
public void setListener( ComplexSampleListener listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
{
mListener = null;
}
}

View File

@@ -1,12 +1,12 @@
package dsp.gain;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
public class ComplexGain implements Listener<ComplexSample>
public class ComplexGain implements Listener<Complex>
{
private float mGain;
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
public ComplexGain( float gain )
{
@@ -14,7 +14,7 @@ public class ComplexGain implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
if( mListener != null )
{
@@ -24,7 +24,7 @@ public class ComplexGain implements Listener<ComplexSample>
}
}
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}

View File

@@ -35,7 +35,6 @@ import instrument.tap.Tap;
import instrument.tap.stream.EyeDiagramData;
import instrument.tap.stream.EyeDiagramDataTap;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -44,24 +43,22 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import source.tuner.frequency.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.QPSKInterpolator;
import dsp.filter.interpolator.RealInterpolator;
import dsp.symbol.Dibit;
/**
* Implements a CQPSK demodulator using a Gardner Detector to determine optimal
* sampling timing and a Costas Loop as a phase locked loop synchronized with
* the incoming signal carrier frequency.
* Implements a LSM (CQPSK) demodulator using a Gardner Detector to determine
* optimal sampling timing and a Costas Loop as a phase locked loop synchronized
* with the incoming signal carrier frequency.
*
* Sample Rate: 48000
* Symbol Rate: 4800
*/
public class CQPSKDemodulator implements Instrumentable,
Listener<ComplexSample>,
Provider<ComplexSample>
public class CQPSKDemodulator implements Instrumentable, ComplexSampleListener
{
private final static Logger mLog =
LoggerFactory.getLogger( CQPSKDemodulator.class );
@@ -70,12 +67,10 @@ public class CQPSKDemodulator implements Instrumentable,
public static final float THETA = (float)( Math.PI / 4.0d );
/* 45 degree point */
public static final ComplexSample POINT_45_DEGREES =
new ComplexSample( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
public static final Complex POINT_45_DEGREES =
new Complex( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
private DecimalFormat mDecimalFormat = new DecimalFormat( "0.000000" );
private Listener<ComplexSample> mListener;
private Listener<Complex> mSymbolListener;
private GardnerSymbolTiming mGardnerDetector = new GardnerSymbolTiming();
@@ -89,10 +84,20 @@ public class CQPSKDemodulator implements Instrumentable,
{
}
@Override
public void receive( ComplexSample sample )
public void setSymbolListener( Listener<Complex> listener )
{
mGardnerDetector.receive( sample );
mSymbolListener = listener;
}
public void removeSymbolListener()
{
mSymbolListener = null;
}
@Override
public void receive( float inphase, float quadrature )
{
mGardnerDetector.receive( inphase, quadrature );
}
/**
@@ -211,14 +216,15 @@ public class CQPSKDemodulator implements Instrumentable,
/**
* Gardner symbol timing detector
*/
public class GardnerSymbolTiming implements Listener<ComplexSample>
public class GardnerSymbolTiming implements ComplexSampleListener
{
public static final int HALF_SAMPLES_PER_SYMBOL = 5;
public static final int SAMPLES_PER_SYMBOL = 10;
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
private ComplexSample[] mDelayLine =
new ComplexSample[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineInphase = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineQuadrature = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private int mDelayLinePointer = 0;
/* Sampling point */
@@ -231,25 +237,21 @@ public class CQPSKDemodulator implements Instrumentable,
private float mOmegaRel = 0.005f;
private float mOmegaMid = 10.0f;
private QPSKInterpolator mInterpolator = new QPSKInterpolator( 1.0f );
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
private Complex mPreviousSample;
private Complex mPreviousMiddleSample;
private Complex mPreviousSymbol;
private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f );
private ComplexSample mPreviousMiddleSample = new ComplexSample( 0.0f, 0.0f );
private ComplexSample mPreviousSymbol = new ComplexSample( 0.0f, 0.0f );
/**
* Provides symbol sampling timing control
*/
public GardnerSymbolTiming()
{
for( int x = 0; x < ( 2 * TWICE_SAMPLES_PER_SYMBOL ); x++ )
{
mDelayLine[ x ] = new ComplexSample( 0.0f, 0.0f );
}
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
/* Count down samples per symbol until we calculate the symbol */
mMu--;
@@ -258,14 +260,20 @@ public class CQPSKDemodulator implements Instrumentable,
/* Mix incoming sample with costas loop to remove any rotation
* that is present from a mis-tuned carrier frequency */
ComplexSample derotatedSample = ComplexSample
.multiply( mCostasLoop.getCurrentVector(), sample );
Complex costas = mCostasLoop.getCurrentVector();
float derotatedInphase = Complex.multiplyInphase( inphase, quadrature, costas.inphase(), costas.quadrature() );
float derotatedQuadrature = Complex.multiplyQuadrature( inphase, quadrature, costas.inphase(), costas.quadrature() );
/* Fill up the delay line to use with the interpolator */
mDelayLine[ mDelayLinePointer ] = derotatedSample;
mDelayLine[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample;
mDelayLineInphase[ mDelayLinePointer ] = derotatedInphase;
mDelayLineInphase[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedInphase;
/* Imcrement pointer and keep pointer in bounds */
mDelayLineQuadrature[ mDelayLinePointer ] = derotatedQuadrature;
mDelayLineQuadrature[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedQuadrature;
/* Increment pointer and keep pointer in bounds */
mDelayLinePointer = ( mDelayLinePointer + 1 ) % TWICE_SAMPLES_PER_SYMBOL;
/* Calculate the symbol once we've stored enough samples */
@@ -282,19 +290,24 @@ public class CQPSKDemodulator implements Instrumentable,
}
/* Calculate interpolated middle sample and current sample */
ComplexSample middleSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer, mMu );
ComplexSample currentSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + half_sps, half_mu );
/* Multiply current and previous samples to get symbols to use
* for gardner error feedback */
ComplexSample middleSymbol = ComplexSample.multiply(
middleSample, mPreviousMiddleSample.conjugate() );
float middleSampleInphase = mInterpolator.filter(
mDelayLineInphase, mDelayLinePointer, mMu );
float middleSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, mDelayLinePointer, mMu );
ComplexSample currentSymbol = ComplexSample.multiply(
currentSample, mPreviousSample.conjugate() );
int index = mDelayLinePointer + half_sps;
float currentSampleInphase = mInterpolator.filter(
mDelayLineInphase, index, half_mu );
float currentSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, index, half_mu );
/* Multiply current sample and conjugate (negated quadrature) of
* previous sample to get symbols to use for gardner error feedback */
Complex middleSymbol = Complex.multiply( middleSampleInphase,
middleSampleQuadrature, mPreviousMiddleSample.conjugate() );
Complex currentSymbol = Complex.multiply( currentSampleInphase,
currentSampleQuadrature, mPreviousSample.conjugate() );
/* Set gain to unity */
middleSymbol.normalize();
@@ -312,8 +325,10 @@ public class CQPSKDemodulator implements Instrumentable,
if( mEyeDiagramDataTap != null )
{
mEyeDiagramDataTap.receive(
new EyeDiagramData( Arrays.copyOfRange( mDelayLine,
0, 20 ), mMu, (float)half_sps + half_mu, gardnerError ) );
new EyeDiagramData(
Arrays.copyOfRange( mDelayLineInphase, 0, 20 ),
Arrays.copyOfRange( mDelayLineQuadrature, 0, 20 ),
mMu, (float)half_sps + half_mu, gardnerError ) );
}
/* mOmega is samples per symbol and is constrained to floating
@@ -325,8 +340,8 @@ public class CQPSKDemodulator implements Instrumentable,
mMu += mOmega + ( mGainMu * gardnerError );
/* Store current samples/symbols to use for the next period */
mPreviousSample = currentSample;
mPreviousMiddleSample = middleSample;
mPreviousSample.setValues( currentSampleInphase, currentSampleQuadrature );
mPreviousMiddleSample.setValues( middleSampleInphase, middleSampleQuadrature );
mPreviousSymbol = currentSymbol;
/* Update costas loop using phase error present in current
@@ -337,9 +352,9 @@ public class CQPSKDemodulator implements Instrumentable,
// ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
/* Dispatch the differentiated symbol to the registered listener */
if( mListener != null )
if( mSymbolListener != null )
{
mListener.receive( currentSymbol );
mSymbolListener.receive( currentSymbol );
}
}
}
@@ -356,7 +371,7 @@ public class CQPSKDemodulator implements Instrumentable,
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
* OP25/cqpsk.py
*/
public class CostasLoop implements Listener<ComplexSample>
public class CostasLoop implements Listener<Complex>
{
public static final double TWO_PI = 2.0 * Math.PI;
@@ -442,19 +457,19 @@ public class CQPSKDemodulator implements Instrumentable,
/**
* Current vector of the loop rotated by THETA degrees.
*/
public ComplexSample getCurrentVector()
public Complex getCurrentVector()
{
// return ComplexSample.fromAngle( mLoopPhase + THETA );
return ComplexSample.fromAngle( mLoopPhase );
return Complex.fromAngle( mLoopPhase );
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex complex )
{
/* Calculate phase error */
float phaseError = getPhaseError( sample );
float phaseError = getPhaseError( complex );
Dibit d = QPSKPolarSlicer.decide( sample );
Dibit d = QPSKPolarSlicer.decide( complex );
// mLog.debug( "Dibit: " + d.name() + " Error: " + phaseError );
/* Adjust for phase error */
@@ -503,30 +518,30 @@ public class CQPSKDemodulator implements Instrumentable,
* with the carrier frequency. Provides error feedback to adjust
* mixer frequency.
*/
public float getPhaseError( ComplexSample sample )
public float getPhaseError( Complex complex )
{
float phase_error = 0;
if( Math.abs( sample.real() ) > Math.abs( sample.imaginary() ) )
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
{
if( sample.real() > 0 )
if( complex.inphase() > 0 )
{
phase_error = -sample.imaginary();
phase_error = -complex.quadrature();
}
else
{
phase_error = sample.imaginary();
phase_error = complex.quadrature();
}
}
else
{
if( sample.imaginary() > 0 )
if( complex.quadrature() > 0 )
{
phase_error = sample.real();
phase_error = complex.inphase();
}
else
{
phase_error = -sample.real();
phase_error = -complex.inphase();
}
}
@@ -534,18 +549,6 @@ public class CQPSKDemodulator implements Instrumentable,
}
}
@Override
public void setListener( Listener<ComplexSample> listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
{
mListener = null;
}
@Override
public List<Tap> getTaps()
{

View File

@@ -45,10 +45,11 @@ import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import source.tuner.frequency.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.QPSKInterpolator;
import dsp.filter.interpolator.RealInterpolator;
import dsp.symbol.Dibit;
/**
@@ -59,9 +60,8 @@ import dsp.symbol.Dibit;
* Sample Rate: 48000
* Symbol Rate: 4800
*/
public class CQPSKDemodulator2 implements Instrumentable,
Listener<ComplexSample>,
Provider<ComplexSample>
public class CQPSKDemodulator2 implements Instrumentable, ComplexSampleListener,
Provider<Complex>
{
private final static Logger mLog =
LoggerFactory.getLogger( CQPSKDemodulator2.class );
@@ -70,12 +70,12 @@ public class CQPSKDemodulator2 implements Instrumentable,
public static final float THETA = (float)( Math.PI / 4.0d );
/* 45 degree point */
public static final ComplexSample POINT_45_DEGREES =
new ComplexSample( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
public static final Complex POINT_45_DEGREES =
new Complex( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
private DecimalFormat mDecimalFormat = new DecimalFormat( "0.000000" );
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
private EarlyLateSymbolTiming mGardnerDetector = new EarlyLateSymbolTiming();
@@ -90,9 +90,9 @@ public class CQPSKDemodulator2 implements Instrumentable,
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
mGardnerDetector.receive( sample );
mGardnerDetector.receive( inphase, quadrature );
}
/**
@@ -218,13 +218,14 @@ public class CQPSKDemodulator2 implements Instrumentable,
* attempting to find the largest magnitude value between two interpolated
* sample points during each symbol period.
*/
public class EarlyLateSymbolTiming implements Listener<ComplexSample>
public class EarlyLateSymbolTiming implements ComplexSampleListener
{
public static final int SAMPLES_PER_SYMBOL = 10;
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
private ComplexSample[] mDelayLine =
new ComplexSample[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineInphase = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineQuadrature = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private int mDelayLinePointer = 0;
/* Sampling point */
@@ -237,23 +238,19 @@ public class CQPSKDemodulator2 implements Instrumentable,
private float mOmegaRel = 0.005f;
private float mOmegaMid = 10.0f;
private QPSKInterpolator mInterpolator = new QPSKInterpolator( 1.0f );
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f );
private Complex mPreviousSample = new Complex( 0.0f, 0.0f );
/**
* Provides symbol sampling timing control
*/
public EarlyLateSymbolTiming()
{
for( int x = 0; x < ( 2 * TWICE_SAMPLES_PER_SYMBOL ); x++ )
{
mDelayLine[ x ] = new ComplexSample( 0.0f, 0.0f );
}
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
/* Count down samples per symbol until we calculate the symbol */
mMu--;
@@ -262,29 +259,31 @@ public class CQPSKDemodulator2 implements Instrumentable,
/* Mix incoming sample with costas loop to remove any rotation
* that is present from a mis-tuned carrier frequency */
ComplexSample derotatedSample = ComplexSample
.multiply( mCostasLoop.getCurrentVector(), sample );
Complex derotatedSample = Complex.multiply(
mCostasLoop.getCurrentVector(), inphase, quadrature );
/* Fill up the delay line to use with the interpolator */
mDelayLine[ mDelayLinePointer ] = derotatedSample;
mDelayLine[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample;
mDelayLineInphase[ mDelayLinePointer ] = derotatedSample.inphase();
mDelayLineInphase[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample.inphase();
mDelayLineQuadrature[ mDelayLinePointer ] = derotatedSample.quadrature();
mDelayLineQuadrature[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample.quadrature();
/* Imcrement pointer and keep pointer in bounds */
mDelayLinePointer = ( mDelayLinePointer + 1 ) % TWICE_SAMPLES_PER_SYMBOL;
/* Calculate the symbol once we've stored enough samples */
if( mMu <= 1.0f )
{
ComplexSample earlySample = mInterpolator
.filter( mDelayLine, mDelayLinePointer, mMu );
Complex earlySample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer, mMu );
ComplexSample middleSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + 1, mMu );
Complex middleSample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer + 1, mMu );
ComplexSample lateSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + 2, mMu );
Complex lateSample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer + 2, mMu );
ComplexSample currentSymbol = ComplexSample.multiply(
Complex currentSymbol = Complex.multiply(
middleSample, mPreviousSample.conjugate() );
/* Set symbol gain to unity */
@@ -295,10 +294,12 @@ public class CQPSKDemodulator2 implements Instrumentable,
if( mEyeDiagramDataTap != null )
{
mEyeDiagramDataTap.receive(
new EyeDiagramData( Arrays.copyOfRange( mDelayLine, 0, 20 ),
(float)mDelayLinePointer + 4.0f + mMu,
(float)mDelayLinePointer + 4.0f + mMu + 0.25f, earlyLateError ) );
mEyeDiagramDataTap.receive( new EyeDiagramData(
Arrays.copyOfRange( mDelayLineInphase, 0, 20 ),
Arrays.copyOfRange( mDelayLineQuadrature, 0, 20 ),
(float)mDelayLinePointer + 4.0f + mMu,
(float)mDelayLinePointer + 4.0f + mMu + 0.25f,
earlyLateError ) );
}
/* mOmega is samples per symbol and is constrained to floating
@@ -341,7 +342,7 @@ public class CQPSKDemodulator2 implements Instrumentable,
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
* OP25/cqpsk.py
*/
public class CostasLoop implements Listener<ComplexSample>
public class CostasLoop implements Listener<Complex>
{
public static final double TWO_PI = 2.0 * Math.PI;
@@ -427,14 +428,14 @@ public class CQPSKDemodulator2 implements Instrumentable,
/**
* Current vector of the loop rotated by THETA degrees.
*/
public ComplexSample getCurrentVector()
public Complex getCurrentVector()
{
// return ComplexSample.fromAngle( mLoopPhase + THETA );
return ComplexSample.fromAngle( mLoopPhase );
return Complex.fromAngle( mLoopPhase );
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
/* Calculate phase error */
float phaseError = getPhaseError( sample );
@@ -488,7 +489,7 @@ public class CQPSKDemodulator2 implements Instrumentable,
* with the carrier frequency. Provides error feedback to adjust
* mixer frequency.
*/
public float getPhaseError( ComplexSample sample )
public float getPhaseError( Complex sample )
{
float phase_error = 0;
@@ -520,13 +521,13 @@ public class CQPSKDemodulator2 implements Instrumentable,
}
@Override
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
public void removeListener( Listener<Complex> listener )
{
mListener = null;
}

View File

@@ -2,10 +2,27 @@ package dsp.psk;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import dsp.symbol.Dibit;
public class QPSKPolarSlicer implements Listener<ComplexSample>
/*******************************************************************************
* 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/>
******************************************************************************/
public class QPSKPolarSlicer implements Listener<Complex>
{
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
@@ -15,7 +32,7 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
*
* \ 00 /
* \ /
* 11 \/ 10
* 01 \/ 10
* /\
* / \
* / 11 \
@@ -41,16 +58,16 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex complex )
{
mBroadcaster.broadcast( decide( sample ) );
mBroadcaster.broadcast( decide( complex ) );
}
public static Dibit decide( ComplexSample sample )
public static Dibit decide( Complex complex )
{
if( sample.inPhaseAbsolute() > sample.quadratureAbsolute() )
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
{
if( sample.inphase() > 0 )
if( complex.inphase() > 0 )
{
return Dibit.D10_MINUS_1;
}
@@ -61,7 +78,7 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
}
else
{
if( sample.quadrature() > 0 )
if( complex.quadrature() > 0 )
{
return Dibit.D00_PLUS_1;
}

View File

@@ -2,10 +2,10 @@ package dsp.psk;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import dsp.symbol.Dibit;
public class QPSKStarSlicer implements Listener<ComplexSample>
public class QPSKStarSlicer implements Listener<Complex>
{
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
@@ -38,12 +38,12 @@ public class QPSKStarSlicer implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mBroadcaster.broadcast( decide( sample ) );
}
public static Dibit decide( ComplexSample sample )
public static Dibit decide( Complex sample )
{
if( sample.inphase() > 0 )
{