mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Improved LTR FSK decoder for LTR, LTR-Net and Passport decoders. Added IIR DC blocking filter and used improved slicer from the FSK decoder.
Added instrumentation to Passport decoder and updated some of the instrumentation components.
This commit is contained in:
16
.classpath
16
.classpath
@@ -2,5 +2,21 @@
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="lib" path="imports/commons-lang3-3.2.1.jar"/>
|
||||
<classpathentry kind="lib" path="imports/jide-oss-3.5.15.jar"/>
|
||||
<classpathentry kind="lib" path="imports/jsr80-1.0.1.jar"/>
|
||||
<classpathentry kind="lib" path="imports/jtransforms-2.4.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-arm.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86_64.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86_64.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86_64.jar"/>
|
||||
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86.jar"/>
|
||||
<classpathentry kind="lib" path="imports/miglayout-4.0-swing.jar"/>
|
||||
<classpathentry kind="lib" path="imports/usb-api-1.0.2.jar"/>
|
||||
<classpathentry kind="lib" path="imports/usb4java-1.2.0.jar"/>
|
||||
<classpathentry kind="lib" path="imports/usb4java-javax-1.2.0.jar"/>
|
||||
<classpathentry kind="lib" path="config"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
||||
@@ -27,12 +27,12 @@ import java.util.Arrays;
|
||||
*
|
||||
* Also provides averaging over the elements in the buffer
|
||||
*/
|
||||
public class AveragingCircularBooleanBuffer {
|
||||
public class BooleanAveragingBuffer {
|
||||
|
||||
private boolean[] mBuffer;
|
||||
private int mBufferPointer;
|
||||
|
||||
public AveragingCircularBooleanBuffer( int length )
|
||||
public BooleanAveragingBuffer( int length )
|
||||
{
|
||||
mBuffer = new boolean[ length ];
|
||||
|
||||
67
src/buffer/DelayBuffer.java
Normal file
67
src/buffer/DelayBuffer.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package buffer;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
/**
|
||||
* Delay Buffer - implements a circular delay buffer backed by an object array
|
||||
*/
|
||||
public class DelayBuffer<T> implements Listener<T>
|
||||
{
|
||||
private Object[] mBuffer;
|
||||
private int mBufferPointer = 0;
|
||||
private Listener<T> mListener;
|
||||
|
||||
public DelayBuffer( int length )
|
||||
{
|
||||
mBuffer = new Object[ length ];
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public void receive( T value )
|
||||
{
|
||||
Object delayed = mBuffer[ mBufferPointer ];
|
||||
|
||||
mBuffer[ mBufferPointer ] = value;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
/* Wrap the buffer pointer around to 0 when necessary */
|
||||
if( mBufferPointer >= mBuffer.length )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
if( mListener != null && delayed != null )
|
||||
{
|
||||
mListener.receive( (T)delayed );
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener( Listener<T> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( Listener<T> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,8 @@ public enum DecoderType
|
||||
{
|
||||
return EnumSet.of( DecoderType.FLEETSYNC2,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.MPT1327 );
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.PASSPORT );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,8 @@ import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import decode.Decoder;
|
||||
import decode.DecoderType;
|
||||
import decode.ltrstandard.LTRFSKDemodulator;
|
||||
import dsp.filter.DCRemovalFilter2;
|
||||
import dsp.fsk.LTRFSKDecoder;
|
||||
import dsp.nbfm.FilteringNBFMDemodulator;
|
||||
|
||||
public class LTRNetDecoder extends Decoder
|
||||
@@ -39,7 +39,7 @@ public class LTRNetDecoder extends Decoder
|
||||
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
|
||||
private FilteringNBFMDemodulator mDemodulator;
|
||||
private DCRemovalFilter2 mDCRemovalFilter;
|
||||
private LTRFSKDemodulator mLTRFSKDemodulator;
|
||||
private LTRFSKDecoder mLTRFSKDecoder;
|
||||
private MessageFramer mLTRMessageFramer;
|
||||
private LTRNetMessageProcessor mLTRMessageProcessor;
|
||||
|
||||
@@ -75,7 +75,7 @@ public class LTRNetDecoder extends Decoder
|
||||
mDCRemovalFilter.setListener( this.getFloatReceiver() );
|
||||
}
|
||||
|
||||
mLTRFSKDemodulator = new LTRFSKDemodulator();
|
||||
mLTRFSKDecoder = new LTRFSKDecoder();
|
||||
|
||||
/**
|
||||
* The DC removal filter will impact performance of the LTRFSKDemod,
|
||||
@@ -83,11 +83,11 @@ public class LTRNetDecoder extends Decoder
|
||||
*/
|
||||
if( mSourceSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
mDemodulator.addListener( mLTRFSKDemodulator );
|
||||
mDemodulator.addListener( mLTRFSKDecoder );
|
||||
}
|
||||
else
|
||||
{
|
||||
mDCRemovalFilter.setListener( mLTRFSKDemodulator );
|
||||
mDCRemovalFilter.setListener( mLTRFSKDecoder );
|
||||
}
|
||||
|
||||
if( direction == MessageDirection.OSW )
|
||||
@@ -103,7 +103,7 @@ public class LTRNetDecoder extends Decoder
|
||||
sLTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
|
||||
mLTRFSKDemodulator.addListener( mLTRMessageFramer );
|
||||
mLTRFSKDecoder.addListener( mLTRMessageFramer );
|
||||
|
||||
mLTRMessageProcessor = new LTRNetMessageProcessor( direction, aliasList );
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package decode.ltrstandard;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import dsp.BooleanZeroCrossingDetector;
|
||||
import dsp.FloatToBinarySlicer;
|
||||
import dsp.filter.BooleanAveragingFilter;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.FloatFIRFilter;
|
||||
import dsp.filter.FloatHalfBandFilter;
|
||||
import dsp.filter.FloatHalfBandNoDecimateFilter;
|
||||
import dsp.filter.SquaringFilter;
|
||||
|
||||
public class LTRFSKDemodulator implements Listener<Float>
|
||||
{
|
||||
FloatHalfBandFilter mHBFilter1;
|
||||
FloatHalfBandFilter mHBFilter2;
|
||||
FloatHalfBandFilter mHBFilter3;
|
||||
FloatHalfBandFilter mHBFilter4;
|
||||
FloatHalfBandFilter mHBFilter5;
|
||||
FloatHalfBandNoDecimateFilter mHBFilter6;
|
||||
FloatFIRFilter mDCBlocker;
|
||||
|
||||
SquaringFilter mSquaringFilter;
|
||||
BooleanAveragingFilter mAveragingFilter;
|
||||
BooleanZeroCrossingDetector mZeroCrossingDetector;
|
||||
FloatToBinarySlicer mSlicer;
|
||||
Broadcaster<Boolean> mBroadcaster = new Broadcaster<Boolean>();
|
||||
|
||||
//debug
|
||||
// FloatWaveRecorder mRecorder;
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
|
||||
* decoder. Expects a 48k short-valued sample rate, which is decimated
|
||||
* to a final 1.5k sample rate via five cascaded decimating half-band
|
||||
* filters, and applies a sixth, non-decimating half-band filter to bring
|
||||
* the overall bandwidth down to .75k. Each half band filter requires
|
||||
* 8 multiply/accumulate (MAC) cycles per input sample, totaling 2560
|
||||
* MAC filtering cycles per output bit, to get from 48k input to 300-baud
|
||||
* bit stream output.
|
||||
*
|
||||
* Applies a balancer to the demodulated fsk waveform to normalize the
|
||||
* values about the zero-axis and to offset fluctuations induced by the
|
||||
* DC correction filter.
|
||||
*
|
||||
* Converts filtered, decimated samples to squared bit values and then
|
||||
* applies a zero crossing detector to make a baud decision every 5 samples.
|
||||
*
|
||||
* Outputs the decoded bit stream.
|
||||
*/
|
||||
public LTRFSKDemodulator()
|
||||
{
|
||||
mHBFilter1 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
|
||||
mHBFilter2 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter1.setListener( mHBFilter2 );
|
||||
|
||||
mHBFilter3 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter2.setListener( mHBFilter3 );
|
||||
|
||||
mHBFilter4 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter3.setListener( mHBFilter4 );
|
||||
|
||||
mHBFilter5 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter4.setListener( mHBFilter5 );
|
||||
|
||||
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter5.setListener( mHBFilter6 );
|
||||
|
||||
// mDCBlocker = new FloatFIRFilter(
|
||||
// Filters.FIRHP_LTR_DCBLOCK_750FS_50FC.getCoefficients(), 1.0f );
|
||||
// mHBFilter6.setListener( mDCBlocker );
|
||||
|
||||
/**
|
||||
* Set the slicer's long buffer to a sample length of 14 baud periods
|
||||
* ( 5 * 14 )since 12 bauds should be the longest string of ones we'll
|
||||
* encounter
|
||||
*
|
||||
* Set the short buffer to two baud periods (5 * 2)
|
||||
*/
|
||||
//Set the buffer to 5 samples per baud times 12 bauds. Set the sync
|
||||
//lock period to 5 samples per baud times 31 bits (155 samples)
|
||||
mSlicer = new FloatToBinarySlicer( 70, 5 );
|
||||
mHBFilter6.setListener( mSlicer );
|
||||
// mDCBlocker.setListener( mSlicer );
|
||||
|
||||
mAveragingFilter = new BooleanAveragingFilter( 5 );
|
||||
mSlicer.setListener( mAveragingFilter );
|
||||
|
||||
mZeroCrossingDetector = new BooleanZeroCrossingDetector( 5 );
|
||||
mAveragingFilter.setListener( mZeroCrossingDetector );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Float sample )
|
||||
{
|
||||
mHBFilter1.receive( sample );
|
||||
}
|
||||
|
||||
public void addListener( Listener<Boolean> listener )
|
||||
{
|
||||
mZeroCrossingDetector.setListener( listener );
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mZeroCrossingDetector.removeListener( listener );
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package decode.ltrstandard;
|
||||
|
||||
import sample.Listener;
|
||||
import dsp.BooleanZeroCrossingDetector;
|
||||
import dsp.FloatToBinarySlicerLTR;
|
||||
import dsp.SyncDetectListener;
|
||||
import dsp.filter.BooleanAveragingFilter;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.FloatHalfBandFilter;
|
||||
import dsp.filter.FloatHalfBandNoDecimateFilter;
|
||||
import dsp.filter.SquaringFilter;
|
||||
|
||||
public class LTRFSKDemodulator2 implements Listener<Float>, SyncDetectListener
|
||||
{
|
||||
FloatHalfBandFilter mHBFilter1;
|
||||
FloatHalfBandFilter mHBFilter2;
|
||||
FloatHalfBandFilter mHBFilter3;
|
||||
FloatHalfBandFilter mHBFilter4;
|
||||
FloatHalfBandFilter mHBFilter5;
|
||||
FloatHalfBandNoDecimateFilter mHBFilter6;
|
||||
|
||||
SquaringFilter mSquaringFilter;
|
||||
BooleanAveragingFilter mAveragingFilter;
|
||||
BooleanZeroCrossingDetector mZeroCrossingDetector;
|
||||
FloatToBinarySlicerLTR mSlicer;
|
||||
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
|
||||
* decoder. Expects a 48k short-valued sample rate, which is decimated
|
||||
* to a final 1.5k sample rate via five cascaded decimating half-band
|
||||
* filters, and applies a sixth, non-decimating half-band filter to bring
|
||||
* the overall bandwidth down to .75k. Each half band filter requires
|
||||
* 8 multiply/accumulate (MAC) cycles per input sample, totaling 2560
|
||||
* MAC filtering cycles per output bit, to get from 48k input to 300-baud
|
||||
* bit stream output.
|
||||
*
|
||||
* Applies a balancer to the demodulated fsk waveform to normalize the
|
||||
* values about the zero-axis and to offset fluctuations induced by the
|
||||
* DC correction filter.
|
||||
*
|
||||
* Converts filtered, decimated samples to squared bit values and then
|
||||
* applies a zero crossing detector to make a baud decision every 5 samples.
|
||||
*
|
||||
* Outputs the decoded bit stream.
|
||||
*/
|
||||
public LTRFSKDemodulator2( int syncLength, int messageLength )
|
||||
{
|
||||
mHBFilter1 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
|
||||
mHBFilter2 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter1.setListener( mHBFilter2 );
|
||||
|
||||
mHBFilter3 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter2.setListener( mHBFilter3 );
|
||||
|
||||
mHBFilter4 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter3.setListener( mHBFilter4 );
|
||||
|
||||
mHBFilter5 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter4.setListener( mHBFilter5 );
|
||||
|
||||
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
|
||||
mHBFilter5.setListener( mHBFilter6 );
|
||||
|
||||
mSlicer = new FloatToBinarySlicerLTR( syncLength, messageLength, 5 );
|
||||
mHBFilter6.setListener( mSlicer );
|
||||
|
||||
mAveragingFilter = new BooleanAveragingFilter( 5 );
|
||||
mSlicer.setListener( mAveragingFilter );
|
||||
|
||||
mZeroCrossingDetector = new BooleanZeroCrossingDetector( 5 );
|
||||
mAveragingFilter.setListener( mZeroCrossingDetector );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Float sample )
|
||||
{
|
||||
mHBFilter1.receive( sample );
|
||||
}
|
||||
|
||||
public void setListener( Listener<Boolean> listener )
|
||||
{
|
||||
mZeroCrossingDetector.setListener( listener );
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mZeroCrossingDetector.removeListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the sync detection down to the slicer
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mSlicer.syncDetected();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import bits.SyncPattern;
|
||||
import decode.Decoder;
|
||||
import decode.DecoderType;
|
||||
import dsp.filter.DCRemovalFilter2;
|
||||
import dsp.fsk.LTRFSKDecoder;
|
||||
import dsp.nbfm.FilteringNBFMDemodulator;
|
||||
|
||||
public class LTRStandardDecoder extends Decoder
|
||||
@@ -38,7 +39,7 @@ public class LTRStandardDecoder extends Decoder
|
||||
public static final int sLTR_STANDARD_MESSAGE_LENGTH = 40;
|
||||
private FilteringNBFMDemodulator mDemodulator;
|
||||
private DCRemovalFilter2 mDCRemovalFilter;
|
||||
private LTRFSKDemodulator mLTRFSKDemodulator;
|
||||
private LTRFSKDecoder mLTRFSKDecoder;
|
||||
private MessageFramer mLTRMessageFramer;
|
||||
private LTRStandardMessageProcessor mLTRMessageProcessor;
|
||||
|
||||
@@ -74,7 +75,7 @@ public class LTRStandardDecoder extends Decoder
|
||||
mDCRemovalFilter.setListener( this.getFloatReceiver() );
|
||||
}
|
||||
|
||||
mLTRFSKDemodulator = new LTRFSKDemodulator();
|
||||
mLTRFSKDecoder = new LTRFSKDecoder();
|
||||
|
||||
/**
|
||||
* The DC removal filter will impact performance of the LTRFSKDemod,
|
||||
@@ -82,11 +83,11 @@ public class LTRStandardDecoder extends Decoder
|
||||
*/
|
||||
if( mSourceSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
mDemodulator.addListener( mLTRFSKDemodulator );
|
||||
mDemodulator.addListener( mLTRFSKDecoder );
|
||||
}
|
||||
else
|
||||
{
|
||||
mDCRemovalFilter.setListener( mLTRFSKDemodulator );
|
||||
mDCRemovalFilter.setListener( mLTRFSKDecoder );
|
||||
}
|
||||
|
||||
if( direction == MessageDirection.OSW )
|
||||
@@ -102,7 +103,7 @@ public class LTRStandardDecoder extends Decoder
|
||||
sLTR_STANDARD_MESSAGE_LENGTH );
|
||||
}
|
||||
|
||||
mLTRFSKDemodulator.addListener( mLTRMessageFramer );
|
||||
mLTRFSKDecoder.addListener( mLTRMessageFramer );
|
||||
|
||||
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
|
||||
@@ -17,17 +17,23 @@
|
||||
******************************************************************************/
|
||||
package decode.passport;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import source.Source.SampleType;
|
||||
import alias.AliasList;
|
||||
import bits.MessageFramer;
|
||||
import bits.SyncPattern;
|
||||
import decode.Decoder;
|
||||
import decode.DecoderType;
|
||||
import decode.ltrstandard.LTRFSKDemodulator;
|
||||
import dsp.filter.DCRemovalFilter2;
|
||||
import dsp.fsk.LTRFSKDecoder;
|
||||
import dsp.nbfm.FilteringNBFMDemodulator;
|
||||
|
||||
public class PassportDecoder extends Decoder
|
||||
public class PassportDecoder extends Decoder implements Instrumentable
|
||||
{
|
||||
/**
|
||||
* This value determines how quickly the DC remove filter responds to
|
||||
@@ -39,9 +45,11 @@ public class PassportDecoder extends Decoder
|
||||
public static final int sPASSPORT_SYNC_LENGTH = 9;
|
||||
private FilteringNBFMDemodulator mNBFMDemodulator;
|
||||
private DCRemovalFilter2 mDCRemovalFilter;
|
||||
private LTRFSKDemodulator mPassportFSKDemodulator;
|
||||
private LTRFSKDecoder mPassportFSKDecoder;
|
||||
private MessageFramer mPassportMessageFramer;
|
||||
private PassportMessageProcessor mPassportMessageProcessor;
|
||||
|
||||
private List<Tap> mAvailableTaps;
|
||||
|
||||
public PassportDecoder( SampleType sampleType, AliasList aliasList )
|
||||
{
|
||||
@@ -72,8 +80,8 @@ public class PassportDecoder extends Decoder
|
||||
mDCRemovalFilter.setListener( this.getFloatReceiver() );
|
||||
}
|
||||
|
||||
mPassportFSKDemodulator =
|
||||
new LTRFSKDemodulator();
|
||||
mPassportFSKDecoder =
|
||||
new LTRFSKDecoder();
|
||||
|
||||
/**
|
||||
* The DC removal filter will impact performance of the LTRFSKDemod,
|
||||
@@ -81,11 +89,11 @@ public class PassportDecoder extends Decoder
|
||||
*/
|
||||
if( mSourceSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
mNBFMDemodulator.addListener( mPassportFSKDemodulator );
|
||||
mNBFMDemodulator.addListener( mPassportFSKDecoder );
|
||||
}
|
||||
else
|
||||
{
|
||||
addFloatListener( mPassportFSKDemodulator );
|
||||
addFloatListener( mPassportFSKDecoder );
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +101,7 @@ public class PassportDecoder extends Decoder
|
||||
new MessageFramer( SyncPattern.PASSPORT.getPattern(),
|
||||
sPASSPORT_MESSAGE_LENGTH );
|
||||
|
||||
mPassportFSKDemodulator.addListener( mPassportMessageFramer );
|
||||
mPassportFSKDecoder.addListener( mPassportMessageFramer );
|
||||
|
||||
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
|
||||
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
|
||||
@@ -105,4 +113,29 @@ public class PassportDecoder extends Decoder
|
||||
{
|
||||
return DecoderType.PASSPORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
|
||||
mAvailableTaps.addAll( mPassportFSKDecoder.getTaps() );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
mPassportFSKDecoder.addTap( tap );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
mPassportFSKDecoder.removeTap( tap );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,8 +175,13 @@ public class PassportMessage extends Message
|
||||
public Alias getTalkgroupIDAlias()
|
||||
{
|
||||
int tg = getTalkgroupID();
|
||||
|
||||
return mAliasList.getTalkgroupAlias( String.valueOf( tg ) );
|
||||
|
||||
if( mAliasList != null )
|
||||
{
|
||||
return mAliasList.getTalkgroupAlias( String.valueOf( tg ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getLCN()
|
||||
@@ -262,7 +267,7 @@ public class PassportMessage extends Message
|
||||
{
|
||||
String min = getMobileID();
|
||||
|
||||
if( min != null )
|
||||
if( mAliasList != null && min != null )
|
||||
{
|
||||
return mAliasList.getMobileIDNumberAlias( min );
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package dsp;
|
||||
|
||||
import sample.Listener;
|
||||
import buffer.FloatCircularAveragingBuffer2;
|
||||
|
||||
/**
|
||||
* Binary slicer tuned to operate on LTR type short burst signals in a heavy
|
||||
* DC bias environment coupled with transmitter automatic frequency correction
|
||||
* being applied as the transmission progresses.
|
||||
*
|
||||
* Uses a pair of float buffers that calculate the average of the samples
|
||||
* contained in the buffer.
|
||||
*
|
||||
* The leading and trailing buffers are the same length as the sync pattern.
|
||||
*
|
||||
* The slicer uses the Leading buffer's average as the slicing reference for
|
||||
* normal operation.
|
||||
*
|
||||
* Once a sync pattern is detected, the trailing buffer should have the sync
|
||||
* pattern bauds and the leading buffer should have the first of the message
|
||||
* bits. At this point, ie sync detect, we switch to using the average of both
|
||||
* buffers as the slicing reference point.
|
||||
*
|
||||
* Once the reference counter counts down to indicate that there are sync length
|
||||
* bauds remaining of the message, and those remaining bauds should be in the
|
||||
* leading buffer, we swich to using the trailing buffer as the slicing
|
||||
* reference point for the remainder of the message.
|
||||
*
|
||||
* As the reference counter decrements to 0, meaning the message should be
|
||||
* clear of both buffers, we switch back to normal operation of using the
|
||||
* leading buffer as the slicing reference.
|
||||
*
|
||||
* An external sync detector indicates a sync pattern match by using the
|
||||
* syncDetected() method, which immediately switches the slicing average
|
||||
* reference to both, and sets the counter to the expected message length.
|
||||
*/
|
||||
public class FloatToBinarySlicerLTR implements Listener<Float>,
|
||||
SyncDetectListener
|
||||
|
||||
{
|
||||
private enum SlicingReference
|
||||
{
|
||||
LEADING, TRAILING, BOTH;
|
||||
}
|
||||
|
||||
private SlicingReference mSlicingReference = SlicingReference.LEADING;
|
||||
private FloatCircularAveragingBuffer2 mLeadingBuffer;
|
||||
private FloatCircularAveragingBuffer2 mTrailingBuffer;
|
||||
private int mReferenceCounter = 0;
|
||||
private int mSyncLength;
|
||||
private int mMessageLength;
|
||||
private Listener<Boolean> mListener;
|
||||
|
||||
public FloatToBinarySlicerLTR( int syncLength,
|
||||
int messageLength,
|
||||
int samplesPerBaud )
|
||||
{
|
||||
mSyncLength = syncLength * samplesPerBaud;
|
||||
mMessageLength = messageLength * samplesPerBaud;
|
||||
|
||||
mLeadingBuffer = new FloatCircularAveragingBuffer2( mSyncLength );
|
||||
mTrailingBuffer = new FloatCircularAveragingBuffer2( mSyncLength );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Float newestSample )
|
||||
{
|
||||
float middleSample = mLeadingBuffer.get( newestSample );
|
||||
mTrailingBuffer.get( middleSample );
|
||||
|
||||
//Dispatch the decision
|
||||
if( mListener != null )
|
||||
{
|
||||
switch( mSlicingReference )
|
||||
{
|
||||
case LEADING:
|
||||
mListener.receive( middleSample > mLeadingBuffer.average() );
|
||||
break;
|
||||
case BOTH:
|
||||
mListener.receive( middleSample >
|
||||
( ( mLeadingBuffer.average() +
|
||||
mTrailingBuffer.average() ) / 2.0f ) );
|
||||
break;
|
||||
case TRAILING:
|
||||
mListener.receive( middleSample > mTrailingBuffer.average() );
|
||||
break;
|
||||
}
|
||||
|
||||
mReferenceCounter--;
|
||||
|
||||
if( mReferenceCounter <= mSyncLength && mReferenceCounter > 0 )
|
||||
{
|
||||
mSlicingReference = SlicingReference.TRAILING;
|
||||
}
|
||||
else if( mReferenceCounter <= 0 )
|
||||
{
|
||||
mSlicingReference = SlicingReference.LEADING;
|
||||
|
||||
mReferenceCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mSlicingReference = SlicingReference.BOTH;
|
||||
mReferenceCounter = mMessageLength;
|
||||
}
|
||||
|
||||
public void setListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void clearListener()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
155
src/dsp/Slicer.java
Normal file
155
src/dsp/Slicer.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package dsp;
|
||||
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.SymbolEventTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import sample.Listener;
|
||||
import dsp.fsk.SymbolEvent;
|
||||
import dsp.fsk.SymbolEvent.Shift;
|
||||
|
||||
/**
|
||||
* Symbol slicer with auto-aligning baud timing
|
||||
*/
|
||||
public class Slicer implements Listener<Boolean>
|
||||
{
|
||||
public enum Output{ NORMAL, INVERTED };
|
||||
|
||||
private BitSet mBitSet = new BitSet();
|
||||
private int mSymbolLength;
|
||||
private int mDecisionThreshold;
|
||||
private int mSampleCounter;
|
||||
private SymbolEventTap mSymbolEventTap;
|
||||
|
||||
private boolean mNormalOutput = true;
|
||||
private Listener<Boolean> mListener;
|
||||
|
||||
public Slicer( Output output, int samplesPerSymbol )
|
||||
{
|
||||
mNormalOutput = ( output == Output.NORMAL );
|
||||
|
||||
mSymbolLength = samplesPerSymbol;
|
||||
|
||||
/* Round up */
|
||||
mDecisionThreshold = (int)( mSymbolLength / 2 ) + ( mSymbolLength % 2 );
|
||||
}
|
||||
|
||||
public void receive( Boolean sample )
|
||||
{
|
||||
if( mSampleCounter >= 0 )
|
||||
{
|
||||
if( sample )
|
||||
{
|
||||
mBitSet.set( mSampleCounter );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBitSet.clear( mSampleCounter );
|
||||
}
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
|
||||
if( mSampleCounter >= mSymbolLength )
|
||||
{
|
||||
boolean decision = mBitSet.cardinality() >= mDecisionThreshold;
|
||||
|
||||
send( decision );
|
||||
|
||||
/* Shift timing left if the left bit in the bitset is opposite
|
||||
* the decision and the right bit is the same */
|
||||
if( ( mBitSet.get( 0 ) ^ decision ) &&
|
||||
( !( mBitSet.get( mSymbolLength - 1 ) ^ decision ) ) )
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.LEFT, decision );
|
||||
|
||||
reset();
|
||||
|
||||
mSampleCounter--;
|
||||
}
|
||||
/* Shift timing right if the left bit is the same as the
|
||||
* decision and the right bit is opposite */
|
||||
else if( ( !( mBitSet.get( 0 ) ^ decision ) ) &&
|
||||
( mBitSet.get( mSymbolLength - 1 ) ^ decision ) )
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.RIGHT, decision );
|
||||
|
||||
/* Last bit from previous symbol to pre-fill next symbol */
|
||||
boolean previousSoftBit = mBitSet.get( mSymbolLength - 1 );
|
||||
|
||||
reset();
|
||||
|
||||
if( previousSoftBit )
|
||||
{
|
||||
mBitSet.set( 0 );
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
}
|
||||
/* No shift */
|
||||
else
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.NONE, decision );
|
||||
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the bit decision to the listener
|
||||
*/
|
||||
private void send( boolean decision )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( mNormalOutput ? decision : !decision );
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
mBitSet.clear();
|
||||
mSampleCounter = 0;
|
||||
}
|
||||
|
||||
public void setListener( Listener<Boolean> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends instrumentation tap event to all registered listeners
|
||||
*/
|
||||
private void sendTapEvent( BitSet bitset, Shift shift, boolean decision )
|
||||
{
|
||||
if( mSymbolEventTap != null )
|
||||
{
|
||||
SymbolEvent event =
|
||||
new SymbolEvent( bitset.get( 0, mSymbolLength ),
|
||||
mSymbolLength,
|
||||
decision,
|
||||
shift );
|
||||
|
||||
mSymbolEventTap.receive( event );
|
||||
}
|
||||
}
|
||||
|
||||
public void addTap( SymbolEventTap tap )
|
||||
{
|
||||
mSymbolEventTap = tap;
|
||||
}
|
||||
|
||||
public void removeTap()
|
||||
{
|
||||
mSymbolEventTap = null;
|
||||
}
|
||||
}
|
||||
@@ -17,84 +17,50 @@
|
||||
******************************************************************************/
|
||||
package dsp.filter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
/**
|
||||
* Averages (low pass filter) boolean values over the defined length
|
||||
* Averages (low pass filter) boolean values over the defined size
|
||||
*/
|
||||
public class BooleanAveragingFilter implements Listener<Boolean>
|
||||
{
|
||||
private boolean[] mBuffer;
|
||||
private BitSet mBuffer = new BitSet();
|
||||
private int mBufferPointer;
|
||||
private int mBufferLength;
|
||||
private int mThreshold;
|
||||
private Listener<Boolean> mListener;
|
||||
|
||||
public BooleanAveragingFilter( int length )
|
||||
{
|
||||
mBuffer = new boolean[ length ];
|
||||
|
||||
if( length % 2 == 0 )
|
||||
mBufferLength = length;
|
||||
|
||||
/* Round up */
|
||||
mThreshold = (int)( length / 2) + ( length % 2 );
|
||||
}
|
||||
|
||||
public void receive( Boolean value )
|
||||
{
|
||||
if( value )
|
||||
{
|
||||
mThreshold = (int)( length /2 );
|
||||
mBuffer.set( mBufferPointer );
|
||||
}
|
||||
else
|
||||
{
|
||||
mThreshold = (int)( ( length + 1 ) / 2 );
|
||||
mBuffer.clear( mBufferPointer );
|
||||
}
|
||||
|
||||
//Preload the array with false values
|
||||
Arrays.fill( mBuffer, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the newValue into this buffer and adjusts the buffer pointer
|
||||
* to prepare for the next get/put cycle
|
||||
*/
|
||||
public void put( boolean newValue )
|
||||
{
|
||||
//Store the new value to the buffer
|
||||
mBuffer[ mBufferPointer ] = newValue;
|
||||
|
||||
//Increment the buffer pointer
|
||||
mBufferPointer++;
|
||||
|
||||
//Wrap the buffer pointer around to 0 when necessary
|
||||
if( mBufferPointer >= mBuffer.length )
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBufferLength )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the newValue into the buffer, calculates the average
|
||||
* and returns that average from this method
|
||||
*
|
||||
* This effectively performs low-pass filtering
|
||||
*/
|
||||
public void receive( Boolean newValue )
|
||||
{
|
||||
//Load the new value into the buffer
|
||||
put( newValue );
|
||||
|
||||
int trueCount = 0;
|
||||
|
||||
for( int x = 0; x < mBuffer.length; x++ )
|
||||
{
|
||||
if( mBuffer[ x ] )
|
||||
{
|
||||
trueCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the number of true values in the buffer is
|
||||
* more than half, send true, otherwise, false
|
||||
*/
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( trueCount >= mThreshold );
|
||||
mListener.receive( mBuffer.cardinality() >= mThreshold );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +69,7 @@ public class BooleanAveragingFilter implements Listener<Boolean>
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void clearListener()
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
45
src/dsp/filter/DCRemovalFilter3.java
Normal file
45
src/dsp/filter/DCRemovalFilter3.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package dsp.filter;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public class DCRemovalFilter3 implements Listener<Float>
|
||||
{
|
||||
private float mAlpha;
|
||||
private float mPreviousInput = 0.0f;
|
||||
private float mPreviousOutput = 0.0f;
|
||||
|
||||
private Listener<Float> mListener;
|
||||
|
||||
/**
|
||||
* IIR single-pole DC removal filter, as described by J M de Freitas in
|
||||
* 29Jan2007 paper at:
|
||||
*
|
||||
* http://www.mathworks.com/matlabcentral/fileexchange/downloads/72134/download
|
||||
*
|
||||
* @param alpha 0.0 - 1.0 float - the closer alpha is to unity, the closer
|
||||
* the cutoff frequency is to DC.
|
||||
*/
|
||||
public DCRemovalFilter3( float alpha )
|
||||
{
|
||||
mAlpha = alpha;
|
||||
}
|
||||
@Override
|
||||
public void receive( Float currentInput )
|
||||
{
|
||||
float currentOutput = ( currentInput - mPreviousInput ) +
|
||||
( mAlpha * mPreviousOutput );
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( currentOutput );
|
||||
}
|
||||
|
||||
mPreviousInput = currentInput;
|
||||
mPreviousOutput = currentOutput;
|
||||
}
|
||||
|
||||
public void setListener( Listener<Float> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
103
src/dsp/filter/LTRPulseShapingFilter.java
Normal file
103
src/dsp/filter/LTRPulseShapingFilter.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package dsp.filter;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public class LTRPulseShapingFilter implements Listener<Boolean>
|
||||
{
|
||||
private int[][] mIndexes = { { 0, 1, 2, 3, 4 },
|
||||
{ 1, 2, 3, 4, 0 },
|
||||
{ 2, 3, 4, 0, 1 },
|
||||
{ 3, 4, 0, 1, 2 },
|
||||
{ 4, 0, 1, 2, 3 } };
|
||||
|
||||
private BitSet mBuffer;
|
||||
private int mBufferPointer = 0;
|
||||
private Listener<Boolean> mListener;
|
||||
|
||||
/**
|
||||
* Specialized pulse shaping filter that detects a weak 0 or 1 pulse that
|
||||
* is characterized by two consecutive values, true or false, within a
|
||||
* sequence of five boolean values where the remainder are the opposite
|
||||
* value, and elongates that weak pulse to be three consecutive values,
|
||||
* which is enough to cause the slicer to render a correct value.
|
||||
*/
|
||||
public LTRPulseShapingFilter()
|
||||
{
|
||||
mBuffer = new BitSet();
|
||||
mBuffer.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Boolean current )
|
||||
{
|
||||
boolean previous = mBuffer.get( mBufferPointer );
|
||||
|
||||
if( current )
|
||||
{
|
||||
mBuffer.set( mBufferPointer );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBuffer.clear( mBufferPointer );
|
||||
}
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= 5 )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
/* Check for weak positive pulse*/
|
||||
if( mBuffer.cardinality() == 2 )
|
||||
{
|
||||
if( mBuffer.get( mIndexes[ mBufferPointer ][ 1 ] ) &&
|
||||
mBuffer.get( mIndexes[ mBufferPointer ][ 2 ] ) )
|
||||
{
|
||||
mBuffer.set( mIndexes[ mBufferPointer][ 3 ] );
|
||||
}
|
||||
}
|
||||
/* Or, a weak negative pulse */
|
||||
else if( mBuffer.cardinality() == 3 )
|
||||
{
|
||||
if( !mBuffer.get( mIndexes[ mBufferPointer][ 1 ] ) &&
|
||||
!mBuffer.get( mIndexes[ mBufferPointer][ 2 ] ) )
|
||||
{
|
||||
mBuffer.clear( mIndexes[ mBufferPointer][ 3 ] );
|
||||
}
|
||||
}
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( previous );
|
||||
}
|
||||
}
|
||||
|
||||
public String getIndexSet( int pointer )
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( "[" );
|
||||
|
||||
for( int x = 0; x < 5; x++ )
|
||||
{
|
||||
sb.append( mIndexes[ pointer ][ x ] );
|
||||
sb.append( "," );
|
||||
}
|
||||
|
||||
sb.append( "[" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class SquaringFilter implements Listener<Float>
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( ( sample >= 0 ? true : false ) );
|
||||
mListener.receive( sample >= 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import sample.Listener;
|
||||
import buffer.AveragingCircularBooleanBuffer;
|
||||
import buffer.BooleanAveragingBuffer;
|
||||
import dsp.fsk.SymbolEvent.Shift;
|
||||
|
||||
/**
|
||||
@@ -60,8 +60,8 @@ public class FSK2Decoder implements Instrumentable, Listener<Float>
|
||||
private ArrayList<SymbolEventTap> mTaps = new ArrayList<SymbolEventTap>();
|
||||
|
||||
private Listener<Boolean> mListener;
|
||||
private AveragingCircularBooleanBuffer mDelayBuffer;
|
||||
private AveragingCircularBooleanBuffer mLowPassFilter;
|
||||
private BooleanAveragingBuffer mDelayBuffer;
|
||||
private BooleanAveragingBuffer mLowPassFilter;
|
||||
private Slicer mSlicer;
|
||||
private boolean mNormalOutput;
|
||||
private int mSamplesPerSymbol;
|
||||
@@ -76,8 +76,8 @@ public class FSK2Decoder implements Instrumentable, Listener<Float>
|
||||
mNormalOutput = ( output == Output.NORMAL );
|
||||
mSymbolRate = symbolRate;
|
||||
|
||||
mDelayBuffer = new AveragingCircularBooleanBuffer( mSamplesPerSymbol );
|
||||
mLowPassFilter = new AveragingCircularBooleanBuffer( mSamplesPerSymbol );
|
||||
mDelayBuffer = new BooleanAveragingBuffer( mSamplesPerSymbol );
|
||||
mLowPassFilter = new BooleanAveragingBuffer( mSamplesPerSymbol );
|
||||
mSlicer = new Slicer( mSamplesPerSymbol );
|
||||
}
|
||||
|
||||
|
||||
227
src/dsp/fsk/LTRFSKDecoder.java
Normal file
227
src/dsp/fsk/LTRFSKDecoder.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package dsp.fsk;
|
||||
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.stream.BinaryTap;
|
||||
import instrument.tap.stream.FloatTap;
|
||||
import instrument.tap.stream.SymbolEventTap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import sample.Listener;
|
||||
import dsp.Slicer;
|
||||
import dsp.Slicer.Output;
|
||||
import dsp.filter.DCRemovalFilter3;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.FloatHalfBandFilter;
|
||||
import dsp.filter.FloatHalfBandNoDecimateFilter;
|
||||
import dsp.filter.LTRPulseShapingFilter;
|
||||
import dsp.filter.SquaringFilter;
|
||||
|
||||
public class LTRFSKDecoder implements Listener<Float>, Instrumentable
|
||||
{
|
||||
private FloatHalfBandFilter mHBFilter1;
|
||||
private FloatHalfBandFilter mHBFilter2;
|
||||
private FloatHalfBandFilter mHBFilter3;
|
||||
private FloatHalfBandFilter mHBFilter4;
|
||||
private FloatHalfBandFilter mHBFilter5;
|
||||
private FloatHalfBandNoDecimateFilter mHBFilter6;
|
||||
private DCRemovalFilter3 mDCFilter;
|
||||
private SquaringFilter mSquaringFilter;
|
||||
private LTRPulseShapingFilter mPulseShaper;
|
||||
private Slicer mSlicer;
|
||||
|
||||
private List<Tap> mAvailableTaps;
|
||||
private final String TAP_F1_F2 = "LTR FSK Demod Filter1 >< Filter2";
|
||||
private final String TAP_F2_F3 = "LTR FSK Demod Filter2 >< Filter3";
|
||||
private final String TAP_F3_F4 = "LTR FSK Demod Filter3 >< Filter4";
|
||||
private final String TAP_F4_F5 = "LTR FSK Demod Filter4 >< Filter5";
|
||||
private final String TAP_F5_F6 = "LTR FSK Demod Filter5 >< Filter6";
|
||||
private final String TAP_F6_DT = "LTR FSK Demod Filter6 >< DC Filter";
|
||||
private final String TAP_DT_SF1 = "LTR FSK Demod DC Filter >< Squaring Filter";
|
||||
private final String TAP_SF1_PS = "LTR FSK Demod Squaring Filter >< Pulse Shaper";
|
||||
private final String TAP_PS_SLICER = "LTR FSK Demod Pulse Shaper >< Slicer";
|
||||
private final String TAP_SYMBOL_EVENT = "LTR FSK Demod Symbol Event";
|
||||
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling
|
||||
* decoder. Expects a 48000 sample rate input.
|
||||
*/
|
||||
public LTRFSKDecoder()
|
||||
{
|
||||
mHBFilter1 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
|
||||
mHBFilter2 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
mHBFilter1.setListener( mHBFilter2 );
|
||||
|
||||
mHBFilter3 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
mHBFilter2.setListener( mHBFilter3 );
|
||||
|
||||
mHBFilter4 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
mHBFilter3.setListener( mHBFilter4 );
|
||||
|
||||
mHBFilter5 = new FloatHalfBandFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
mHBFilter4.setListener( mHBFilter5 );
|
||||
|
||||
mHBFilter6 = new FloatHalfBandNoDecimateFilter(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.1002 );
|
||||
mHBFilter5.setListener( mHBFilter6 );
|
||||
|
||||
mDCFilter = new DCRemovalFilter3( 0.9946f );
|
||||
mHBFilter6.setListener( mDCFilter );
|
||||
|
||||
mSquaringFilter = new SquaringFilter();
|
||||
mDCFilter.setListener( mSquaringFilter );
|
||||
|
||||
mPulseShaper = new LTRPulseShapingFilter();
|
||||
mSquaringFilter.setListener( mPulseShaper );
|
||||
|
||||
mSlicer = new Slicer( Output.NORMAL, 5 );
|
||||
mPulseShaper.setListener( mSlicer );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Float sample )
|
||||
{
|
||||
mHBFilter1.receive( sample );
|
||||
}
|
||||
|
||||
public void addListener( Listener<Boolean> listener )
|
||||
{
|
||||
mSlicer.setListener( listener );
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mSlicer.removeListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tap> getTaps()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<Tap>();
|
||||
mAvailableTaps.add( new FloatTap( TAP_F1_F2, 0, 1.0f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_F2_F3, 8, 0.5f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_F3_F4, 12, 0.25f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_F4_F5, 14, 0.125f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_F5_F6, 15, 0.0625f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_F6_DT, 31, 0.0625f ) );
|
||||
mAvailableTaps.add( new FloatTap( TAP_DT_SF1, 31, 0.0625f ) );
|
||||
mAvailableTaps.add( new BinaryTap( TAP_SF1_PS, 33, 0.0625f ) );
|
||||
mAvailableTaps.add( new BinaryTap( TAP_PS_SLICER, 33, 0.0625f ) );
|
||||
mAvailableTaps.add( new SymbolEventTap( TAP_SYMBOL_EVENT, 7, 0.0125f ) );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTap( Tap tap )
|
||||
{
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case TAP_F1_F2:
|
||||
mHBFilter1.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mHBFilter2 );
|
||||
break;
|
||||
case TAP_F2_F3:
|
||||
mHBFilter2.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mHBFilter3 );
|
||||
break;
|
||||
case TAP_F3_F4:
|
||||
mHBFilter3.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mHBFilter4 );
|
||||
break;
|
||||
case TAP_F4_F5:
|
||||
mHBFilter4.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mHBFilter5 );
|
||||
break;
|
||||
case TAP_F5_F6:
|
||||
mHBFilter5.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mHBFilter6 );
|
||||
break;
|
||||
case TAP_F6_DT:
|
||||
mHBFilter6.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mDCFilter );
|
||||
break;
|
||||
case TAP_DT_SF1:
|
||||
mDCFilter.setListener( (FloatTap)tap );
|
||||
((FloatTap)tap).setListener( mSquaringFilter );
|
||||
break;
|
||||
case TAP_SF1_PS:
|
||||
mSquaringFilter.setListener( (BinaryTap)tap );
|
||||
((BinaryTap)tap).setListener( mPulseShaper );
|
||||
break;
|
||||
case TAP_PS_SLICER:
|
||||
mPulseShaper.setListener( (BinaryTap)tap );
|
||||
((BinaryTap)tap).setListener( mSlicer );
|
||||
break;
|
||||
case TAP_SYMBOL_EVENT:
|
||||
mSlicer.addTap( (SymbolEventTap)tap );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTap( Tap tap )
|
||||
{
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case TAP_F1_F2:
|
||||
mHBFilter1.setListener( mHBFilter2 );
|
||||
break;
|
||||
case TAP_F2_F3:
|
||||
mHBFilter2.setListener( mHBFilter3 );
|
||||
break;
|
||||
case TAP_F3_F4:
|
||||
mHBFilter3.setListener( mHBFilter4 );
|
||||
break;
|
||||
case TAP_F4_F5:
|
||||
mHBFilter4.setListener( mHBFilter5 );
|
||||
break;
|
||||
case TAP_F5_F6:
|
||||
mHBFilter5.setListener( mHBFilter6 );
|
||||
break;
|
||||
case TAP_F6_DT:
|
||||
mHBFilter6.setListener( mDCFilter );
|
||||
break;
|
||||
case TAP_DT_SF1:
|
||||
mDCFilter.setListener( mSquaringFilter );
|
||||
break;
|
||||
case TAP_SF1_PS:
|
||||
mSquaringFilter.setListener( mPulseShaper );
|
||||
break;
|
||||
case TAP_PS_SLICER:
|
||||
mPulseShaper.setListener( mSlicer );
|
||||
break;
|
||||
case TAP_SYMBOL_EVENT:
|
||||
mSlicer.removeTap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,8 @@ public class BinaryTapViewPanel extends TapViewPanel
|
||||
|
||||
getModel().setSampleCount(
|
||||
(int)( getModel().getSampleCount() * tap.getSampleRateRatio() ) );
|
||||
|
||||
getModel().setDelay( tap.getDelay() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,10 +29,12 @@ import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JInternalFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSeparator;
|
||||
|
||||
@@ -101,8 +103,12 @@ public class DecoderViewFrame extends JInternalFrame
|
||||
{
|
||||
JMenu tapMenu = new JMenu( "Taps" );
|
||||
popup.add( tapMenu );
|
||||
|
||||
|
||||
Instrumentable i = (Instrumentable)mDecoder;
|
||||
|
||||
tapMenu.add( new AddAllTapsItem( i.getTaps() ) );
|
||||
|
||||
tapMenu.add( new JSeparator() );
|
||||
|
||||
for( Tap tap: i.getTaps() )
|
||||
{
|
||||
@@ -157,13 +163,21 @@ public class DecoderViewFrame extends JInternalFrame
|
||||
break;
|
||||
}
|
||||
|
||||
add( panel, "span" );
|
||||
|
||||
validate();
|
||||
|
||||
mPanelMap.put( tap, panel );
|
||||
if( panel != null )
|
||||
{
|
||||
add( panel, "span" );
|
||||
|
||||
validate();
|
||||
|
||||
mPanelMap.put( tap, panel );
|
||||
|
||||
((Instrumentable)mDecoder).addTap( tap );
|
||||
((Instrumentable)mDecoder).addTap( tap );
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.info( "Tap panel is null, couldn't add for tap " +
|
||||
tap.getName() + "[" + tap.getType().toString() + "]" );
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Tap tap )
|
||||
@@ -178,9 +192,34 @@ public class DecoderViewFrame extends JInternalFrame
|
||||
|
||||
mPanelMap.remove( tap );
|
||||
}
|
||||
|
||||
public class AddAllTapsItem extends JMenuItem
|
||||
{
|
||||
private final List<Tap> mTaps;
|
||||
|
||||
public AddAllTapsItem( final List<Tap> taps )
|
||||
{
|
||||
super( "All Taps" );
|
||||
|
||||
mTaps = taps;
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
for( Tap tap: mTaps )
|
||||
{
|
||||
DecoderViewFrame.this.add( tap );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class TapSelectionItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Tap mTap;
|
||||
|
||||
public TapSelectionItem( Tap tap )
|
||||
|
||||
@@ -28,8 +28,11 @@ import java.awt.geom.Point2D;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
|
||||
import log.Log;
|
||||
|
||||
public class FloatTapViewPanel extends TapViewPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Tap mTap;
|
||||
private List<Float> mSamples;
|
||||
private int mSampleCount;
|
||||
@@ -40,16 +43,20 @@ public class FloatTapViewPanel extends TapViewPanel
|
||||
|
||||
mTap = tap;
|
||||
mTap.addListener( getModel() );
|
||||
|
||||
mSampleCount = (int)( 2000 * tap.getSampleRateRatio() );
|
||||
|
||||
getModel().setSampleCount( mSampleCount );
|
||||
getModel().setDelay( tap.getDelay() );
|
||||
|
||||
getModel().setSampleCount(
|
||||
(int)( getModel().getSampleCount() * tap.getSampleRateRatio() ) );
|
||||
Log.info( "Float Tap Panel [" + tap.getName() + "] count: " +
|
||||
getModel().getSampleCount() + " delay:" + getModel().getDelay() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( Observable arg0, Object arg1 )
|
||||
{
|
||||
mSamples = (List<Float>)getModel().getSamples();
|
||||
mSampleCount = getModel().getSampleCount();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -68,7 +75,8 @@ public class FloatTapViewPanel extends TapViewPanel
|
||||
|
||||
g2.drawLine( 0, (int)middle, width, (int)middle );
|
||||
|
||||
if( mSamples != null && mSamples.size() > 0 )
|
||||
if( mSamples != null &&
|
||||
mSamples.size() > 0 )
|
||||
{
|
||||
g2.setColor( getForeground() );
|
||||
|
||||
|
||||
@@ -24,9 +24,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
|
||||
import log.Log;
|
||||
import dsp.fsk.SymbolEvent;
|
||||
|
||||
public class SampleModel<T> extends Observable implements TapListener<T>
|
||||
{
|
||||
private int mSampleCount = 2000;
|
||||
private int mDelay = 0;
|
||||
private ArrayList<T> mSamples = new ArrayList<T>();
|
||||
|
||||
public SampleModel()
|
||||
@@ -56,15 +60,32 @@ public class SampleModel<T> extends Observable implements TapListener<T>
|
||||
|
||||
changed();
|
||||
}
|
||||
|
||||
public void setDelay( int delay )
|
||||
{
|
||||
mDelay = delay;
|
||||
}
|
||||
|
||||
public int getDelay()
|
||||
{
|
||||
return mDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( T t )
|
||||
{
|
||||
mSamples.add( t );
|
||||
|
||||
while( mSamples.size() > mSampleCount )
|
||||
if( mDelay > 0 )
|
||||
{
|
||||
mSamples.remove( 0 );
|
||||
mDelay--;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSamples.add( t );
|
||||
|
||||
while( mSamples.size() > mSampleCount )
|
||||
{
|
||||
mSamples.remove( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
changed();
|
||||
|
||||
@@ -28,14 +28,16 @@ import java.awt.geom.Rectangle2D;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
|
||||
import log.Log;
|
||||
import dsp.fsk.SymbolEvent;
|
||||
|
||||
public class SymbolEventTapViewPanel extends TapViewPanel
|
||||
{
|
||||
private SymbolEventTap mTap;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private SymbolEventTap mTap;
|
||||
private List<SymbolEvent> mSamples;
|
||||
private int mSampleCount;
|
||||
private int mOffset = 0;
|
||||
|
||||
public SymbolEventTapViewPanel( SymbolEventTap tap )
|
||||
{
|
||||
@@ -44,15 +46,18 @@ public class SymbolEventTapViewPanel extends TapViewPanel
|
||||
mTap = tap;
|
||||
mTap.addListener( getModel() );
|
||||
|
||||
getModel().setSampleCount(
|
||||
(int)( getModel().getSampleCount() * tap.getSampleRateRatio() ) );
|
||||
mSampleCount = (int)( getModel().getSampleCount() * tap.getSampleRateRatio() );
|
||||
|
||||
getModel().setSampleCount( mSampleCount );
|
||||
|
||||
getModel().setDelay( tap.getDelay() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( Observable arg0, Object arg1 )
|
||||
{
|
||||
mSamples = (List<SymbolEvent>)getModel().getSamples();
|
||||
mSampleCount = getModel().getSampleCount();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import decode.DecoderType;
|
||||
import decode.fleetsync2.Fleetsync2Decoder;
|
||||
import decode.mdc1200.MDCDecoder;
|
||||
import decode.mpt1327.MPT1327Decoder;
|
||||
import decode.passport.PassportDecoder;
|
||||
|
||||
public class WaveSourceFrame extends JInternalFrame implements PositionListener
|
||||
{
|
||||
@@ -138,6 +139,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
|
||||
case MPT1327:
|
||||
decoder = new MPT1327Decoder( SampleType.FLOAT, null );
|
||||
break;
|
||||
case PASSPORT:
|
||||
decoder = new PassportDecoder( SampleType.FLOAT, null );
|
||||
}
|
||||
|
||||
if( decoder != null )
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package instrument.tap.stream;
|
||||
|
||||
import log.Log;
|
||||
import instrument.tap.TapListener;
|
||||
import instrument.tap.TapType;
|
||||
import sample.Listener;
|
||||
|
||||
Reference in New Issue
Block a user