mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
P25 - work in progress to improve CQPSK LSM audio output. Audio output is still choppy but greatly improved over the previous test release.
This commit is contained in:
@@ -262,7 +262,6 @@ public class ComplexCICDecimate extends ComplexFilter
|
||||
mDecimation = decimation;
|
||||
mCleanupFilter = new ComplexFIRFilter( FilterFactory
|
||||
.getCICCleanupFilter( outputSampleRate,
|
||||
1,
|
||||
12000,
|
||||
60,
|
||||
WindowType.BLACKMAN ), 1.0d );
|
||||
|
||||
@@ -177,4 +177,18 @@ public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
double[] c = Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.mCoefficients;
|
||||
|
||||
double sum = 0;
|
||||
|
||||
for( int x = 0; x < c.length; x++ )
|
||||
{
|
||||
sum += c[ x ];
|
||||
}
|
||||
|
||||
System.out.println( sum );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexSample;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import sample.decimator.ComplexDecimator;
|
||||
@@ -72,15 +71,14 @@ public class ComplexPrimeCICDecimate
|
||||
else
|
||||
{
|
||||
/* Wire the current stage to the previous stage */
|
||||
mDecimatingStages.get( x - 1 ).addListener( stage );
|
||||
mDecimatingStages.get( x - 1 ).setListener( stage );
|
||||
}
|
||||
}
|
||||
|
||||
mOutput = new Output( 48000, order, decimation, passFrequency,
|
||||
attenuation, windowType );
|
||||
mOutput = new Output( 48000, passFrequency, attenuation, windowType );
|
||||
|
||||
mDecimatingStages.get( mDecimatingStages.size() - 1 )
|
||||
.addListener( mOutput );
|
||||
.setListener( mOutput );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -170,7 +168,16 @@ public class ComplexPrimeCICDecimate
|
||||
{
|
||||
for( int x = 0; x < order; x++ )
|
||||
{
|
||||
Stage stage = new Stage( size );
|
||||
Stage stage;
|
||||
|
||||
if( size == 2 )
|
||||
{
|
||||
stage = new TwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = new Stage( size );
|
||||
}
|
||||
|
||||
mStages.add( stage );
|
||||
|
||||
@@ -180,13 +187,13 @@ public class ComplexPrimeCICDecimate
|
||||
}
|
||||
else
|
||||
{
|
||||
mStages.get( x - 1 ).addListener( stage );
|
||||
mStages.get( x - 1 ).setListener( stage );
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new ComplexDecimator( size );
|
||||
|
||||
mStages.get( mStages.size() - 1 ).addListener( mDecimator );
|
||||
mStages.get( mStages.size() - 1 ).setListener( mDecimator );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -209,29 +216,35 @@ public class ComplexPrimeCICDecimate
|
||||
mFirstStage.receive( i, q );
|
||||
}
|
||||
|
||||
public void addListener( ComplexSampleListener listener )
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mDecimator.setListener( listener );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer
|
||||
* internally to implement the stage so that stage size has essentially
|
||||
* no impact on the performance of the stage
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||
* running average internally to implement the stage so that stage size has
|
||||
* essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements ComplexSampleListener
|
||||
{
|
||||
private ComplexSampleListener mListener;
|
||||
protected ComplexSampleListener mListener;
|
||||
|
||||
private float[] mISamples;
|
||||
private float[] mQSamples;
|
||||
|
||||
private float mISum;
|
||||
private float mQSum;
|
||||
protected float mISum;
|
||||
protected float mQSum;
|
||||
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
|
||||
protected float mGain;
|
||||
|
||||
public Stage()
|
||||
{
|
||||
}
|
||||
|
||||
public Stage( int size )
|
||||
{
|
||||
@@ -239,6 +252,8 @@ public class ComplexPrimeCICDecimate
|
||||
|
||||
mISamples = new float[ mSize ];
|
||||
mQSamples = new float[ mSize ];
|
||||
|
||||
mGain = 1.0f / (float)size;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -265,15 +280,44 @@ public class ComplexPrimeCICDecimate
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( mISum, mQSum );
|
||||
mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( ComplexSampleListener listener )
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||
* a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
super();
|
||||
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive( float i, float q )
|
||||
{
|
||||
float iSum = mISum + i;
|
||||
float qSum = mQSum + q;
|
||||
|
||||
mISum = i;
|
||||
mQSum = q;
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( ( iSum * mGain ), ( qSum * mGain ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction, applies cleanup filter, casts
|
||||
@@ -282,24 +326,21 @@ public class ComplexPrimeCICDecimate
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
private int mDecimation;
|
||||
private int mOrder;
|
||||
private double mGain = 1.0d;
|
||||
private ComplexFilter mCleanupFilter;
|
||||
private ComplexHalfBandNoDecimateFilter mHalfBandFilter =
|
||||
new ComplexHalfBandNoDecimateFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 0.4 );
|
||||
|
||||
public Output( int outputSampleRate, int order, int decimation,
|
||||
int passFrequency, int attenuation, WindowType windowType )
|
||||
public Output( int outputSampleRate, int passFrequency, int attenuation,
|
||||
WindowType windowType )
|
||||
{
|
||||
mOrder = order;
|
||||
mDecimation = decimation;
|
||||
mCleanupFilter = new ComplexFIRFilter( FilterFactory
|
||||
.getCICCleanupFilter( outputSampleRate,
|
||||
mOrder,
|
||||
passFrequency,
|
||||
attenuation,
|
||||
windowType ), 1.0d );
|
||||
windowType ), 0.4d );
|
||||
|
||||
setGain();
|
||||
mCleanupFilter.setListener( mHalfBandFilter );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,26 +351,15 @@ public class ComplexPrimeCICDecimate
|
||||
@Override
|
||||
public void receive( float left, float right )
|
||||
{
|
||||
mCleanupFilter.receive(
|
||||
new ComplexSample( (float)( left * mGain ),
|
||||
(float)( right * mGain ) ) );
|
||||
mCleanupFilter.receive( new ComplexSample( left, right ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate
|
||||
*/
|
||||
public void setDecimationRate( int rate )
|
||||
{
|
||||
mDecimation = rate;
|
||||
setGain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener( Listener<ComplexSample> listener )
|
||||
{
|
||||
mCleanupFilter.setListener( listener );
|
||||
mHalfBandFilter.setListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,31 +367,12 @@ public class ComplexPrimeCICDecimate
|
||||
*/
|
||||
public void removeListener( Listener<ComplexSample> listener )
|
||||
{
|
||||
mCleanupFilter.setListener( null );
|
||||
mHalfBandFilter.setListener( null );
|
||||
}
|
||||
|
||||
public Listener<ComplexSample> getListener()
|
||||
{
|
||||
return mCleanupFilter.getListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the gain correction factor for output samples.
|
||||
*
|
||||
* The CIC decimation filter induces significant gain as a factor of
|
||||
* delay, decimation rate and stage count. So, we apply a gain reduction
|
||||
* to offset the added gain estimate:
|
||||
*
|
||||
* gain = ( decimation * delay ) to power of stageCount
|
||||
*
|
||||
* Delay is ONE for the Comb used in this filter.
|
||||
*/
|
||||
private void setGain()
|
||||
{
|
||||
/* decimation is NOT multiplied by delay since delay is 1 */
|
||||
double gain = Math.pow( (double)mDecimation, (double)mOrder );
|
||||
|
||||
mGain = 1.0d / gain;
|
||||
return mHalfBandFilter.getListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import dsp.filter.Window.WindowType;
|
||||
|
||||
public class FilterFactory
|
||||
{
|
||||
// private final static Logger mLog =
|
||||
// LoggerFactory.getLogger( FilterFactory.class );
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( FilterFactory.class );
|
||||
|
||||
/**
|
||||
* Generates coefficients for a unity-gain, windowed low-pass filter
|
||||
@@ -575,7 +575,6 @@ public class FilterFactory
|
||||
* @return
|
||||
*/
|
||||
public static double[] getCICCleanupFilter( int outputSampleRate,
|
||||
int order,
|
||||
int passFrequency,
|
||||
int attenuation,
|
||||
WindowType window )
|
||||
@@ -590,7 +589,7 @@ public class FilterFactory
|
||||
}
|
||||
|
||||
double[] frequencyResponse =
|
||||
getCICResponseArray( outputSampleRate, passFrequency, taps, order );
|
||||
getCICResponseArray( outputSampleRate, passFrequency, taps );
|
||||
|
||||
//Apply Inverse DFT against frequency response unity values, leaving the
|
||||
//IDFT bin results in the frequency response array
|
||||
@@ -615,13 +614,14 @@ public class FilterFactory
|
||||
//Apply the window against the coefficients
|
||||
coefficients = Window.apply( window, coefficients );
|
||||
|
||||
normalize( coefficients );
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
public static double[] getCICResponseArray( int sampleRate,
|
||||
int frequency,
|
||||
int length,
|
||||
int order )
|
||||
int length )
|
||||
{
|
||||
double[] cicArray = new double[ length * 2 ];
|
||||
|
||||
@@ -630,15 +630,15 @@ public class FilterFactory
|
||||
|
||||
cicArray[ 0 ] = 1.0d;
|
||||
|
||||
double unityResponse = Math.pow( Math.sin( 1.0d / (double)length ) /
|
||||
( 1.0d / (double)length ), order );
|
||||
double unityResponse = 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.pow( ( Math.sin( (double)x / (double)length ) /
|
||||
( (double)x / (double)length ) ), order ) );
|
||||
( Math.sin( (double)x / (double)length ) /
|
||||
( (double)x / (double)length ) ) );
|
||||
|
||||
cicArray[ x ] = compensated;
|
||||
cicArray[ length - x ] = compensated;
|
||||
@@ -648,38 +648,40 @@ public class FilterFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates root raised cosine filter coefficients.
|
||||
* Creates root raised cosine filter coefficients with a tap count equal
|
||||
* to the symbols x samplesPerSymbol + 1.
|
||||
*
|
||||
* Symbol count should be an even integer.
|
||||
*
|
||||
* Ported to java from gnuradio/filter/firdes.cc
|
||||
*
|
||||
* @param gain - gain value to apply
|
||||
* @param sampleRate - sample rate in hertz
|
||||
* @param symbolRate - symbol rate in hertz
|
||||
* For 40db attenuation, calculate the number of symbols based on the
|
||||
* following formula:
|
||||
*
|
||||
* Symbols = -44 * alpha + 33
|
||||
*
|
||||
* @param samplesPerSymbol - number of samples per symbol
|
||||
* @param symbols - number of symbols - must be even
|
||||
* @param alpha - roll-off factor
|
||||
* @param tapCount - odd number of taps, or will be increased to make odd
|
||||
*
|
||||
* @return - filter coefficients
|
||||
*/
|
||||
public static double[] getRootRaisedCosine( double gain,
|
||||
double sampleRate,
|
||||
double symbolRate,
|
||||
double alpha,
|
||||
int tapCount )
|
||||
public static double[] getRootRaisedCosine( int samplesPerSymbol,
|
||||
int symbols,
|
||||
double alpha )
|
||||
{
|
||||
/* Ensure odd number of taps */
|
||||
int taps = tapCount | 1;
|
||||
int taps = samplesPerSymbol * symbols + 1;
|
||||
|
||||
double scale = 0;
|
||||
|
||||
double samplesPerSymbol = sampleRate / symbolRate;
|
||||
|
||||
double[] coefficients = new double[ taps ];
|
||||
|
||||
for( int x = 0; x < taps; x++ )
|
||||
{
|
||||
double index = x - ( taps / 2 );
|
||||
|
||||
double x1 = Math.PI * index / samplesPerSymbol;
|
||||
double x2 = 4.0 * alpha * index / samplesPerSymbol;
|
||||
double x1 = Math.PI * index / (double)samplesPerSymbol;
|
||||
double x2 = 4.0 * alpha * index / (double)samplesPerSymbol;
|
||||
double x3 = x2 * x2 - 1;
|
||||
|
||||
double numerator, denominator;
|
||||
@@ -690,7 +692,7 @@ public class FilterFactory
|
||||
{
|
||||
numerator = Math.cos( ( 1.0 + alpha ) * x1 ) +
|
||||
Math.sin( ( 1.0 - alpha ) * x1 ) /
|
||||
( 4.0 * alpha * index / samplesPerSymbol );
|
||||
( 4.0 * alpha * index / (double)samplesPerSymbol );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -712,12 +714,13 @@ public class FilterFactory
|
||||
x2 = ( 1.0 + alpha ) * x1;
|
||||
|
||||
numerator = ( Math.sin( x2 ) * ( 1.0 + alpha ) * Math.PI -
|
||||
Math.cos( x3 ) * ( ( 1.0 - alpha ) * Math.PI * samplesPerSymbol ) /
|
||||
( 4 * alpha * index ) +
|
||||
Math.sin( x3 ) * samplesPerSymbol * samplesPerSymbol /
|
||||
( 4.0 * alpha * index * index ) );
|
||||
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 / samplesPerSymbol;
|
||||
denominator = -32.0 * Math.PI * alpha * alpha * index /
|
||||
(double)samplesPerSymbol;
|
||||
}
|
||||
|
||||
coefficients[ x ] = 4.0 * alpha * numerator / denominator;
|
||||
@@ -730,16 +733,18 @@ public class FilterFactory
|
||||
*/
|
||||
for( int x = 0; x < taps; x++ )
|
||||
{
|
||||
coefficients[ x ] = coefficients[ x ] * gain / scale;
|
||||
coefficients[ x ] = coefficients[ x ] / scale;
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
double[] coefficients = getCICCleanupFilter( 48000, 2, 60, 24000, WindowType.BLACKMAN );
|
||||
double[] c = FilterFactory.getRootRaisedCosine( 10, 4, 0.2 );
|
||||
|
||||
System.out.println( Arrays.toString( coefficients ) );
|
||||
mLog.debug( "C:" + Arrays.toString( c ) );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ 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
|
||||
@@ -50,6 +56,6 @@ public class QPSKInterpolator extends Interpolator
|
||||
imaginaryAccumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ].imaginary() );
|
||||
imaginaryAccumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ].imaginary() );
|
||||
|
||||
return new ComplexSample( realAccumulator, imaginaryAccumulator );
|
||||
return new ComplexSample( realAccumulator * mGain, imaginaryAccumulator * mGain );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user