[WIP] Creates a new AFSK-1200 correlation decoder and updates the

Fleetsync II decoder to use it.  Adds instrumented version of the decoder
and a new Fleetsync2 FX pane to the DemodulatorViewerFX tool.

Note: the remaining AFSK-1200 based decoders will all be ported to use
this new AFSK1200 decoder (MDC1200, Tait1200, LowJack1200).
This commit is contained in:
Denny
2018-06-09 08:17:18 -04:00
parent a5c8552937
commit 9dda1f95a1
14 changed files with 547 additions and 207 deletions

View File

@@ -3,6 +3,7 @@
<words>
<w>bahroo</w>
<w>broadcastify</w>
<w>correlator</w>
<w>dinf</w>
<w>esnid</w>
<w>fleetsync</w>

View File

@@ -13,10 +13,9 @@
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.fsk;
package io.github.dsheirer.dsp.afsk;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.buffer.BooleanAveragingBuffer;
import io.github.dsheirer.buffer.FloatAveragingBuffer;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
@@ -25,16 +24,21 @@ import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Binary Frequency Shift Keying (2FSK) correlation decoder. Provides normal or inverted decoded output.
* Audio Frequency Shift Keying (AFSK) 1200-baud correlation decoder for decoding 8 kHz FM-demodulated audio samples.
*
* Note: internally the incoming 8 kHz sample rate is resampled to 7200 Hz to be an integral of the 1200 Hz baud rate
* which equates to six samples per symbol. Each symbol is correlated over an eight sample period with correlation
* values averaged over a seven correlation value period.
*
* Provides normal or inverted decoded output.
*/
public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectListener, ISyncStateListener
public class AFSK1200Decoder implements Listener<ReusableBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
@@ -43,11 +47,15 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
NORMAL, INVERTED
}
public static final double TARGET_SAMPLE_RATE = 7200.0d;
public static final int SAMPLES_PER_SYMBOL = 6; //TARGET_SAMPLE_RATE / 1200;
public static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
public static final double SAMPLE_RATE = 7200.0d;
public static final int SAMPLES_PER_SYMBOL = 6; //SAMPLE_RATE / 1200 baud;
//Correlation period of 8 samples works well and should align nicely with SIMD intrinsic vector sizes
public static final int CORRELATION_PERIOD = SAMPLES_PER_SYMBOL + 2;
public static final int AVERAGING_PERIOD = SAMPLES_PER_SYMBOL + 1;
public static final double MARK = 1200.0;
public static final double SPACE = 1800.0;
public static final float TIMING_ERROR_GAIN = 1.0f / 3.0f; //Timing error adjustments over 3 symbol periods
private static float[] sBandPassFilterCoefficients;
@@ -78,38 +86,47 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
}
}
private Correlator mCorrelator1200 = new Correlator(TARGET_SAMPLE_RATE, 1200.0, SAMPLES_PER_SYMBOL);
private Correlator mCorrelator1800 = new Correlator(TARGET_SAMPLE_RATE, 1800.0, SAMPLES_PER_SYMBOL);
private Correlator mCorrelator1200 = new Correlator(SAMPLE_RATE, MARK, AVERAGING_PERIOD, CORRELATION_PERIOD);
private Correlator mCorrelator1800 = new Correlator(SAMPLE_RATE, SPACE, AVERAGING_PERIOD, CORRELATION_PERIOD);
private float[] mCorrelationValues1200;
private float[] mCorrelationValues1800;
protected boolean mNormalOutput;
protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
protected SampleBuffer mSampleBuffer;
protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
protected SynchronizationMonitor mSynchronizationMonitor;
protected float mSymbolTimingGain = TIMING_ERROR_GAIN;
protected AFSKSampleBuffer mSampleBuffer;
protected AFSKTimingErrorDetector mTimingErrorDetector = new AFSKTimingErrorDetector(SAMPLES_PER_SYMBOL);
private RealFIRFilter2 mBandPassFilter = new RealFIRFilter2(sBandPassFilterCoefficients);
protected MessageFramer mMessageFramer;
private boolean mSampleDecision;
//Resample to an integral of the baud rate 1200 baud * 6 samples per symbol = 7200.0 Hertz
private RealResampler mResampler = new RealResampler(8000.0, TARGET_SAMPLE_RATE, 2000, 1);
private RealResampler mResampler = new RealResampler(8000.0, SAMPLE_RATE, 2000, 1);
public AFSK1200Decoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector, boolean invertOutput)
/**
* Constructs a decoder using the provided arguments.
*
* @param sampleBuffer for storing incoming samples and calculating symbols
* @param detector for timing error alignment
* @param output NORMAL: 1200Hz = Mark(1) and 1800Hz = Space(0), or INVERTED (vice-versa)
*/
public AFSK1200Decoder(AFSKSampleBuffer sampleBuffer, AFSKTimingErrorDetector detector, Output output)
{
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
mSynchronizationMonitor.setListener(this);
mTimingErrorDetector = detector;
mSampleBuffer = sampleBuffer;
mSampleBuffer.setTimingGain(mSymbolTimingGain);
mResampler.setListener(new Decoder(SAMPLES_PER_SYMBOL));
mNormalOutput = !invertOutput;
mResampler.setListener(new Decoder());
mNormalOutput = (output == Output.NORMAL);
}
public AFSK1200Decoder(int messageLength, boolean invertOutput)
/**
* Constructs a decoder using the provided arguments.
*
* @param output NORMAL: 1200Hz = Mark(1) and 1800Hz = Space(0), or INVERTED (vice-versa)
*/
public AFSK1200Decoder(Output output)
{
this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL), invertOutput);
this(new AFSKSampleBuffer(SAMPLES_PER_SYMBOL, TIMING_ERROR_GAIN),
new AFSKTimingErrorDetector(SAMPLES_PER_SYMBOL), output);
}
/**
@@ -119,19 +136,6 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
{
}
/**
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
*
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
* synchronization has been achieved.
*/
@Override
public void syncDetected()
{
mSynchronizationMonitor.syncDetected();
}
/**
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
@@ -142,6 +146,8 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
@Override
public void receive(ReusableBuffer buffer)
{
//TODO: remove the passband filter ... the correlators should be able to deal with the noise and the filters may be
//TODO: introducing noise anyway
// ReusableBuffer bandPassFiltered = mBandPassFilter.filter(buffer);
// mResampler.resample(bandPassFiltered);
mResampler.resample(buffer);
@@ -174,46 +180,8 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
}
/**
* Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
* error gain levels on the internal timing error detector.
*
* Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
* ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
* error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
*
* @param syncState from an external synchronization state monitor
*/
@Override
public void setSyncState(SyncState syncState)
{
switch(syncState)
{
case FINE:
mSymbolTimingGain = FINE_TIMING_GAIN;
break;
case MEDIUM:
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
break;
case COARSE:
mSymbolTimingGain = COARSE_TIMING_GAIN;
break;
default:
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
}
mSampleBuffer.setTimingGain(mSymbolTimingGain);
}
public class Decoder implements Listener<ReusableBuffer>
{
private BooleanAveragingBuffer mSampleAveragingBuffer;
private Decoder(int samplesPerSymbol)
{
mSampleAveragingBuffer = new BooleanAveragingBuffer(samplesPerSymbol);
}
@Override
public void receive(ReusableBuffer buffer)
{
@@ -227,26 +195,30 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
{
//1200 = Mark (1) and 1800 = Space (0)
mSampleDecision = mCorrelationValues1200[x] > mCorrelationValues1800[x];
// mSampleDecision = mSampleAveragingBuffer.getAverage(mSampleDecision);
mSampleBuffer.receive(mSampleDecision);
mTimingErrorDetector.receive(mSampleDecision);
if(mSampleBuffer.hasSymbol())
{
dispatch(mSampleBuffer.getSymbol());
// mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
mSampleBuffer.resetAndAdjust(0.0f);
mSynchronizationMonitor.increment();
mSampleBuffer.resetAndAdjust(mTimingErrorDetector.getError());
}
}
}
}
/**
* Generates a correlation value for each FM demodulated signal against an established reference frequency,
* correlated and averaged over separate periods relative to the samples per symbol period.
* Generates a correlation value for each FM demodulated sample against samples generated for an established
* reference frequency, correlated and averaged over separate periods relative to the samples per symbol period.
*
* The sample rate of the correlator should be an integral value of the target frequency for optimal performance,
* This correlator only works across integral sample periods and does not provide intra-sample interpolation.
*
* Correlation period (ie number of samples) should be relative to the samples per symbol. You may have to
* experiment with various values of (samplesPerSymbol +/- 1, 2 or 3) to obtain the optimal decoder performance.
*
* Averaging period defines the number of correlation values to average before producing the final correlation
* value for each sample.
*/
public class Correlator
{
@@ -257,24 +229,21 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
private float mCorrelationAccumulator;
/**
* Constructs a correlator instance for the specified parameters
* Constructs a correlator instance,
*
* @param sampleRate of the incoming sample stream. Note: this should be an integral of the symbol rate.
* @param frequency of the mark or space symbol to test for correlation
* @param samplesPerSymbol to determine the correlation and averaging periods
* @param averagingPeriod is the number of correlation values to average each period
* @param correlationPeriod is the number of samples to correlate each period
*/
public Correlator(double sampleRate, double frequency, int samplesPerSymbol)
public Correlator(double sampleRate, double frequency, int averagingPeriod, int correlationPeriod)
{
//Adjust correlation period and averaging period to provide optimal correlation for a given samples/symbol.
//These values are optimized for 6 samples per symbol with averaging over 7 samples and correlation over an
//8 sample period.
int correlationPeriod = samplesPerSymbol + 2;
mAveragingBuffer = new FloatAveragingBuffer(averagingPeriod);
mAveragingBuffer = new FloatAveragingBuffer(samplesPerSymbol + 1);
IOscillator referenceSignalGenerator = new Oscillator(frequency, sampleRate);
mReferenceSamples = referenceSignalGenerator.generateReal(correlationPeriod);
mDemodulatedSamples = new float[correlationPeriod];
IOscillator referenceGenerator = new Oscillator(frequency, sampleRate);
mReferenceSamples = referenceGenerator.generateReal(correlationPeriod);
}
/**
@@ -298,7 +267,7 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
for(int x = 0; x < samples.length; x++)
{
//Note: we're using array copy and dot product structures that the JRE can promote to SIMD intrinsics
//when the host processor provides SIMD capabilities
//when the host processor supports SIMD instructions
System.arraycopy(mDemodulatedSamples, 1, mDemodulatedSamples, 0, mDemodulatedSamples.length - 1);
mDemodulatedSamples[mDemodulatedSamples.length - 1] = samples[x];
@@ -310,7 +279,8 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>, ISyncDetectLis
}
//Add the absolute value of correlation accumulator value to the averaging buffer and store the current
// average as the correlation value for this sample
//average as the correlation value for this sample. We use absolute value because we don't care if the
//signal is out of phase with the reference samples
mCorrelationValues[x] = mAveragingBuffer.get(Math.abs(mCorrelationAccumulator));
}

View File

@@ -13,7 +13,7 @@
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.fsk;
package io.github.dsheirer.dsp.afsk;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
@@ -22,20 +22,20 @@ public class AFSK1200DecoderInstrumented extends AFSK1200Decoder
{
private Broadcaster<Boolean> mSymbolBroadcaster = new Broadcaster<>();
public AFSK1200DecoderInstrumented(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector, boolean invertOutput)
public AFSK1200DecoderInstrumented(boolean invertOutput)
{
super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL), invertOutput);
super(new AFSKSampleBufferInstrumented(SAMPLES_PER_SYMBOL, TIMING_ERROR_GAIN),
new AFSKTimingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL), Output.NORMAL);
}
public SampleBufferInstrumented getSampleBuffer()
public AFSKSampleBufferInstrumented getSampleBuffer()
{
return (SampleBufferInstrumented)mSampleBuffer;
return (AFSKSampleBufferInstrumented)mSampleBuffer;
}
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
public AFSKTimingErrorDetectorInstrumented getErrorDetector()
{
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
return (AFSKTimingErrorDetectorInstrumented)mTimingErrorDetector;
}
protected void dispatch(boolean symbol)

View File

@@ -0,0 +1,121 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.afsk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AFSKSampleBuffer
{
private final static Logger mLog = LoggerFactory.getLogger(AFSKSampleBuffer.class);
private final static float SYMBOL_SAMPLE_THRESHOLD = 1.0f;
protected boolean[] mDelayLine;
protected int mDelayLinePointer = 0;
private int mHalfIntegerSamplesPerSymbol;
private int mPositiveSampleDecisionCount;
protected int mTwiceSamplesPerSymbol;
protected int mSymbolStart;
protected int mSymbolEnd;
protected int mSamplesPerSymbol;
protected float mSymbolSamplingPoint;
protected float mSymbolTimingGain;
/**
* Buffer to store complex sample data and produce interpolated samples.
* @param samplesPerSymbol
*/
public AFSKSampleBuffer(int samplesPerSymbol, float symbolTimingGain)
{
mSamplesPerSymbol = samplesPerSymbol;
mSymbolTimingGain = symbolTimingGain;
mHalfIntegerSamplesPerSymbol = samplesPerSymbol / 2;
mSymbolSamplingPoint = samplesPerSymbol;
mTwiceSamplesPerSymbol = 2 * samplesPerSymbol;
mDelayLine = new boolean[2 * mTwiceSamplesPerSymbol];
}
/**
* Stores the sample in the sample buffer to support making symbol decisions.
*
* Note: use the hasSymbol() method to determine if sufficient samples have been collected to make a symbol
* determination.
*
* @param sample to store in the sample buffer
*/
public void receive(boolean sample)
{
mSymbolSamplingPoint--;
mDelayLine[mDelayLinePointer] = sample;
mDelayLine[mDelayLinePointer + mTwiceSamplesPerSymbol] = sample;
//Increment pointer and keep pointer in bounds
mDelayLinePointer++;
mDelayLinePointer = mDelayLinePointer % mTwiceSamplesPerSymbol;
}
/**
* Adjusts samples per symbol and symbol timing counters and increments the sample counter to collect another
* symbol.
*
* @param symbolTimingError from a symbol timing error detector
*/
public void resetAndAdjust(float symbolTimingError)
{
mSymbolSamplingPoint += (mSamplesPerSymbol + (symbolTimingError * mSymbolTimingGain));
}
public void setTimingGain(float gain)
{
mSymbolTimingGain = gain;
}
/**
* Indicates if this buffer has accumulated enough samples to represent a full symbol
*/
public boolean hasSymbol()
{
return mSymbolSamplingPoint < SYMBOL_SAMPLE_THRESHOLD;
}
/**
* Calculates the symbol as a majority decision where the number of positive/true samples are greater than half
* of the samples per symbol. Symbol decision is based on the samples collected over the preceeding 1.0 to 2.0
* symbol periods.
*
* @return a true symbol if the majority of the samples were true or positive, otherwise a false symbol.
*/
public boolean getSymbol()
{
mPositiveSampleDecisionCount = 0;
mSymbolStart = mDelayLinePointer;
mSymbolEnd = mDelayLinePointer + mSamplesPerSymbol - 1;
for(int x = mSymbolStart; x <= mSymbolEnd; x++)
{
if(mDelayLine[x])
{
mPositiveSampleDecisionCount++;
}
}
return mPositiveSampleDecisionCount > mHalfIntegerSamplesPerSymbol;
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.afsk;
public class AFSKSampleBufferInstrumented extends AFSKSampleBuffer
{
public boolean mLastSymbol;
/**
* Buffer to store complex sample data and produce interpolated samples.
*
* @param samplesPerSymbol
* @param symbolTimingGain
*/
public AFSKSampleBufferInstrumented(int samplesPerSymbol, float symbolTimingGain)
{
super(samplesPerSymbol, symbolTimingGain);
}
/**
* Sample delay buffer containing boolean (ie positive or negative) samples.
*/
public boolean[] getDelayLine()
{
return mDelayLine;
}
/**
* Current delay buffer pointer which points to where the next sample will be stored and/or the oldest sample in
* the buffer.
*/
public int getDelayLinePointer()
{
return mDelayLinePointer;
}
public int getDelayLineSecondPointer()
{
return mDelayLinePointer + mTwiceSamplesPerSymbol;
}
/**
* Current symbol timing gain value
* @return
*/
public float getSymbolTimingGain()
{
return mSymbolTimingGain;
}
public float getSamplesPerSymbol()
{
return mSamplesPerSymbol;
}
@Override
public boolean getSymbol()
{
mLastSymbol = super.getSymbol();
return mLastSymbol;
}
public boolean getLastSymbol()
{
return mLastSymbol;
}
public int getSymbolStart()
{
return mSymbolStart;
}
public int getSymbolEnd()
{
return mSymbolEnd;
}
}

View File

@@ -13,7 +13,7 @@
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.fsk;
package io.github.dsheirer.dsp.afsk;
import io.github.dsheirer.buffer.FloatAveragingBuffer;
import io.github.dsheirer.dsp.mixer.IOscillator;

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.afsk;
/**
* Zero crossing error detector. Provides symbol timing error correction values by detecting zero-crossings within
* each symbol period. Ideally there should be only 0 or 1 zero crossings within each symbol period. However, when
* there is a slight DC offset in the demodulated samples, there can be up to 2 zero crossings within a symbol
* period.
*
* This detector will only provide error corrections when there are 1 or 2 zero crossings detected. If the number
* of zero crossings is greater than 2, then it is assumed that there is no signal present and the returned timing
* error value is zero. This detector provides an error correction value relative to (approximately) the middle of
* a symbol period and therefore continuously attempts to align zero crossings to the middle of the period.
*
* Since this detector does NOT use interpolation, the error signal will contain minor levels of symbol timing
* jitter. Error signal values are reported as integrals of the sampling period.
*/
public class AFSKTimingErrorDetector
{
protected boolean[] mBuffer;
protected int mDetectedZeroCrossing;
protected float mSymbolMiddle;
private int mZeroCrossingCount;
/**
* Constructs the detector for the specified samples per symbol.
*
* @param samplesPerSymbol
*/
public AFSKTimingErrorDetector(int samplesPerSymbol)
{
mBuffer = new boolean[samplesPerSymbol];
mSymbolMiddle = (float)(samplesPerSymbol - 1) / 2.0f;
}
/**
* Stores the sample in the buffer. Use the getError() method once enough samples have been processed to determine
* the error for a symbol period.
*
* @param sample
*/
public void receive(boolean sample)
{
//Shift all samples left and store new sample at the end
System.arraycopy(mBuffer, 1, mBuffer, 0, mBuffer.length - 1);
mBuffer[mBuffer.length - 1] = sample;
}
/**
* Determines the symbol timing error for the samples processed thus far. This method should be invoked once per
* symbol period, triggered via an external samples per symbol tracking process.
*
* @return symbol timing error calculated as the distance between the approximate symbol center and the next closest
* zero crossing index. An error value of zero is returned if the number of zero crossings is less than one or
* greater than two.
*/
public int getError()
{
mZeroCrossingCount = 0;
for(int x = 0; x < mBuffer.length - 1; x++)
{
if(mBuffer[x] ^ mBuffer[x + 1])
{
mDetectedZeroCrossing = x + 1;
mZeroCrossingCount++;
if(mZeroCrossingCount > 1)
{
return 0; //Too many crossings
}
}
}
if(mZeroCrossingCount == 1)
{
if(mDetectedZeroCrossing > mSymbolMiddle)
{
return -(mBuffer.length - mDetectedZeroCrossing);
}
else
{
return mDetectedZeroCrossing + 1;
}
}
return 0;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.afsk;
import javafx.beans.property.SimpleIntegerProperty;
public class AFSKTimingErrorDetectorInstrumented extends AFSKTimingErrorDetector
{
public SimpleIntegerProperty timingError = new SimpleIntegerProperty();
/**
* Constructs the detector for the specified samples per symbol.
*
* @param samplesPerSymbol
*/
public AFSKTimingErrorDetectorInstrumented(int samplesPerSymbol)
{
super(samplesPerSymbol);
}
@Override
public int getError()
{
int error = super.getError();
timingError.setValue(error);
return error;
}
public int getDetectedZeroCrossing()
{
return mDetectedZeroCrossing;
}
public boolean[] getBuffer()
{
return mBuffer;
}
}

View File

@@ -38,8 +38,8 @@ public class Fleetsync2SampleBufferChart extends LineChart
private ObservableList<Data<Number,Number>> mSymbolSamples = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
private ObservableList<Data<Number,Number>> mSymbol = FXCollections.observableArrayList();
private Series<Number,Number> mSymbolSeries = new Series<>("Previous Symbol", mSymbol);
private ObservableList<Data<Number,Number>> mPreviousSymbol = FXCollections.observableArrayList();
private Series<Number,Number> mPreviousSymbolSeries = new Series<>("Previous Symbol", mPreviousSymbol);
private ObservableList<Data<Number,Number>> mZeroCrossing = FXCollections.observableArrayList();
private Series<Number,Number> mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
@@ -52,7 +52,7 @@ public class Fleetsync2SampleBufferChart extends LineChart
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
mSymbolSamplesSeries, mSymbolSeries, mZeroCrossingSeries);
mSymbolSamplesSeries, mPreviousSymbolSeries, mZeroCrossingSeries);
setData(observableList);
@@ -91,10 +91,10 @@ public class Fleetsync2SampleBufferChart extends LineChart
{
setBuffer(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLine());
while(mSymbol.size() < 2)
while(mPreviousSymbol.size() < 2)
{
Data<Number,Number> sample = new Data<>(0.0, -0.55);
mSymbol.add(sample);
mPreviousSymbol.add(sample);
}
while(mZeroCrossing.size() < 2)
@@ -121,23 +121,23 @@ public class Fleetsync2SampleBufferChart extends LineChart
mSamplePoints.get(0).setXValue(pointer1);
mSamplePoints.get(1).setXValue(pointer2);
float samplesRemaining = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getMidSymbolSamplingPoint();
float samplesPerSymbol = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getSamplesPerSymbol();
float start = pointer1 + samplesRemaining;
float end = start + samplesPerSymbol;
mSymbolSamples.get(0).setXValue(start);
mSymbolSamples.get(1).setXValue(end);
boolean symbol = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getLastSymbol();
int symbolStart = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getSymbolStart();
int symbolEnd = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getSymbolEnd();
mSymbol.get(0).setXValue(symbolStart);
mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
mSymbol.get(1).setXValue(symbolEnd);
mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
float start = pointer1 + samplesPerSymbol - 1;
float end = symbolEnd;
mSymbolSamples.get(0).setXValue(start);
mSymbolSamples.get(1).setXValue(end);
mPreviousSymbol.get(0).setXValue(symbolStart);
mPreviousSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
mPreviousSymbol.get(1).setXValue(symbolEnd);
mPreviousSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
}
}
@@ -155,17 +155,16 @@ public class Fleetsync2SampleBufferChart extends LineChart
int pointer1 = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer();
int pointer2 = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer();
float zeroCrossingIdeal = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getZeroCrossingIdeal();
float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
int detectedZeroCrossing = ((Number)newValue).intValue();
int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
reference--;
float start = reference - zeroCrossingIdeal;
float end = reference - detectedZeroCrossing;
mZeroCrossing.get(0).setXValue(start);
mZeroCrossing.get(1).setXValue(end);
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
// reference--;
//
// float start = reference - zeroCrossingIdeal;
// float end = reference - detectedZeroCrossing;
//
// mZeroCrossing.get(0).setXValue(start);
// mZeroCrossing.get(1).setXValue(end);
}
}
}

