mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Merges #34 Major redesign of Tuner, Channel and Alias management.
Changes to model, view and controller components and updates all gui editors. Removes the configuration panel and tree and replaces with 3 new panels. Updates playlist.xml to a new playlist_v2.xml format.
This commit is contained in:
@@ -1,342 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.Provider;
|
||||
import sample.complex.Complex;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
/**
|
||||
* Cascaded Integrator Comb (CIC) filter - anti-aliasing decimating filter for
|
||||
* reducing a high rate complex sample stream to a lower rate by an integral
|
||||
* decimation (ie reduction) value.
|
||||
*
|
||||
* Note: comb delay is fixed at 1 for this filter.
|
||||
*/
|
||||
public class ComplexCICDecimate extends ComplexFilter
|
||||
implements Provider<Complex>
|
||||
{
|
||||
private Integrator[] mIntegrator;
|
||||
private Comb[] mComb;
|
||||
private Decimator mDecimator;
|
||||
private Output mOutput;
|
||||
|
||||
/**
|
||||
* Constructs the complex sample CIC filter
|
||||
*
|
||||
* @param decimation - decimation factor
|
||||
*
|
||||
* @param stageCount - number of integrator/comb sections to use. The
|
||||
* higher the number of stages, the sharper dropoff of the filter response
|
||||
*
|
||||
* @param outputSampleRate - output sample rate is used to calculate the
|
||||
* coefficients for the final cleanup filter to adjust for frequency
|
||||
* response droop induced by this CIC filter
|
||||
*/
|
||||
public ComplexCICDecimate( int decimation,
|
||||
int stageCount,
|
||||
int outputSampleRate )
|
||||
{
|
||||
/* Construct integrator(s) and comb(s) */
|
||||
mIntegrator = new Integrator[ stageCount ];
|
||||
mComb = new Comb[ stageCount ];
|
||||
|
||||
for( int x = 0; x < stageCount; x++ )
|
||||
{
|
||||
mIntegrator[ x ] = new Integrator();
|
||||
mComb[ x ] = new Comb();
|
||||
}
|
||||
|
||||
/* Connect each stage */
|
||||
int y = 1;
|
||||
|
||||
while( y < stageCount )
|
||||
{
|
||||
mIntegrator[ y - 1 ].setListener( mIntegrator[ y ] );
|
||||
mComb[ y - 1 ].setListener( mComb[ y ] );
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
/* Connect decimator to integrator output and comb input */
|
||||
mDecimator = new Decimator( decimation );
|
||||
|
||||
mIntegrator[ stageCount - 1 ].setListener( mDecimator );
|
||||
mDecimator.setListener( mComb[ 0 ] );
|
||||
|
||||
/* Connect output adapter to the comb output */
|
||||
mOutput = new Output( outputSampleRate, stageCount, decimation );
|
||||
mComb[ stageCount - 1 ].setListener( mOutput );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate for this filter. This method can be invoked
|
||||
* after filter construction to change the decimation rate dynamically.
|
||||
*/
|
||||
public void setDecimationRate( int rate )
|
||||
{
|
||||
mDecimator.setDecimationRate( rate );
|
||||
mOutput.setDecimationRate( rate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Input method for this filter to receive complex samples.
|
||||
*/
|
||||
@Override
|
||||
public void receive( Complex sample )
|
||||
{
|
||||
mIntegrator[ 0 ].receive( (double)sample.left(), (double)sample.right() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive the output of this CIC filter
|
||||
*/
|
||||
@Override
|
||||
public void setListener( Listener<Complex> listener )
|
||||
{
|
||||
mOutput.setListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving the output of this CIC filter
|
||||
*/
|
||||
@Override
|
||||
public void removeListener( Listener<Complex> listener )
|
||||
{
|
||||
mOutput.removeListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<Complex> getListener()
|
||||
{
|
||||
return mOutput.getListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* CIC Comb filter section
|
||||
*/
|
||||
public class Comb implements InternalListener
|
||||
{
|
||||
private double mPreviousLeft;
|
||||
private double mPreviousRight;
|
||||
private InternalListener mCombListener;
|
||||
|
||||
/**
|
||||
* Performs comb filtering on the received values, subtracting the
|
||||
* previous received samples from these samples.
|
||||
*/
|
||||
@Override
|
||||
public void receive( double left, double right )
|
||||
{
|
||||
mCombListener.receive( ( left - mPreviousLeft ),
|
||||
( right - mPreviousRight ) );
|
||||
|
||||
mPreviousLeft = left;
|
||||
mPreviousRight = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comb output listener.
|
||||
*/
|
||||
public void setListener( InternalListener listener )
|
||||
{
|
||||
mCombListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CIC Integrator section
|
||||
*/
|
||||
public class Integrator implements InternalListener
|
||||
{
|
||||
private double mRunningSumLeft = 0.0d;
|
||||
private double mRunningSumRight = 0.0d;
|
||||
private InternalListener mIntegratorListener;
|
||||
|
||||
/**
|
||||
* Integrates the received values with the previous running sum and
|
||||
* sends the new running sum to the registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive( double left, double right )
|
||||
{
|
||||
mRunningSumLeft += left;
|
||||
mRunningSumRight += right;
|
||||
|
||||
mIntegratorListener.receive( mRunningSumLeft, mRunningSumRight );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the integrator output listener
|
||||
*/
|
||||
public void setListener( InternalListener listener )
|
||||
{
|
||||
mIntegratorListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimator - passes only 1 of xx samples to a registered listener and
|
||||
* discards all other samples.
|
||||
*/
|
||||
public class Decimator implements InternalListener
|
||||
{
|
||||
private InternalListener mDecimatorListener;
|
||||
private int mPointer = 0;
|
||||
private int mDecimationRate;
|
||||
|
||||
/**
|
||||
* Constructs a new Decimator object with the specified decimation rate.
|
||||
*/
|
||||
public Decimator( int rate )
|
||||
{
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate. Rate can be changed after this object
|
||||
* is constructed.
|
||||
*/
|
||||
public synchronized void setDecimationRate( int rate )
|
||||
{
|
||||
mDecimationRate = rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives samples allowing only 1 of every (rate) sample to go on
|
||||
* to the registered listener
|
||||
*/
|
||||
@Override
|
||||
public void receive( double left, double right )
|
||||
{
|
||||
mPointer++;
|
||||
|
||||
if( mPointer >= mDecimationRate )
|
||||
{
|
||||
mDecimatorListener.receive( left, right );
|
||||
|
||||
mPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decimated output listener
|
||||
*/
|
||||
public void setListener( InternalListener listener )
|
||||
{
|
||||
mDecimatorListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction, applies cleanup filter, casts
|
||||
* double-valued samples into float-valued complex sample and sends the
|
||||
* result to the registered listener.
|
||||
*/
|
||||
public class Output implements InternalListener
|
||||
{
|
||||
private int mStageCount;
|
||||
private int mDecimation;
|
||||
private double mGain = 1.0d;
|
||||
private ComplexFilter mCleanupFilter;
|
||||
|
||||
public Output( int outputSampleRate, int stageCount, int decimation )
|
||||
{
|
||||
mStageCount = stageCount;
|
||||
mDecimation = decimation;
|
||||
mCleanupFilter = new ComplexFIRFilterOld( FilterFactory
|
||||
.getCICCleanupFilter( outputSampleRate,
|
||||
12000,
|
||||
60,
|
||||
WindowType.BLACKMAN ), 1.0f );
|
||||
|
||||
setGain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver method for the output adapter to receive a filtered,
|
||||
* decimated sample, apply gain correction, apply cleanup filtering
|
||||
* and output the sample values as a complex sample.
|
||||
*/
|
||||
@Override
|
||||
public void receive( double left, double right )
|
||||
{
|
||||
mCleanupFilter.receive(
|
||||
new Complex( (float)( left * mGain ),
|
||||
(float)( right * mGain ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new decimation rate
|
||||
*/
|
||||
public void setDecimationRate( int rate )
|
||||
{
|
||||
mDecimation = rate;
|
||||
setGain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener( Listener<Complex> listener )
|
||||
{
|
||||
mCleanupFilter.setListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving output samples
|
||||
*/
|
||||
public void removeListener( Listener<Complex> listener )
|
||||
{
|
||||
mCleanupFilter.setListener( null );
|
||||
}
|
||||
|
||||
public Listener<Complex> 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)mStageCount );
|
||||
|
||||
mGain = 10.0d / gain;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal interface for each of the CIC components to pass samples
|
||||
*/
|
||||
private interface InternalListener
|
||||
{
|
||||
public void receive( double left, double right );
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import sample.complex.Complex;
|
||||
|
||||
public class ComplexFIRFilterOld extends ComplexFilter
|
||||
{
|
||||
private ArrayList<Complex> mBuffer;
|
||||
private int mBufferSize = 1; //Temporary initial value
|
||||
private int mBufferPointer = 0;
|
||||
private float[] mCoefficients;
|
||||
private int[][] mIndexMap;
|
||||
private int mCenterCoefficient;
|
||||
private int mCenterCoefficientMapIndex;
|
||||
private float mGain;
|
||||
|
||||
public ComplexFIRFilterOld( float[] coefficients, float gain )
|
||||
{
|
||||
mCoefficients = coefficients;
|
||||
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 Complex( (float)0.0, (float)0.0 ) );
|
||||
}
|
||||
|
||||
generateIndexMap( mCoefficients.length );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBuffer.clear();
|
||||
mBuffer = null;
|
||||
|
||||
mCoefficients = null;
|
||||
mIndexMap = null;
|
||||
}
|
||||
|
||||
public void receive( Complex newSample )
|
||||
{
|
||||
//Add the new sample to the buffer
|
||||
mBuffer.set( mBufferPointer, newSample );
|
||||
|
||||
//Increment & Adjust the buffer pointer for circular wrap around
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferSize )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
//Convolution - multiply filter coefficients by the circular buffer
|
||||
//samples to calculate a new filtered value
|
||||
double leftAccumulator = 0;
|
||||
double rightAccumulator = 0;
|
||||
|
||||
//Start with the center tap value
|
||||
leftAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).left();
|
||||
rightAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).right();
|
||||
|
||||
//For the remaining coefficients, add the symmetric samples, oldest and newest
|
||||
//first, then multiply by the single coefficient
|
||||
for( int x = 0; x < mCenterCoefficient; x++ )
|
||||
{
|
||||
leftAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).left() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + mCenterCoefficient ] ).left() );
|
||||
|
||||
rightAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).right() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + mCenterCoefficient ] ).right() );
|
||||
}
|
||||
|
||||
//We're almost finished ... apply gain, cast the doubles to floats and
|
||||
//send it on it's merry way
|
||||
send( new Complex( (float)( leftAccumulator * mGain ),
|
||||
(float)( rightAccumulator * mGain ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
mIndexMap = new int[ size ][ size ];
|
||||
|
||||
//Last column will be the center coefficient index value for each row
|
||||
mCenterCoefficientMapIndex = size - 1;
|
||||
mCenterCoefficient = (int)( size / 2 );
|
||||
|
||||
//Setup the first row. Offset is the first row's center coefficient value
|
||||
//and will become the index value we place in the last column
|
||||
mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient;
|
||||
|
||||
//Indexes 0 to 1/2 are their index value, and indexes 1/2 to end are
|
||||
//an offset of the first half of the values
|
||||
for( int x = 0; x < mCenterCoefficient; x++ )
|
||||
{
|
||||
mIndexMap[ 0 ][ x ] = x;
|
||||
mIndexMap[ 0 ][ x + mCenterCoefficient ] = size - 1 - x;
|
||||
}
|
||||
|
||||
//For each subsequent map row, increment the value of the preceding row
|
||||
//same column by 1, wrapping to zero when we exceed the size value
|
||||
for( int x = 1; x < size; x++ )
|
||||
{
|
||||
for( int y = 0; y < size; y++ )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1;
|
||||
|
||||
if( mIndexMap[ x ][ y ] >= size )
|
||||
{
|
||||
mIndexMap[ x ][ y ] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String printIndexMap()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for( int x = 0; x < mCoefficients.length; x++ )
|
||||
{
|
||||
for( int y = 0; y < mCoefficients.length; y++ )
|
||||
{
|
||||
sb.append( mIndexMap[ x ][ y ] + " " );
|
||||
}
|
||||
sb.append( "\n" );
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import sample.complex.Complex;
|
||||
|
||||
public class ComplexHalfBandFilter extends ComplexFilter
|
||||
{
|
||||
private ArrayList<Complex> 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;
|
||||
private boolean mDispatchFlag = false;
|
||||
|
||||
/**
|
||||
* Half-Band filter with decimation by 2 against complex samples composed of
|
||||
* real valued floats.
|
||||
*
|
||||
* Takes advantage of the symmetrical nature of FIR filter coefficients by
|
||||
* adding oldest and newest sample first, then multiplying once by the
|
||||
* corresponding coefficient
|
||||
*
|
||||
* Also, 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 ComplexHalfBandFilter( Filters filter, float gain )
|
||||
{
|
||||
mCoefficients = filter.getCoefficients();
|
||||
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 Complex( (float)0.0, (float)0.0 ) );
|
||||
}
|
||||
|
||||
generateIndexMap( mCoefficients.length );
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the filtered value by applying the coefficients against
|
||||
* the complex samples in mBuffer
|
||||
*/
|
||||
public void receive( Complex newSample )
|
||||
{
|
||||
//Add the new sample to the buffer
|
||||
mBuffer.set( mBufferPointer, newSample );
|
||||
|
||||
//Increment & Adjust the buffer pointer for circular wrap around
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferSize )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
//Toggle the flag every time, so that we can calculate and dispatch a
|
||||
//sample every other time, when the flag is true (ie decimate by 2)
|
||||
mDispatchFlag = !mDispatchFlag;
|
||||
|
||||
//Calculate a filtered sample when the flag is true
|
||||
if( mDispatchFlag )
|
||||
{
|
||||
//Convolution - multiply filter coefficients by the circular buffer
|
||||
//samples to calculate a new filtered value
|
||||
double leftAccumulator = 0;
|
||||
double rightAccumulator = 0;
|
||||
|
||||
//Start with the center tap value
|
||||
leftAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).left();
|
||||
rightAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).right();
|
||||
|
||||
//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 )
|
||||
{
|
||||
leftAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).left() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ).left() );
|
||||
|
||||
rightAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).right() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ).right() );
|
||||
}
|
||||
|
||||
//We're almost finished ... apply gain, cast the doubles to floats and
|
||||
//send it on it's merry way
|
||||
send( new Complex( (float)( leftAccumulator * mGain ),
|
||||
(float)( rightAccumulator * 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
|
||||
public class ComplexHalfBandNoDecimateFilter extends ComplexFilter
|
||||
{
|
||||
private Listener<Complex> mListener;
|
||||
private ArrayList<Complex> 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 with no decimation against complex samples composed of
|
||||
* real valued floats.
|
||||
*
|
||||
* Takes advantage of the symmetrical nature of FIR filter coefficients by
|
||||
* adding oldest and newest sample first, then multiplying once by the
|
||||
* corresponding coefficient
|
||||
*
|
||||
* Also, 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 ComplexHalfBandNoDecimateFilter( Filters filter, float gain )
|
||||
{
|
||||
mCoefficients = filter.getCoefficients();
|
||||
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 Complex( (float)0.0, (float)0.0 ) );
|
||||
}
|
||||
|
||||
generateIndexMap( mCoefficients.length );
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the filtered value by applying the coefficients against
|
||||
* the complex samples in mBuffer
|
||||
*/
|
||||
public void receive( Complex newSample )
|
||||
{
|
||||
//Add the new sample to the buffer
|
||||
mBuffer.set( mBufferPointer, newSample );
|
||||
|
||||
//Increment & Adjust the buffer pointer for circular wrap around
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferSize )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
//Convolution - multiply filter coefficients by the circular buffer
|
||||
//samples to calculate a new filtered value
|
||||
float leftAccumulator = 0;
|
||||
float rightAccumulator = 0;
|
||||
|
||||
//Start with the center tap value
|
||||
leftAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).left();
|
||||
rightAccumulator += mCoefficients[ mCenterCoefficient ] *
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ).right();
|
||||
|
||||
//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 )
|
||||
{
|
||||
leftAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).left() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ).left() );
|
||||
|
||||
rightAccumulator += mCoefficients[ x ] *
|
||||
( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ).right() +
|
||||
mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ).right() );
|
||||
}
|
||||
|
||||
//We're almost finished ... apply gain, cast the floats to floats and
|
||||
//send it on it's merry way
|
||||
send( new Complex( ( leftAccumulator * mGain ),
|
||||
( rightAccumulator * 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
float[] c = Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.mCoefficients;
|
||||
|
||||
float sum = 0;
|
||||
|
||||
for( int x = 0; x < c.length; x++ )
|
||||
{
|
||||
sum += c[ x ];
|
||||
}
|
||||
|
||||
System.out.println( sum );
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import buffer.ComplexTappedCircularBuffer;
|
||||
|
||||
public class ComplexPolyphaseDecimationFilter extends ComplexFilter
|
||||
{
|
||||
private FilterPhase[] mFilterPhases;
|
||||
private int mPhasePointer = 0;
|
||||
private int mPhaseCount;
|
||||
|
||||
/**
|
||||
* Implements a decimating poly-phase filter bank for complex samples, as
|
||||
* described in Understanding Digital Signal Processing, 3e, Lyons, in
|
||||
* section 10.7, figure 10-14.
|
||||
*
|
||||
* Constraints:
|
||||
* - Low-pass filter coefficient length must be an integer multiple
|
||||
* of the decimation rate:
|
||||
*
|
||||
* Consideration:
|
||||
* - Low pass filter cutoff frequency must be less than:
|
||||
* ( input sample rate / 2 / decimation )
|
||||
*
|
||||
* @param coefficients - low pass filter coefficients.
|
||||
* @param decimationRate - integer decimation rate
|
||||
*/
|
||||
public ComplexPolyphaseDecimationFilter( double[] coefficients,
|
||||
int decimationRate )
|
||||
{
|
||||
/* Verify that the filter length is an integer multiple of the
|
||||
* decimation rate */
|
||||
assert( coefficients.length % decimationRate == 0 );
|
||||
|
||||
mPhaseCount = decimationRate;
|
||||
|
||||
/* Construct a number of filter phases equal to the decimation rate */
|
||||
mFilterPhases = new FilterPhase[ mPhaseCount ];
|
||||
|
||||
for( int phaseNumber = 0; phaseNumber < mPhaseCount; phaseNumber++ )
|
||||
{
|
||||
double[] phaseCoefficients =
|
||||
getPhaseCoefficients( coefficients, decimationRate, phaseNumber );
|
||||
|
||||
mFilterPhases[ phaseNumber ] = new FilterPhase( phaseCoefficients );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inputs a new sample into the filter at the non-decimated rate
|
||||
*/
|
||||
@Override
|
||||
public void receive( Complex sample )
|
||||
{
|
||||
mFilterPhases[ mPhasePointer++ ].receive( sample );
|
||||
|
||||
if( mPhasePointer >= mPhaseCount )
|
||||
{
|
||||
/* Broadcast a new output sample */
|
||||
calculate();
|
||||
|
||||
/* Reset the pointer */
|
||||
mPhasePointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a complex sample at the decimated rate, comprised of the sum of
|
||||
* the outputs of the individual filter stages, trigger by a decimation rate
|
||||
* counter on the input side.
|
||||
*/
|
||||
private void calculate()
|
||||
{
|
||||
if( hasListener() )
|
||||
{
|
||||
/* Accumulators */
|
||||
float left = 0.0f;
|
||||
float right = 0.0f;
|
||||
|
||||
for( int x = 0; x < mPhaseCount; x++ )
|
||||
{
|
||||
Complex phaseSample = mFilterPhases[ x ].calculate();
|
||||
|
||||
left += phaseSample.left();
|
||||
right += phaseSample.right();
|
||||
}
|
||||
|
||||
send( new Complex( left, right ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of filter coefficients that belong to the filter bank
|
||||
* phase.
|
||||
*/
|
||||
private double[] getPhaseCoefficients( double[] coefficients,
|
||||
int phaseCount,
|
||||
int phaseNumber )
|
||||
{
|
||||
int phaseFilterLength = (int)( coefficients.length / phaseCount );
|
||||
|
||||
double[] phaseCoefficients = new double[ phaseFilterLength ];
|
||||
|
||||
for( int x = 0; x < phaseFilterLength; x++ )
|
||||
{
|
||||
phaseCoefficients[ x ] = coefficients[ x * phaseCount + phaseNumber ];
|
||||
}
|
||||
|
||||
return phaseCoefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a single phase of a poly-phase filter bank
|
||||
*/
|
||||
public class FilterPhase implements Listener<Complex>
|
||||
{
|
||||
private int mLength;
|
||||
private double[] mCoefficients;
|
||||
private ComplexTappedCircularBuffer mBuffer;
|
||||
|
||||
/**
|
||||
* @param coefficients - subset of the filter coefficients for this
|
||||
* phase of the poly-phase filter bank
|
||||
*/
|
||||
public FilterPhase( double[] coefficients )
|
||||
{
|
||||
mCoefficients = coefficients;
|
||||
mLength = coefficients.length;
|
||||
|
||||
/* The constructor preloads the buffer with 0-valued samples */
|
||||
mBuffer = new ComplexTappedCircularBuffer( coefficients.length );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Complex sample )
|
||||
{
|
||||
mBuffer.add( sample );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs convolution by multiplying the buffer samples by their
|
||||
* corresponding filter coefficients and returns the sum
|
||||
*/
|
||||
public Complex calculate()
|
||||
{
|
||||
/* Accumulators */
|
||||
double left = 0.0d;
|
||||
double right = 0.0d;
|
||||
|
||||
for( int x = 0; x < mLength; x++ )
|
||||
{
|
||||
left += ( mBuffer.get( x ).left() * mCoefficients[ x ] );
|
||||
right += ( mBuffer.get( x ).right() * mCoefficients[ x ] );
|
||||
}
|
||||
|
||||
return new Complex( (float)left, (float)right );
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/dsp/filter/dc/DCRemovalFilter_RealBuffer.java
Normal file
60
src/dsp/filter/dc/DCRemovalFilter_RealBuffer.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package dsp.filter.dc;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
public class DCRemovalFilter_RealBuffer implements Listener<RealBuffer>
|
||||
{
|
||||
private Listener<RealBuffer> mListener;
|
||||
/**
|
||||
* Removes DC bias present across the samples in the buffer by calculating
|
||||
* the mean DC component and subtracting that component from each of the
|
||||
* samples.
|
||||
*/
|
||||
public DCRemovalFilter_RealBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public RealBuffer filter( RealBuffer buffer )
|
||||
{
|
||||
double accumulator = 0.0d;
|
||||
|
||||
for( float sample: buffer.getSamples() )
|
||||
{
|
||||
accumulator += sample;
|
||||
}
|
||||
|
||||
float mean = (float)( accumulator / (double)buffer.getSamples().length );
|
||||
|
||||
for( float sample: buffer.getSamples() )
|
||||
{
|
||||
sample -= mean;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( filter( buffer ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.fm;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.real.RealSampleBroadcaster;
|
||||
import sample.real.RealSampleListener;
|
||||
import dsp.filter.ComplexFIRFilterOld;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.FloatFIRFilter;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
@Deprecated
|
||||
public class FilteringNBFMDemodulator implements Listener<Complex>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( FilteringNBFMDemodulator.class );
|
||||
|
||||
private RealSampleBroadcaster mBroadcaster = new RealSampleBroadcaster();
|
||||
private ComplexFIRFilterOld mIQFilter;
|
||||
private FloatFIRFilter mAudioFilter;
|
||||
private FMDiscriminator mDiscriminator;
|
||||
|
||||
public FilteringNBFMDemodulator( float[] iqFilter, float iqGain,
|
||||
float[] audioFilter, float audioGain )
|
||||
{
|
||||
mIQFilter = new ComplexFIRFilterOld( iqFilter, iqGain );
|
||||
|
||||
mAudioFilter = new FloatFIRFilter( audioFilter, audioGain );
|
||||
|
||||
mDiscriminator = new FMDiscriminator( 1 );
|
||||
|
||||
mIQFilter.setListener( mDiscriminator );
|
||||
|
||||
mDiscriminator.setListener( mAudioFilter );
|
||||
|
||||
mAudioFilter.setListener( mBroadcaster );
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a quadrature narrow-band demodulator that produces float
|
||||
* valued demodulated, audio filtered output samples.
|
||||
*/
|
||||
public FilteringNBFMDemodulator()
|
||||
{
|
||||
this( FilterFactory.getLowPass( 48000, 5000, 7000, 48, WindowType.HAMMING, true ),
|
||||
1.0002f,
|
||||
FilterFactory.getLowPass( 48000, 3000, 6000, 48, WindowType.HAMMING, true ),
|
||||
2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive method for complex samples that are fed into this class to be
|
||||
* processed
|
||||
*/
|
||||
@Override
|
||||
public void receive( Complex quadratureSample )
|
||||
{
|
||||
mIQFilter.receive( quadratureSample );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive demodulated samples
|
||||
*/
|
||||
public void addListener( RealSampleListener listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from receiving demodulated samples
|
||||
*/
|
||||
public void removeListener( RealSampleListener listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.fm;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.real.RealSampleBroadcaster;
|
||||
import sample.real.RealSampleListener;
|
||||
import dsp.filter.ComplexFIRFilterOld;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
@Deprecated
|
||||
public class NBFMDemodulator implements Listener<Complex>
|
||||
{
|
||||
private RealSampleBroadcaster mBroadcaster = new RealSampleBroadcaster();
|
||||
private ComplexFIRFilterOld mIQFilter;
|
||||
private FMDiscriminator mDiscriminator;
|
||||
|
||||
/**
|
||||
* Implements a quadrature narrow-band demodulator that produces float
|
||||
* valued demodulated, audio filtered output samples.
|
||||
*/
|
||||
public NBFMDemodulator()
|
||||
{
|
||||
this( FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ),
|
||||
1.0002f, true );
|
||||
}
|
||||
|
||||
public NBFMDemodulator( float[] iqFilter, float iqGain, boolean afc )
|
||||
{
|
||||
mIQFilter = new ComplexFIRFilterOld( iqFilter, iqGain );
|
||||
|
||||
mDiscriminator = new FMDiscriminator( 1 );
|
||||
|
||||
mIQFilter.setListener( mDiscriminator );
|
||||
|
||||
mDiscriminator.setListener( mBroadcaster );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDiscriminator.dispose();
|
||||
|
||||
mBroadcaster.dispose();
|
||||
|
||||
mIQFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive method for complex samples that are fed into this class to be
|
||||
* processed
|
||||
*/
|
||||
@Override
|
||||
public void receive( Complex sample )
|
||||
{
|
||||
mIQFilter.receive( sample );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive demodulated samples
|
||||
*/
|
||||
public void addListener( RealSampleListener listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from receiving demodulated samples
|
||||
*/
|
||||
public void removeListener( RealSampleListener listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,7 @@ public class AutomaticGainControl
|
||||
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) );
|
||||
|
||||
/* Hang timer release decay time constant in seconds */
|
||||
@SuppressWarnings( "unused" )
|
||||
private static final double RELEASE_TIME_CONSTANT = 0.05;
|
||||
|
||||
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
|
||||
|
||||
@@ -96,6 +96,7 @@ public class ComplexAutomaticGainControl implements Listener<Complex>,
|
||||
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) );
|
||||
|
||||
/* Hang timer release decay time constant in seconds */
|
||||
@SuppressWarnings( "unused" )
|
||||
private static final double RELEASE_TIME_CONSTANT = 0.05;
|
||||
|
||||
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package dsp.gain;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import buffer.FloatCircularBuffer;
|
||||
@@ -32,9 +29,6 @@ import buffer.FloatCircularBuffer;
|
||||
******************************************************************************/
|
||||
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( ComplexFeedForwardGainControl.class );
|
||||
|
||||
public static final float OBJECTIVE_ENVELOPE = 1.0f;
|
||||
public static final float MINIMUM_ENVELOPE = 0.0001f;
|
||||
|
||||
|
||||
@@ -17,20 +17,14 @@
|
||||
******************************************************************************/
|
||||
package dsp.mixer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.hilbert.HilbertTransform;
|
||||
import sample.complex.Complex;
|
||||
|
||||
public class Oscillator
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Oscillator.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger( Oscillator.class );
|
||||
|
||||
private double mFrequency;
|
||||
private double mSampleRate;
|
||||
|
||||
@@ -40,13 +40,10 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import dsp.filter.interpolator.RealInterpolator;
|
||||
import dsp.symbol.Dibit;
|
||||
@@ -61,9 +58,6 @@ import dsp.symbol.Dibit;
|
||||
*/
|
||||
public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( LSMDemodulator.class );
|
||||
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
/* 45 degree rotation angle */
|
||||
@@ -79,7 +73,7 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
|
||||
private CostasLoop mCostasLoop = new CostasLoop();
|
||||
|
||||
private FrequencyChangeListener mFrequencyChangeListener;
|
||||
private IFrequencyChangeListener mFrequencyChangeListener;
|
||||
|
||||
private EyeDiagramDataTap mEyeDiagramDataTap;
|
||||
|
||||
@@ -153,12 +147,12 @@ public class LSMDemodulator implements Instrumentable, ComplexSampleListener
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( FrequencyChangeListener listener )
|
||||
public void addListener( IFrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( FrequencyChangeListener listener )
|
||||
public void removeListener( IFrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = null;
|
||||
}
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
package dsp.psk;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014, 2015 Dennis Sheirer
|
||||
*
|
||||
* Gardner detector, Costas loop and Interpolator derived from:
|
||||
*
|
||||
* OP25 - gardner_costas_cc_impl.cc
|
||||
* Copyright 2010, 2011, 2012, 2013 KA1RBI (gardner_costas_cc_impl.cc)
|
||||
*
|
||||
* OP25 - scope.py
|
||||
* Copyright 2008-2011 Steve Glass
|
||||
* Copyright 2011, 2012, 2013 KA1RBI
|
||||
*
|
||||
* GNURadio - control_loop and mpsk_receiver_cc
|
||||
* Copyright 2011,2013 Free Software Foundation, Inc.
|
||||
*
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.TapGroup;
|
||||
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;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.Provider;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import source.tuner.frequency.FrequencyChangeListener;
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import dsp.filter.interpolator.RealInterpolator;
|
||||
import dsp.symbol.Dibit;
|
||||
|
||||
/**
|
||||
* Implements an LSM (Pi/4) demodulator using an early/late symbol timing error
|
||||
* detector and a Costas Loop as a phase locked loop synchronized with the
|
||||
* incoming signal carrier frequency.
|
||||
*
|
||||
* Sample Rate: 48000
|
||||
* Symbol Rate: 4800
|
||||
*/
|
||||
public class LSMDemodulator2 implements Instrumentable, ComplexSampleListener,
|
||||
Provider<Complex>
|
||||
{
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( LSMDemodulator2.class );
|
||||
|
||||
/* 45 degree rotation angle */
|
||||
public static final float THETA = (float)( Math.PI / 4.0d );
|
||||
|
||||
/* 45 degree point */
|
||||
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<Complex> mListener;
|
||||
|
||||
private EarlyLateSymbolTiming mGardnerDetector = new EarlyLateSymbolTiming();
|
||||
|
||||
private CostasLoop mCostasLoop = new CostasLoop();
|
||||
|
||||
private FrequencyChangeListener mFrequencyChangeListener;
|
||||
|
||||
private EyeDiagramDataTap mEyeDiagramDataTap;
|
||||
|
||||
public LSMDemodulator2()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( float inphase, float quadrature )
|
||||
{
|
||||
mGardnerDetector.receive( inphase, quadrature );
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a phase correction value to the costas loop to correct when a
|
||||
* phase lock error is detected in the binary output stream.
|
||||
*
|
||||
* @param correction - value in radians
|
||||
*/
|
||||
public void correctPhaseError( double correction )
|
||||
{
|
||||
mCostasLoop.correctPhaseError( correction );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains value to the range of ( -maximum <> maximum )
|
||||
*/
|
||||
public static float clip( float value, float maximum )
|
||||
{
|
||||
if( value > maximum )
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
else if( value < -maximum )
|
||||
{
|
||||
return -maximum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains timing error to +/- the maximum value and corrects any
|
||||
* floating point invalid numbers
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private float normalize( float error, float maximum )
|
||||
{
|
||||
if( Float.isNaN( error ) )
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return clip( error, maximum );
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( FrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( FrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current loop frequency of the costas loop and broadcasts
|
||||
* tuner frequency offset adjustments 10 times a second as needed to keep
|
||||
* the frequency offset below half of the maximum frequency
|
||||
*/
|
||||
public class FrequencyControl
|
||||
{
|
||||
/* Set the trigger threshold at half of the maximum costas loop control
|
||||
* frequency */
|
||||
private static final float CORRECTION_THRESHOLD_FREQUENCY =
|
||||
( 2.0f * (float)Math.PI * 600.0f ) / 48000.0f;
|
||||
|
||||
private FloatAveragingBuffer mBuffer = new FloatAveragingBuffer( 40 );
|
||||
|
||||
private long mFrequencyError = 0;
|
||||
|
||||
private int mCounter = 0;
|
||||
|
||||
public void receive( float frequency )
|
||||
{
|
||||
// mCounter++;
|
||||
//
|
||||
// if( mCounter >= 480 )
|
||||
// {
|
||||
// float average = mBuffer.get( frequency );
|
||||
// mLog.debug( "Avg:" + average + " Threshold:" + CORRECTION_THRESHOLD_FREQUENCY);
|
||||
//
|
||||
// boolean correctionNeeded = false;
|
||||
//
|
||||
// if( average > CORRECTION_THRESHOLD_FREQUENCY )
|
||||
// {
|
||||
// mFrequencyError--;
|
||||
//
|
||||
// correctionNeeded = true;
|
||||
// }
|
||||
// else if( average < -CORRECTION_THRESHOLD_FREQUENCY )
|
||||
// {
|
||||
// mFrequencyError++;
|
||||
//
|
||||
// correctionNeeded = true;
|
||||
// }
|
||||
//
|
||||
// if( correctionNeeded && mFrequencyChangeListener != null )
|
||||
// {
|
||||
// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError );
|
||||
// mFrequencyChangeListener.frequencyChanged(
|
||||
// new FrequencyChangeEvent( Attribute.FREQUENCY_ERROR,
|
||||
// mFrequencyError ) );
|
||||
// }
|
||||
//
|
||||
// mCounter = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mBuffer.get( frequency );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Early/Late symbol timing error detector. Linear Simulcast Modulation (LSM)
|
||||
* uses a Raised Cosine Filter at the transmitter which causes the
|
||||
* signal magnitude to rise to unity at the optimal sampling point and drop
|
||||
* to zero or a midpoint between unity and zero between each symbol period.
|
||||
*
|
||||
* This error detector continually adjusts symbol timing by continuously
|
||||
* attempting to find the largest magnitude value between two interpolated
|
||||
* sample points during each symbol period.
|
||||
*/
|
||||
public class EarlyLateSymbolTiming implements ComplexSampleListener
|
||||
{
|
||||
public static final int SAMPLES_PER_SYMBOL = 10;
|
||||
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
|
||||
|
||||
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 */
|
||||
private float mMu = 10.0f;
|
||||
private float mGainMu = 0.5f;
|
||||
|
||||
/* Samples per symbol */
|
||||
private float mOmega = 10.0f;
|
||||
private float mGainOmega = 0.1f * mGainMu * mGainMu;
|
||||
private float mOmegaRel = 0.005f;
|
||||
private float mOmegaMid = 10.0f;
|
||||
|
||||
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
|
||||
|
||||
private Complex mPreviousSample = new Complex( 0.0f, 0.0f );
|
||||
|
||||
/**
|
||||
* Provides symbol sampling timing control
|
||||
*/
|
||||
public EarlyLateSymbolTiming()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( float inphase, float quadrature )
|
||||
{
|
||||
/* Count down samples per symbol until we calculate the symbol */
|
||||
mMu--;
|
||||
|
||||
mCostasLoop.increment();
|
||||
|
||||
/* Mix incoming sample with costas loop to remove any rotation
|
||||
* that is present from a mis-tuned carrier frequency */
|
||||
Complex derotatedSample = Complex.multiply(
|
||||
mCostasLoop.getCurrentVector(), inphase, quadrature );
|
||||
|
||||
/* Fill up the delay line to use with the interpolator */
|
||||
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 )
|
||||
{
|
||||
Complex earlySample = mInterpolator.filter( mDelayLineInphase,
|
||||
mDelayLineQuadrature, mDelayLinePointer, mMu );
|
||||
|
||||
Complex middleSample = mInterpolator.filter( mDelayLineInphase,
|
||||
mDelayLineQuadrature, mDelayLinePointer + 1, mMu );
|
||||
|
||||
Complex lateSample = mInterpolator.filter( mDelayLineInphase,
|
||||
mDelayLineQuadrature, mDelayLinePointer + 2, mMu );
|
||||
|
||||
Complex currentSymbol = Complex.multiply(
|
||||
middleSample, mPreviousSample.conjugate() );
|
||||
|
||||
/* Set symbol gain to unity */
|
||||
currentSymbol.normalize();
|
||||
|
||||
float earlyLateError =
|
||||
lateSample.magnitude() - earlySample.magnitude();
|
||||
|
||||
if( mEyeDiagramDataTap != null )
|
||||
{
|
||||
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
|
||||
* between +/- .005 of the nominal 10.0 samples per symbol */
|
||||
mOmega = mOmega + mGainOmega * earlyLateError;
|
||||
mOmega = mOmegaMid + clip( mOmega - mOmegaMid, mOmegaRel );
|
||||
|
||||
|
||||
/* Adjust sample timing based on error of current sample */
|
||||
mMu += mOmega + ( mGainMu * earlyLateError );
|
||||
|
||||
|
||||
/* Store current samples/symbols to use for the next period */
|
||||
mPreviousSample = middleSample;
|
||||
|
||||
/* Update costas loop using phase error present in current
|
||||
* symbol. The symbol is rotated from star orientation to polar
|
||||
* orientation to simplify error calculation */
|
||||
mCostasLoop.receive( currentSymbol );
|
||||
// mCostasLoop.receive(
|
||||
// ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
|
||||
|
||||
/* Dispatch the differentiated symbol to the registered listener */
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( currentSymbol );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Costas Loop - phase locked loop synchronized to the frequency offset of
|
||||
* the incoming signal to enable the signal to be mixed down to zero offset
|
||||
* for proper synchronization. The mFrequency value indicates the detected
|
||||
* frequency offset. We attempt to keep that value close to zero by issuing
|
||||
* frequency adjustments to the tuner channel source.
|
||||
*
|
||||
* Most of the costas loop code was ported from gnuradio/control_loop and
|
||||
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
|
||||
* OP25/cqpsk.py
|
||||
*/
|
||||
public class CostasLoop implements Listener<Complex>
|
||||
{
|
||||
public static final double TWO_PI = 2.0 * Math.PI;
|
||||
|
||||
private static final float MAXIMUM_FREQUENCY =
|
||||
( 2400.0f * (float)TWO_PI ) / 48000.0f;
|
||||
|
||||
/* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html */
|
||||
private float mDamping = (float)Math.sqrt( 2.0 ) / 2.0f;
|
||||
|
||||
/* Use denominator between 100 (most) and 400 (least) to adjust costas
|
||||
* loop control level */
|
||||
private float mLoopBandwidth = (float)( TWO_PI / 400.0d );
|
||||
|
||||
private float mAlphaGain = ( 4.0f * mDamping * mLoopBandwidth ) /
|
||||
( 1.0f + ( 2.0f * mDamping * mLoopBandwidth ) +
|
||||
( mLoopBandwidth * mLoopBandwidth ) );
|
||||
|
||||
private float mBetaGain = ( 4.0f * mLoopBandwidth * mLoopBandwidth ) /
|
||||
( 1.0f + ( 2.0f * mDamping * mLoopBandwidth ) +
|
||||
( mLoopBandwidth * mLoopBandwidth ) );
|
||||
|
||||
private float mLoopPhase = 0.0f;
|
||||
|
||||
private float mLoopFrequency = 0.0f;
|
||||
|
||||
private FrequencyControl mFrequencyControl = new FrequencyControl();
|
||||
|
||||
public CostasLoop()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a phase correction value to the current phasor. Use this
|
||||
* method to apply correction when a +/- 90, or 180 degree phase error
|
||||
* is detected in the binary output stream.
|
||||
*
|
||||
* If the supplied correction value places the loop frequency outside
|
||||
* of the max frequency, then the frequency will be corrected 360
|
||||
* degrees in the opposite direction to maintain within the max
|
||||
* frequency bounds.
|
||||
*
|
||||
* @param correction - correction value in radians
|
||||
*/
|
||||
public void correctPhaseError( double correction )
|
||||
{
|
||||
mLoopFrequency += correction;
|
||||
|
||||
if( mLoopFrequency > MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency -= 2.0d * MAXIMUM_FREQUENCY;
|
||||
}
|
||||
|
||||
if( mLoopFrequency < -MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency += 2.0d * MAXIMUM_FREQUENCY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the phase of the loop
|
||||
*/
|
||||
public void increment()
|
||||
{
|
||||
mLoopPhase += mLoopFrequency;
|
||||
|
||||
/* Keep the loop phase in bounds */
|
||||
unwrapPhase();
|
||||
}
|
||||
|
||||
private void unwrapPhase()
|
||||
{
|
||||
while( mLoopPhase > TWO_PI )
|
||||
{
|
||||
mLoopPhase -= TWO_PI;
|
||||
}
|
||||
|
||||
while( mLoopPhase < -TWO_PI )
|
||||
{
|
||||
mLoopPhase += TWO_PI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current vector of the loop rotated by THETA degrees.
|
||||
*/
|
||||
public Complex getCurrentVector()
|
||||
{
|
||||
// return ComplexSample.fromAngle( mLoopPhase + THETA );
|
||||
return Complex.fromAngle( mLoopPhase );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Complex sample )
|
||||
{
|
||||
/* Calculate phase error */
|
||||
float phaseError = getPhaseError( sample );
|
||||
|
||||
Dibit d = QPSKPolarSlicer.decide( sample );
|
||||
// mLog.debug( "Dibit: " + d.name() + " Error: " + phaseError );
|
||||
|
||||
/* Adjust for phase error */
|
||||
adjust( phaseError );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the costas loop frequency and phase to adjust for the phase
|
||||
* error value
|
||||
*
|
||||
* @param phase_error - (-)= late and (+)= early
|
||||
*/
|
||||
private void adjust( float phase_error )
|
||||
{
|
||||
mLoopFrequency += mBetaGain * phase_error;
|
||||
mLoopPhase += mLoopFrequency + mAlphaGain * phase_error;
|
||||
|
||||
/* Maintain phase between +/- 2 * PI */
|
||||
unwrapPhase();
|
||||
|
||||
/* Limit frequency to +/- maximum loop frequency */
|
||||
limitFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains the frequency within the bounds of +/- loop frequency
|
||||
*/
|
||||
private void limitFrequency()
|
||||
{
|
||||
/* Check for and issue tuner offset correction */
|
||||
mFrequencyControl.receive( mLoopFrequency );
|
||||
|
||||
if( mLoopFrequency > MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency = MAXIMUM_FREQUENCY;
|
||||
}
|
||||
|
||||
if( mLoopFrequency < -MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency = -MAXIMUM_FREQUENCY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects rotational error present when the costas loop is not aligned
|
||||
* with the carrier frequency. Provides error feedback to adjust
|
||||
* mixer frequency.
|
||||
*/
|
||||
public float getPhaseError( Complex sample )
|
||||
{
|
||||
float phase_error = 0;
|
||||
|
||||
if( Math.abs( sample.real() ) > Math.abs( sample.imaginary() ) )
|
||||
{
|
||||
if( sample.real() > 0 )
|
||||
{
|
||||
phase_error = -sample.imaginary();
|
||||
}
|
||||
else
|
||||
{
|
||||
phase_error = sample.imaginary();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( sample.imaginary() > 0 )
|
||||
{
|
||||
phase_error = sample.real();
|
||||
}
|
||||
else
|
||||
{
|
||||
phase_error = -sample.real();
|
||||
}
|
||||
}
|
||||
|
||||
return phase_error;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener( Listener<Complex> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( Listener<Complex> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup( "LSM Demodulator 2" );
|
||||
group.add( new EyeDiagramDataTap( "Eye Diagram", 0, 4800 ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
if( tap instanceof EyeDiagramDataTap )
|
||||
{
|
||||
mEyeDiagramDataTap = (EyeDiagramDataTap)tap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
if( tap instanceof EyeDiagramDataTap )
|
||||
{
|
||||
mEyeDiagramDataTap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user