Automatic Frequency Control enhancement. Provides default AFC and ability to disable in the Decoder tab of each channel. Spectral display includes visual indicators of frequency center (grey) and current AFC offset (yellow).

This commit is contained in:
dennis.sheirer
2014-09-11 22:46:05 +00:00
parent 4e0fd9b7b0
commit 0f43c7ed22
23 changed files with 546 additions and 119 deletions

View File

@@ -0,0 +1,169 @@
package dsp.afc;
import sample.Listener;
import source.tuner.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
public class AutomaticFrequencyControl implements Listener<Float>,
FrequencyChangeListener
{
private static float LARGE_ERROR = 10000.0f;
private static float MEDIUM_ERROR = 5000.0f;
private static float SMALL_ERROR = 1000.0f;
private static float FINE_ERROR = 500.0f;
private static int SAMPLE_THRESHOLD = 15000;
private Mode mMode = Mode.FAST;
private FloatAveragingBuffer mBuffer =
new FloatAveragingBuffer( SAMPLE_THRESHOLD );
private int mSampleCounter = 0;
private int mSkipCounter = 0;
private int mSkipThreshold = 4;
private int mErrorCorrection = 0;
private long mCurrentFrequency;
private int mCurrentSampleRate;
private FrequencyErrorListener mListener;
public enum Mode
{
NORMAL,FAST;
}
public AutomaticFrequencyControl( FrequencyErrorListener listener )
{
mListener = listener;
}
public void dispose()
{
mListener = null;
mBuffer = null;
}
public int getErrorCorrection()
{
return mErrorCorrection;
}
public void reset()
{
mErrorCorrection = 0;
mMode = Mode.FAST;
dispatch();
}
private void dispatch()
{
if( mListener != null )
{
mListener.setError( mErrorCorrection );
}
}
@Override
public void receive( Float sample )
{
if( mMode == Mode.FAST )
{
float average = mBuffer.get( sample.floatValue() );
mSampleCounter++;
if( mSampleCounter >= SAMPLE_THRESHOLD )
{
mSampleCounter = 0;
update( average );
}
}
else
{
mSkipCounter++;
if( mSkipCounter >= mSkipThreshold )
{
mSkipCounter = 0;
float average = mBuffer.get( sample.floatValue() );
mSampleCounter++;
if( mSampleCounter >= SAMPLE_THRESHOLD )
{
mSampleCounter = 0;
update( average );
}
}
}
}
private void update( float average )
{
int correction = 0;
if( average > LARGE_ERROR )
{
correction = -1000;
mMode = Mode.FAST;
}
else if( average > MEDIUM_ERROR )
{
correction = -500;
mMode = Mode.FAST;
}
else if( average > SMALL_ERROR )
{
correction = -100;
mMode = Mode.NORMAL;
}
else if( average > FINE_ERROR )
{
correction = -50;
mMode = Mode.NORMAL;
}
else if( average < -LARGE_ERROR )
{
correction = 1000;
mMode = Mode.FAST;
}
else if( average < -MEDIUM_ERROR )
{
correction = 500;
mMode = Mode.FAST;
}
else if( average < -SMALL_ERROR )
{
correction = 100;
mMode = Mode.NORMAL;
}
else if( average < -FINE_ERROR )
{
correction = 50;
mMode = Mode.NORMAL;
}
if( correction != 0 )
{
mErrorCorrection -= correction;
dispatch();
}
else
{
mMode = Mode.NORMAL;
}
}
@Override
public void frequencyChanged( long frequency, int bandwidth )
{
if( mCurrentFrequency != frequency || mCurrentSampleRate != bandwidth )
{
mCurrentFrequency = frequency;
mCurrentSampleRate = bandwidth;
reset();
}
}
}

View File

@@ -0,0 +1,10 @@
package dsp.afc;
import source.tuner.FrequencyChangeListener;
public interface FrequencyErrorListener
{
public void setError( int error );
public void addListener( FrequencyChangeListener listener );
}

View File

@@ -17,9 +17,11 @@
******************************************************************************/
package dsp.nbfm;
import log.Log;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import dsp.afc.AutomaticFrequencyControl;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
@@ -30,7 +32,17 @@ public class NBFMDemodulator implements Listener<ComplexSample>
private ComplexFIRFilter mIQFilter;
private FMDiscriminator mDiscriminator;
public NBFMDemodulator( double[] iqFilter, double iqGain )
/**
* Implements a quadrature narrow-band demodulator that produces float
* valued demodulated, audio filtered output samples.
*/
public NBFMDemodulator()
{
this( FilterFactory.getLowPass( 48000, 4000, 73, WindowType.HAMMING ),
1.0002, true );
}
public NBFMDemodulator( double[] iqFilter, double iqGain, boolean afc )
{
mIQFilter = new ComplexFIRFilter( iqFilter, iqGain );
@@ -54,16 +66,6 @@ public class NBFMDemodulator implements Listener<ComplexSample>
mIQFilter.dispose();
}
/**
* Implements a quadrature narrow-band demodulator that produces float
* valued demodulated, audio filtered output samples.
*/
public NBFMDemodulator()
{
this( FilterFactory.getLowPass( 48000, 4000, 73, WindowType.HAMMING ),
1.0002 );
}
/**
* Receive method for complex samples that are fed into this class to be
* processed