diff --git a/src/main/java/io/github/dsheirer/bits/SyncPattern.java b/src/main/java/io/github/dsheirer/bits/SyncPattern.java index 5ef8f4be..8f589bbb 100644 --- a/src/main/java/io/github/dsheirer/bits/SyncPattern.java +++ b/src/main/java/io/github/dsheirer/bits/SyncPattern.java @@ -29,7 +29,7 @@ public enum SyncPattern false, false, false, false //0000 0x0 } ), - /* Revs (0x0A) and Sync (0x23EB) = 10101 0010001111101011 */ + /* Revs (0x0A) and Sync (0x23EB) = 0101 000 1 000 11111 0 1 0 11 */ FLEETSYNC2( new boolean[] { false, true, false, true, false, //End of bit revs diff --git a/src/main/java/io/github/dsheirer/bits/SyncPatternMatcher.java b/src/main/java/io/github/dsheirer/bits/SyncPatternMatcher.java index 3748fa4e..7adf426f 100644 --- a/src/main/java/io/github/dsheirer/bits/SyncPatternMatcher.java +++ b/src/main/java/io/github/dsheirer/bits/SyncPatternMatcher.java @@ -1,17 +1,17 @@ /******************************************************************************* * 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 ******************************************************************************/ @@ -20,113 +20,112 @@ package io.github.dsheirer.bits; /** * Sync pattern matcher. - * + * * Note: works for sync patterns up to 63 bits (integer size - 1 ) long. * If a 64 bit or larger sync pattern is applied, then the wrapping feature * of the Long.rotate methods will wrap the MSB around to the LSB and * corrupt the matching value. - * */ public class SyncPatternMatcher { - private long mBits = 0; - private long mMask = 0; - private long mSync = 0; - private boolean mSoftMode = false; - private int mSoftModeErrorThreshold = 0; - - public SyncPatternMatcher( boolean[] syncPattern ) - { - //Setup a bit mask of all ones the length of the sync pattern - mMask = (long)( ( Math.pow( 2, syncPattern.length ) ) - 1 ); - - //Convert the sync bits into a long value for comparison - for( int x = 0; x < syncPattern.length; x++ ) - { - if( syncPattern[ x ] ) - { - mSync += 1l << ( syncPattern.length - 1 - x ); - } - } - } - - public SyncPatternMatcher( long sync ) - { - mSync = sync; - mMask = getMask( sync ); - } + private long mBits = 0; + private long mMask = 0; + private long mSync = 0; + private boolean mSoftMode = false; + private int mSoftModeErrorThreshold = 0; - /** - * Constructs the sync pattern matcher and defines the soft mode maximum - * number of bit errors to allow in the pattern match. - * - * @param sync pattern - * @param softModeErrorThreshold - maximum bit errors (ie pattern mismatch - * positions) when considering a sync match - */ - public SyncPatternMatcher( long sync, int softModeErrorThreshold ) - { - this( sync ); - - mSoftModeErrorThreshold = softModeErrorThreshold; - } + public SyncPatternMatcher(boolean[] syncPattern) + { + //Setup a bit mask of all ones the length of the sync pattern + mMask = (long)((Math.pow(2, syncPattern.length)) - 1); - /** - * Generates an all ones mask corresponding to the sync pattern - */ - private static long getMask( long sync ) - { - long mask = sync; - - for( int x = 1; x < 64; x *= 2 ) - { - mask |= ( mask >> x ); - } - - return mask; - } - - - public void receive( boolean bit ) - { - //Left shift the previous value - mBits = Long.rotateLeft( mBits, 1 ); - - //Apply the mask to erase the previous MSB - mBits &= mMask; - - //Add in the new bit - if( bit ) - { - mBits += 1; - } - } + //Convert the sync bits into a long value for comparison + for(int x = 0; x < syncPattern.length; x++) + { + if(syncPattern[x]) + { + mSync += 1l << (syncPattern.length - 1 - x); + } + } + } - /** - * Indicates if the most recently received bit sequence matches the - * sync pattern - */ - public boolean matches() - { - if( mSoftMode ) - { - long difference = mBits ^ mSync; - - return difference == 0 || - Long.bitCount( difference ) <= mSoftModeErrorThreshold; - } - else - { - return ( mBits == mSync ); - } - } + public SyncPatternMatcher(long sync) + { + mSync = sync; + mMask = getMask(sync); + } - /** - * Sets the sync pattern matcher in soft mode which allows up to the soft - * mode threshold number of bit errors when considering a sync pattern match - */ - public void setSoftMode( boolean softMode ) - { - mSoftMode = softMode; - } + /** + * Constructs the sync pattern matcher and defines the soft mode maximum + * number of bit errors to allow in the pattern match. + * + * @param sync pattern + * @param softModeErrorThreshold - maximum bit errors (ie pattern mismatch + * positions) when considering a sync match + */ + public SyncPatternMatcher(long sync, int softModeErrorThreshold) + { + this(sync); + + mSoftModeErrorThreshold = softModeErrorThreshold; + } + + /** + * Generates an all ones mask corresponding to the sync pattern + */ + private static long getMask(long sync) + { + long mask = sync; + + for(int x = 1; x < 64; x *= 2) + { + mask |= (mask >> x); + } + + return mask; + } + + + public void receive(boolean bit) + { + //Left shift the previous value + mBits = Long.rotateLeft(mBits, 1); + + //Apply the mask to erase the previous MSB + mBits &= mMask; + + //Add in the new bit + if(bit) + { + mBits += 1; + } + } + + /** + * Indicates if the most recently received bit sequence matches the + * sync pattern + */ + public boolean matches() + { + if(mSoftMode) + { + long difference = mBits ^ mSync; + + return difference == 0 || + Long.bitCount(difference) <= mSoftModeErrorThreshold; + } + else + { + return (mBits == mSync); + } + } + + /** + * Sets the sync pattern matcher in soft mode which allows up to the soft + * mode threshold number of bit errors when considering a sync pattern match + */ + public void setSoftMode(boolean softMode) + { + mSoftMode = softMode; + } } diff --git a/src/main/java/io/github/dsheirer/buffer/BooleanAveragingBuffer.java b/src/main/java/io/github/dsheirer/buffer/BooleanAveragingBuffer.java index 5117972a..9d37d2e3 100644 --- a/src/main/java/io/github/dsheirer/buffer/BooleanAveragingBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/BooleanAveragingBuffer.java @@ -1,107 +1,89 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package io.github.dsheirer.buffer; -import java.util.Arrays; - - /** * Circular Buffer - implements a circular buffer with a method to get the * current head of the buffer, and put a new value in its place at the same * time - * + * * Also provides averaging over the elements in the buffer */ -public class BooleanAveragingBuffer { +public class BooleanAveragingBuffer +{ + private boolean[] mBuffer; + private int mBufferPointer; + private int mThreshold; + private int mTrueCount; - private boolean[] mBuffer; - private int mBufferPointer; - - public BooleanAveragingBuffer( int length ) - { - mBuffer = new boolean[ length ]; - - //Preload the array with false values - Arrays.fill( mBuffer, false ); - } - - public boolean get( boolean newValue ) - { - //Fetch current boolean value - boolean retVal = mBuffer[ mBufferPointer ]; + public BooleanAveragingBuffer(int length) + { + mBuffer = new boolean[length]; + mThreshold = length / 2; + } - //Load the new value into the buffer - put( newValue ); - - return retVal; - } + public boolean get(boolean newValue) + { + //Fetch current boolean value + boolean retVal = mBuffer[mBufferPointer]; - /** - * Loads the newValue into this buffer and adjusts the buffer pointer - * to prepare for the next get/put cycle - */ - private void put( boolean newValue ) - { - //Store the new value to the buffer - mBuffer[ mBufferPointer ] = newValue; - - //Increment the buffer pointer - mBufferPointer++; + //Load the new value into the buffer + put(newValue); - //Wrap the buffer pointer around to 0 when necessary - if( mBufferPointer >= mBuffer.length ) - { - mBufferPointer = 0; - } - } + return retVal; + } - /** - * Loads the newValue into the buffer, calculates the average - * and returns that average from this method - * - * This effectively performs low-pass filtering - */ - public boolean getAverage( 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++; - } - } + /** + * Loads the newValue into this buffer and adjusts the buffer pointer + * to prepare for the next get/put cycle + */ + private void put(boolean newValue) + { + //Store the new value to the buffer + mBuffer[mBufferPointer] = newValue; - /** - * If the number of true values in the buffer is - * more than half, return true, otherwise, false - */ - if( trueCount > (int)( mBuffer.length / 2 ) ) - { - return true; - } - else - { - return false; - } - } + //Increment the buffer pointer + mBufferPointer++; + + mBufferPointer = mBufferPointer % mBuffer.length; + } + + /** + * Loads the newValue into the buffer, calculates the average + * and returns that average from this method + * + * This effectively performs low-pass filtering + */ + public boolean getAverage(boolean newValue) + { + //Load the new value into the buffer + put(newValue); + + mTrueCount = 0; + + for(int x = 0; x < mBuffer.length; x++) + { + if(mBuffer[x]) + { + mTrueCount++; + } + } + + return mTrueCount > mThreshold; + } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java index 821c432b..9cf838eb 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java @@ -51,26 +51,26 @@ public class FilterViewer extends Application // .stopBandRipple(0.03) //Approximately 60 dB attenuation // .build(); // - FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() - .sampleRate(8000) - .stopBandCutoff(200) - .stopBandAmplitude(0.0) - .stopBandRipple(0.025) - .passBandStart(300) - .passBandAmplitude(1.0) - .passBandRipple(0.01) - .build(); - -// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() +// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() // .sampleRate(8000) -// .stopFrequency1(200) -// .passFrequencyBegin(300) -// .passFrequencyEnd(3800) -// .stopFrequency2(3900) -// .stopRipple(0.01) -// .passRipple(0.01) +// .stopBandCutoff(200) +// .stopBandAmplitude(0.0) +// .stopBandRipple(0.025) +// .passBandStart(300) +// .passBandAmplitude(1.0) +// .passBandRipple(0.01) // .build(); + FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() + .sampleRate(8000) + .stopFrequency1(1000) + .passFrequencyBegin(1100) + .passFrequencyEnd(1900) + .stopFrequency2(2000) + .stopRipple(0.000001) + .passRipple(0.00001) + .build(); + float[] taps = null; // try diff --git a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java index 39238e8c..496c786c 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java @@ -33,7 +33,7 @@ public class RealResampler private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler"); private Resampler mResampler; private Listener mResampledListener; - private BufferManager mBufferManager = new BufferManager(); + private BufferManager mBufferManager; private double mResampleFactor; /** @@ -41,10 +41,11 @@ public class RealResampler * @param inputRate * @param outputRate */ - public RealResampler(double inputRate, double outputRate) + public RealResampler(double inputRate, double outputRate, int inputBufferSize, int outputBufferSize) { mResampleFactor = outputRate / inputRate; mResampler = new Resampler(true, mResampleFactor, mResampleFactor); + mBufferManager = new BufferManager(inputBufferSize, outputBufferSize); } /** @@ -72,16 +73,17 @@ public class RealResampler */ public class BufferManager implements SampleBuffers { - private int mBufferLength = 8000; - private FloatBuffer mInputBuffer = ByteBuffer.allocate(mBufferLength).asFloatBuffer(); - private FloatBuffer mOutputBuffer = ByteBuffer.allocate(mBufferLength).asFloatBuffer(); + private int mOutputBufferSize; + private FloatBuffer mInputBuffer; + private FloatBuffer mOutputBuffer; - /** - * Current length of the input and output buffers - */ - public int getBufferLength() + public BufferManager(int inputBufferSize, int outputBufferSize) { - return mBufferLength; + mOutputBufferSize = outputBufferSize; + + //The size of the underlying byte buffer in bytes is 4 times the requested input buffer size in floats + mInputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); + mOutputBuffer = ByteBuffer.allocate(inputBufferSize * 4).asFloatBuffer(); } /** @@ -124,9 +126,9 @@ public class RealResampler { mOutputBuffer.put(samples, offset, length); - while(mOutputBuffer.position() > 1000) + while(mOutputBuffer.position() > mOutputBufferSize) { - ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(1000); + ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize); outputBuffer.incrementUserCount(); mOutputBuffer.flip(); diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200Decoder.java b/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200Decoder.java new file mode 100644 index 00000000..be69104e --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200Decoder.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.dsp.fsk; + +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; +import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2; +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. + */ +public class AFSK1200Decoder implements Listener, ISyncDetectListener, ISyncStateListener +{ + private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class); + + public enum Output + { + 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; + + private static float[] sBandPassFilterCoefficients; + + static + { + FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() + .sampleRate(8000) + .stopFrequency1(1000) + .passFrequencyBegin(1100) + .passFrequencyEnd(1900) + .stopFrequency2(2000) + .stopRipple(0.000001) + .passRipple(0.00001) + .build(); + + try + { + RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); + + if(designer.isValid()) + { + sBandPassFilterCoefficients = designer.getImpulseResponse(); + } + } + catch(FilterDesignException fde) + { + mLog.error("Filter design error", fde); + } + } + + 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 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; + 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); + + public AFSK1200Decoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector, boolean invertOutput) + { + mSynchronizationMonitor = new SynchronizationMonitor(messageLength); + mSynchronizationMonitor.setListener(this); + mTimingErrorDetector = detector; + mSampleBuffer = sampleBuffer; + mSampleBuffer.setTimingGain(mSymbolTimingGain); + mResampler.setListener(new Decoder(SAMPLES_PER_SYMBOL)); + mNormalOutput = !invertOutput; + } + + public AFSK1200Decoder(int messageLength, boolean invertOutput) + { + this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN), + new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL), invertOutput); + } + + /** + * Disposes of all references to prepare for garbage collection + */ + public void dispose() + { + } + + /** + * 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 + * adjusted and then each of the symbols are decoded using a simple majority decision. + * + * @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling. + */ + @Override + public void receive(ReusableBuffer buffer) + { +// ReusableBuffer bandPassFiltered = mBandPassFilter.filter(buffer); +// mResampler.resample(bandPassFiltered); + mResampler.resample(buffer); + } + + protected void dispatch(boolean symbol) + { + if(mMessageFramer != null) + { + mMessageFramer.receive(mNormalOutput ? symbol : !symbol); + } + } + + /** + * Registers a listener to receive decoded LTR symbols. + * + * @param messageFramer to receive symbols. + */ + public void setMessageFramer(MessageFramer messageFramer) + { + mMessageFramer = messageFramer; + } + + /** + * Removes the symbol listener from receiving decoded LTR symbols. + */ + public void removeListener() + { + mMessageFramer = null; + } + + + /** + * 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 + { + private BooleanAveragingBuffer mSampleAveragingBuffer; + + private Decoder(int samplesPerSymbol) + { + mSampleAveragingBuffer = new BooleanAveragingBuffer(samplesPerSymbol); + } + + @Override + public void receive(ReusableBuffer buffer) + { + //Calculate correlation values against each 1200/1800 reference signal + mCorrelationValues1200 = mCorrelator1200.process(buffer); + mCorrelationValues1800 = mCorrelator1800.process(buffer); + + buffer.decrementUserCount(); + + for(int x = 0; x < mCorrelationValues1200.length; x++) + { + //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(); + } + } + } + } + + /** + * 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. + */ + public class Correlator + { + private FloatAveragingBuffer mAveragingBuffer; + private float[] mReferenceSamples; + private float[] mDemodulatedSamples; + private float[] mCorrelationValues; + private float mCorrelationAccumulator; + + /** + * Constructs a correlator instance for the specified parameters + * @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 + */ + public Correlator(double sampleRate, double frequency, int samplesPerSymbol) + { + //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(samplesPerSymbol + 1); + + mDemodulatedSamples = new float[correlationPeriod]; + + IOscillator referenceGenerator = new Oscillator(frequency, sampleRate); + mReferenceSamples = referenceGenerator.generateReal(correlationPeriod); + } + + /** + * Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol + * period to derive a correlation value that is in-turn averaged over one symbol period. + * + * @param reusableBuffer containing FM demodulated samples + * @return a reusable array of correlation values for each sample + */ + public float[] process(ReusableBuffer reusableBuffer) + { + if(mCorrelationValues == null || mCorrelationValues.length != reusableBuffer.getSampleCount()) + { + mCorrelationValues = new float[reusableBuffer.getSampleCount()]; + } + + float[] samples = reusableBuffer.getSamples(); + + int y; + + 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 + System.arraycopy(mDemodulatedSamples, 1, mDemodulatedSamples, 0, mDemodulatedSamples.length - 1); + mDemodulatedSamples[mDemodulatedSamples.length - 1] = samples[x]; + + mCorrelationAccumulator = 0.0f; + + for(y = 0; y < mDemodulatedSamples.length; y++) + { + mCorrelationAccumulator += mDemodulatedSamples[y] * mReferenceSamples[y]; + } + + //Add the absolute value of correlation accumulator value to the averaging buffer and store the current + // average as the correlation value for this sample + mCorrelationValues[x] = mAveragingBuffer.get(Math.abs(mCorrelationAccumulator)); + } + + return mCorrelationValues; + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200DecoderInstrumented.java b/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200DecoderInstrumented.java new file mode 100644 index 00000000..659918b7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fsk/AFSK1200DecoderInstrumented.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.dsp.fsk; + +import io.github.dsheirer.sample.Broadcaster; +import io.github.dsheirer.sample.Listener; + +public class AFSK1200DecoderInstrumented extends AFSK1200Decoder +{ + private Broadcaster mSymbolBroadcaster = new Broadcaster<>(); + + public AFSK1200DecoderInstrumented(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector, boolean invertOutput) + { + super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN), + new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL), invertOutput); + } + + public SampleBufferInstrumented getSampleBuffer() + { + return (SampleBufferInstrumented)mSampleBuffer; + } + + public ZeroCrossingErrorDetectorInstrumented getErrorDetector() + { + return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector; + } + + protected void dispatch(boolean symbol) + { + if(mMessageFramer != null) + { + mMessageFramer.receive(mNormalOutput ? symbol : !symbol); + } + + mSymbolBroadcaster.broadcast(symbol); + } + + public void addListener(Listener listener) + { + mSymbolBroadcaster.addListener(listener); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/AFSKTest.java b/src/main/java/io/github/dsheirer/dsp/fsk/AFSKTest.java new file mode 100644 index 00000000..d34a3f62 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/fsk/AFSKTest.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.dsp.fsk; + +import io.github.dsheirer.buffer.FloatAveragingBuffer; +import io.github.dsheirer.dsp.mixer.IOscillator; +import io.github.dsheirer.dsp.mixer.Oscillator; +import io.github.dsheirer.sample.complex.Complex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; + +public class AFSKTest +{ + private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class); + + private static final DecimalFormat df = new DecimalFormat("00.000"); + + public static String format(double value) + { + if(value > 0) + { + return " " + df.format(value); + } + else + { + return df.format(value); + } + } + + public static float[] generateAFSK1200(boolean[] symbols, int samplesPerSymbol, float gain) + { + float[] samples = new float[symbols.length * samplesPerSymbol]; + + Oscillator oscillator = new Oscillator(1200.0, (double)samplesPerSymbol * 1200); + + for(int x = 0; x < symbols.length; x++) + { + oscillator.setFrequency(symbols[x] ? 1200.0 : 1800.0); + + for(int y = 0; y < samplesPerSymbol; y++) + { + Complex sample = new Complex(oscillator.inphase(), oscillator.quadrature()); + sample.multiply(gain); + samples[x * samplesPerSymbol + y] = sample.inphase(); + oscillator.rotate(); + } + } + + return samples; + } + + public static void main(String[] args) + { + int sampleSize = 6; + + FloatAveragingBuffer avg1200 = new FloatAveragingBuffer(sampleSize); + FloatAveragingBuffer avg1800 = new FloatAveragingBuffer(sampleSize); + + IOscillator oscillator1200 = new Oscillator(1200.0, 7200.0); + IOscillator oscillator1800 = new Oscillator(1800.0, 7200.0); + oscillator1200.generateReal(1); + oscillator1800.generateReal(1); + + float[] reference1200 = oscillator1200.generateReal(sampleSize); + float[] reference1800 = oscillator1800.generateReal(sampleSize); + + boolean[] symbols = new boolean[]{true,false,true,false,true,false,true,false,true,true,true,false,true,false, + false,false,true,false,false,false,false,true,false,true,true,true,true,false,true,true,false,true,false, + true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, + true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, + true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true, + false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false, + true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}; + + float[] samples = generateAFSK1200(symbols, 6, 0.5f); + + mLog.debug("Length:" + samples.length); + + int offset = 0; + + float correlation1200 = 0.0f; + float correlation1800 = 0.0f; + + while(offset + sampleSize < samples.length) + { + correlation1200 = 0.0f; + correlation1800 = 0.0f; + + for(int x = 0; x < sampleSize; x++) + { + correlation1200 += reference1200[x] * samples[x + offset]; + correlation1800 += reference1800[x] * samples[x + offset]; + } + + correlation1200 = avg1200.get(Math.abs(correlation1200)); + correlation1800 = avg1800.get(Math.abs(correlation1800)); + + mLog.debug(offset + " 1200:" + format(correlation1200) + " 1800:" + format(correlation1800) + + " Decision: " + (correlation1200 > correlation1800 ? "0" : "1")); + + offset++; + } + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java index c93a09a1..ded627d8 100644 --- a/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java +++ b/src/main/java/io/github/dsheirer/dsp/fsk/ZeroCrossingErrorDetector.java @@ -46,7 +46,8 @@ public class ZeroCrossingErrorDetector public ZeroCrossingErrorDetector(float samplesPerSymbol) { mBuffer = new boolean[(int)Math.ceil(samplesPerSymbol)]; - mZeroCrossingIdeal = mBuffer.length - 1.5f - (samplesPerSymbol / 2.0f); +// mZeroCrossingIdeal = mBuffer.length - 1.5f - (samplesPerSymbol / 2.0f); //Preserve this for LTR + mZeroCrossingIdeal = (samplesPerSymbol / 2.0f); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java index 3f7df3f8..fa012fe4 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java @@ -15,6 +15,7 @@ ******************************************************************************/ package io.github.dsheirer.dsp.mixer; +import io.github.dsheirer.sample.buffer.ReusableBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.sample.complex.Complex; import org.slf4j.Logger; @@ -178,4 +179,14 @@ public abstract class AbstractOscillator implements IOscillator reusableComplexBuffer.reloadFrom(mSampleBuffer, System.currentTimeMillis()); } + + /** + * Generates enough real samples to fill the reusable buffer + * @param reusableBuffer to fill with real samples + */ + @Override + public void generateReal(ReusableBuffer reusableBuffer) + { + reusableBuffer.reloadFrom(generateReal(reusableBuffer.getSampleCount()), System.currentTimeMillis()); + } } diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java index 18f522d5..2d0b4930 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java @@ -15,6 +15,7 @@ ******************************************************************************/ package io.github.dsheirer.dsp.mixer; +import io.github.dsheirer.sample.buffer.ReusableBuffer; import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; /** @@ -97,6 +98,14 @@ public interface IOscillator */ void generateComplex(ReusableComplexBuffer reusableComplexBuffer); + /** + * Generates real samples and fills the reusable buffer using the current system time in millis for + * the update timestamp. + * + * @param reusableBuffer to fill with complex samples + */ + void generateReal(ReusableBuffer reusableBuffer); + /** * Mixes (heterodynes) the complex sample array using the current settings of this oscillator. * @param complexSamples to mix to a new frequency diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/DecodedSymbolChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/DecodedSymbolChart.java new file mode 100644 index 00000000..f2b20124 --- /dev/null +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/DecodedSymbolChart.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.instrument.gui.viewer.chart; + +import io.github.dsheirer.sample.Listener; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DecodedSymbolChart extends LineChart implements Listener +{ + private final static Logger mLog = LoggerFactory.getLogger(DecodedSymbolChart.class); + + private ObservableList> mSymbols = FXCollections.observableArrayList(); + private Series mSymbolSeries = new Series<>("Samples", mSymbols); + private boolean[] mSymbolBuffer; + private int mSymbolBufferPointer; + + public DecodedSymbolChart(int length) + { + super(new NumberAxis("Symbols", 0, length, 5), + new NumberAxis("Value", -1.0, 1.0, 0.25)); + + ObservableList observableList = FXCollections.observableArrayList(mSymbolSeries); + setData(observableList); + + for(int x = 0; x < length; x++) + { + Data symbol = new Data<>(x, -0.5f); + mSymbols.add(symbol); + } + + mSymbolBuffer = new boolean[length]; + } + + @Override + public void receive(Boolean symbol) + { + mSymbolBuffer[mSymbolBufferPointer++] = symbol; + mSymbolBufferPointer %= mSymbolBuffer.length; + + update(); + } + + private void update() + { + for(int x = 0; x < mSymbolBuffer.length; x++) + { + Data symbol = mSymbols.get(x); + + int offset = x + mSymbolBufferPointer; + offset %= mSymbolBuffer.length; + + symbol.setYValue(mSymbolBuffer[offset] ? 0.5f : -0.5f); + } + } +} diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/Fleetsync2SampleBufferChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/Fleetsync2SampleBufferChart.java new file mode 100644 index 00000000..bb3a8ff2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/Fleetsync2SampleBufferChart.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.instrument.gui.viewer.chart; + +import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderInstrumented; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.layout.StackPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Fleetsync2SampleBufferChart extends LineChart +{ + private final static Logger mLog = LoggerFactory.getLogger(Fleetsync2SampleBufferChart.class); + private ObservableList> mSamples = FXCollections.observableArrayList(); + private Series mSampleSeries = new Series<>("Samples", mSamples); + + private ObservableList> mSamplePoints = FXCollections.observableArrayList(); + private Series mSamplePointSeries = new Series<>("Pointers", mSamplePoints); + + private ObservableList> mSymbolSamples = FXCollections.observableArrayList(); + private Series mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples); + + private ObservableList> mSymbol = FXCollections.observableArrayList(); + private Series mSymbolSeries = new Series<>("Previous Symbol", mSymbol); + + private ObservableList> mZeroCrossing = FXCollections.observableArrayList(); + private Series mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing); + + private Fleetsync2DecoderInstrumented mFleetsync2DecoderInstrumented; + + public Fleetsync2SampleBufferChart(Fleetsync2DecoderInstrumented decoder, int length) + { + super(new NumberAxis("Samples", 0, length, 5), + new NumberAxis("Value", -1.0, 1.0, 0.25)); + + ObservableList observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries, + mSymbolSamplesSeries, mSymbolSeries, mZeroCrossingSeries); + + setData(observableList); + + mFleetsync2DecoderInstrumented = decoder; + decoder.bufferCount.addListener(new BufferChangeListener()); + decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener()); + } + + public void setBuffer(boolean[] buffer) + { + while(mSamples.size() < buffer.length) + { + Data sample = new Data<>(mSamples.size(), 0.0f); + mSamples.add(sample); + + //Turn off the data point marker symbols - yes I know this is inefficient + for(Data data: mSampleSeries.getData()) + { + StackPane stackPane = (StackPane)data.getNode(); + stackPane.setVisible(false); + } + } + + for(int x = 0; x < buffer.length; x++) + { + Data sample = mSamples.get(x); + + sample.setYValue(buffer[x] ? 0.5f : -0.5f); + } + } + + public class BufferChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + setBuffer(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLine()); + + while(mSymbol.size() < 2) + { + Data sample = new Data<>(0.0, -0.55); + mSymbol.add(sample); + } + + while(mZeroCrossing.size() < 2) + { + Data sample = new Data<>(0.0, -0.65); + mZeroCrossing.add(sample); + } + + while(mSamplePoints.size() < 2) + { + Data sample = new Data<>(0.0, -0.75); + mSamplePoints.add(sample); + } + + while(mSymbolSamples.size() < 2) + { + Data sample = new Data<>(0.0, -0.85); + mSymbolSamples.add(sample); + } + + int pointer1 = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer(); + int pointer2 = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer(); + + 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); + } + } + + public class ErrorChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + while(mZeroCrossing.size() < 2) + { + Data sample = new Data<>(0.0, -0.65); + mZeroCrossing.add(sample); + } + + 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 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); + } + } +} diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/FleetsyncZeroCrossingErrorDetectorChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/FleetsyncZeroCrossingErrorDetectorChart.java new file mode 100644 index 00000000..7891c036 --- /dev/null +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/FleetsyncZeroCrossingErrorDetectorChart.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.instrument.gui.viewer.chart; + +import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderInstrumented; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FleetsyncZeroCrossingErrorDetectorChart extends LineChart +{ + private final static Logger mLog = LoggerFactory.getLogger(FleetsyncZeroCrossingErrorDetectorChart.class); + + private ObservableList> mCurrentSamples = FXCollections.observableArrayList(); + private Series mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples); + + private ObservableList> mPreviousSamples = FXCollections.observableArrayList(); + private Series mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples); + + private ObservableList> mIdeal = FXCollections.observableArrayList(); + private Series mIdealSeries = new Series<>("Ideal", mIdeal); + + private ObservableList> mDetected = FXCollections.observableArrayList(); + private Series mDetectedSeries = new Series<>("Detected", mDetected); + + private Fleetsync2DecoderInstrumented mFleetsync2DecoderInstrumented; + + public FleetsyncZeroCrossingErrorDetectorChart(Fleetsync2DecoderInstrumented decoder, int length) + { + super(new NumberAxis("Samples", 0, length, 5), + new NumberAxis("Value", -1.0, 1.0, 0.25)); + + ObservableList observableList = FXCollections.observableArrayList(mCurrentSampleSeries, + mPreviousSampleSeries, mIdealSeries, mDetectedSeries); + setData(observableList); + + mFleetsync2DecoderInstrumented = decoder; + decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener()); + decoder.bufferCount.addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + updateBuffer(); + } + }); + + for(int x = 0; x < length; x++) + { + Data sample = new Data<>(x, 0.0f); + mCurrentSamples.add(sample); + Data sample2 = new Data<>(x, 0.0f); + mPreviousSamples.add(sample2); + } + + Data ideal1 = new Data<>(5, -0.1f); + mIdeal.add(ideal1); + Data ideal2 = new Data<>(5, -0.9f); + mIdeal.add(ideal2); + + Data detected1 = new Data<>(5, -0.25f); + mDetected.add(detected1); + Data detected2 = new Data<>(5, -0.75f); + mDetected.add(detected2); + } + + private void updateBuffer() + { + boolean[] samples = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getBuffer(); + + for(int x = 0; x < samples.length; x++) + { + Data sample = mCurrentSamples.get(x); + sample.setYValue(samples[x] ? 0.7f : 0.3f); + } + } + + public class ErrorChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + float error = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getError(); + + boolean[] samples = mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getBuffer(); + + for(int x = 0; x < samples.length; x++) + { + Data sample = mPreviousSamples.get(x); + sample.setYValue(samples[x] ? -0.4f : -0.6f); + } + + Data ideal1 = mIdeal.get(0); + ideal1.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getZeroCrossingIdeal()); + Data ideal2 = mIdeal.get(1); + ideal2.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getZeroCrossingIdeal()); + + if(error == 0.0f) + { + Data detected1 = mDetected.get(0); + detected1.setXValue(0.0f); + Data detected2 = mDetected.get(1); + detected2.setXValue(0.0f); + } + else + { + Data detected1 = mDetected.get(0); + detected1.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing()); + Data detected2 = mDetected.get(1); + detected2.setXValue(mFleetsync2DecoderInstrumented.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing()); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/LTRNetSampleBufferChart.java similarity index 96% rename from src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java rename to src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/LTRNetSampleBufferChart.java index 799776e9..ade62f19 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SampleBufferChart.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/LTRNetSampleBufferChart.java @@ -26,9 +26,9 @@ import javafx.scene.layout.StackPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SampleBufferChart extends LineChart +public class LTRNetSampleBufferChart extends LineChart { - private final static Logger mLog = LoggerFactory.getLogger(SampleBufferChart.class); + private final static Logger mLog = LoggerFactory.getLogger(LTRNetSampleBufferChart.class); private ObservableList> mSamples = FXCollections.observableArrayList(); private Series mSampleSeries = new Series<>("Samples", mSamples); @@ -46,7 +46,7 @@ public class SampleBufferChart extends LineChart private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented; - public SampleBufferChart(LTRNetDecoderInstrumented decoder, int length) + public LTRNetSampleBufferChart(LTRNetDecoderInstrumented decoder, int length) { super(new NumberAxis("Samples", 0, length, 5), new NumberAxis("Value", -1.0, 1.0, 0.25)); diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java index 03e61517..11327c27 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/DecoderPaneFactory.java @@ -23,8 +23,7 @@ import java.util.EnumSet; public class DecoderPaneFactory { private static final EnumSet SUPPORTED_DECODER_TYPES = - EnumSet.of(DecoderType.P25_PHASE1, - DecoderType.LTR_NET); + EnumSet.of(DecoderType.P25_PHASE1, DecoderType.LTR_NET, DecoderType.FLEETSYNC2); /** * Creates a decoder pane for the decoder type @@ -33,6 +32,8 @@ public class DecoderPaneFactory { switch(decoderType) { + case FLEETSYNC2: + return new Fleetsync2Pane(); case LTR_NET: return new LTRNetPane(); case P25_PHASE1: diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/Fleetsync2Pane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/Fleetsync2Pane.java new file mode 100644 index 00000000..647015b6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/Fleetsync2Pane.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +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; +import io.github.dsheirer.instrument.gui.viewer.chart.RealSampleLineChart; +import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderInstrumented; +import io.github.dsheirer.sample.Listener; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Fleetsync2Pane extends RealDecoderPane +{ + private final static Logger mLog = LoggerFactory.getLogger(Fleetsync2Pane.class); + + private HBox mSampleChartBox; + private RealSampleLineChart mRealSampleLineChart; + private HBox mSymbolDecoderBox; + private Fleetsync2SampleBufferChart mSampleBufferChart; + private FleetsyncZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart; + private DecodedSymbolChart mDecodedSymbolChart; + + private Fleetsync2DecoderInstrumented mDecoder; + + public Fleetsync2Pane() + { + super(DecoderType.LTR_NET); + init(); + } + + private void init() + { + addListener(getDecoder()); + addListener(getSampleLineChart()); + + HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS); + HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS); + + VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS); + VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS); + + getSampleChartBox().setMaxWidth(Double.MAX_VALUE); + getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE); + + getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox()); + + getDecoder().getAFSK1200Decoder().addListener(getDecodedSymbolChart()); + + getDecoder().setSyncDetectListener(new ISyncDetectListener() + { + @Override + public void syncDetected() + { + mLog.debug("Sync Detected!!!!!"); + } + }); + + getDecoder().setMessageListener(new Listener() + { + @Override + public void receive(Message message) + { + mLog.debug(message.getMessage()); + } + }); + } + + private Fleetsync2DecoderInstrumented getDecoder() + { + if(mDecoder == null) + { + mDecoder = new Fleetsync2DecoderInstrumented(null); + mDecoder.setMessageListener(new Listener() + { + @Override + public void receive(Message message) + { + mLog.debug(message.getMessage()); + } + }); + } + + return mDecoder; + } + + private HBox getSampleChartBox() + { + if(mSampleChartBox == null) + { + mSampleChartBox = new HBox(); + mSampleChartBox.setMaxHeight(Double.MAX_VALUE); + + getSampleLineChart().setMaxWidth(Double.MAX_VALUE); + getDecodedSymbolChart().setMaxWidth(Double.MAX_VALUE); + + HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS); + HBox.setHgrow(getDecodedSymbolChart(), Priority.ALWAYS); + + mSampleChartBox.getChildren().addAll(getSampleLineChart(), getDecodedSymbolChart()); + } + + return mSampleChartBox; + } + + private RealSampleLineChart getSampleLineChart() + { + if(mRealSampleLineChart == null) + { + mRealSampleLineChart = new RealSampleLineChart(200, (10.0)); + } + + return mRealSampleLineChart; + } + + private HBox getSymbolDecoderBox() + { + if(mSymbolDecoderBox == null) + { + mSymbolDecoderBox = new HBox(); + mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE); + + getSampleBufferChart().setMaxWidth(Double.MAX_VALUE); + getZeroCrossingErrorDetectorChart().setMaxWidth(Double.MAX_VALUE); + + HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS); + HBox.setHgrow(getZeroCrossingErrorDetectorChart(), Priority.ALWAYS); + + mSymbolDecoderBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart()); + } + + return mSymbolDecoderBox; + } + + private Fleetsync2SampleBufferChart getSampleBufferChart() + { + if(mSampleBufferChart == null) + { + mSampleBufferChart = new Fleetsync2SampleBufferChart(getDecoder(), 106); + } + + return mSampleBufferChart; + } + + private FleetsyncZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart() + { + if(mZeroCrossingErrorDetectorChart == null) + { + mZeroCrossingErrorDetectorChart = new FleetsyncZeroCrossingErrorDetectorChart(getDecoder(), + getDecoder().getAFSK1200Decoder().getErrorDetector().getBuffer().length); + } + + return mZeroCrossingErrorDetectorChart; + } + + private DecodedSymbolChart getDecodedSymbolChart() + { + if(mDecodedSymbolChart == null) + { + mDecodedSymbolChart = new DecodedSymbolChart(50); + } + + return mDecodedSymbolChart; + } +} diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java index 1cc29446..ee8162f2 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/LTRNetPane.java @@ -15,8 +15,8 @@ ******************************************************************************/ package io.github.dsheirer.instrument.gui.viewer.decoder; +import io.github.dsheirer.instrument.gui.viewer.chart.LTRNetSampleBufferChart; import io.github.dsheirer.instrument.gui.viewer.chart.RealSampleLineChart; -import io.github.dsheirer.instrument.gui.viewer.chart.SampleBufferChart; import io.github.dsheirer.instrument.gui.viewer.chart.ZeroCrossingErrorDetectorChart; import io.github.dsheirer.message.Message; import io.github.dsheirer.message.MessageDirection; @@ -37,7 +37,7 @@ public class LTRNetPane extends RealDecoderPane private HBox mSampleChartBox; private RealSampleLineChart mRealSampleLineChart; private HBox mSymbolDecoderBox; - private SampleBufferChart mSampleBufferChart; + private LTRNetSampleBufferChart mLTRNetSampleBufferChart; private ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart; private LTRNetDecoderInstrumented mDecoder; @@ -128,14 +128,14 @@ public class LTRNetPane extends RealDecoderPane return mSymbolDecoderBox; } - private SampleBufferChart getSampleBufferChart() + private LTRNetSampleBufferChart getSampleBufferChart() { - if(mSampleBufferChart == null) + if(mLTRNetSampleBufferChart == null) { - mSampleBufferChart = new SampleBufferChart(getDecoder(), 106); + mLTRNetSampleBufferChart = new LTRNetSampleBufferChart(getDecoder(), 106); } - return mSampleBufferChart; + return mLTRNetSampleBufferChart; } private ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart() diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java index 42b05e81..689ba6fc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderType.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderType.java @@ -30,7 +30,7 @@ public enum DecoderType AM( "AM", "AM", "images/am.png", 10000 ), APRS( "APRS", "APRS", "images/aprs.png", 12500 ), DMR( "DMR", "DMR", "images/dmr.png", 12500 ), - FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm.png", 12500 ), + FLEETSYNC2( "Fleetsync II", "Fleetsync2", "images/fm.png", 12500 ), LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ), LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ), LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ), diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java index bbd992cd..f05d9366 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2Decoder.java @@ -1,17 +1,17 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014,2015 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 ******************************************************************************/ @@ -20,156 +20,76 @@ 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.filter.Filters; -import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB; -import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter_RB_RB; -import io.github.dsheirer.dsp.fsk.FSK2Decoder; -import io.github.dsheirer.dsp.fsk.FSK2Decoder.Output; -import io.github.dsheirer.instrument.Instrumentable; -import io.github.dsheirer.instrument.tap.Tap; -import io.github.dsheirer.instrument.tap.TapGroup; -import io.github.dsheirer.instrument.tap.stream.BinaryTap; -import io.github.dsheirer.instrument.tap.stream.FloatBufferTap; +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.module.decode.DecoderType; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.real.IFilteredRealBufferListener; -import io.github.dsheirer.sample.real.RealBuffer; - -import java.util.ArrayList; -import java.util.List; +import io.github.dsheirer.sample.buffer.IReusableBufferListener; +import io.github.dsheirer.sample.buffer.ReusableBuffer; /** - * Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate - * floating point samples and output fully framed Fleetsync II messages + * Fleetsync II Decoder */ -public class Fleetsync2Decoder extends Decoder implements IFilteredRealBufferListener, - Instrumentable +public class Fleetsync2Decoder extends Decoder implements IReusableBufferListener, Listener, + SyncDetectProvider { - /* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */ - private static final int sDECIMATED_SAMPLE_RATE = 24000; - - /* Baud or Symbol Rate */ - private static final int sSYMBOL_RATE = 1200; + /* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */ + protected static final int MESSAGE_LENGTH = 537; - /* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */ - private static final int sMESSAGE_LENGTH = 537; - - /* Instrumentation Taps */ - private ArrayList mAvailableTaps; - private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD = - "Tap Point: Bandpass Filter > < FSK2 Decoder"; - private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER = - "Tap Point: FSK2 Decoder > < Message Framer"; - - private FSK2Decoder mFSKDecoder; - private HalfBandFilter_RB_RB mDecimationFilter; - private RealFIRFilter_RB_RB mBandPassFilter; + protected AFSK1200Decoder mAFSK1200Decoder; private MessageFramer mMessageFramer; private Fleetsync2MessageProcessor mMessageProcessor; - - public Fleetsync2Decoder( AliasList aliasList ) - { - mDecimationFilter = new HalfBandFilter_RB_RB( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true ); - - mBandPassFilter = new RealFIRFilter_RB_RB( - Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02f ); - mDecimationFilter.setListener( mBandPassFilter ); + public Fleetsync2Decoder(AFSK1200Decoder afsk1200Decoder, AliasList aliasList) + { + mAFSK1200Decoder = afsk1200Decoder; + mMessageFramer = new MessageFramer(SyncPattern.FLEETSYNC2.getPattern(), MESSAGE_LENGTH); + mAFSK1200Decoder.setMessageFramer(mMessageFramer); + mMessageFramer.setSyncDetectListener(mAFSK1200Decoder); + mMessageProcessor = new Fleetsync2MessageProcessor(aliasList); + mMessageFramer.addMessageListener(mMessageProcessor); + mMessageProcessor.setMessageListener(getMessageListener()); + } - mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE, - sSYMBOL_RATE, Output.INVERTED ); - mBandPassFilter.setListener( mFSKDecoder ); + public Fleetsync2Decoder(AliasList aliasList) + { + this(new AFSK1200Decoder(MESSAGE_LENGTH, false), aliasList); + } - mMessageFramer = new MessageFramer( - SyncPattern.FLEETSYNC2.getPattern(), sMESSAGE_LENGTH ); - mFSKDecoder.setListener( mMessageFramer ); - - mMessageProcessor = new Fleetsync2MessageProcessor( aliasList ); - mMessageFramer.addMessageListener( mMessageProcessor ); - - mMessageProcessor.setMessageListener( getMessageListener() ); - } - public void dispose() { - super.dispose(); - - mBandPassFilter.dispose(); - mDecimationFilter.dispose(); - mFSKDecoder.dispose(); - mMessageFramer.dispose(); - mMessageProcessor.dispose(); + super.dispose(); } - @Override - public DecoderType getDecoderType() - { - return DecoderType.FLEETSYNC2; - } - - @Override - public List getTapGroups() + @Override + public DecoderType getDecoderType() { - if( mAvailableTaps == null ) - { - mAvailableTaps = new ArrayList(); - - TapGroup group = new TapGroup( "Fleetsync 2 Decoder" ); - - group.add( new FloatBufferTap( - INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) ); - group.add( new BinaryTap( - INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) ); - - mAvailableTaps.add( group ); - - mAvailableTaps.addAll( mFSKDecoder.getTapGroups() ); - } - - return mAvailableTaps; + return DecoderType.FLEETSYNC2; } - @Override - public void registerTap( Tap tap ) + @Override + public Listener getReusableBufferListener() { - mFSKDecoder.registerTap( tap ); - - switch( tap.getName() ) - { - case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: - FloatBufferTap bpTap = (FloatBufferTap)tap; - mBandPassFilter.setListener( bpTap ); - bpTap.setListener( mFSKDecoder ); - break; - case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER: - BinaryTap decoderTap = (BinaryTap)tap; - mFSKDecoder.setListener( decoderTap ); - decoderTap.setListener( mMessageFramer ); - break; - } + return mAFSK1200Decoder; } - @Override - public void unregisterTap( Tap tap ) + @Override + public void receive(ReusableBuffer reusableBuffer) { - mFSKDecoder.unregisterTap( tap ); - - switch( tap.getName() ) - { - case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD: - mBandPassFilter.setListener( mFSKDecoder ); - break; - case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER: - mFSKDecoder.setListener( mMessageFramer ); - break; - } + mAFSK1200Decoder.receive(reusableBuffer); } - @Override - public Listener getFilteredRealBufferListener() - { - return mDecimationFilter; - } + @Override + public void reset() + { + mMessageFramer.reset(); + } + + @Override + public void setSyncDetectListener(ISyncDetectListener listener) + { + mMessageFramer.setSyncDetectListener(listener); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java new file mode 100644 index 00000000..e2b845af --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderInstrumented.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +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.sample.buffer.ReusableBuffer; +import javafx.beans.property.SimpleIntegerProperty; + +public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder +{ + public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty(); + + public Fleetsync2DecoderInstrumented(AliasList aliasList) + { + super(new AFSK1200DecoderInstrumented(MESSAGE_LENGTH, + new SampleBufferInstrumented(AFSK1200Decoder.SAMPLES_PER_SYMBOL, AFSK1200Decoder.COARSE_TIMING_GAIN), + new ZeroCrossingErrorDetectorInstrumented(AFSK1200Decoder.SAMPLES_PER_SYMBOL), false), aliasList); + } + + public AFSK1200DecoderInstrumented getAFSK1200Decoder() + { + return (AFSK1200DecoderInstrumented)mAFSK1200Decoder; + } + + @Override + public void receive(ReusableBuffer reusableBuffer) + { + super.receive(reusableBuffer); + + bufferCount.setValue(bufferCount.intValue() + 1); + } +} diff --git a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java index d3da75e8..7dfc7a6e 100644 --- a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java @@ -202,7 +202,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener, mIQFilter = new ComplexFIRFilter2(filterTaps); - mResampler = new RealResampler(sampleRate, mOutputSampleRate); + mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000); mResampler.setListener(new Listener() {