View File

@@ -35,9 +35,6 @@ public class FleetsyncZeroCrossingErrorDetectorChart extends LineChart
private ObservableList<Data<Number,Number>> mPreviousSamples = FXCollections.observableArrayList();
private Series<Number,Number> mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
private ObservableList<Data<Number,Number>> mIdeal = FXCollections.observableArrayList();
private Series<Number,Number> mIdealSeries = new Series<>("Ideal", mIdeal);
private ObservableList<Data<Number,Number>> mDetected = FXCollections.observableArrayList();
private Series<Number,Number> mDetectedSeries = new Series<>("Detected", mDetected);
@@ -49,7 +46,7 @@ public class FleetsyncZeroCrossingErrorDetectorChart extends LineChart
new NumberAxis("Value", -1.0, 1.0, 0.25));
ObservableList<Series> observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
mPreviousSampleSeries, mIdealSeries, mDetectedSeries);
mPreviousSampleSeries, mDetectedSeries);
setData(observableList);
mFleetsync2DecoderInstrumented = decoder;
@@ -71,11 +68,6 @@ public class FleetsyncZeroCrossingErrorDetectorChart extends LineChart
mPreviousSamples.add(sample2);
}
Data<Number,Number> ideal1 = new Data<>(5, -0.1f);
mIdeal.add(ideal1);
Data<Number,Number> ideal2 = new Data<>(5, -0.9f);
mIdeal.add(ideal2);
Data<Number,Number> detected1 = new Data<>(5, -0.25f);
mDetected.add(detected1);
Data<Number,Number> detected2 = new Data<>(5, -0.75f);
@@ -108,11 +100,6 @@ public class FleetsyncZeroCrossingErrorDetectorChart extends LineChart
sample.setYValue(samples[x] ? -0.4f : -0.6f);
}
Data<Number,Number> ideal1 = mIdeal.get(0);
ideal1.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getZeroCrossingIdeal());
Data<Number,Number> ideal2 = mIdeal.get(1);
ideal2.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getZeroCrossingIdeal());
if(error == 0.0f)
{
Data<Number,Number> detected1 = mDetected.get(0);

View File

@@ -15,7 +15,6 @@
******************************************************************************/
package io.github.dsheirer.instrument.gui.viewer.decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.instrument.gui.viewer.chart.DecodedSymbolChart;
import io.github.dsheirer.instrument.gui.viewer.chart.Fleetsync2SampleBufferChart;
import io.github.dsheirer.instrument.gui.viewer.chart.FleetsyncZeroCrossingErrorDetectorChart;
@@ -67,15 +66,6 @@ public class Fleetsync2Pane extends RealDecoderPane
getDecoder().getAFSK1200Decoder().addListener(getDecodedSymbolChart());
getDecoder().setSyncDetectListener(new ISyncDetectListener()
{
@Override
public void syncDetected()
{
mLog.debug("Sync Detected!!!!!");
}
});
getDecoder().setMessageListener(new Listener<Message>()
{
@Override
@@ -90,7 +80,7 @@ public class Fleetsync2Pane extends RealDecoderPane
{
if(mDecoder == null)
{
mDecoder = new Fleetsync2DecoderInstrumented(null);
mDecoder = new Fleetsync2DecoderInstrumented();
mDecoder.setMessageListener(new Listener<Message>()
{
@Override

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.module.decode.afsk;
import io.github.dsheirer.dsp.afsk.AFSK1200Decoder;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
/**
* Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules.
*
* This class handles the AFSK 1200 decoder and incoming sample stream buffer management.
*
* Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream.
*/
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
{
private AFSK1200Decoder mAFSK1200Decoder;
public AbstractAFSKDecoder(AFSK1200Decoder decoder)
{
mAFSK1200Decoder = decoder;
}
public AbstractAFSKDecoder(AFSK1200Decoder.Output output)
{
mAFSK1200Decoder = new AFSK1200Decoder(output);
}
protected AFSK1200Decoder getDecoder()
{
return mAFSK1200Decoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
{
mAFSK1200Decoder.receive(reusableBuffer);
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
{
return this;
}
}

View File

@@ -20,34 +20,25 @@ package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.bits.SyncPattern;
import io.github.dsheirer.dsp.fsk.AFSK1200Decoder;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.dsp.symbol.SyncDetectProvider;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.dsp.afsk.AFSK1200Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.module.decode.afsk.AbstractAFSKDecoder;
/**
* Fleetsync II Decoder
*/
public class Fleetsync2Decoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>,
SyncDetectProvider
public class Fleetsync2Decoder extends AbstractAFSKDecoder
{
/* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */
protected static final int MESSAGE_LENGTH = 537;
protected AFSK1200Decoder mAFSK1200Decoder;
//Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks
private static final int MESSAGE_LENGTH = 537;
private MessageFramer mMessageFramer;
private Fleetsync2MessageProcessor mMessageProcessor;
public Fleetsync2Decoder(AFSK1200Decoder afsk1200Decoder, AliasList aliasList)
{
mAFSK1200Decoder = afsk1200Decoder;
super(afsk1200Decoder);
mMessageFramer = new MessageFramer(SyncPattern.FLEETSYNC2.getPattern(), MESSAGE_LENGTH);
mAFSK1200Decoder.setMessageFramer(mMessageFramer);
mMessageFramer.setSyncDetectListener(mAFSK1200Decoder);
getDecoder().setMessageFramer(mMessageFramer);
mMessageProcessor = new Fleetsync2MessageProcessor(aliasList);
mMessageFramer.addMessageListener(mMessageProcessor);
mMessageProcessor.setMessageListener(getMessageListener());
@@ -55,12 +46,7 @@ public class Fleetsync2Decoder extends Decoder implements IReusableBufferListene
public Fleetsync2Decoder(AliasList aliasList)
{
this(new AFSK1200Decoder(MESSAGE_LENGTH, false), aliasList);
}
public void dispose()
{
super.dispose();
this(new AFSK1200Decoder(AFSK1200Decoder.Output.NORMAL), aliasList);
}
@Override
@@ -69,27 +55,9 @@ public class Fleetsync2Decoder extends Decoder implements IReusableBufferListene
return DecoderType.FLEETSYNC2;
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
{
return mAFSK1200Decoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
{
mAFSK1200Decoder.receive(reusableBuffer);
}
@Override
public void reset()
{
mMessageFramer.reset();
}
@Override
public void setSyncDetectListener(ISyncDetectListener listener)
{
mMessageFramer.setSyncDetectListener(listener);
}
}

View File

@@ -15,28 +15,25 @@
******************************************************************************/
package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.fsk.AFSK1200Decoder;
import io.github.dsheirer.dsp.fsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.dsp.fsk.SampleBufferInstrumented;
import io.github.dsheirer.dsp.fsk.ZeroCrossingErrorDetectorInstrumented;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
* Instrumented version of the AFSK-1200 decoder for use with DemodulatorViewerFX
*/
public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder
{
public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
public Fleetsync2DecoderInstrumented(AliasList aliasList)
public Fleetsync2DecoderInstrumented()
{
super(new AFSK1200DecoderInstrumented(MESSAGE_LENGTH,
new SampleBufferInstrumented(AFSK1200Decoder.SAMPLES_PER_SYMBOL, AFSK1200Decoder.COARSE_TIMING_GAIN),
new ZeroCrossingErrorDetectorInstrumented(AFSK1200Decoder.SAMPLES_PER_SYMBOL), false), aliasList);
super(new AFSK1200DecoderInstrumented(false), null);
}
public AFSK1200DecoderInstrumented getAFSK1200Decoder()
{
return (AFSK1200DecoderInstrumented)mAFSK1200Decoder;
return (AFSK1200DecoderInstrumented)getDecoder();
}
@Override