330 WIP - updates the demodulator viewer with an eye diagram and symbol

chart.  Testing various configurations to see which yields the best
PLL and symbol timing locks.
This commit is contained in:
Denny
2018-02-18 16:20:45 -05:00
parent eaf7c7d11e
commit dda37a1198
20 changed files with 844 additions and 279 deletions

View File

@@ -12,155 +12,169 @@ import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URL;
public class FilterView extends BorderPane
{
private final static Logger mLog = LoggerFactory.getLogger( FilterView.class );
private final static Logger mLog = LoggerFactory.getLogger(FilterView.class);
private CurveFittedAreaChart mImpulseChart;
private NumberAxis mImpulseXAxis;
private NumberAxis mImpulseYAxis;
private CurveFittedAreaChart mDFTChart;
private NumberAxis mDFTXAxis;
private NumberAxis mDFTYAxis;
private static final int FFT_SIZE = 4096;
private FloatFFT_1D mFFT = new FloatFFT_1D( FFT_SIZE );
public FilterView( float[] taps )
{
init();
private CurveFittedAreaChart mImpulseChart;
private NumberAxis mImpulseXAxis;
private NumberAxis mImpulseYAxis;
private CurveFittedAreaChart mDFTChart;
private NumberAxis mDFTXAxis;
private NumberAxis mDFTYAxis;
private static final int FFT_SIZE = 4096;
private FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE);
if( taps != null )
{
setImpulseTaps( taps );
public FilterView(float[] taps)
{
init();
float[] dft = new float[ FFT_SIZE ];
System.arraycopy( taps, 0, dft, 0, taps.length );
mFFT.realForward( dft );
setDFTTaps( convertDFTToDecibel( dft, taps.length ) );
}
}
private void init()
{
HBox hbox = new HBox();
mImpulseXAxis = new NumberAxis( 0.0, 0.5, 1.0 );
mImpulseYAxis = new NumberAxis( -0.2, 1.2, .1 );
mImpulseChart = new CurveFittedAreaChart( mImpulseXAxis, mImpulseYAxis );
mImpulseChart.setLegendVisible( false );
mImpulseChart.setHorizontalGridLinesVisible( false );
mImpulseChart.setVerticalGridLinesVisible(false);
if(taps != null)
{
setImpulseTaps(taps);
float[] dft = new float[FFT_SIZE];
System.arraycopy(taps, 0, dft, 0, taps.length);
mFFT.realForward(dft);
setDFTTaps(convertDFTToDecibel(dft, taps.length));
}
}
private void init()
{
HBox hbox = new HBox();
mImpulseXAxis = new NumberAxis(0.0, 0.5, 1.0);
mImpulseYAxis = new NumberAxis(-0.2, 1.2, .1);
mImpulseChart = new CurveFittedAreaChart(mImpulseXAxis, mImpulseYAxis);
mImpulseChart.setLegendVisible(false);
mImpulseChart.setHorizontalGridLinesVisible(false);
mImpulseChart.setVerticalGridLinesVisible(false);
mImpulseChart.setAlternativeColumnFillVisible(false);
mImpulseChart.setAlternativeRowFillVisible(false);
String css = FilterView.class.getResource( "FilterView.css" ).toExternalForm();
mImpulseChart.getStylesheets().add( css );
hbox.getChildren().add( mImpulseChart );
HBox.setHgrow( mImpulseChart, Priority.ALWAYS );
mDFTXAxis = new NumberAxis( 0.0, FFT_SIZE / 2, FFT_SIZE / 8 );
mDFTYAxis = new NumberAxis( -200.0, 10.0, 10.0 );
mDFTChart = new CurveFittedAreaChart( mDFTXAxis, mDFTYAxis );
mDFTChart.setLegendVisible( false );
mDFTChart.setHorizontalGridLinesVisible( false );
mDFTChart.setVerticalGridLinesVisible(false);
URL cssURL = FilterView.class.getResource("FilterView.css");
String css = null;
if(cssURL != null)
{
css = cssURL.toExternalForm();
}
hbox.getChildren().add(mImpulseChart);
HBox.setHgrow(mImpulseChart, Priority.ALWAYS);
mDFTXAxis = new NumberAxis(0.0, FFT_SIZE / 2, FFT_SIZE / 8);
mDFTYAxis = new NumberAxis(-200.0, 10.0, 10.0);
mDFTChart = new CurveFittedAreaChart(mDFTXAxis, mDFTYAxis);
mDFTChart.setLegendVisible(false);
mDFTChart.setHorizontalGridLinesVisible(false);
mDFTChart.setVerticalGridLinesVisible(false);
mDFTChart.setAlternativeColumnFillVisible(false);
mDFTChart.setAlternativeRowFillVisible(false);
mDFTChart.getStylesheets().add( css );
hbox.getChildren().add( mDFTChart );
HBox.setHgrow( mDFTChart, Priority.ALWAYS );
setCenter( hbox );
}
public void setImpulseTaps( double[] taps )
{
float[] convertedTaps = new float[ taps.length ];
for( int x = 0; x < taps.length; x++ )
{
convertedTaps[ x ] = (float)taps[ x ];
}
setImpulseTaps( convertedTaps );
}
public void setImpulseTaps( float[] taps )
{
double smallest = 0.0;
double largest = 0.0;
final XYChart.Series<Number, Number> series = new XYChart.Series<>();
for( int x = 0; x < taps.length; x++ )
if(css != null)
{
if( taps[ x ] < smallest )
{
smallest = taps[ x ];
}
if( taps[ x ] > largest )
{
largest = taps[ x ];
}
series.getData().add( new XYChart.Data<Number, Number>( x, taps[ x ] ) );
mImpulseChart.getStylesheets().add(css);
mDFTChart.getStylesheets().add(css);
}
mImpulseXAxis.setLowerBound( 0.0 );
mImpulseXAxis.setUpperBound( taps.length - 1 );
mImpulseXAxis.setTickUnit( 1.0 );
double padding = Math.abs( largest - smallest ) * .1;
mImpulseYAxis.setLowerBound( smallest - padding );
mImpulseYAxis.setUpperBound( largest + padding );
mImpulseYAxis.setTickUnit( padding );
hbox.getChildren().add(mDFTChart);
HBox.setHgrow(mDFTChart, Priority.ALWAYS);
setCenter(hbox);
}
public void setImpulseTaps(double[] taps)
{
float[] convertedTaps = new float[taps.length];
for(int x = 0; x < taps.length; x++)
{
convertedTaps[x] = (float)taps[x];
}
setImpulseTaps(convertedTaps);
}
public void setImpulseTaps(float[] taps)
{
double smallest = 0.0;
double largest = 0.0;
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
for(int x = 0; x < taps.length; x++)
{
if(taps[x] < smallest)
{
smallest = taps[x];
}
if(taps[x] > largest)
{
largest = taps[x];
}
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
}
mImpulseXAxis.setLowerBound(0.0);
mImpulseXAxis.setUpperBound(taps.length - 1);
mImpulseXAxis.setTickUnit(1.0);
double padding = Math.abs(largest - smallest) * .1;
mImpulseYAxis.setLowerBound(smallest - padding);
mImpulseYAxis.setUpperBound(largest + padding);
mImpulseYAxis.setTickUnit(padding);
ObservableList<Series<Number,Number>> list = mImpulseChart.getData();
list.add( series );
}
public void setDFTTaps( float[] taps )
{
double smallest = 0.0;
final XYChart.Series<Number, Number> series = new XYChart.Series<>();
for( int x = 0; x < taps.length; x++ )
{
if( taps[ x ] < smallest )
{
smallest = taps[ x ];
}
series.getData().add( new XYChart.Data<Number, Number>( x, taps[ x ] ) );
}
ObservableList<Series<Number,Number>> list = mDFTChart.getData();
list.add( series );
}
public float[] convertDFTToDecibel( float[] dft, int tapLength )
list.add(series);
}
public void setDFTTaps(float[] taps)
{
float[] decibels = new float[ dft.length / 2 ];
double smallest = 0.0;
int index = 0;
for( int x = 0; x < decibels.length; x ++ )
{
index = x * 2;
decibels[ x ] = 20.0f * (float)Math.log10( ( ( dft[ index ] * dft[ index ] ) +
( dft[ index + 1 ] * dft[ index + 1 ] ) ) );
}
final XYChart.Series<Number,Number> series = new XYChart.Series<>();
return decibels;
for(int x = 0; x < taps.length; x++)
{
if(taps[x] < smallest)
{
smallest = taps[x];
}
series.getData().add(new XYChart.Data<Number,Number>(x, taps[x]));
}
ObservableList<Series<Number,Number>> list = mDFTChart.getData();
list.add(series);
}
public float[] convertDFTToDecibel(float[] dft, int tapLength)
{
float[] decibels = new float[dft.length / 2];
int index = 0;
for(int x = 0; x < decibels.length; x++)
{
index = x * 2;
decibels[x] = 20.0f * (float)Math.log10(((dft[index] * dft[index]) +
(dft[index + 1] * dft[index + 1])));
}
return decibels;
}
}

