diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 6dee242a..5a44b784 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -56,6 +56,7 @@ import controller.system.System; import decode.config.AuxDecodeConfiguration; import decode.config.DecodeConfigFactory; import decode.config.DecodeConfiguration; +import dsp.afc.AutomaticFrequencyControl; import eventlog.config.EventLogConfiguration; @XmlSeeAlso( { Configuration.class } ) @@ -134,6 +135,27 @@ public class Channel extends Configuration return mChannelType; } + /** + * Indicates if the channel is using Automatic Frequency Control + * @return + */ + public boolean hasAFC() + { + return getAFC() != null; + } + + public AutomaticFrequencyControl getAFC() + { + if( isProcessing() ) + { + return getProcessingChain().getAFC(); + } + else + { + return null; + } + } + /** * Sets the resource manager responsible for this channel and registers * the channel manager as a listener to this channel. Fires channel add diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java index d94b2b62..6915c512 100644 --- a/src/controller/channel/ProcessingChain.java +++ b/src/controller/channel/ProcessingChain.java @@ -34,13 +34,11 @@ import record.wave.ComplexWaveRecorder; import record.wave.FloatWaveRecorder; import sample.Broadcaster; import sample.Listener; -import sample.Provider; import sample.complex.ComplexSample; import source.ComplexSource; import source.FloatArraySource; import source.FloatSource; import source.Source; -import source.Source.SampleType; import source.SourceException; import source.config.SourceConfigMixer; import source.tuner.Tuner; @@ -57,6 +55,8 @@ import controller.state.ChannelState; import controller.state.ChannelState.State; import decode.Decoder; import decode.DecoderFactory; +import dsp.afc.AutomaticFrequencyControl; +import dsp.afc.FrequencyErrorListener; import eventlog.EventLogType; import eventlog.EventLogger; @@ -73,6 +73,7 @@ public class ProcessingChain implements Listener protected List mRecorders = new ArrayList(); protected ArrayList mEventLoggers = new ArrayList(); protected AudioOutput mAudioOutput; + protected AutomaticFrequencyControl mAFC; protected ChannelState mChannelState; protected ResourceManager mResourceManager; private ScheduledFuture mProcessorTask; @@ -101,6 +102,14 @@ public class ProcessingChain implements Listener mChannel.getAliasListName() ); } } + + /** + * Automatic Frequency Control, or null if disabled + */ + public AutomaticFrequencyControl getAFC() + { + return mAFC; + } public Listener> getComplexListener() { @@ -379,6 +388,28 @@ public class ProcessingChain implements Listener mAudioOutput = new AudioOutput( threadName ); addFloatListener( mAudioOutput ); + + /* Automatic frequency control */ + if( mAFC != null ) + { + mAFC.dispose(); + mAFC = null; + } + + if( mSource instanceof FrequencyErrorListener && + mChannel.getDecodeConfiguration().isAFCEnabled() ) + { + FrequencyErrorListener listener = + (FrequencyErrorListener)mSource; + + mAFC = new AutomaticFrequencyControl( listener ); + + /* Register AFC to receive frequency change events */ + listener.addListener( mAFC ); + + /* Add AFC as listener to the decoder */ + mDecoder.addUnfilteredFloatListener( mAFC ); + } } } diff --git a/src/decode/DecodeEditor.java b/src/decode/DecodeEditor.java index cdc1170c..99a3dac2 100644 --- a/src/decode/DecodeEditor.java +++ b/src/decode/DecodeEditor.java @@ -17,6 +17,11 @@ ******************************************************************************/ package decode; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; + import controller.Editor; import decode.config.DecodeConfiguration; @@ -25,10 +30,25 @@ public class DecodeEditor extends Editor private static final long serialVersionUID = 1L; protected DecodeConfiguration mConfig; - + + protected JCheckBox mAFC; + public DecodeEditor( DecodeConfiguration config ) { mConfig = config; + + mAFC = new JCheckBox( "Automatic Frequency Control" ); + mAFC.setSelected( mConfig.isAFCEnabled() ); + mAFC.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + mConfig.setAFC( mAFC.isSelected() ); + } + } ); + + add( mAFC, "wrap" ); } public DecodeConfiguration getConfig() diff --git a/src/decode/Decoder.java b/src/decode/Decoder.java index add1f52a..d8f2d5a4 100644 --- a/src/decode/Decoder.java +++ b/src/decode/Decoder.java @@ -20,6 +20,7 @@ package decode; import java.util.ArrayList; import java.util.List; +import dsp.afc.AutomaticFrequencyControl; import message.Message; import sample.Broadcaster; import sample.Listener; @@ -130,6 +131,8 @@ public abstract class Decoder implements Listener { mFloatBroadcaster.addListener( listener ); } + + public abstract void addUnfilteredFloatListener( Listener listener ); public void removeFloatListener( Listener listener ) { diff --git a/src/decode/config/DecodeConfiguration.java b/src/decode/config/DecodeConfiguration.java index 8ede32d2..b4cc4db1 100644 --- a/src/decode/config/DecodeConfiguration.java +++ b/src/decode/config/DecodeConfiguration.java @@ -17,6 +17,7 @@ ******************************************************************************/ package decode.config; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSeeAlso; @@ -32,6 +33,7 @@ import decode.DecoderType; public abstract class DecodeConfiguration extends Configuration { private DecoderType mDecoderType = DecoderType.NBFM; + private boolean mAFCEnabled = true; public DecodeConfiguration() { @@ -47,4 +49,20 @@ public abstract class DecodeConfiguration extends Configuration { return mDecoderType; } + + @XmlElement( name = "afc" ) + public boolean getAFC() + { + return mAFCEnabled; + } + + public boolean isAFCEnabled() + { + return mAFCEnabled; + } + + public void setAFC( boolean enabled ) + { + mAFCEnabled = enabled; + } } diff --git a/src/decode/fleetsync2/Fleetsync2Decoder.java b/src/decode/fleetsync2/Fleetsync2Decoder.java index 2e56ad9d..4760b2d0 100644 --- a/src/decode/fleetsync2/Fleetsync2Decoder.java +++ b/src/decode/fleetsync2/Fleetsync2Decoder.java @@ -181,4 +181,10 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable break; } } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + //Not implemented + } } diff --git a/src/decode/ltrnet/LTRNetDecoder.java b/src/decode/ltrnet/LTRNetDecoder.java index 39d185b4..1e550b63 100644 --- a/src/decode/ltrnet/LTRNetDecoder.java +++ b/src/decode/ltrnet/LTRNetDecoder.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import message.MessageDirection; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -148,4 +149,14 @@ public class LTRNetDecoder extends Decoder implements Instrumentable { mLTRFSKDecoder.removeTap( tap ); } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + if( mDemodulator != null ) + { + mDemodulator.addListener( listener ); + } + // TODO Auto-generated method stub + } } diff --git a/src/decode/ltrnet/LTRNetEditor.java b/src/decode/ltrnet/LTRNetEditor.java index 39ae0eb5..1c3dc760 100644 --- a/src/decode/ltrnet/LTRNetEditor.java +++ b/src/decode/ltrnet/LTRNetEditor.java @@ -21,6 +21,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.DefaultComboBoxModel; +import javax.swing.JCheckBox; import javax.swing.JComboBox; import message.MessageDirection; diff --git a/src/decode/ltrstandard/LTRStandardDecoder.java b/src/decode/ltrstandard/LTRStandardDecoder.java index a4d3f5d9..251f4ea9 100644 --- a/src/decode/ltrstandard/LTRStandardDecoder.java +++ b/src/decode/ltrstandard/LTRStandardDecoder.java @@ -18,6 +18,7 @@ package decode.ltrstandard; import message.MessageDirection; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -115,4 +116,13 @@ public class LTRStandardDecoder extends Decoder { return DecoderType.LTR_STANDARD; } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + if( mDemodulator != null ) + { + mDemodulator.addListener( listener ); + } + } } diff --git a/src/decode/mdc1200/MDCDecoder.java b/src/decode/mdc1200/MDCDecoder.java index 16662c9f..c71c8753 100644 --- a/src/decode/mdc1200/MDCDecoder.java +++ b/src/decode/mdc1200/MDCDecoder.java @@ -25,6 +25,7 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; @@ -199,4 +200,10 @@ public class MDCDecoder extends Decoder implements Instrumentable default: } } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + //Not implemented + } } diff --git a/src/decode/mpt1327/MPT1327ChannelState.java b/src/decode/mpt1327/MPT1327ChannelState.java index a0c83617..68114d92 100644 --- a/src/decode/mpt1327/MPT1327ChannelState.java +++ b/src/decode/mpt1327/MPT1327ChannelState.java @@ -43,6 +43,7 @@ public class MPT1327ChannelState extends ChannelState private ChannelMap mChannelMap; private MPT1327ActivitySummary mActivitySummary; + public MPT1327ChannelState( ProcessingChain processingChain, AliasList aliasList, ChannelMap channelMap ) @@ -156,72 +157,72 @@ public class MPT1327ChannelState extends ChannelState setState( State.CONTROL ); break; case GTC: - int channelNumber = mpt.getChannel(); - - if( !getProcessingChain().getChannel() - .hasTrafficChannel( channelNumber ) ) + if( mpt.isValidCall() ) { - String aliasListName = mProcessingChain - .getChannel().getAliasListName(); + int channelNumber = mpt.getChannel(); - Channel traffic = getTrafficChannel( channelNumber, - aliasListName ); - - /* Set the system and site to same as control channel */ - traffic.setSystem( getProcessingChain().getChannel() - .getSystem(), false ); - traffic.setSite( getProcessingChain().getChannel() - .getSite(), false ); + if( !getProcessingChain().getChannel() + .hasTrafficChannel( channelNumber ) ) + { + String aliasListName = mProcessingChain + .getChannel().getAliasListName(); + + Channel traffic = getTrafficChannel( channelNumber, + aliasListName ); + + /* Set the system and site to same as control channel */ + traffic.setSystem( getProcessingChain().getChannel() + .getSystem(), false ); + traffic.setSite( getProcessingChain().getChannel() + .getSite(), false ); - /* Add the traffic channel to the parent control channel */ - getProcessingChain().getChannel() - .addTrafficChannel( channelNumber, traffic ); + /* Add the traffic channel to the parent control channel */ + getProcessingChain().getChannel() + .addTrafficChannel( channelNumber, traffic ); - /* Start the traffic channel */ - traffic.setEnabled( true ); - - /* Set traffic channel state info */ - MPT1327ChannelState trafficState = - (MPT1327ChannelState)traffic - .getProcessingChain().getChannelState(); - - MPT1327ChannelState controlState = - (MPT1327ChannelState)getProcessingChain() - .getChannelState(); - - trafficState.setChannelMap( controlState.getChannelMap() ); - trafficState.setChannelNumber( channelNumber ); - trafficState.setFromTalkgroup( mpt.getFromID() ); - trafficState.setToTalkgroup( mpt.getToID() ); + /* Start the traffic channel */ + traffic.setEnabled( true ); + + /* Set traffic channel state info */ + MPT1327ChannelState trafficState = + (MPT1327ChannelState)traffic + .getProcessingChain().getChannelState(); + + MPT1327ChannelState controlState = + (MPT1327ChannelState)getProcessingChain() + .getChannelState(); + + trafficState.setChannelMap( controlState.getChannelMap() ); + trafficState.setChannelNumber( channelNumber ); + trafficState.setFromTalkgroup( mpt.getFromID() ); + trafficState.setToTalkgroup( mpt.getToID() ); - /* Add this control channel as message listener on the - * traffic channel so we can receive call tear-down - * messages */ - traffic.addListener( (Listener)this ); - - CallEventType type = traffic.isProcessing() ? - CallEventType.CALL_START : - CallEventType.CALL_START_NO_TUNER; - - /* - * Set the traffic channel's call event model to - * share the control channel's call event model - */ - traffic.getProcessingChain().getChannelState() - .setCallEventModel( mCallEventModel ); - - mCallEventModel.add( - new MPT1327CallEvent.Builder( type ) - .aliasList( traffic.getProcessingChain().getAliasList() ) - .channel( channelNumber ) - .frequency( mChannelMap.getFrequency( channelNumber ) ) - .from( mpt.getFromID() ) - .to( mpt.getToID() ) - .build() ); + /* Add this control channel as message listener on the + * traffic channel so we can receive call tear-down + * messages */ + traffic.addListener( (Listener)this ); + + CallEventType type = traffic.isProcessing() ? + CallEventType.CALL_START : + CallEventType.CALL_START_NO_TUNER; + + /* + * Set the traffic channel's call event model to + * share the control channel's call event model + */ + traffic.getProcessingChain().getChannelState() + .setCallEventModel( mCallEventModel ); + + mCallEventModel.add( + new MPT1327CallEvent.Builder( type ) + .aliasList( traffic.getProcessingChain().getAliasList() ) + .channel( channelNumber ) + .frequency( mChannelMap.getFrequency( channelNumber ) ) + .from( mpt.getFromID() ) + .to( mpt.getToID() ) + .build() ); + } } - - long frequency = mChannelMap.getFrequency( channelNumber ); - break; case CLEAR: case MAINT: @@ -237,7 +238,7 @@ public class MPT1327ChannelState extends ChannelState mCallEventModel.add( new MPT1327CallEvent.Builder( CallEventType.SDM ) .aliasList( mAliasList ) - .details( mpt.getMessageType().getDescription() ) + .details( mpt.getMessage() ) .from( mpt.getFromID() ) .to( mpt.getToID() ) .build() ); @@ -378,6 +379,7 @@ public class MPT1327ChannelState extends ChannelState traffic.setSourceConfiguration( source ); DecodeConfigMPT1327 decode = new DecodeConfigMPT1327(); + traffic.setDecodeConfiguration( decode ); traffic.setAliasListName( aliasListName ); diff --git a/src/decode/mpt1327/MPT1327Decoder.java b/src/decode/mpt1327/MPT1327Decoder.java index 689b0d06..6b93a9e0 100644 --- a/src/decode/mpt1327/MPT1327Decoder.java +++ b/src/decode/mpt1327/MPT1327Decoder.java @@ -25,13 +25,16 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; +import log.Log; import sample.Broadcaster; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; import bits.SyncPattern; import decode.Decoder; import decode.DecoderType; +import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter; import dsp.filter.FilterFactory; import dsp.filter.Filters; @@ -95,7 +98,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable /* I/Q low pass filtering narrow band FM demodulator */ mNBFMDemodulator = new NBFMDemodulator( FilterFactory.getLowPass( 48000, 4000, 73, WindowType.HAMMING ), - 1.0002 ); + 1.0002, true ); this.addComplexListener( mNBFMDemodulator ); @@ -166,6 +169,15 @@ public class MPT1327Decoder extends Decoder implements Instrumentable mControlMessageFramer.addMessageListener( mMessageProcessor ); mTrafficMessageFramer.addMessageListener( mMessageProcessor ); } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + if( mNBFMDemodulator != null ) + { + mNBFMDemodulator.addListener( listener ); + } + } @Override public DecoderType getType() diff --git a/src/decode/mpt1327/MPT1327Message.java b/src/decode/mpt1327/MPT1327Message.java index 1bcd42e2..52697e16 100644 --- a/src/decode/mpt1327/MPT1327Message.java +++ b/src/decode/mpt1327/MPT1327Message.java @@ -281,6 +281,13 @@ public class MPT1327Message extends Message return mCRC[ 0 ] == CRC.PASSED || mCRC[ 0 ] == CRC.CORRECTED; } + + public boolean isValidCall() + { + return getPrefix() != 0 && + getIdent1() != 0 && + getIdent2() != 0; + } /** * Constructs a decoded message string @@ -312,9 +319,9 @@ public class MPT1327Message extends Message */ public MPTMessageType getMessageType() { - int type = mMessage.getInt( B1_MESSAGE_TYPE ); + int value = mMessage.getInt( B1_MESSAGE_TYPE ); - return MPTMessageType.fromNumber( type ); + return MPTMessageType.fromNumber( value ); } /** @@ -766,6 +773,7 @@ public class MPT1327Message extends Message case HEAD_PLUS2: case HEAD_PLUS3: case HEAD_PLUS4: + sb.append( " " ); sb.append( getSDM() ); break; } @@ -947,9 +955,12 @@ public class MPT1327Message extends Message break; case USER: default: - sb.append( format( prefix, 3 ) ); - sb.append( "-" ); - sb.append( format( ident, 4) ); + if( prefix != 0 || ident != 0 ) + { + sb.append( format( prefix, 3 ) ); + sb.append( "-" ); + sb.append( format( ident, 4) ); + } break; } @@ -1701,7 +1712,7 @@ public class MPT1327Message extends Message public enum Descriptor { DESC0( "EXTENDED ADDRESSING INFORMATION", - "SERIAL NUMBER", + "ESN", "SINGLE SEGMENT TRANSACTIONS (SST) ONLY" ), DESC1( "PSTN DIALED DIGITS", "RESERVED", "N/A" ), DESC2( "PABX EXTENSION", "RESERVED", "N/A" ), diff --git a/src/decode/nbfm/NBFMConventionalDecoder.java b/src/decode/nbfm/NBFMConventionalDecoder.java index 2d67cd2c..c494df23 100644 --- a/src/decode/nbfm/NBFMConventionalDecoder.java +++ b/src/decode/nbfm/NBFMConventionalDecoder.java @@ -17,9 +17,11 @@ ******************************************************************************/ package decode.nbfm; +import sample.Listener; import source.Source.SampleType; import decode.Decoder; import decode.DecoderType; +import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.nbfm.FilteringNBFMDemodulator; @@ -32,7 +34,7 @@ public class NBFMConventionalDecoder extends Decoder private static final double sDC_REMOVAL_RATIO = 0.000003; private FilteringNBFMDemodulator mDemodulator; - private DCRemovalFilter2 mDCRemovalFilter; + private DCRemovalFilter2 mDCRemovalFilter; public NBFMConventionalDecoder( SampleType sampleType ) { @@ -60,7 +62,7 @@ public class NBFMConventionalDecoder extends Decoder */ mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); mDemodulator.addListener( mDCRemovalFilter ); - + /** * Route the demodulated, filtered samples back to this class to send * to all registered listeners @@ -74,4 +76,13 @@ public class NBFMConventionalDecoder extends Decoder { return DecoderType.NBFM; } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + if( mDemodulator != null ) + { + mDemodulator.addListener( listener ); + } + } } diff --git a/src/decode/nbfm/NBFMEditor.java b/src/decode/nbfm/NBFMEditor.java index 4fa0472a..e5be3f75 100644 --- a/src/decode/nbfm/NBFMEditor.java +++ b/src/decode/nbfm/NBFMEditor.java @@ -17,12 +17,16 @@ ******************************************************************************/ package decode.nbfm; +import javax.swing.JCheckBox; + import decode.DecodeEditor; import decode.config.DecodeConfiguration; public class NBFMEditor extends DecodeEditor { private static final long serialVersionUID = 1L; + + private JCheckBox mAFC; public NBFMEditor( DecodeConfiguration config ) { diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index 9942d993..4ba3877c 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -24,10 +24,12 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import decode.Decoder; import decode.DecoderType; +import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.fsk.C4FMDecoder; import dsp.nbfm.FilteringNBFMDemodulator; @@ -141,4 +143,11 @@ public class P25Decoder extends Decoder implements Instrumentable break; } } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + // TODO Auto-generated method stub + + } } diff --git a/src/decode/passport/PassportDecoder.java b/src/decode/passport/PassportDecoder.java index b4ad78e1..591f6ca9 100644 --- a/src/decode/passport/PassportDecoder.java +++ b/src/decode/passport/PassportDecoder.java @@ -23,12 +23,14 @@ import instrument.tap.Tap; import java.util.ArrayList; import java.util.List; +import sample.Listener; import source.Source.SampleType; import alias.AliasList; import bits.MessageFramer; import bits.SyncPattern; import decode.Decoder; import decode.DecoderType; +import dsp.afc.AutomaticFrequencyControl; import dsp.filter.DCRemovalFilter2; import dsp.fsk.LTRFSKDecoder; import dsp.nbfm.FilteringNBFMDemodulator; @@ -138,4 +140,13 @@ public class PassportDecoder extends Decoder implements Instrumentable { mPassportFSKDecoder.removeTap( tap ); } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + if( mNBFMDemodulator != null ) + { + mNBFMDemodulator.addListener( listener ); + } + } } diff --git a/src/dsp/afc/AutomaticFrequencyControl.java b/src/dsp/afc/AutomaticFrequencyControl.java new file mode 100644 index 00000000..3dc211be --- /dev/null +++ b/src/dsp/afc/AutomaticFrequencyControl.java @@ -0,0 +1,169 @@ +package dsp.afc; + +import sample.Listener; +import source.tuner.FrequencyChangeListener; +import buffer.FloatAveragingBuffer; + +public class AutomaticFrequencyControl implements Listener, + 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(); + } + } +} diff --git a/src/dsp/afc/FrequencyErrorListener.java b/src/dsp/afc/FrequencyErrorListener.java new file mode 100644 index 00000000..b327bbee --- /dev/null +++ b/src/dsp/afc/FrequencyErrorListener.java @@ -0,0 +1,10 @@ +package dsp.afc; + +import source.tuner.FrequencyChangeListener; + +public interface FrequencyErrorListener +{ + public void setError( int error ); + + public void addListener( FrequencyChangeListener listener ); +} diff --git a/src/dsp/nbfm/NBFMDemodulator.java b/src/dsp/nbfm/NBFMDemodulator.java index 7eb918c3..ff548369 100644 --- a/src/dsp/nbfm/NBFMDemodulator.java +++ b/src/dsp/nbfm/NBFMDemodulator.java @@ -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 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 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 diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index b00f1486..f28f3114 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -24,22 +24,23 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import log.Log; import sample.Listener; -import sample.Provider; import sample.complex.ComplexSample; import sample.complex.ComplexSampleAssembler; import source.ComplexSource; -import source.Source.SampleType; import source.SourceException; import util.Oscillator; import controller.ThreadPoolManager; import controller.ThreadPoolManager.ThreadType; +import dsp.afc.FrequencyErrorListener; import dsp.filter.ComplexFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; public class TunerChannelSource extends ComplexSource implements FrequencyChangeListener, + FrequencyErrorListener, Listener { private static int sCHANNEL_RATE = 48000; @@ -55,8 +56,10 @@ public class TunerChannelSource extends ComplexSource private ComplexSampleAssembler mBufferAssembler = new ComplexSampleAssembler( 3000 ); - private long mTunerFrequency; + private long mTunerFrequency = 0; + private int mTunerFrequencyError = 0; private int mTunerSampleRate; + private FrequencyChangeListener mFrequencyChangeListener; private ThreadPoolManager mThreadPoolManager; private ScheduledFuture mTaskHandle; @@ -95,6 +98,8 @@ public class TunerChannelSource extends ComplexSource @Override public void dispose() { + mFrequencyChangeListener = null; + //Tell the tuner to release our resources mTuner.removeListener( (FrequencyChangeListener)this ); mTuner.releaseChannel( this ); @@ -155,43 +160,33 @@ public class TunerChannelSource extends ComplexSource @Override public void frequencyChanged( long frequency, int bandwidth ) { - - /* Frequency change */ - if( frequency != mTunerFrequency ) + /* Send change to Automatic Frequency Control */ + if( mFrequencyChangeListener != null ) { - long frequencyOffset = frequency - mTunerChannel.getFrequency(); - - if( mSineWaveGenerator == null ) - { - mSineWaveGenerator = - new Oscillator( frequencyOffset, bandwidth ); - } - else - { - mSineWaveGenerator.setFrequency( frequencyOffset ); - } - - /* Clear the buffer for the frequency to take immediate effect */ - mBuffer.clear(); - - mTunerFrequency = frequency; + mFrequencyChangeListener.frequencyChanged( frequency, bandwidth ); } + + long frequencyOffset = frequency - + mTunerChannel.getFrequency() - + mTunerFrequencyError; + + if( mSineWaveGenerator == null ) + { + mSineWaveGenerator = new Oscillator( frequencyOffset, bandwidth ); + } + else + { + mSineWaveGenerator.setFrequency( frequencyOffset ); + } + + /* Clear the buffer for the frequency to take immediate effect */ +// mBuffer.clear(); + + mTunerFrequency = frequency; /* Bandwidth/Sample rate change */ if( bandwidth != mTunerSampleRate ) { - long frequencyOffset = frequency - mTunerChannel.getFrequency(); - - if( mSineWaveGenerator == null ) - { - mSineWaveGenerator = - new Oscillator( frequencyOffset, bandwidth ); - } - else - { - mSineWaveGenerator.setSampleRate( bandwidth ); - } - /* Get the original decimation filter output listener */ Listener listener = null; @@ -270,4 +265,26 @@ public class TunerChannelSource extends ComplexSource } } } + + /** + * Automatic Frequency Control + */ + @Override + public void setError( int error ) + { + if( mTunerFrequencyError != error ) + { + mTunerFrequencyError = error; + frequencyChanged( mTunerFrequency, mTunerSampleRate ); + } + + Log.info( "TunerChannelSource - mistuned quantity:" + + mTunerFrequencyError + " Hz" ); + } + + @Override + public void addListener( FrequencyChangeListener listener ) + { + mFrequencyChangeListener = listener; + } } diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index f9e3cedb..43e5e466 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -1354,7 +1354,7 @@ public abstract class RTL2832TunerController extends TunerController RATE_1_440MHZ( 0x0500, 0x0000, 1440000, "1.440 MHz" ), RATE_1_680MHZ( 0x044A, 0x0000, 1680000, "1.680 MHz" ), RATE_1_824MHZ( 0x03F3, 0x0000, 1824000, "1.824 MHz" ), - RATE_2_016MHZ( 0x0387, 0x0000, 2016000, "2.016 MHz" ); + RATE_2_016MHZ( 0x038F, 0x0000, 2016000, "2.016 MHz" ); /* Note: usb4java library buffer processing scheme doesn't seem capable * of supporting these rates */ diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index d58c993b..731c8507 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -302,6 +302,32 @@ public class OverlayPanel extends JPanel xaxis, (float)(getSize().getHeight()) - mSpectrumInset ) ); } + /** + * Draws a vertical line at the xaxis + */ + private void drawAFC( Graphics2D graphics, float xaxis, boolean isError ) + { + float height = (float)(getSize().getHeight()) - mSpectrumInset; + + if( isError ) + { + graphics.setColor( Color.YELLOW ); + + graphics.draw( new Line2D.Float( xaxis, height * 0.75f, + xaxis, height - 1.0f ) ); + } + else + { + graphics.setColor( Color.LIGHT_GRAY ); + + graphics.draw( new Line2D.Float( xaxis, height * 0.65f, + xaxis, height - 1.0f ) ); + } + + + } + + /** * Returns the x-axis value corresponding to the frequency */ @@ -436,8 +462,22 @@ public class OverlayPanel extends JPanel yAxis, width ); } + + /* Draw Automatic Frequency Control line */ + if( channel.hasAFC() ) + { + int frequency = (int)tunerChannel.getFrequency(); + + int error = frequency + channel.getAFC().getErrorCorrection(); + + drawAFC( graphics, getAxisFromFrequency( frequency ), false ); + + drawAFC( graphics, getAxisFromFrequency( error ), true ); + } } } + + /** * Draws a textual label at the x/y position, clipping the end of the text * to fit within the maxwidth value.