From ae0568ff3b48928b26b6928c9dd37ede369e95c6 Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Sat, 1 Nov 2014 17:39:06 +0000 Subject: [PATCH] Updated spectral display to used dBm scale and removed baseline/amplification items. Updated sample handling code to use -1.0 to 1.0 float values throughout, to support P25 decoder and symbol filter. --- src/buffer/FloatSampleBufferAssembler.java | 2 +- src/controller/state/ChannelStateList.java | 6 + src/decode/DecodeComponentEditor.java | 1 - src/decode/DecodeEditorFactory.java | 4 + src/decode/DecoderFactory.java | 8 + src/decode/DecoderType.java | 7 +- src/decode/config/DecodeConfigFactory.java | 3 + src/decode/config/DecodeConfigP25Phase1.java | 35 ++ src/decode/config/DecodeConfiguration.java | 3 +- src/decode/p25/P25ChannelState.java | 65 ++++ src/decode/p25/P25Decoder.java | 83 ++--- src/decode/p25/P25Editor.java | 31 ++ src/decode/p25/P25Panel.java | 136 ++++++++ src/dsp/afc/AutomaticFrequencyControl.java | 36 +- src/dsp/filter/C4FMSymbolFilter.java | 309 ++++++++++++++++++ src/dsp/fsk/C4FMDecoder.java | 42 ++- src/dsp/nbfm/FilteringNBFMDemodulator.java | 6 +- src/dsp/nbfm/NBFMDemodulator.java | 8 +- src/instrument/gui/WaveSourceFrame.java | 2 +- .../adapter}/ByteSampleAdapter.java | 9 +- src/sample/adapter/ChannelShortAdapter.java | 5 +- src/sample/adapter/ShortAdapter.java | 3 +- src/sample/adapter/ShortToFloatMap.java | 19 ++ .../tuner/hackrf/HackRFTunerController.java | 2 +- .../tuner/rtl/RTL2832TunerController.java | 1 + src/source/wave/FloatWaveSource.java | 4 +- src/spectrum/ChannelSpectrumPanel.java | 33 +- src/spectrum/SpectralDisplayAdjuster.java | 36 +- src/spectrum/SpectralDisplayPanel.java | 37 +-- src/spectrum/SpectrumPanel.java | 81 ++--- src/spectrum/WaterfallPanel.java | 65 +--- .../converter/ComplexDecibelConverter.java | 79 +++-- src/spectrum/menu/AmplificationItem.java | 57 ---- .../{BaselineItem.java => DBScaleItem.java} | 15 +- 34 files changed, 821 insertions(+), 412 deletions(-) create mode 100644 src/decode/config/DecodeConfigP25Phase1.java create mode 100644 src/decode/p25/P25ChannelState.java create mode 100644 src/decode/p25/P25Editor.java create mode 100644 src/decode/p25/P25Panel.java create mode 100644 src/dsp/filter/C4FMSymbolFilter.java rename src/{source/tuner/rtl => sample/adapter}/ByteSampleAdapter.java (87%) create mode 100644 src/sample/adapter/ShortToFloatMap.java delete mode 100644 src/spectrum/menu/AmplificationItem.java rename src/spectrum/menu/{BaselineItem.java => DBScaleItem.java} (73%) diff --git a/src/buffer/FloatSampleBufferAssembler.java b/src/buffer/FloatSampleBufferAssembler.java index babb7a8b..de4d53c2 100644 --- a/src/buffer/FloatSampleBufferAssembler.java +++ b/src/buffer/FloatSampleBufferAssembler.java @@ -40,7 +40,7 @@ public class FloatSampleBufferAssembler */ public void put( float sample ) { - mByteBuffer.putShort( Float.valueOf( sample ).shortValue() ); + mByteBuffer.putShort( (short)( sample * 32768 ) ); } /** diff --git a/src/controller/state/ChannelStateList.java b/src/controller/state/ChannelStateList.java index 8ed1566e..41eb9300 100644 --- a/src/controller/state/ChannelStateList.java +++ b/src/controller/state/ChannelStateList.java @@ -53,6 +53,8 @@ import decode.mpt1327.MPT1327ChannelState; import decode.mpt1327.MPT1327Panel; import decode.nbfm.NBFMChannelState; import decode.nbfm.NBFMPanel; +import decode.p25.P25ChannelState; +import decode.p25.P25Panel; import decode.passport.PassportChannelState; import decode.passport.PassportPanel; @@ -229,6 +231,10 @@ public class ChannelStateList extends JPanel implements ChannelEventListener } } } + else if( state instanceof P25ChannelState ) + { + panel = new P25Panel( channel ); + } if( panel != null ) { diff --git a/src/decode/DecodeComponentEditor.java b/src/decode/DecodeComponentEditor.java index 114dce05..ece532ab 100644 --- a/src/decode/DecodeComponentEditor.java +++ b/src/decode/DecodeComponentEditor.java @@ -21,7 +21,6 @@ package decode; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; import javax.swing.JComboBox; import javax.swing.SwingUtilities; diff --git a/src/decode/DecodeEditorFactory.java b/src/decode/DecodeEditorFactory.java index ed95699a..9d284a57 100644 --- a/src/decode/DecodeEditorFactory.java +++ b/src/decode/DecodeEditorFactory.java @@ -23,6 +23,7 @@ import decode.ltrnet.LTRNetEditor; import decode.ltrstandard.LTRStandardEditor; import decode.mpt1327.MPT1327Editor; import decode.nbfm.NBFMEditor; +import decode.p25.P25Editor; import decode.passport.PassportEditor; public class DecodeEditorFactory @@ -49,6 +50,9 @@ public class DecodeEditorFactory case PASSPORT: configuredPanel = new PassportEditor( config ); break; + case P25_PHASE1: + configuredPanel = new P25Editor( config ); + break; default: configuredPanel = new DecodeEditor( config ); break; diff --git a/src/decode/DecoderFactory.java b/src/decode/DecoderFactory.java index 62a07178..b192fe2f 100644 --- a/src/decode/DecoderFactory.java +++ b/src/decode/DecoderFactory.java @@ -42,6 +42,8 @@ import decode.mpt1327.MPT1327ChannelState; import decode.mpt1327.MPT1327Decoder; import decode.nbfm.NBFMChannelState; import decode.nbfm.NBFMConventionalDecoder; +import decode.p25.P25ChannelState; +import decode.p25.P25Decoder; import decode.passport.PassportChannelState; import decode.passport.PassportDecoder; @@ -93,6 +95,9 @@ public class DecoderFactory case PASSPORT: retVal = new PassportDecoder( sampleType, aliasList ); break; + case P25_PHASE1: + retVal = new P25Decoder( sampleType, aliasList ); + break; default: throw new IllegalArgumentException( "Unknown decoder type [" + @@ -159,6 +164,9 @@ public class DecoderFactory case PASSPORT: retVal = new PassportChannelState( chain, aliasList ); break; + case P25_PHASE1: + retVal = new P25ChannelState( chain, aliasList ); + break; default: break; } diff --git a/src/decode/DecoderType.java b/src/decode/DecoderType.java index d72c8680..233db3f0 100644 --- a/src/decode/DecoderType.java +++ b/src/decode/DecoderType.java @@ -33,7 +33,7 @@ public enum DecoderType MPT1327( "MPT1327", "MPT1327", "images/mpt1327", 12500 ), NBFM( "NBFM", "NBFM", "images/fm", 12500 ), PASSPORT( "Passport", "Passport", "images/passport", 12500 ), - P25( "P25", "P25", "images/p25", 12500 ); + P25_PHASE1( "P25 Phase 1", "P25-1", "images/p25p1", 12500 ); private String mDisplayString; private String mShortDisplayString; @@ -59,7 +59,8 @@ public enum DecoderType DecoderType.LTR_STANDARD, DecoderType.MPT1327, DecoderType.NBFM, - DecoderType.PASSPORT ); + DecoderType.PASSPORT, + DecoderType.P25_PHASE1 ); } /** @@ -72,7 +73,7 @@ public enum DecoderType DecoderType.MDC1200, DecoderType.MPT1327, DecoderType.PASSPORT, - DecoderType.P25 ); + DecoderType.P25_PHASE1 ); } /** diff --git a/src/decode/config/DecodeConfigFactory.java b/src/decode/config/DecodeConfigFactory.java index 9bbc7d2f..449309d9 100644 --- a/src/decode/config/DecodeConfigFactory.java +++ b/src/decode/config/DecodeConfigFactory.java @@ -51,6 +51,9 @@ public class DecodeConfigFactory case PASSPORT: retVal = new DecodeConfigPassport(); break; + case P25_PHASE1: + retVal = new DecodeConfigP25Phase1(); + break; default: throw new IllegalArgumentException( "DecodeConfigFactory - " + "unknown decoder type [" + decoder.toString() + "]" ); diff --git a/src/decode/config/DecodeConfigP25Phase1.java b/src/decode/config/DecodeConfigP25Phase1.java new file mode 100644 index 00000000..bb064b22 --- /dev/null +++ b/src/decode/config/DecodeConfigP25Phase1.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package decode.config; + +import decode.DecoderType; + +public class DecodeConfigP25Phase1 extends DecodeConfiguration +{ + public DecodeConfigP25Phase1() + { + super( DecoderType.P25_PHASE1 ); + + setAFC( false ); + } + + public boolean supportsAFC() + { + return false; + } +} diff --git a/src/decode/config/DecodeConfiguration.java b/src/decode/config/DecodeConfiguration.java index ed089e13..b5ec7bb8 100644 --- a/src/decode/config/DecodeConfiguration.java +++ b/src/decode/config/DecodeConfiguration.java @@ -29,7 +29,8 @@ import decode.DecoderType; DecodeConfigLTRNet.class, DecodeConfigLTRStandard.class, DecodeConfigMPT1327.class, - DecodeConfigPassport.class } ) + DecodeConfigPassport.class, + DecodeConfigP25Phase1.class } ) @XmlRootElement( name = "decode_configuration" ) public abstract class DecodeConfiguration extends Configuration { diff --git a/src/decode/p25/P25ChannelState.java b/src/decode/p25/P25ChannelState.java new file mode 100644 index 00000000..4603c32e --- /dev/null +++ b/src/decode/p25/P25ChannelState.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package decode.p25; + +import alias.AliasList; +import audio.SquelchListener; +import audio.SquelchListener.SquelchState; +import controller.channel.ProcessingChain; +import controller.state.AuxChannelState; +import controller.state.ChannelState; + +public class P25ChannelState extends ChannelState +{ + public P25ChannelState( ProcessingChain chain, AliasList aliasList ) + { + super( chain, aliasList ); + } + + /** + * Make the ConventionalChannelState always unsquelched + */ + public void addListener( SquelchListener listener ) + { + super.addListener( listener ); + + super.setSquelchState( SquelchState.UNSQUELCH ); + } + + @Override + public void setSquelchState( SquelchState state ) + { + //do nothing ... we want the squelch state always unsquelched (for now) + } + @Override + public String getActivitySummary() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Activity Summary\n" ); + sb.append( "\tDecoder:\tP25\n\n" ); + + for( AuxChannelState state: mAuxChannelStates ) + { + sb.append( state.getActivitySummary() ); + sb.append( "\n\n" ); + } + + return sb.toString(); + } +} diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index 4563a3c3..f259cf0a 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -19,9 +19,7 @@ package decode.p25; import instrument.Instrumentable; import instrument.tap.Tap; -import instrument.tap.stream.FloatTap; -import java.util.ArrayList; import java.util.List; import sample.real.RealSampleListener; @@ -29,27 +27,23 @@ import source.Source.SampleType; import alias.AliasList; import decode.Decoder; import decode.DecoderType; -import dsp.filter.DCRemovalFilter2; +import dsp.filter.ComplexFIRFilter; +import dsp.filter.FilterFactory; +import dsp.filter.FloatFIRFilter; +import dsp.filter.Window.WindowType; import dsp.fsk.C4FMDecoder; -import dsp.nbfm.FilteringNBFMDemodulator; +import dsp.nbfm.FMDiscriminator; public class P25Decoder extends Decoder implements Instrumentable { - /** - * This value determines how quickly the DC remove filter responds to - * changes in frequency. - */ - private static final double sDC_REMOVAL_RATIO = 0.000003; - - private final String TAP_DEMOD_TO_DECODER = "Demodulated Audio <> C4FM Decoder"; - - private FilteringNBFMDemodulator mDemodulator; - private DCRemovalFilter2 mDCRemovalFilter; - private C4FMDecoder mC4FMDecoder; + private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter( + FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ), 1.0 ); + private FMDiscriminator mFMDiscriminator = new FMDiscriminator( 1 ); +// private FloatFIRFilter mAudioFilter = new FloatFIRFilter( FilterFactory +// .getLowPass( 48000, 6000, 73, WindowType.HAMMING ), 1.0 ); + private C4FMDecoder mC4FMDecoder = new C4FMDecoder(); private AliasList mAliasList; - private ArrayList mTaps; - public P25Decoder( SampleType sampleType, AliasList aliasList ) { super( sampleType ); @@ -70,82 +64,43 @@ public class P25Decoder extends Decoder implements Instrumentable * back to this class, so we can receive the demodulated output * to process */ - mDemodulator = new FilteringNBFMDemodulator(); - this.addComplexListener( mDemodulator ); - - /** - * Remove the DC component that is present when we're mistuned - */ - mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); - mDemodulator.addListener( mDCRemovalFilter ); - - /** - * Route the demodulated, filtered samples back to this class to send - * to all registered listeners - */ - mDCRemovalFilter.setListener( this.getRealReceiver() ); - + this.addComplexListener( mBasebandFilter ); + mBasebandFilter.setListener( mFMDiscriminator ); + mFMDiscriminator.setListener( getRealReceiver() ); +// mFMDiscriminator.setListener( mAudioFilter ); +// mAudioFilter.setListener( getRealReceiver() ); } - mC4FMDecoder = new C4FMDecoder(); addRealSampleListener( mC4FMDecoder ); } @Override public DecoderType getType() { - return DecoderType.NBFM; + return DecoderType.P25_PHASE1; } @Override public List getTaps() { - if( mTaps == null ) - { - mTaps = new ArrayList(); - mTaps.add( new FloatTap( TAP_DEMOD_TO_DECODER, 0, 1.0f ) ); - - mTaps.addAll( mC4FMDecoder.getTaps() ); - } - - return mTaps; + return mC4FMDecoder.getTaps(); } @Override public void addTap( Tap tap ) { mC4FMDecoder.addTap( tap ); - - switch( tap.getName() ) - { - case TAP_DEMOD_TO_DECODER: - FloatTap demodTap = (FloatTap)tap; - removeRealListener( mC4FMDecoder ); - addRealSampleListener( demodTap ); - demodTap.setListener( mC4FMDecoder ); - break; - } } @Override public void removeTap( Tap tap ) { mC4FMDecoder.removeTap( tap ); - - switch( tap.getName() ) - { - case TAP_DEMOD_TO_DECODER: - FloatTap demodTap = (FloatTap)tap; - removeRealListener( demodTap ); - demodTap.removeListener( mC4FMDecoder ); - addRealSampleListener( mC4FMDecoder ); - break; - } } @Override public void addUnfilteredRealSampleListener( RealSampleListener listener ) { - // TODO Auto-generated method stub + throw new IllegalArgumentException( "unfiltered real sample provider not implemented in P25 Decoder" ); } } diff --git a/src/decode/p25/P25Editor.java b/src/decode/p25/P25Editor.java new file mode 100644 index 00000000..abfb7512 --- /dev/null +++ b/src/decode/p25/P25Editor.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package decode.p25; + +import decode.DecodeEditor; +import decode.config.DecodeConfiguration; + +public class P25Editor extends DecodeEditor +{ + private static final long serialVersionUID = 1L; + + public P25Editor( DecodeConfiguration config ) + { + super( config ); + } +} diff --git a/src/decode/p25/P25Panel.java b/src/decode/p25/P25Panel.java new file mode 100644 index 00000000..9fbfd0f7 --- /dev/null +++ b/src/decode/p25/P25Panel.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package decode.p25; + +import java.awt.EventQueue; + +import javax.swing.JLabel; + +import settings.ColorSetting; +import settings.Setting; +import controller.channel.Channel; +import controller.state.ChannelState.ChangedAttribute; +import controller.state.ChannelStatePanel; + +public class P25Panel extends ChannelStatePanel +{ + private static final long serialVersionUID = 1L; + + private JLabel mStateLabel; + private JLabel mSourceLabel; + private JLabel mChannelLabel; + + private JLabel mProtocol = new JLabel( "P25" ); + + public P25Panel( Channel channel ) + { + super( channel ); + + init(); + } + + public void init() + { + mStateLabel = new JLabel( mChannel.getProcessingChain(). + getChannelState().getState().getDisplayValue() ); + mStateLabel.setFont( mFontDecoder ); + mStateLabel.setForeground( mColorLabelDecoder ); + + mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() ); + mSourceLabel.setFont( mFontDetails ); + mSourceLabel.setForeground( mColorLabelDetails ); + + mChannelLabel = new JLabel( mChannel.getChannelDisplayName() ); + mChannelLabel.setFont( mFontDetails ); + mChannelLabel.setForeground( mColorLabelDetails ); + + mProtocol.setFont( mFontDecoder ); + mProtocol.setForeground( mColorLabelDecoder ); + + add( mStateLabel ); + add( mSourceLabel ); + add( mChannelLabel, "wrap" ); + + add( mProtocol, "wrap" ); + } + + @Override + public void receive( final ChangedAttribute changedAttribute ) + { + EventQueue.invokeLater( new Runnable() + { + @Override + public void run() + { + switch( changedAttribute ) + { + case CHANNEL_STATE: + mStateLabel.setText( mChannel.getProcessingChain(). + getChannelState().getState().getDisplayValue() ); + break; + case SOURCE: + mSourceLabel.setText( mChannel.getSourceConfiguration() + .getDescription() ); + break; + case CHANNEL_NAME: + case SITE_NAME: + case SYSTEM_NAME: + mChannelLabel.setText( mChannel.getChannelDisplayName() ); + break; + } + + repaint(); + } + }); + } + + @Override + public void settingChanged( Setting setting ) + { + super.settingChanged( setting ); + + if( setting instanceof ColorSetting ) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch( colorSetting.getColorSettingName() ) + { + case CHANNEL_STATE_LABEL_DECODER: + if( mStateLabel != null ) + { + mStateLabel.setForeground( mColorLabelDecoder ); + } + if( mProtocol != null ) + { + mProtocol.setForeground( mColorLabelDecoder ); + } + break; + case CHANNEL_STATE_LABEL_DETAILS: + if( mSourceLabel != null ) + { + mSourceLabel.setForeground( mColorLabelDetails ); + } + if( mChannelLabel != null ) + { + mChannelLabel.setForeground( mColorLabelDetails ); + } + break; + } + } + } +} diff --git a/src/dsp/afc/AutomaticFrequencyControl.java b/src/dsp/afc/AutomaticFrequencyControl.java index 491616b0..5f57544a 100644 --- a/src/dsp/afc/AutomaticFrequencyControl.java +++ b/src/dsp/afc/AutomaticFrequencyControl.java @@ -1,5 +1,8 @@ package dsp.afc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import sample.real.RealSampleListener; import source.tuner.FrequencyChangeEvent; import source.tuner.FrequencyChangeEvent.Attribute; @@ -9,10 +12,19 @@ import buffer.FloatAveragingBuffer; public class AutomaticFrequencyControl implements RealSampleListener, 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 final static Logger mLog = + LoggerFactory.getLogger( AutomaticFrequencyControl.class ); + + private static float LARGE_ERROR = 0.300f; + private static float MEDIUM_ERROR = 0.150f; + private static float SMALL_ERROR = 0.030f; + private static float FINE_ERROR = 0.015f; + + private static int LARGE_FREQUENCY_CORRECTION = 1500; //Hertz + private static int MEDIUM_FREQUENCY_CORRECTION = 500; + private static int SMALL_FREQUENCY_CORRECTION = 100; + private static int FINE_FREQUENCY_CORRECTION = 50; + private static int SAMPLE_THRESHOLD = 15000; private Mode mMode = Mode.FAST; @@ -111,42 +123,42 @@ public class AutomaticFrequencyControl implements RealSampleListener, if( average > LARGE_ERROR ) { - correction = -1000; + correction = -LARGE_FREQUENCY_CORRECTION; mMode = Mode.FAST; } else if( average > MEDIUM_ERROR ) { - correction = -500; + correction = -MEDIUM_FREQUENCY_CORRECTION; mMode = Mode.FAST; } else if( average > SMALL_ERROR ) { - correction = -100; + correction = -SMALL_FREQUENCY_CORRECTION; mMode = Mode.NORMAL; } else if( average > FINE_ERROR ) { - correction = -50; + correction = -FINE_FREQUENCY_CORRECTION; mMode = Mode.NORMAL; } else if( average < -LARGE_ERROR ) { - correction = 1000; + correction = LARGE_FREQUENCY_CORRECTION; mMode = Mode.FAST; } else if( average < -MEDIUM_ERROR ) { - correction = 500; + correction = MEDIUM_FREQUENCY_CORRECTION; mMode = Mode.FAST; } else if( average < -SMALL_ERROR ) { - correction = 100; + correction = SMALL_FREQUENCY_CORRECTION; mMode = Mode.NORMAL; } else if( average < -FINE_ERROR ) { - correction = 50; + correction = FINE_FREQUENCY_CORRECTION; mMode = Mode.NORMAL; } diff --git a/src/dsp/filter/C4FMSymbolFilter.java b/src/dsp/filter/C4FMSymbolFilter.java new file mode 100644 index 00000000..dcf60443 --- /dev/null +++ b/src/dsp/filter/C4FMSymbolFilter.java @@ -0,0 +1,309 @@ +package dsp.filter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sample.real.RealSampleListener; +import sample.real.RealSampleProvider; + +public class C4FMSymbolFilter implements RealSampleListener, + RealSampleProvider +{ + private final static Logger mLog = + LoggerFactory.getLogger( C4FMSymbolFilter.class ); + + private static final float TAPS[][] = + { + { 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128 + { -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128 + { -3.09412e-04f, 1.70888e-03f, -5.55134e-03f, 1.58840e-02f, 9.96891e-01f, -1.07209e-02f, 2.47942e-03f, -3.96391e-04f }, // 2/128 + { -4.64053e-04f, 2.56486e-03f, -8.34364e-03f, 2.39714e-02f, 9.95074e-01f, -1.59305e-02f, 3.69852e-03f, -5.92100e-04f }, // 3/128 + { -6.18544e-04f, 3.42130e-03f, -1.11453e-02f, 3.21531e-02f, 9.93082e-01f, -2.10389e-02f, 4.90322e-03f, -7.86031e-04f }, // 4/128 + { -7.72802e-04f, 4.27773e-03f, -1.39548e-02f, 4.04274e-02f, 9.90917e-01f, -2.60456e-02f, 6.09305e-03f, -9.78093e-04f }, // 5/128 + { -9.26747e-04f, 5.13372e-03f, -1.67710e-02f, 4.87921e-02f, 9.88580e-01f, -3.09503e-02f, 7.26755e-03f, -1.16820e-03f }, // 6/128 + { -1.08030e-03f, 5.98883e-03f, -1.95925e-02f, 5.72454e-02f, 9.86071e-01f, -3.57525e-02f, 8.42626e-03f, -1.35627e-03f }, // 7/128 + { -1.23337e-03f, 6.84261e-03f, -2.24178e-02f, 6.57852e-02f, 9.83392e-01f, -4.04519e-02f, 9.56876e-03f, -1.54221e-03f }, // 8/128 + { -1.38589e-03f, 7.69462e-03f, -2.52457e-02f, 7.44095e-02f, 9.80543e-01f, -4.50483e-02f, 1.06946e-02f, -1.72594e-03f }, // 9/128 + { -1.53777e-03f, 8.54441e-03f, -2.80746e-02f, 8.31162e-02f, 9.77526e-01f, -4.95412e-02f, 1.18034e-02f, -1.90738e-03f }, // 10/128 + { -1.68894e-03f, 9.39154e-03f, -3.09033e-02f, 9.19033e-02f, 9.74342e-01f, -5.39305e-02f, 1.28947e-02f, -2.08645e-03f }, // 11/128 + { -1.83931e-03f, 1.02356e-02f, -3.37303e-02f, 1.00769e-01f, 9.70992e-01f, -5.82159e-02f, 1.39681e-02f, -2.26307e-03f }, // 12/128 + { -1.98880e-03f, 1.10760e-02f, -3.65541e-02f, 1.09710e-01f, 9.67477e-01f, -6.23972e-02f, 1.50233e-02f, -2.43718e-03f }, // 13/128 + { -2.13733e-03f, 1.19125e-02f, -3.93735e-02f, 1.18725e-01f, 9.63798e-01f, -6.64743e-02f, 1.60599e-02f, -2.60868e-03f }, // 14/128 + { -2.28483e-03f, 1.27445e-02f, -4.21869e-02f, 1.27812e-01f, 9.59958e-01f, -7.04471e-02f, 1.70776e-02f, -2.77751e-03f }, // 15/128 + { -2.43121e-03f, 1.35716e-02f, -4.49929e-02f, 1.36968e-01f, 9.55956e-01f, -7.43154e-02f, 1.80759e-02f, -2.94361e-03f }, // 16/128 + { -2.57640e-03f, 1.43934e-02f, -4.77900e-02f, 1.46192e-01f, 9.51795e-01f, -7.80792e-02f, 1.90545e-02f, -3.10689e-03f }, // 17/128 + { -2.72032e-03f, 1.52095e-02f, -5.05770e-02f, 1.55480e-01f, 9.47477e-01f, -8.17385e-02f, 2.00132e-02f, -3.26730e-03f }, // 18/128 + { -2.86289e-03f, 1.60193e-02f, -5.33522e-02f, 1.64831e-01f, 9.43001e-01f, -8.52933e-02f, 2.09516e-02f, -3.42477e-03f }, // 19/128 + { -3.00403e-03f, 1.68225e-02f, -5.61142e-02f, 1.74242e-01f, 9.38371e-01f, -8.87435e-02f, 2.18695e-02f, -3.57923e-03f }, // 20/128 + { -3.14367e-03f, 1.76185e-02f, -5.88617e-02f, 1.83711e-01f, 9.33586e-01f, -9.20893e-02f, 2.27664e-02f, -3.73062e-03f }, // 21/128 + { -3.28174e-03f, 1.84071e-02f, -6.15931e-02f, 1.93236e-01f, 9.28650e-01f, -9.53307e-02f, 2.36423e-02f, -3.87888e-03f }, // 22/128 + { -3.41815e-03f, 1.91877e-02f, -6.43069e-02f, 2.02814e-01f, 9.23564e-01f, -9.84679e-02f, 2.44967e-02f, -4.02397e-03f }, // 23/128 + { -3.55283e-03f, 1.99599e-02f, -6.70018e-02f, 2.12443e-01f, 9.18329e-01f, -1.01501e-01f, 2.53295e-02f, -4.16581e-03f }, // 24/128 + { -3.68570e-03f, 2.07233e-02f, -6.96762e-02f, 2.22120e-01f, 9.12947e-01f, -1.04430e-01f, 2.61404e-02f, -4.30435e-03f }, // 25/128 + { -3.81671e-03f, 2.14774e-02f, -7.23286e-02f, 2.31843e-01f, 9.07420e-01f, -1.07256e-01f, 2.69293e-02f, -4.43955e-03f }, // 26/128 + { -3.94576e-03f, 2.22218e-02f, -7.49577e-02f, 2.41609e-01f, 9.01749e-01f, -1.09978e-01f, 2.76957e-02f, -4.57135e-03f }, // 27/128 + { -4.07279e-03f, 2.29562e-02f, -7.75620e-02f, 2.51417e-01f, 8.95936e-01f, -1.12597e-01f, 2.84397e-02f, -4.69970e-03f }, // 28/128 + { -4.19774e-03f, 2.36801e-02f, -8.01399e-02f, 2.61263e-01f, 8.89984e-01f, -1.15113e-01f, 2.91609e-02f, -4.82456e-03f }, // 29/128 + { -4.32052e-03f, 2.43930e-02f, -8.26900e-02f, 2.71144e-01f, 8.83893e-01f, -1.17526e-01f, 2.98593e-02f, -4.94589e-03f }, // 30/128 + { -4.44107e-03f, 2.50946e-02f, -8.52109e-02f, 2.81060e-01f, 8.77666e-01f, -1.19837e-01f, 3.05345e-02f, -5.06363e-03f }, // 31/128 + { -4.55932e-03f, 2.57844e-02f, -8.77011e-02f, 2.91006e-01f, 8.71305e-01f, -1.22047e-01f, 3.11866e-02f, -5.17776e-03f }, // 32/128 + { -4.67520e-03f, 2.64621e-02f, -9.01591e-02f, 3.00980e-01f, 8.64812e-01f, -1.24154e-01f, 3.18153e-02f, -5.28823e-03f }, // 33/128 + { -4.78866e-03f, 2.71272e-02f, -9.25834e-02f, 3.10980e-01f, 8.58189e-01f, -1.26161e-01f, 3.24205e-02f, -5.39500e-03f }, // 34/128 + { -4.89961e-03f, 2.77794e-02f, -9.49727e-02f, 3.21004e-01f, 8.51437e-01f, -1.28068e-01f, 3.30021e-02f, -5.49804e-03f }, // 35/128 + { -5.00800e-03f, 2.84182e-02f, -9.73254e-02f, 3.31048e-01f, 8.44559e-01f, -1.29874e-01f, 3.35600e-02f, -5.59731e-03f }, // 36/128 + { -5.11376e-03f, 2.90433e-02f, -9.96402e-02f, 3.41109e-01f, 8.37557e-01f, -1.31581e-01f, 3.40940e-02f, -5.69280e-03f }, // 37/128 + { -5.21683e-03f, 2.96543e-02f, -1.01915e-01f, 3.51186e-01f, 8.30432e-01f, -1.33189e-01f, 3.46042e-02f, -5.78446e-03f }, // 38/128 + { -5.31716e-03f, 3.02507e-02f, -1.04150e-01f, 3.61276e-01f, 8.23188e-01f, -1.34699e-01f, 3.50903e-02f, -5.87227e-03f }, // 39/128 + { -5.41467e-03f, 3.08323e-02f, -1.06342e-01f, 3.71376e-01f, 8.15826e-01f, -1.36111e-01f, 3.55525e-02f, -5.95620e-03f }, // 40/128 + { -5.50931e-03f, 3.13987e-02f, -1.08490e-01f, 3.81484e-01f, 8.08348e-01f, -1.37426e-01f, 3.59905e-02f, -6.03624e-03f }, // 41/128 + { -5.60103e-03f, 3.19495e-02f, -1.10593e-01f, 3.91596e-01f, 8.00757e-01f, -1.38644e-01f, 3.64044e-02f, -6.11236e-03f }, // 42/128 + { -5.68976e-03f, 3.24843e-02f, -1.12650e-01f, 4.01710e-01f, 7.93055e-01f, -1.39767e-01f, 3.67941e-02f, -6.18454e-03f }, // 43/128 + { -5.77544e-03f, 3.30027e-02f, -1.14659e-01f, 4.11823e-01f, 7.85244e-01f, -1.40794e-01f, 3.71596e-02f, -6.25277e-03f }, // 44/128 + { -5.85804e-03f, 3.35046e-02f, -1.16618e-01f, 4.21934e-01f, 7.77327e-01f, -1.41727e-01f, 3.75010e-02f, -6.31703e-03f }, // 45/128 + { -5.93749e-03f, 3.39894e-02f, -1.18526e-01f, 4.32038e-01f, 7.69305e-01f, -1.42566e-01f, 3.78182e-02f, -6.37730e-03f }, // 46/128 + { -6.01374e-03f, 3.44568e-02f, -1.20382e-01f, 4.42134e-01f, 7.61181e-01f, -1.43313e-01f, 3.81111e-02f, -6.43358e-03f }, // 47/128 + { -6.08674e-03f, 3.49066e-02f, -1.22185e-01f, 4.52218e-01f, 7.52958e-01f, -1.43968e-01f, 3.83800e-02f, -6.48585e-03f }, // 48/128 + { -6.15644e-03f, 3.53384e-02f, -1.23933e-01f, 4.62289e-01f, 7.44637e-01f, -1.44531e-01f, 3.86247e-02f, -6.53412e-03f }, // 49/128 + { -6.22280e-03f, 3.57519e-02f, -1.25624e-01f, 4.72342e-01f, 7.36222e-01f, -1.45004e-01f, 3.88454e-02f, -6.57836e-03f }, // 50/128 + { -6.28577e-03f, 3.61468e-02f, -1.27258e-01f, 4.82377e-01f, 7.27714e-01f, -1.45387e-01f, 3.90420e-02f, -6.61859e-03f }, // 51/128 + { -6.34530e-03f, 3.65227e-02f, -1.28832e-01f, 4.92389e-01f, 7.19116e-01f, -1.45682e-01f, 3.92147e-02f, -6.65479e-03f }, // 52/128 + { -6.40135e-03f, 3.68795e-02f, -1.30347e-01f, 5.02377e-01f, 7.10431e-01f, -1.45889e-01f, 3.93636e-02f, -6.68698e-03f }, // 53/128 + { -6.45388e-03f, 3.72167e-02f, -1.31800e-01f, 5.12337e-01f, 7.01661e-01f, -1.46009e-01f, 3.94886e-02f, -6.71514e-03f }, // 54/128 + { -6.50285e-03f, 3.75341e-02f, -1.33190e-01f, 5.22267e-01f, 6.92808e-01f, -1.46043e-01f, 3.95900e-02f, -6.73929e-03f }, // 55/128 + { -6.54823e-03f, 3.78315e-02f, -1.34515e-01f, 5.32164e-01f, 6.83875e-01f, -1.45993e-01f, 3.96678e-02f, -6.75943e-03f }, // 56/128 + { -6.58996e-03f, 3.81085e-02f, -1.35775e-01f, 5.42025e-01f, 6.74865e-01f, -1.45859e-01f, 3.97222e-02f, -6.77557e-03f }, // 57/128 + { -6.62802e-03f, 3.83650e-02f, -1.36969e-01f, 5.51849e-01f, 6.65779e-01f, -1.45641e-01f, 3.97532e-02f, -6.78771e-03f }, // 58/128 + { -6.66238e-03f, 3.86006e-02f, -1.38094e-01f, 5.61631e-01f, 6.56621e-01f, -1.45343e-01f, 3.97610e-02f, -6.79588e-03f }, // 59/128 + { -6.69300e-03f, 3.88151e-02f, -1.39150e-01f, 5.71370e-01f, 6.47394e-01f, -1.44963e-01f, 3.97458e-02f, -6.80007e-03f }, // 60/128 + { -6.71985e-03f, 3.90083e-02f, -1.40136e-01f, 5.81063e-01f, 6.38099e-01f, -1.44503e-01f, 3.97077e-02f, -6.80032e-03f }, // 61/128 + { -6.74291e-03f, 3.91800e-02f, -1.41050e-01f, 5.90706e-01f, 6.28739e-01f, -1.43965e-01f, 3.96469e-02f, -6.79662e-03f }, // 62/128 + { -6.76214e-03f, 3.93299e-02f, -1.41891e-01f, 6.00298e-01f, 6.19318e-01f, -1.43350e-01f, 3.95635e-02f, -6.78902e-03f }, // 63/128 + { -6.77751e-03f, 3.94578e-02f, -1.42658e-01f, 6.09836e-01f, 6.09836e-01f, -1.42658e-01f, 3.94578e-02f, -6.77751e-03f }, // 64/128 + { -6.78902e-03f, 3.95635e-02f, -1.43350e-01f, 6.19318e-01f, 6.00298e-01f, -1.41891e-01f, 3.93299e-02f, -6.76214e-03f }, // 65/128 + { -6.79662e-03f, 3.96469e-02f, -1.43965e-01f, 6.28739e-01f, 5.90706e-01f, -1.41050e-01f, 3.91800e-02f, -6.74291e-03f }, // 66/128 + { -6.80032e-03f, 3.97077e-02f, -1.44503e-01f, 6.38099e-01f, 5.81063e-01f, -1.40136e-01f, 3.90083e-02f, -6.71985e-03f }, // 67/128 + { -6.80007e-03f, 3.97458e-02f, -1.44963e-01f, 6.47394e-01f, 5.71370e-01f, -1.39150e-01f, 3.88151e-02f, -6.69300e-03f }, // 68/128 + { -6.79588e-03f, 3.97610e-02f, -1.45343e-01f, 6.56621e-01f, 5.61631e-01f, -1.38094e-01f, 3.86006e-02f, -6.66238e-03f }, // 69/128 + { -6.78771e-03f, 3.97532e-02f, -1.45641e-01f, 6.65779e-01f, 5.51849e-01f, -1.36969e-01f, 3.83650e-02f, -6.62802e-03f }, // 70/128 + { -6.77557e-03f, 3.97222e-02f, -1.45859e-01f, 6.74865e-01f, 5.42025e-01f, -1.35775e-01f, 3.81085e-02f, -6.58996e-03f }, // 71/128 + { -6.75943e-03f, 3.96678e-02f, -1.45993e-01f, 6.83875e-01f, 5.32164e-01f, -1.34515e-01f, 3.78315e-02f, -6.54823e-03f }, // 72/128 + { -6.73929e-03f, 3.95900e-02f, -1.46043e-01f, 6.92808e-01f, 5.22267e-01f, -1.33190e-01f, 3.75341e-02f, -6.50285e-03f }, // 73/128 + { -6.71514e-03f, 3.94886e-02f, -1.46009e-01f, 7.01661e-01f, 5.12337e-01f, -1.31800e-01f, 3.72167e-02f, -6.45388e-03f }, // 74/128 + { -6.68698e-03f, 3.93636e-02f, -1.45889e-01f, 7.10431e-01f, 5.02377e-01f, -1.30347e-01f, 3.68795e-02f, -6.40135e-03f }, // 75/128 + { -6.65479e-03f, 3.92147e-02f, -1.45682e-01f, 7.19116e-01f, 4.92389e-01f, -1.28832e-01f, 3.65227e-02f, -6.34530e-03f }, // 76/128 + { -6.61859e-03f, 3.90420e-02f, -1.45387e-01f, 7.27714e-01f, 4.82377e-01f, -1.27258e-01f, 3.61468e-02f, -6.28577e-03f }, // 77/128 + { -6.57836e-03f, 3.88454e-02f, -1.45004e-01f, 7.36222e-01f, 4.72342e-01f, -1.25624e-01f, 3.57519e-02f, -6.22280e-03f }, // 78/128 + { -6.53412e-03f, 3.86247e-02f, -1.44531e-01f, 7.44637e-01f, 4.62289e-01f, -1.23933e-01f, 3.53384e-02f, -6.15644e-03f }, // 79/128 + { -6.48585e-03f, 3.83800e-02f, -1.43968e-01f, 7.52958e-01f, 4.52218e-01f, -1.22185e-01f, 3.49066e-02f, -6.08674e-03f }, // 80/128 + { -6.43358e-03f, 3.81111e-02f, -1.43313e-01f, 7.61181e-01f, 4.42134e-01f, -1.20382e-01f, 3.44568e-02f, -6.01374e-03f }, // 81/128 + { -6.37730e-03f, 3.78182e-02f, -1.42566e-01f, 7.69305e-01f, 4.32038e-01f, -1.18526e-01f, 3.39894e-02f, -5.93749e-03f }, // 82/128 + { -6.31703e-03f, 3.75010e-02f, -1.41727e-01f, 7.77327e-01f, 4.21934e-01f, -1.16618e-01f, 3.35046e-02f, -5.85804e-03f }, // 83/128 + { -6.25277e-03f, 3.71596e-02f, -1.40794e-01f, 7.85244e-01f, 4.11823e-01f, -1.14659e-01f, 3.30027e-02f, -5.77544e-03f }, // 84/128 + { -6.18454e-03f, 3.67941e-02f, -1.39767e-01f, 7.93055e-01f, 4.01710e-01f, -1.12650e-01f, 3.24843e-02f, -5.68976e-03f }, // 85/128 + { -6.11236e-03f, 3.64044e-02f, -1.38644e-01f, 8.00757e-01f, 3.91596e-01f, -1.10593e-01f, 3.19495e-02f, -5.60103e-03f }, // 86/128 + { -6.03624e-03f, 3.59905e-02f, -1.37426e-01f, 8.08348e-01f, 3.81484e-01f, -1.08490e-01f, 3.13987e-02f, -5.50931e-03f }, // 87/128 + { -5.95620e-03f, 3.55525e-02f, -1.36111e-01f, 8.15826e-01f, 3.71376e-01f, -1.06342e-01f, 3.08323e-02f, -5.41467e-03f }, // 88/128 + { -5.87227e-03f, 3.50903e-02f, -1.34699e-01f, 8.23188e-01f, 3.61276e-01f, -1.04150e-01f, 3.02507e-02f, -5.31716e-03f }, // 89/128 + { -5.78446e-03f, 3.46042e-02f, -1.33189e-01f, 8.30432e-01f, 3.51186e-01f, -1.01915e-01f, 2.96543e-02f, -5.21683e-03f }, // 90/128 + { -5.69280e-03f, 3.40940e-02f, -1.31581e-01f, 8.37557e-01f, 3.41109e-01f, -9.96402e-02f, 2.90433e-02f, -5.11376e-03f }, // 91/128 + { -5.59731e-03f, 3.35600e-02f, -1.29874e-01f, 8.44559e-01f, 3.31048e-01f, -9.73254e-02f, 2.84182e-02f, -5.00800e-03f }, // 92/128 + { -5.49804e-03f, 3.30021e-02f, -1.28068e-01f, 8.51437e-01f, 3.21004e-01f, -9.49727e-02f, 2.77794e-02f, -4.89961e-03f }, // 93/128 + { -5.39500e-03f, 3.24205e-02f, -1.26161e-01f, 8.58189e-01f, 3.10980e-01f, -9.25834e-02f, 2.71272e-02f, -4.78866e-03f }, // 94/128 + { -5.28823e-03f, 3.18153e-02f, -1.24154e-01f, 8.64812e-01f, 3.00980e-01f, -9.01591e-02f, 2.64621e-02f, -4.67520e-03f }, // 95/128 + { -5.17776e-03f, 3.11866e-02f, -1.22047e-01f, 8.71305e-01f, 2.91006e-01f, -8.77011e-02f, 2.57844e-02f, -4.55932e-03f }, // 96/128 + { -5.06363e-03f, 3.05345e-02f, -1.19837e-01f, 8.77666e-01f, 2.81060e-01f, -8.52109e-02f, 2.50946e-02f, -4.44107e-03f }, // 97/128 + { -4.94589e-03f, 2.98593e-02f, -1.17526e-01f, 8.83893e-01f, 2.71144e-01f, -8.26900e-02f, 2.43930e-02f, -4.32052e-03f }, // 98/128 + { -4.82456e-03f, 2.91609e-02f, -1.15113e-01f, 8.89984e-01f, 2.61263e-01f, -8.01399e-02f, 2.36801e-02f, -4.19774e-03f }, // 99/128 + { -4.69970e-03f, 2.84397e-02f, -1.12597e-01f, 8.95936e-01f, 2.51417e-01f, -7.75620e-02f, 2.29562e-02f, -4.07279e-03f }, // 100/128 + { -4.57135e-03f, 2.76957e-02f, -1.09978e-01f, 9.01749e-01f, 2.41609e-01f, -7.49577e-02f, 2.22218e-02f, -3.94576e-03f }, // 101/128 + { -4.43955e-03f, 2.69293e-02f, -1.07256e-01f, 9.07420e-01f, 2.31843e-01f, -7.23286e-02f, 2.14774e-02f, -3.81671e-03f }, // 102/128 + { -4.30435e-03f, 2.61404e-02f, -1.04430e-01f, 9.12947e-01f, 2.22120e-01f, -6.96762e-02f, 2.07233e-02f, -3.68570e-03f }, // 103/128 + { -4.16581e-03f, 2.53295e-02f, -1.01501e-01f, 9.18329e-01f, 2.12443e-01f, -6.70018e-02f, 1.99599e-02f, -3.55283e-03f }, // 104/128 + { -4.02397e-03f, 2.44967e-02f, -9.84679e-02f, 9.23564e-01f, 2.02814e-01f, -6.43069e-02f, 1.91877e-02f, -3.41815e-03f }, // 105/128 + { -3.87888e-03f, 2.36423e-02f, -9.53307e-02f, 9.28650e-01f, 1.93236e-01f, -6.15931e-02f, 1.84071e-02f, -3.28174e-03f }, // 106/128 + { -3.73062e-03f, 2.27664e-02f, -9.20893e-02f, 9.33586e-01f, 1.83711e-01f, -5.88617e-02f, 1.76185e-02f, -3.14367e-03f }, // 107/128 + { -3.57923e-03f, 2.18695e-02f, -8.87435e-02f, 9.38371e-01f, 1.74242e-01f, -5.61142e-02f, 1.68225e-02f, -3.00403e-03f }, // 108/128 + { -3.42477e-03f, 2.09516e-02f, -8.52933e-02f, 9.43001e-01f, 1.64831e-01f, -5.33522e-02f, 1.60193e-02f, -2.86289e-03f }, // 109/128 + { -3.26730e-03f, 2.00132e-02f, -8.17385e-02f, 9.47477e-01f, 1.55480e-01f, -5.05770e-02f, 1.52095e-02f, -2.72032e-03f }, // 110/128 + { -3.10689e-03f, 1.90545e-02f, -7.80792e-02f, 9.51795e-01f, 1.46192e-01f, -4.77900e-02f, 1.43934e-02f, -2.57640e-03f }, // 111/128 + { -2.94361e-03f, 1.80759e-02f, -7.43154e-02f, 9.55956e-01f, 1.36968e-01f, -4.49929e-02f, 1.35716e-02f, -2.43121e-03f }, // 112/128 + { -2.77751e-03f, 1.70776e-02f, -7.04471e-02f, 9.59958e-01f, 1.27812e-01f, -4.21869e-02f, 1.27445e-02f, -2.28483e-03f }, // 113/128 + { -2.60868e-03f, 1.60599e-02f, -6.64743e-02f, 9.63798e-01f, 1.18725e-01f, -3.93735e-02f, 1.19125e-02f, -2.13733e-03f }, // 114/128 + { -2.43718e-03f, 1.50233e-02f, -6.23972e-02f, 9.67477e-01f, 1.09710e-01f, -3.65541e-02f, 1.10760e-02f, -1.98880e-03f }, // 115/128 + { -2.26307e-03f, 1.39681e-02f, -5.82159e-02f, 9.70992e-01f, 1.00769e-01f, -3.37303e-02f, 1.02356e-02f, -1.83931e-03f }, // 116/128 + { -2.08645e-03f, 1.28947e-02f, -5.39305e-02f, 9.74342e-01f, 9.19033e-02f, -3.09033e-02f, 9.39154e-03f, -1.68894e-03f }, // 117/128 + { -1.90738e-03f, 1.18034e-02f, -4.95412e-02f, 9.77526e-01f, 8.31162e-02f, -2.80746e-02f, 8.54441e-03f, -1.53777e-03f }, // 118/128 + { -1.72594e-03f, 1.06946e-02f, -4.50483e-02f, 9.80543e-01f, 7.44095e-02f, -2.52457e-02f, 7.69462e-03f, -1.38589e-03f }, // 119/128 + { -1.54221e-03f, 9.56876e-03f, -4.04519e-02f, 9.83392e-01f, 6.57852e-02f, -2.24178e-02f, 6.84261e-03f, -1.23337e-03f }, // 120/128 + { -1.35627e-03f, 8.42626e-03f, -3.57525e-02f, 9.86071e-01f, 5.72454e-02f, -1.95925e-02f, 5.98883e-03f, -1.08030e-03f }, // 121/128 + { -1.16820e-03f, 7.26755e-03f, -3.09503e-02f, 9.88580e-01f, 4.87921e-02f, -1.67710e-02f, 5.13372e-03f, -9.26747e-04f }, // 122/128 + { -9.78093e-04f, 6.09305e-03f, -2.60456e-02f, 9.90917e-01f, 4.04274e-02f, -1.39548e-02f, 4.27773e-03f, -7.72802e-04f }, // 123/128 + { -7.86031e-04f, 4.90322e-03f, -2.10389e-02f, 9.93082e-01f, 3.21531e-02f, -1.11453e-02f, 3.42130e-03f, -6.18544e-04f }, // 124/128 + { -5.92100e-04f, 3.69852e-03f, -1.59305e-02f, 9.95074e-01f, 2.39714e-02f, -8.34364e-03f, 2.56486e-03f, -4.64053e-04f }, // 125/128 + { -3.96391e-04f, 2.47942e-03f, -1.07209e-02f, 9.96891e-01f, 1.58840e-02f, -5.55134e-03f, 1.70888e-03f, -3.09412e-04f }, // 126/128 + { -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128 + { 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128 + }; + + private static final int NUMBER_FILTER_TAPS = 8; + private static final int NUMBER_FILTER_STEPS = 128; + private static final int SAMPLE_RATE = 48000; + private static final int SYMBOL_RATE = 4800; + + /* Tracking loop gain constant */ + private static final double K_SYMBOL_SPREAD = 0.0100; + + /* Constraints on symbol spreading */ + private static final float SYMBOL_SPREAD_MAX = 2.4f; // upper range limit: +20% + private static final float SYMBOL_SPREAD_MIN = 1.6f; // lower range limit: -20% + + /* Symbol clock tracking loop gain */ + private static final double K_SYMBOL_TIMING = 0.025; + + /* Coarse and fine frequency tracking constants */ + private static final double K_COARSE_FREQUENCY = 0.00125; + private static final double K_FINE_FREQUENCY = 0.125; + + /* Frequency correction broadcast threshold */ + private static final double COARSE_FREQUENCY_DEADBAND = 1.66; + + /* 2.0 symbol spread gives -3, -1, 1, 3 */ + private float mSymbolSpread = 2.0f; + private float mSymbolClock = 0.0f; + private float mSymbolTime = (float)SYMBOL_RATE / (float)SAMPLE_RATE; + private float mFineFrequencyCorrection = 0.0f; + private float mCoarseFrequencyCorrection = 0.0f; + + private float mHistory[] = new float[ NUMBER_FILTER_TAPS ]; + private int mHistoryLast = 0; + + private RealSampleListener mListener; + + + public C4FMSymbolFilter() + { + } + + @Override + public void receive( float sample ) + { + mSymbolClock += mSymbolTime; + + mHistory[ mHistoryLast++ ] = sample; + mHistoryLast %= NUMBER_FILTER_TAPS; + + if( mSymbolClock > 1.0f ) + { + mSymbolClock -= 1.0f; + + int imu = (int)Math.floor( 0.5 + + ( (float)NUMBER_FILTER_STEPS * ( mSymbolClock / mSymbolTime ) ) ); + + int imu_p1 = imu + 1; + + if( imu >= NUMBER_FILTER_STEPS ) + { + imu = NUMBER_FILTER_STEPS - 1; + imu_p1 = NUMBER_FILTER_STEPS; + } + + int j = mHistoryLast; + + double interp = 0.0; + double interp_p1 = 0.0; + + for( int i = 0; i < NUMBER_FILTER_TAPS; i++ ) + { + interp += TAPS[ imu][ i ] * mHistory[ j ]; + interp_p1 += TAPS[ imu_p1 ][ i ] * mHistory[ j ]; + + j = ( j + 1 ) % NUMBER_FILTER_TAPS; + } + + /* Ouput symbol will be interpolated value corrected for symbol + * spread and frequency offset */ + interp -= mFineFrequencyCorrection; + interp_p1 -= mFineFrequencyCorrection; + + /* Correct output for symbol deviation (spread) */ + float output = (float)( 2.0 * interp / mSymbolSpread ); + + /* Detect received symbol error: basically use a hard decision and + * subtract off expected position nominal symbol level which will be + * +/= 0.5 * symbol spread and +/- 1.5 symbol spread. Rembmer that + * nominal symbol spread will be 2.0 */ + double symbolError; + + if( interp < -mSymbolSpread ) + { + /* symbol is -3: Expected at -1.5 * symbol spread */ + symbolError = interp + ( 1.5 * mSymbolSpread ); + mSymbolSpread -= ( symbolError * 0.5 * K_SYMBOL_SPREAD ); + } + else if( interp < 0.0 ) + { + /* symbol is -1: Expected at -0.5 * symbol_spread */ + symbolError = interp + (0.5 * mSymbolSpread ); + mSymbolSpread -= ( symbolError * K_SYMBOL_SPREAD ); + } + else if( interp < mSymbolSpread ) + { + /* symbol is +1: Expected at +0.5 * symbol_spread */ + symbolError = interp - ( 0.5 * mSymbolSpread ); + mSymbolSpread += ( symbolError * K_SYMBOL_SPREAD ); + } + else + { + /* symbol is +3: Expected at +1.5 * symbol_spread */ + symbolError = interp - ( 1.5 * mSymbolSpread ); + mSymbolSpread += ( symbolError * 0.5 * K_SYMBOL_SPREAD ); + } + + /* Symbol clock tracking loop adjustment */ + if( interp_p1 < interp ) + { + mSymbolClock += symbolError * K_SYMBOL_TIMING; + } + else + { + mSymbolClock -= symbolError * K_SYMBOL_TIMING; + } + + mLog.debug( "Symbol Clock Adjusted: " + mSymbolClock ); + + mSymbolSpread = Math.max( mSymbolSpread, SYMBOL_SPREAD_MIN ); + mSymbolSpread = Math.min( mSymbolSpread, SYMBOL_SPREAD_MAX ); + + mCoarseFrequencyCorrection += ( ( mFineFrequencyCorrection - + mCoarseFrequencyCorrection ) * K_COARSE_FREQUENCY ); + + mFineFrequencyCorrection += ( symbolError * K_FINE_FREQUENCY ); + + /* send frequency correction */ + if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND ) + { + } + + mLog.error( "C4FM Filter - output [ " + output + " ] frequency correction: " + + mCoarseFrequencyCorrection ); + + /* dispatch the interpolated value to the listener */ + if( mListener != null ) + { + mListener.receive( output ); + } + } + } + + @Override + public void setListener( RealSampleListener listener ) + { + mListener = listener; + } + + @Override + public void removeListener( RealSampleListener listener ) + { + mListener = null; + } +} diff --git a/src/dsp/fsk/C4FMDecoder.java b/src/dsp/fsk/C4FMDecoder.java index 5184422e..c9eb88de 100644 --- a/src/dsp/fsk/C4FMDecoder.java +++ b/src/dsp/fsk/C4FMDecoder.java @@ -8,17 +8,32 @@ import java.util.ArrayList; import java.util.List; import sample.real.RealSampleListener; +import dsp.filter.C4FMSymbolFilter; public class C4FMDecoder implements RealSampleListener, Instrumentable { - private final String TAP_C4FM_INPUT = "C4FM Input"; + private static final String TAP_C4FM_INPUT = "C4FM Decoder Input"; + private static final String TAP_SYMBOL_FILTER_OUTPUT = "C4FM Symbol Filter Output"; + private ArrayList mTaps; private FloatTap mInputTap; + private FloatTap mSymbolFilterOutputTap; + + private C4FMSymbolFilter mSymbolFilter = new C4FMSymbolFilter(); + + private RealSampleListener mInput; public C4FMDecoder() { + mInput = mSymbolFilter; } + @Override + public void receive( float sample ) + { + mInput.receive( sample ); + } + @Override public List getTaps() { @@ -28,6 +43,9 @@ public class C4FMDecoder implements RealSampleListener, Instrumentable FloatTap floatTap = new FloatTap( TAP_C4FM_INPUT, 0, 1.0f ); mTaps.add( floatTap ); + + FloatTap symbolTap = new FloatTap( TAP_SYMBOL_FILTER_OUTPUT, 10, 0.1f ); + mTaps.add( symbolTap ); } return mTaps; @@ -40,6 +58,12 @@ public class C4FMDecoder implements RealSampleListener, Instrumentable { case TAP_C4FM_INPUT: mInputTap = (FloatTap)tap; + mInput = mInputTap; + mInputTap.setListener( mSymbolFilter ); + break; + case TAP_SYMBOL_FILTER_OUTPUT: + mSymbolFilterOutputTap = (FloatTap)tap; + mSymbolFilter.setListener( mSymbolFilterOutputTap ); break; } } @@ -50,17 +74,13 @@ public class C4FMDecoder implements RealSampleListener, Instrumentable switch( tap.getName() ) { case TAP_C4FM_INPUT: - mInputTap = null; + mInput = mSymbolFilter; + mInputTap = null; + break; + case TAP_SYMBOL_FILTER_OUTPUT: + mSymbolFilter.removeListener( mSymbolFilterOutputTap ); + mSymbolFilterOutputTap = null; break; } } - - @Override - public void receive( float t ) - { - if( mInputTap != null ) - { - mInputTap.receive( t ); - } - } } diff --git a/src/dsp/nbfm/FilteringNBFMDemodulator.java b/src/dsp/nbfm/FilteringNBFMDemodulator.java index 4d74f08d..5d730266 100644 --- a/src/dsp/nbfm/FilteringNBFMDemodulator.java +++ b/src/dsp/nbfm/FilteringNBFMDemodulator.java @@ -40,11 +40,7 @@ public class FilteringNBFMDemodulator implements Listener mAudioFilter = new FloatFIRFilter( audioFilter, audioGain ); - /** - * Add the FM polar discriminator with a gain of 32768, to match - * the fact that we're producing demodulated float values - */ - mDiscriminator = new FMDiscriminator( 32768 ); + mDiscriminator = new FMDiscriminator( 1 ); mIQFilter.setListener( mDiscriminator ); diff --git a/src/dsp/nbfm/NBFMDemodulator.java b/src/dsp/nbfm/NBFMDemodulator.java index 1545c752..94e78f20 100644 --- a/src/dsp/nbfm/NBFMDemodulator.java +++ b/src/dsp/nbfm/NBFMDemodulator.java @@ -37,7 +37,7 @@ public class NBFMDemodulator implements Listener */ public NBFMDemodulator() { - this( FilterFactory.getLowPass( 48000, 4000, 73, WindowType.HAMMING ), + this( FilterFactory.getLowPass( 48000, 6250, 73, WindowType.HAMMING ), 1.0002, true ); } @@ -45,11 +45,7 @@ public class NBFMDemodulator implements Listener { mIQFilter = new ComplexFIRFilter( iqFilter, iqGain ); - /** - * Add the FM polar discriminator with a gain of 32768, to match - * the fact that we're producing demodulated float values - */ - mDiscriminator = new FMDiscriminator( 32768 ); + mDiscriminator = new FMDiscriminator( 1 ); mIQFilter.setListener( mDiscriminator ); diff --git a/src/instrument/gui/WaveSourceFrame.java b/src/instrument/gui/WaveSourceFrame.java index 61d5c648..0ea63073 100644 --- a/src/instrument/gui/WaveSourceFrame.java +++ b/src/instrument/gui/WaveSourceFrame.java @@ -156,7 +156,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener case PASSPORT: decoder = new PassportDecoder( SampleType.REAL, null ); break; - case P25: + case P25_PHASE1: decoder = new P25Decoder( SampleType.REAL, null ); break; } diff --git a/src/source/tuner/rtl/ByteSampleAdapter.java b/src/sample/adapter/ByteSampleAdapter.java similarity index 87% rename from src/source/tuner/rtl/ByteSampleAdapter.java rename to src/sample/adapter/ByteSampleAdapter.java index fb140835..19fd622b 100644 --- a/src/source/tuner/rtl/ByteSampleAdapter.java +++ b/src/sample/adapter/ByteSampleAdapter.java @@ -15,10 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package source.tuner.rtl; - -import sample.adapter.SampleAdapter; -import sample.complex.ComplexBuffer; +package sample.adapter; public class ByteSampleAdapter extends SampleAdapter { @@ -26,7 +23,7 @@ public class ByteSampleAdapter extends SampleAdapter /** * Creates a static lookup table that converts the 8-bit valued range - * from 0 - 255 into scaled float values of -32768.0 to 0 to 32512.0 + * from 0 - 255 into scaled float values of -1.0 to 0 to 1.0 */ static { @@ -34,7 +31,7 @@ public class ByteSampleAdapter extends SampleAdapter for( int x = 0; x < 256; x++ ) { - LOOKUP_VALUES[ x ] = (float)( x - 128 ) * 256.0f; + LOOKUP_VALUES[ x ] = (float)( x - 128 ); } LOOKUP_VALUES[ 128 ] = 0.0000001f; diff --git a/src/sample/adapter/ChannelShortAdapter.java b/src/sample/adapter/ChannelShortAdapter.java index e88f6fa6..983ac9d7 100644 --- a/src/sample/adapter/ChannelShortAdapter.java +++ b/src/sample/adapter/ChannelShortAdapter.java @@ -27,6 +27,7 @@ import source.mixer.MixerChannel; */ public class ChannelShortAdapter extends SampleAdapter { + private ShortToFloatMap mMap = new ShortToFloatMap(); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; private MixerChannel mMixerChannel; @@ -55,7 +56,7 @@ public class ChannelShortAdapter extends SampleAdapter { if( mMixerChannel == MixerChannel.LEFT ) { - processed[ pointer ] = (float)buffer.getShort(); + processed[ pointer ] = mMap.get( buffer.getShort() ); /* Throw away the right channel */ buffer.getShort(); @@ -65,7 +66,7 @@ public class ChannelShortAdapter extends SampleAdapter /* Throw away the left channel */ buffer.getShort(); - processed[ pointer ] = (float)buffer.getShort(); + processed[ pointer ] = mMap.get( buffer.getShort() ); } pointer++; diff --git a/src/sample/adapter/ShortAdapter.java b/src/sample/adapter/ShortAdapter.java index bf3365e9..3d1d44c9 100644 --- a/src/sample/adapter/ShortAdapter.java +++ b/src/sample/adapter/ShortAdapter.java @@ -25,6 +25,7 @@ import java.nio.ByteOrder; */ public class ShortAdapter extends SampleAdapter { + private ShortToFloatMap mMap = new ShortToFloatMap(); private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; @Override @@ -42,7 +43,7 @@ public class ShortAdapter extends SampleAdapter while( buffer.hasRemaining() ) { - processed[ pointer ] = (float)buffer.getShort(); + processed[ pointer ] = mMap.get( buffer.getShort() ); pointer++; } diff --git a/src/sample/adapter/ShortToFloatMap.java b/src/sample/adapter/ShortToFloatMap.java new file mode 100644 index 00000000..c8a07a86 --- /dev/null +++ b/src/sample/adapter/ShortToFloatMap.java @@ -0,0 +1,19 @@ +package sample.adapter; + +public class ShortToFloatMap +{ + private static float[] MAP = new float[ 65536 ]; + + static + { + for( int x = 0; x < 65536; x++ ) + { + MAP[ x] = (float)( x - 32768 ) / 32768.0f; + } + } + + public float get( short value ) + { + return MAP[ 32768 + value ]; + } +} diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java index 045f7d65..aadb63ed 100644 --- a/src/source/tuner/hackrf/HackRFTunerController.java +++ b/src/source/tuner/hackrf/HackRFTunerController.java @@ -79,11 +79,11 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import sample.Listener; +import sample.adapter.ByteSampleAdapter; import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.TunerConfiguration; import source.tuner.TunerController; -import source.tuner.rtl.ByteSampleAdapter; public class HackRFTunerController extends TunerController diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index ba067b5d..8995043a 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -52,6 +52,7 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import sample.Listener; +import sample.adapter.ByteSampleAdapter; import sample.complex.ComplexBuffer; import source.SourceException; import source.tuner.FrequencyChangeEvent; diff --git a/src/source/wave/FloatWaveSource.java b/src/source/wave/FloatWaveSource.java index 8174d122..fbec02fb 100644 --- a/src/source/wave/FloatWaveSource.java +++ b/src/source/wave/FloatWaveSource.java @@ -29,10 +29,12 @@ import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; +import sample.adapter.ShortToFloatMap; import sample.real.RealSampleListener; public class FloatWaveSource extends WaveSource { + private ShortToFloatMap mMap = new ShortToFloatMap(); private boolean mRunning = false; private boolean mLoop = false; @@ -221,7 +223,7 @@ public class FloatWaveSource extends WaveSource if( broadcast && mListener != null ) { /* Convert the short into a float value */ - mListener.receive( (float)sample ); + mListener.receive( mMap.get( sample ) ); } success = true; diff --git a/src/spectrum/ChannelSpectrumPanel.java b/src/spectrum/ChannelSpectrumPanel.java index 7fd4837b..eb9e5e98 100644 --- a/src/spectrum/ChannelSpectrumPanel.java +++ b/src/spectrum/ChannelSpectrumPanel.java @@ -29,9 +29,8 @@ import source.tuner.FrequencyChangeEvent; import source.tuner.FrequencyChangeEvent.Attribute; import spectrum.converter.DFTResultsConverter; import spectrum.converter.RealDecibelConverter; -import spectrum.menu.AmplificationItem; import spectrum.menu.AveragingItem; -import spectrum.menu.BaselineItem; +import spectrum.menu.DBScaleItem; import spectrum.menu.FFTWidthItem; import spectrum.menu.FFTWindowTypeItem; import spectrum.menu.FrameRateItem; @@ -359,14 +358,6 @@ public class ChannelSpectrumPanel extends JPanel JMenu displayMenu = new JMenu( "Display" ); contextMenu.add( displayMenu ); - /** - * Amplification menu - */ - JMenu amplificationMenu = new JMenu( "Amplification" ); - amplificationMenu.add( - new AmplificationItem( ChannelSpectrumPanel.this, 50 ) ); - displayMenu.add( amplificationMenu ); - /** * Averaging menu */ @@ -380,7 +371,7 @@ public class ChannelSpectrumPanel extends JPanel */ JMenu baselineMenu = new JMenu( "Baseline" ); baselineMenu.add( - new BaselineItem( ChannelSpectrumPanel.this, 50 ) ); + new DBScaleItem( ChannelSpectrumPanel.this, 50 ) ); displayMenu.add( baselineMenu ); /** @@ -436,18 +427,6 @@ public class ChannelSpectrumPanel extends JPanel public void mouseReleased( MouseEvent e ) {} } - @Override - public int getAmplification() - { - return mSpectrumPanel.getAmplification(); - } - - @Override - public void setAmplification( int amplification ) - { - mSpectrumPanel.setAmplification( amplification ); - } - @Override public int getAveraging() { @@ -461,15 +440,15 @@ public class ChannelSpectrumPanel extends JPanel } @Override - public int getBaseline() + public int getDBScale() { - return mSpectrumPanel.getBaseline(); + return mSpectrumPanel.getDBScale(); } @Override - public void setBaseline( int baseline ) + public void setDBScale( int baseline ) { - mSpectrumPanel.setBaseline( baseline ); + mSpectrumPanel.setDBScale( baseline ); } public class DecimatingSampleAssembler diff --git a/src/spectrum/SpectralDisplayAdjuster.java b/src/spectrum/SpectralDisplayAdjuster.java index a627b966..b26ef324 100644 --- a/src/spectrum/SpectralDisplayAdjuster.java +++ b/src/spectrum/SpectralDisplayAdjuster.java @@ -2,23 +2,7 @@ package spectrum; public interface SpectralDisplayAdjuster { - /** - * Gets the amplification multiplier that is applied against the FFT bin - * results when displaying the value in the spectral display. - * - * @return amplification value ( 1 - 100 ) - */ - public int getAmplification(); - - /** - * Sets the amplification multiplier that is applied against the FFT bin - * results when displaying the value in the spectral display. - * - * @param amplification value ( 1 - 100 ) - */ - public void setAmplification( int amplification ); - - /** +/** * Gets the averaging value indicating the number of FFT results that are * averaged to produce each FFT results output. * @@ -34,22 +18,18 @@ public interface SpectralDisplayAdjuster public void setAveraging( int averaging ); /** - * Gets the baseline used when plotting the FFT bin results in the FFT - * spectral display. This value determines the vertical position within the - * spectral display upon which the amplified values are added and then - * plotted. + * Gets the dBm scale used when plotting the FFT bin results in the FFT + * spectral display. This value determines the smallest displayed power + * spectrum value that is shown at the bottom of the display. * * @return averaging value ( 1 - 50 ) */ - public int getBaseline(); + public int getDBScale(); /** - * Sets the baseline used when plotting the FFT bin results in the FFT - * spectral display. This value determines the vertical position within the - * spectral display upon which the amplified values are added and then - * plotted. + * Sets the dBm scale from 0 (top of display) to the scale value (bottom). * - * @param baseline value ( 1 - 50 ) + * @param baseline value ( -160 to -30 dBm ) */ - public void setBaseline( int baseline ); + public void setDBScale( int baseline ); } diff --git a/src/spectrum/SpectralDisplayPanel.java b/src/spectrum/SpectralDisplayPanel.java index 692e4205..9417d629 100644 --- a/src/spectrum/SpectralDisplayPanel.java +++ b/src/spectrum/SpectralDisplayPanel.java @@ -64,9 +64,8 @@ import source.tuner.TunerSelectionListener; import spectrum.OverlayPanel.ChannelDisplay; import spectrum.converter.ComplexDecibelConverter; import spectrum.converter.DFTResultsConverter; -import spectrum.menu.AmplificationItem; import spectrum.menu.AveragingItem; -import spectrum.menu.BaselineItem; +import spectrum.menu.DBScaleItem; import spectrum.menu.FFTWidthItem; import spectrum.menu.FFTWindowTypeItem; import spectrum.menu.FrameRateItem; @@ -591,42 +590,24 @@ public class SpectralDisplayPanel extends JPanel JMenu displayMenu = new JMenu( "Display" ); contextMenu.add( displayMenu ); - if( event.getComponent() == mWaterfallPanel ) + if( event.getComponent() != mWaterfallPanel ) { /** - * Baseline menu + * DB Scale menu */ - JMenu baselineMenu = new JMenu( "Baseline" ); - baselineMenu.add( - new BaselineItem( mWaterfallPanel, 50 ) ); - displayMenu.add( baselineMenu ); - } - else - { - /** - * Amplification menu - */ - JMenu amplificationMenu = new JMenu( "Amplify" ); - amplificationMenu.add( - new AmplificationItem( mSpectrumPanel, 50 ) ); - displayMenu.add( amplificationMenu ); - + JMenu dbScaleMenu = new JMenu( "Scale: 0 to dBm" ); + dbScaleMenu.add( + new DBScaleItem( mSpectrumPanel, -100 ) ); + displayMenu.add( dbScaleMenu ); + /** * Averaging menu */ - JMenu averagingMenu = new JMenu( "Average" ); + JMenu averagingMenu = new JMenu( "Averaging" ); averagingMenu.add( new AveragingItem( mSpectrumPanel, 4 ) ); displayMenu.add( averagingMenu ); - /** - * Baseline menu - */ - JMenu baselineMenu = new JMenu( "Baseline" ); - baselineMenu.add( - new BaselineItem( mSpectrumPanel, 50 ) ); - displayMenu.add( baselineMenu ); - /** * Channel Display setting menu */ diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index 2c97cd7f..68790ed3 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -37,7 +37,6 @@ import settings.ColorSetting.ColorSettingName; import settings.Setting; import settings.SettingChangeListener; import buffer.FloatArrayCircularAveragingBuffer; -import buffer.FloatAveragingBuffer; import controller.ResourceManager; public class SpectrumPanel extends JPanel @@ -53,10 +52,8 @@ public class SpectrumPanel extends JPanel * correct width once the first sample set arrives */ private float[] mDisplayFFTBins = new float[ 1 ]; private FloatArrayCircularAveragingBuffer mFFTAveragingBuffer; - private FloatAveragingBuffer mScaleBuffer = new FloatAveragingBuffer( 20 ); - private int mAmplification = 50; private int mAveraging = 4; - private int mBaseline = 50; + private int mDBScale = -100; //dBm /** * Spectral Display Color Settings @@ -96,9 +93,6 @@ public class SpectrumPanel extends JPanel { mAveraging = size; -// mResourceManager.getSettingsManager().setIntegerSetting( -// SPECTRUM_AVERAGING_SIZE, mAveraging ); - mFFTAveragingBuffer = new FloatArrayCircularAveragingBuffer( mAveraging ); } @@ -108,24 +102,17 @@ public class SpectrumPanel extends JPanel return mAveraging; } - public void setAmplification( int amplification ) + /** + * Sets the lowest decibel scale value to show on the display + */ + public void setDBScale( int dbScale ) { - mAmplification = amplification; + mDBScale = dbScale; } - public int getAmplification() + public int getDBScale() { - return mAmplification; - } - - public void setBaseline( int baseline ) - { - mBaseline = baseline; - } - - public int getBaseline() - { - return mBaseline; + return mDBScale; } public void clearSpectrum() @@ -149,17 +136,8 @@ public class SpectrumPanel extends JPanel mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); mAveraging = 4; -// mAveraging = getIntegerSetting( "spectrum_averaging_size", 4 ); } -// private int getIntegerSetting( String name, int defaultValue ) -// { -// IntegerSetting setting = mResourceManager.getSettingsManager() -// .getIntegerSetting( name, defaultValue ); -// -// return setting.getValue(); -// } - private Color getColor( ColorSettingName name ) { ColorSetting setting = @@ -271,43 +249,38 @@ public class SpectrumPanel extends JPanel //If we have FFT data to display ... if( mDisplayFFTBins != null ) { - int binCount = mDisplayFFTBins.length - 1; - - float amplify = mAmplification / 50.0f; - float insideHeight = size.height - mSpectrumInset; - float baseline = ( insideHeight * ( 50.0f / (float)mBaseline ) ) - 20.0f; - - /* Use the DC component as a reference to scale values to panel height */ - float scale = mScaleBuffer.get( ( insideHeight * 0.75f ) / - mDisplayFFTBins[ binCount ] ); + float scalor = insideHeight / ( -mDBScale ); float insideWidth = size.width; + int binCount = mDisplayFFTBins.length; + float binSize = insideWidth / ( binCount ); for( int x = 0; x < binCount; x++ ) { - /** - * Height is an inverse value reflecting the drawable spectral - * display vertical axis between the inset value at the bottom - * and the top of the display. Values range from: - * - * top = 0 - * bottom = insideHeight = panel height - mSpectrumInset - */ - float height = baseline - ( mDisplayFFTBins[ x ] * scale * amplify ); - - if( height > insideHeight ) - { - height = insideHeight; - } + float height; - if( height < 0 ) + if( mDisplayFFTBins[ x ] > 0 ) { height = 0; } + else + { + height = (float)( Math.abs( mDisplayFFTBins[ x ] ) * scalor ); + + if( height > insideHeight ) + { + height = insideHeight; + } + + if( height < 0 ) + { + height = 0; + } + } spectrumShape.lineTo( ( x * binSize ), height ); } diff --git a/src/spectrum/WaterfallPanel.java b/src/spectrum/WaterfallPanel.java index 4fac1214..c57e30fc 100644 --- a/src/spectrum/WaterfallPanel.java +++ b/src/spectrum/WaterfallPanel.java @@ -47,13 +47,12 @@ import controller.ResourceManager; */ public class WaterfallPanel extends JPanel implements DFTResultsListener, Pausable, - SettingChangeListener, - SpectralDisplayAdjuster + SettingChangeListener { private static final long serialVersionUID = 1L; - private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" ); - private static final String sSPECTRUM_CURSOR = "spectrum_cursor"; - private static final String sPAUSED = "PAUSED"; + private static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "0.00000" ); + private static final String SPECTRUM_CURSOR = "spectrum_cursor"; + private static final String PAUSED = "PAUSED"; private byte[] mPixels; private int mFFTSize = 1024; @@ -62,7 +61,6 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, private ColorModel mColorModel = WaterfallColorModel.getDefaultColorModel(); private Color mColorSpectrumCursor; private Image mWaterfallImage; - private int mBaseline = 50; private Point mCursorLocation = new Point( 0, 0 ); private boolean mCursorVisible = false; @@ -71,8 +69,6 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, private ResourceManager mResourceManager; - private FloatAveragingBuffer mIntraBinAveragingBuffer = new FloatAveragingBuffer( 9 ); - public WaterfallPanel( ResourceManager resourceManager ) { super(); @@ -187,7 +183,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, mCursorLocation.x, (float)(getSize().getHeight() ) ) ); - String frequency = sCURSOR_FORMAT.format( mCursorFrequency / 1000000.0D ); + String frequency = CURSOR_FORMAT.format( mCursorFrequency / 1000000.0D ); graphics.drawString( frequency , mCursorLocation.x + 5, @@ -197,7 +193,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, if( mPaused.get() ) { - graphics.drawString( sPAUSED, 20, 20 ); + graphics.drawString( PAUSED, 20, 20 ); } graphics.dispose(); @@ -218,14 +214,9 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, System.arraycopy( mPixels, 0, mPixels, mFFTSize, mPixels.length - mFFTSize ); - float baseline = mBaseline / 50.0f; - /** - * Scale the power spectrum bin values into a byte range (0-255) value - * using the last bin as the scaling factor + * Find the average value and scale the display to it */ - float scale = 256.0f / update[ update.length - 1 ]; - double sum = 0.0d; for( int x = 0; x < update.length - 1; x++ ) @@ -234,10 +225,12 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, } float average = (float)( sum / (double)update.length - 1 ); + + float scale = 256.0f / average; for( int x = 0; x < update.length - 1; x++ ) { - float value = ( update[ x ] - average ) * scale * baseline; + float value = ( average - update[ x ] ) * scale; if( value < 0 ) { @@ -285,42 +278,4 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, @Override public void settingDeleted( Setting setting ) {} - - @Override - public void setBaseline( int baseline ) - { - mBaseline = baseline; - } - - @Override - public int getBaseline() - { - return mBaseline; - } - - @Override - public int getAmplification() - { - /* Not Implemented */ - return 0; - } - - @Override - public void setAmplification( int amplification ) - { - /* Not Implemented */ - } - - @Override - public int getAveraging() - { - /* Not Implemented */ - return 0; - } - - @Override - public void setAveraging( int averaging ) - { - /* Not Implemented */ - } } diff --git a/src/spectrum/converter/ComplexDecibelConverter.java b/src/spectrum/converter/ComplexDecibelConverter.java index e88e0480..9c1eb427 100644 --- a/src/spectrum/converter/ComplexDecibelConverter.java +++ b/src/spectrum/converter/ComplexDecibelConverter.java @@ -1,7 +1,17 @@ package spectrum.converter; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import spectrum.DFTProcessor; + public class ComplexDecibelConverter extends DFTResultsConverter { + private final static Logger mLog = + LoggerFactory.getLogger( DFTResultsConverter.class ); + /** * Converts the output of the JTransforms FloatFFT_1D.complexForward() * calculation into a normalized power spectrum in decibels, per description @@ -10,51 +20,40 @@ public class ComplexDecibelConverter extends DFTResultsConverter @Override public void receive( float[] results ) { - float dc = Math.abs( results[ 0 ] ); + float[] processed = new float[ results.length / 2 ]; + + /* Output from JTransforms 3.0 has the lower and upper halves of the + * spectrum swapped. */ + int half = processed.length / 2; - /* Ensure we have power in the DC power bin (bin 0), otherwise don't - * dispatch the results */ - if( dc > 0 ) + for( int x = 0; x < results.length; x += 2 ) { - float[] processed = new float[ results.length / 2 + 1 ]; - - /** - * Output from JTransforms 3.0 has the lower and upper halves of the - * spectrum swapped. - */ - int half = processed.length / 2; + float magnitude = (float)Math.sqrt( results[ x ] * + results[ x ] + results[ x + 1 ] * results[ x + 1 ] ); - for( int x = 0; x < processed.length - 2; x ++ ) + /* Place upper half of magnitudes in lower half of results, vice-versa */ + if( x / 2 >= half ) { - int index = ( x + 1 ) * 2; - - double magnitude2 = ( ( results[ index ] * results[ index ] ) + - ( results[ index + 1 ] * results[ index + 1 ] ) ); - - if( x < half ) - { - processed[ x + half ] = 10.0f * - (float)( Math.log10( magnitude2 / dc ) ); - } - else - { - processed[ x - half ] = 10.0f * - (float)( Math.log10( magnitude2 / dc ) ); - } + processed[ x / 2 - half ] = magnitude; + } + else + { + processed[ x / 2 + half ] = magnitude; + } + } + + /* Use the DC component as the largest magnitude value to keep the + * display from jumping when a strong bursting signal exists */ + float largestMagnitude = processed[ half ]; + + if( largestMagnitude > 0 ) + { + /* Convert magnitudes to normized power spectrum dBm */ + for( int i = 0; i < processed.length; i++ ) + { + processed[ i ] = 20.0f * + (float)Math.log10( processed[ i ] / largestMagnitude ); } - - /** - * Place scaled dc component in last bin of processed array as - * scaling reference - */ - processed[ results.length / 2 ] = processed[ half ]; - - /** - * Repair the erroneous DC component bin by using the average of - * the neighboring bins - */ - processed[ half ] = ( processed[ half - 1 ] + - processed[ half + 1 ] / 2.0f ); dispatch( processed ); } diff --git a/src/spectrum/menu/AmplificationItem.java b/src/spectrum/menu/AmplificationItem.java deleted file mode 100644 index 13bd307a..00000000 --- a/src/spectrum/menu/AmplificationItem.java +++ /dev/null @@ -1,57 +0,0 @@ -package spectrum.menu; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; - -import javax.swing.JSlider; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import spectrum.SpectralDisplayAdjuster; - -public class AmplificationItem extends JSlider implements ChangeListener -{ - private static final long serialVersionUID = 1L; - - private SpectralDisplayAdjuster mAdjuster; - private int mDefaultValue; - - public AmplificationItem( SpectralDisplayAdjuster adjuster, int defaultValue ) - { - super( JSlider.HORIZONTAL, 1, 100, adjuster.getAmplification() ); - mDefaultValue = defaultValue; - - mAdjuster = adjuster; - - setMajorTickSpacing( 10 ); - setMinorTickSpacing( 5 ); - setPaintTicks( true ); - - addChangeListener( this ); - - addMouseListener( new MouseListener() - { - @Override - public void mouseClicked( MouseEvent event ) - { - if( event.getClickCount() == 2 ) - { - AmplificationItem.this.setValue( mDefaultValue ); - } - } - - public void mouseReleased( MouseEvent arg0 ) {} - public void mousePressed( MouseEvent arg0 ) {} - public void mouseExited( MouseEvent arg0 ) {} - public void mouseEntered( MouseEvent arg0 ) {} - } ); - } - - @Override - public void stateChanged( ChangeEvent event ) - { - int value = ((JSlider)event.getSource()).getValue(); - - mAdjuster.setAmplification( value ); - } -} diff --git a/src/spectrum/menu/BaselineItem.java b/src/spectrum/menu/DBScaleItem.java similarity index 73% rename from src/spectrum/menu/BaselineItem.java rename to src/spectrum/menu/DBScaleItem.java index bfc7bcb6..15b84e9f 100644 --- a/src/spectrum/menu/BaselineItem.java +++ b/src/spectrum/menu/DBScaleItem.java @@ -9,23 +9,24 @@ import javax.swing.event.ChangeListener; import spectrum.SpectralDisplayAdjuster; -public class BaselineItem extends JSlider implements ChangeListener +public class DBScaleItem extends JSlider implements ChangeListener { private static final long serialVersionUID = 1L; private SpectralDisplayAdjuster mAdjuster; private int mDefaultValue; - public BaselineItem( SpectralDisplayAdjuster adjuster, int defaultValue ) + public DBScaleItem( SpectralDisplayAdjuster adjuster, int defaultValue ) { - super( JSlider.HORIZONTAL, 1, 100, adjuster.getBaseline() ); + super( JSlider.HORIZONTAL, -160, -30, adjuster.getDBScale() ); mDefaultValue = defaultValue; mAdjuster = adjuster; - setMajorTickSpacing( 10 ); - setMinorTickSpacing( 5 ); + setMajorTickSpacing( 40 ); + setMinorTickSpacing( 10 ); setPaintTicks( true ); + setPaintLabels( true ); addChangeListener( this ); @@ -36,7 +37,7 @@ public class BaselineItem extends JSlider implements ChangeListener { if( event.getClickCount() == 2 ) { - BaselineItem.this.setValue( mDefaultValue ); + DBScaleItem.this.setValue( mDefaultValue ); } } @@ -52,6 +53,6 @@ public class BaselineItem extends JSlider implements ChangeListener { int value = ((JSlider)event.getSource()).getValue(); - mAdjuster.setBaseline( value ); + mAdjuster.setDBScale( value ); } }