View File

@@ -51,11 +51,11 @@ public class FilterViewer extends Application
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate((int)48000.0)
.passBandCutoff(6000)
.passBandCutoff(2880)
.passBandAmplitude(1.0)
.passBandRipple(0.1)
.passBandRipple(0.01)
.stopBandAmplitude(0.0)
.stopBandStart(6250)
.stopBandStart(3600)
.stopBandRipple(0.01)
.build();
@@ -81,6 +81,9 @@ public class FilterViewer extends Application
float[] taps = null;
// taps = FilterFactory.getSinc(6000, 1920, 47, Window.WindowType.BLACKMAN);
// taps = FilterFactory.getRootRaisedCosine(10, 10, 0.2f);
// taps = FilterFactory.getLowPass(48000, 6750, 7500, 60,
// Window.WindowType.HAMMING, true);

View File

@@ -1,78 +1,103 @@
/*******************************************************************************
* 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 io.github.dsheirer.dsp.fm;
import io.github.dsheirer.sample.complex.Complex;
public abstract class FMDemodulator
{
private float mPreviousI = 0.0f;
private float mPreviousQ = 0.0f;
protected float mGain;
private float mPreviousI = 0.0f;
private float mPreviousQ = 0.0f;
protected float mGain;
public FMDemodulator( float gain )
{
mGain = gain;
}
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 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);
public abstract void dispose();
double angle = 0.0f;
public void reset()
{
mPreviousI = 0.0f;
mPreviousQ = 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);
}
public void setGain( float gain )
{
mGain = gain;
}
/**
* Store the current sample to use during the next iteration
*/
mPreviousI = currentI;
mPreviousQ = currentQ;
return (float)(angle * mGain);
}
public static double demodulate(Complex previous, Complex current)
{
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
double quadrature = (current.quadrature() * previous.inphase()) + (current.inphase() * -previous.quadrature());
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);
}
return angle;
}
public abstract void dispose();
public void reset()
{
mPreviousI = 0.0f;
mPreviousQ = 0.0f;
}
public void setGain(float gain)
{
mGain = gain;
}
}

View File

@@ -26,8 +26,7 @@ import org.slf4j.LoggerFactory;
public class FMDemodulator_CB extends FMDemodulator implements Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( FMDemodulator_CB.class );
private final static Logger mLog = LoggerFactory.getLogger( FMDemodulator_CB.class );
private Listener<RealBuffer> mListener;

View File

@@ -33,11 +33,13 @@ public class InterpolatingSymbolBuffer
private float mOmegaMid;
private int mDelayLinePointer = 0;
private float mSamplesPerSymbol;
private int mTwiceSamplesPerSymbol;
private RealInterpolator mInterpolator = new RealInterpolator(1.0f);
public InterpolatingSymbolBuffer(float samplesPerSymbol)
{
mSamplesPerSymbol = samplesPerSymbol;
mMu = samplesPerSymbol;
mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol);
mDelayLineInphase = new float[2 * mTwiceSamplesPerSymbol];
@@ -58,7 +60,8 @@ public class InterpolatingSymbolBuffer
mDelayLineQuadrature[mDelayLinePointer + mTwiceSamplesPerSymbol] = currentSample.quadrature();
//Increment pointer and keep pointer in bounds
mDelayLinePointer = (mDelayLinePointer + 1) % mTwiceSamplesPerSymbol;
mDelayLinePointer++;
mDelayLinePointer = mDelayLinePointer % mTwiceSamplesPerSymbol;
}
/**
@@ -80,11 +83,11 @@ public class InterpolatingSymbolBuffer
{
//mOmega is samples per symbol and is constrained to floating between +/- .005 of the nominal samples per
//symbol
mOmega = mOmega + mGainOmega * symbolTimingError;
mOmega = mOmega + mGainOmega * -symbolTimingError;
mOmega = mOmegaMid + clip(mOmega - mOmegaMid, mOmegaRel);
//Add another symbol's worth of samples to the counter and adjust timing based on gardner error
increaseSampleCounter((mOmega + (mGainMu * symbolTimingError)));
increaseSampleCounter((mOmega + (mGainMu * -symbolTimingError)));
}
/**
@@ -128,6 +131,18 @@ public class InterpolatingSymbolBuffer
return mCurrentSample;
}
/**
* Contents of the interpolating buffer and the current buffer index and symbol decision offset. This data can
* be used to support an external eye-diagram chart.
* @return symbol decision data.
*/
public SymbolDecisionData getSymbolDecisionData()
{
//TODO: I think that delay line pointer needs to have +4 added to it since the filter is 8 taps long
return new SymbolDecisionData(mDelayLineInphase, mDelayLineQuadrature,
mSamplesPerSymbol, mDelayLinePointer, mMu);
}
/**
* Returns the interpolated inphase value for the specified offset
* @param interpolation into the buffer to calculate the interpolated sample

View File

@@ -15,6 +15,7 @@
******************************************************************************/
package io.github.dsheirer.dsp.psk;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.sample.Listener;
@@ -31,7 +32,9 @@ public class QPSKDemodulator implements ComplexSampleListener
private GardnerDetector mGardnerDetector = new GardnerDetector();
private Listener<Complex> mSymbolListener;
private Listener<Double> mPLLErrorListener;
private Listener<Double> mPLLFrequencyListener;
private Listener<Dibit> mDibitListener;
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
private Complex mPreviousSample = new Complex(0, 0);
private Complex mPreviousMiddleSample = new Complex(0, 0);
@@ -42,6 +45,7 @@ public class QPSKDemodulator implements ComplexSampleListener
private Complex mCurrentSymbol = new Complex(0, 0);
private float mPhaseError;
private float mSymbolTimingError;
private double mSampleRate;
/**
* Decoder for Quaternary Phase Shift Keying (QPSK) and Differential QPSK (DQPSK). This decoder uses both a
@@ -63,6 +67,7 @@ public class QPSKDemodulator implements ComplexSampleListener
"] must be at least 2 x symbol rate [" + symbolRate + "]");
}
mSampleRate = sampleRate;
mPLL = phaseLockedLoop;
mSymbolPhaseErrorCalculator = symbolPhaseErrorCalculator;
mQPSKSymbolDecoder = symbolDecoder;
@@ -108,6 +113,12 @@ public class QPSKDemodulator implements ComplexSampleListener
mMiddleSample = mInterpolatingSymbolBuffer.getMiddleSample();
mCurrentSample = mInterpolatingSymbolBuffer.getCurrentSample();
//Eye diagram listener
if(mSymbolDecisionDataListener != null)
{
mSymbolDecisionDataListener.receive(mInterpolatingSymbolBuffer.getSymbolDecisionData());
}
//Calculate middle and current symbols as the delta between the previous and current samples
mMiddleSymbol.setInphase(Complex.multiplyInphase(mMiddleSample.inphase(), mMiddleSample.quadrature(), mPreviousMiddleSample.inphase(), -mPreviousMiddleSample.quadrature()));
mMiddleSymbol.setQuadrature(Complex.multiplyQuadrature(mMiddleSample.inphase(), mMiddleSample.quadrature(), mPreviousMiddleSample.inphase(), -mPreviousMiddleSample.quadrature()));
@@ -127,12 +138,20 @@ public class QPSKDemodulator implements ComplexSampleListener
mPreviousMiddleSample.setValues(mMiddleSample);
mPreviousSymbol.setValues(mCurrentSymbol);
//Send to an external constellation symbol listener when registered
if(mSymbolListener != null)
{
mSymbolListener.receive(mCurrentSymbol);
}
//Adjust the current symbol as necessary (e.g. for differential encoding)
mSymbolPhaseErrorCalculator.adjust(mCurrentSymbol);
//Calculate the phase error of the current symbol relative to the expected constellation and provide
//feedback to the PLL
mPhaseError = mSymbolPhaseErrorCalculator.getPhaseError(mCurrentSymbol);
mPhaseError = GardnerDetector.clip(mPhaseError, 0.2f);
mPLL.adjust(mPhaseError);
if(mPLLErrorListener != null)
@@ -140,18 +159,21 @@ public class QPSKDemodulator implements ComplexSampleListener
mPLLErrorListener.receive((double)mPhaseError);
}
if(mPLLFrequencyListener != null)
{
double loopFrequency = ((CostasLoop)mPLL).getLoopFrequency();
loopFrequency *= mSampleRate / (2.0 * Math.PI);
mPLLFrequencyListener.receive(loopFrequency);
}
//Decode the dibit from the symbol and send to the listener
if(mDibitListener != null)
{
mDibitListener.receive(mQPSKSymbolDecoder.decode(mCurrentSymbol));
}
//Send to an external constellation symbol listener when registered
if(mSymbolListener != null)
{
mSymbolListener.receive(mCurrentSymbol);
}
//TODO: Assemble dibits here and broadcast
}
}
@@ -179,4 +201,18 @@ public class QPSKDemodulator implements ComplexSampleListener
{
mPLLErrorListener = listener;
}
/**
* Registers the listener to receive PLL error values
*/
public void setPLLFrequencyListener(Listener<Double> listener)
{
mPLLFrequencyListener = listener;
}
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
{
mSymbolDecisionDataListener = listener;
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
public class SymbolDecisionData
{
private float[] mSamplesInphase;
private float[] mSamplesQuadrature;
private float mSamplesPerSymbol;
private int mSampleIndex;
private float mSampleIndexOffset;
/**
* Symbol decision data - samples and timing decision for a single symbol decision instant.
*
* @param samplesInphase
* @param samplesQuadrature
* @param samplesPerSymbol
* @param sampleIndex
* @param sampleIndexOffset
*/
public SymbolDecisionData(float[] samplesInphase, float[] samplesQuadrature, float samplesPerSymbol,
int sampleIndex, float sampleIndexOffset)
{
mSamplesInphase = samplesInphase;
mSamplesQuadrature = samplesQuadrature;
mSamplesPerSymbol = samplesPerSymbol;
mSampleIndex = sampleIndex;
mSampleIndexOffset = sampleIndexOffset;
}
public float[] getInphaseSamples()
{
return mSamplesInphase;
}
public float[] getQuadratureSamples()
{
return mSamplesQuadrature;
}
public float getSamplesPerSymbol()
{
return mSamplesPerSymbol;
}
public int getSampleIndex()
{
return mSampleIndex;
}
public float getSampleIndexOffset()
{
return mSampleIndexOffset;
}
}

View File

@@ -194,20 +194,21 @@ public class CostasLoop implements IPhaseLockedLoop
mErrorBuffer.put(phaseError);
double variance = mErrorBuffer.variance();
double standardDeviation = mErrorBuffer.standardDeviation();
switch(mTracking)
{
case SEARCHING:
if(Tracking.COARSE.meetsThreshold(variance))
if(Tracking.COARSE.contains(standardDeviation))
{
setTracking(Tracking.COARSE);
}
break;
case COARSE:
if(Tracking.COARSE.meetsThreshold(variance))
if(Tracking.COARSE.contains(standardDeviation))
{
if(Tracking.FINE.meetsThreshold(variance))
//Promote if possible - otherwise remain
if(Tracking.FINE.contains(standardDeviation))
{
setTracking(Tracking.FINE);
}
@@ -218,9 +219,10 @@ public class CostasLoop implements IPhaseLockedLoop
}
break;
case FINE:
if(Tracking.FINE.meetsThreshold(variance))
if(Tracking.FINE.contains(standardDeviation))
{
if(Tracking.LOCKED.meetsThreshold(variance))
//Promote if possible - otherwise remain
if(Tracking.LOCKED.contains(standardDeviation))
{
setTracking(Tracking.LOCKED);
}
@@ -231,7 +233,7 @@ public class CostasLoop implements IPhaseLockedLoop
}
break;
case LOCKED:
if(!Tracking.LOCKED.meetsThreshold(variance))
if(!Tracking.LOCKED.contains(standardDeviation))
{
setTracking(Tracking.FINE);
}

View File

@@ -17,32 +17,55 @@ package io.github.dsheirer.dsp.psk.pll;
public enum Tracking
{
SEARCHING(50.0, Math.PI),
COARSE(100.0, 0.01),
FINE(150.0, 0.001),
LOCKED(200.0, 0.0001);
// SEARCHING(50.0, 0.01, Math.PI),
// COARSE(100.0, 0.001, 0.01),
// FINE(150.0, 0.0001, 0.001),
// LOCKED(200.0, 0.0, 0.0001);
SEARCHING(100.0, 0.4, Math.PI),
COARSE(150.0, 0.2, 0.4),
FINE(175.0, 0.1, 0.2),
LOCKED(200.0, 0.0, 0.1);
private double mLoopBandwidth;
private double mVarianceThreshold;
private double mThresholdMinimum;
private double mThresholdMaximum;
Tracking(double loopBandwidth, double varianceThreshold)
Tracking(double loopBandwidth, double thresholdMinimum, double thresholdMaximum)
{
mLoopBandwidth = loopBandwidth;
mVarianceThreshold = varianceThreshold;
mThresholdMinimum = thresholdMinimum;
mThresholdMaximum = thresholdMaximum;
}
/**
* Loop bandwidth setting
*/
public double getLoopBandwidth()
{
return mLoopBandwidth;
}
public double getVarianceThreshold()
/**
* Minimum error value for this entry
*/
public double getThresholdMinimum()
{
return mVarianceThreshold;
return mThresholdMinimum;
}
public boolean meetsThreshold(double variance)
/**
* Maximum error value for this entry
*/
public double getThresholdMaximum()
{
return variance < getVarianceThreshold();
return mThresholdMaximum;
}
/**
* Indicates if the error value is within the range for this entry
*/
public boolean contains(double standardDeviation)
{
return getThresholdMinimum() <= standardDeviation && standardDeviation < getThresholdMaximum();
}
}