diff --git a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java b/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java index ce3d0bd3..640e6506 100644 --- a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.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 ******************************************************************************/ @@ -23,96 +23,98 @@ import java.util.Arrays; * Circular sample buffer - allocates a buffer and stores samples in a circular * fashion, overwriting older samples with newly arrived samples. Initially * fills buffer with 0-valued samples - * - * Can be used as a delay-type buffer, to delay samples by the 'size' amount - * @author denny * + * Can be used as a delay-type buffer, to delay samples by the 'size' amount + * + * @author denny */ public class FloatCircularBuffer { - float[] mBuffer; - int mBufferPointer = 0; - - public FloatCircularBuffer( int size ) - { - mBuffer = new float[ size ]; - - Arrays.fill( mBuffer, (float)0.0 ); - } - - public int getSize() - { - return mBuffer.length; - } - - public float[] getBuffer() - { - return mBuffer; - } + float[] mBuffer; + int mBufferPointer = 0; - /** - * Puts the new value into the buffer and returns the oldest buffer value - * that it replaced - * @param newValue - * @return - */ - public float get( float newValue ) - { - float oldestSample = mBuffer[ mBufferPointer ]; + /** + * Creates a circular double buffer of the specified size and all entries filled with the specified initial value. + * + * @param size of the buffer + * @param initialFillValue to fill the buffer entries + */ + public FloatCircularBuffer(int size, float initialFillValue) + { + mBuffer = new float[size]; + Arrays.fill(mBuffer, initialFillValue); + } - mBuffer[ mBufferPointer ] = newValue; - - mBufferPointer++; - - if( mBufferPointer >= mBuffer.length ) - { - mBufferPointer = 0; - } - - return oldestSample; - } + /** + * Creates a circular double buffer of the specified size and all entries filled with an initial value of zero. + * + * @param size of the buffer + */ + public FloatCircularBuffer(int size) + { + this(size, 0.0f); + } - /** - * Convenience method to put a new value in the buffer, while discarding - * the old value - */ - public void put( float newValue ) - { - get( newValue ); - } + public float[] getBuffer() + { + return mBuffer; + } - /** - * Returns an array of arraySize, loaded with sampleCount of the - * oldest samples, loaded into the array starting at index 0. - * - * This is useful for fetching an array filled half way with samples to - * use in FFT processing. - * @param arraySize - size of the array (must be larger than sampleCount) - * @param sampleCount - number of the oldest samples in the buffer to load - * @return - */ - public float[] get( int arraySize, int sampleCount ) - { - float[] samples = new float[ arraySize ]; + /** + * Puts the new value into the buffer and returns the oldest buffer value that it replaced + * + * @param newValue to add to the buffer + * @return oldest value that was overwritten by the new value + */ + public float get(float newValue) + { + float oldestSample = mBuffer[mBufferPointer]; - if( arraySize > sampleCount ) - { - int tempCounter = mBufferPointer; + mBuffer[mBufferPointer] = newValue; - for( int x = 0; x < sampleCount; x++ ) - { - samples[ x ] = mBuffer[ tempCounter ]; - - tempCounter++; - - if( tempCounter > mBuffer.length ) - { - tempCounter = 0; - } - } - } - - return samples; - } - + mBufferPointer++; + + if(mBufferPointer >= mBuffer.length) + { + mBufferPointer = 0; + } + + return oldestSample; + } + + public float[] getAll() + { + int valuePointer = 0; + float[] values = new float[mBuffer.length]; + int bufferPointer = mBufferPointer; + + while(valuePointer < values.length) + { + values[valuePointer++] = mBuffer[bufferPointer++]; + + if(bufferPointer >= mBuffer.length) + { + bufferPointer = 0; + } + } + + return values; + } + + /** + * Places the new value into the buffer, overwriting the oldest value + * + * @param newValue to add to the buffer + */ + public void put(float newValue) + { + mBuffer[mBufferPointer] = newValue; + + mBufferPointer++; + + if(mBufferPointer >= mBuffer.length) + { + mBufferPointer = 0; + } + } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java index d0b99908..ff933ed1 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java @@ -5,54 +5,86 @@ import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.text.DecimalFormat; +import java.util.Arrays; + public class RealInterpolator extends Interpolator { - private final static Logger mLog = LoggerFactory.getLogger( RealInterpolator.class ); + private final static Logger mLog = LoggerFactory.getLogger(RealInterpolator.class); - private float mGain; - - public RealInterpolator( float gain ) - { - mGain = gain; - } - /** - * Calculates an interpolated value from the 8 samples beginning at the - * offset index. Uses the mu value to determine which of 128 filter kernels - * to use in order to closely approximate the ideal frequency response for - * the value of mu, ranging from 0.0 to 1.0, indicating the interpolated - * position in the sample set ranging from offset to offset + 7. - * - * @param samples - sample array of length at least offset + 7 - * - * @param mu - interpolated sample position between 0 and 1.0 - * - * @return - interpolated sample value - */ - public float filter( float[] samples, int offset, float mu ) - { - /* Ensure we have enough samples in the array */ - Validate.isTrue(samples.length >= offset + 7); + private float mGain; - /* Identify the filter bank that corresponds to mu */ - int index = (int)( NSTEPS * mu ); - - float accumulator = ( TAPS[ index ][ 7 ] * samples[ offset ] ); - accumulator += ( TAPS[ index ][ 6 ] * samples[ offset + 1 ] ); - accumulator += ( TAPS[ index ][ 5 ] * samples[ offset + 2 ] ); - accumulator += ( TAPS[ index ][ 4 ] * samples[ offset + 3 ] ); - accumulator += ( TAPS[ index ][ 3 ] * samples[ offset + 4 ] ); - accumulator += ( TAPS[ index ][ 2 ] * samples[ offset + 5 ] ); - accumulator += ( TAPS[ index ][ 1 ] * samples[ offset + 6 ] ); - accumulator += ( TAPS[ index ][ 0 ] * samples[ offset + 7 ] ); - - return accumulator * mGain; - } - - public Complex filter( float[] iSamples, float[] qSamples, int offset, float mu ) - { - float i = filter( iSamples, offset, mu ); - float q = filter( qSamples, offset, mu ); - - return new Complex( i, q ); - } + /** + * Provides an interpolated sample point along an eight-sample waveform representation using 128 filters to + * provide approximate interpolation with a resolution of 1/128th of a sample. + * + * @param gain to apply to the interpolated sample + */ + public RealInterpolator(float gain) + { + mGain = gain; + } + + /** + * Calculates an interpolated value from eight samples that start at the offset into the sample array. The + * interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and + * offset+4. The mu argument is translated into an index position between 0 and 128, where a mu of 0.0 will be + * converted to index zero and will be equal to the sample at index offset+3 and a mu of 1.0 will be equal to + * the sample at offset+4. All mu values between 0.0 and 1.0 will be converted to a 1 - 127 index and will + * produce an approximated value from among 127 interpolated sample points between indexes offset+3 and offset+4. + * + * @param samples - sample array of length at least offset + 7 + * @param mu - interpolated sample position between 0.0 and 1.0 + * @return - interpolated sample value + */ + public float filter(float[] samples, int offset, float mu) + { + /* Ensure we have enough samples in the array */ + Validate.isTrue(samples.length >= offset + 7); + + /* Identify the filter bank that corresponds to mu */ + int index = (int)(NSTEPS * mu); + + float accumulator = (TAPS[index][7] * samples[offset]); + accumulator += (TAPS[index][6] * samples[offset + 1]); + accumulator += (TAPS[index][5] * samples[offset + 2]); + accumulator += (TAPS[index][4] * samples[offset + 3]); + accumulator += (TAPS[index][3] * samples[offset + 4]); + accumulator += (TAPS[index][2] * samples[offset + 5]); + accumulator += (TAPS[index][1] * samples[offset + 6]); + accumulator += (TAPS[index][0] * samples[offset + 7]); + + return accumulator * mGain; + } + + public Complex filter(float[] iSamples, float[] qSamples, int offset, float mu) + { + float i = filter(iSamples, offset, mu); + float q = filter(qSamples, offset, mu); + + return new Complex(i, q); + } + + public static void main(String[] args) + { + RealInterpolator interpolator = new RealInterpolator(1.0f); + DecimalFormat decimalFormat = new DecimalFormat("0.0000"); + + double TWO_PI = Math.PI * 2.0; + + float[] samples = new float[16]; + + for(int x = 0; x < 16; x++) + { + samples[x] = (float)Math.sin(TWO_PI * (double)x / 8.0); + } + + mLog.debug("Samples: " + Arrays.toString(samples)); + + for(float x = 0.0f; x <= 1.01f; x += 0.1f) + { + mLog.debug(decimalFormat.format(x) + ": " + decimalFormat.format(interpolator.filter(samples, 1, x))); + } + + } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolPhaseErrorCalculator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolPhaseErrorCalculator.java index c299d122..cf5dd9c6 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolPhaseErrorCalculator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolPhaseErrorCalculator.java @@ -20,11 +20,20 @@ import io.github.dsheirer.sample.complex.Complex; public class DQPSKSymbolPhaseErrorCalculator extends QPSKSymbolPhaseErrorCalculator { //Negative 45 degrees rotation to offset for differential additive 45 degrees of rotation - public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(Math.PI / -4.0d); + public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(Math.PI / 4.0d); @Override public void adjust(Complex symbol) { symbol.multiply(DIFFERENTIAL_OFFSET); } + + @Override + public float getPhaseError(Complex symbol) + { + Complex copy = symbol.copy(); + + copy.multiply(DIFFERENTIAL_OFFSET); + return super.getPhaseError(copy); + } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSymbolBuffer.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSymbolBuffer.java index b949efe2..fa050e5e 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSymbolBuffer.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSymbolBuffer.java @@ -25,33 +25,34 @@ public class InterpolatingSymbolBuffer private float[] mDelayLineInphase; private float[] mDelayLineQuadrature; - private float mMu; //Sample Counter - private float mGainMu = 0.05f; //Sample Counter Gain - private float mOmega; - private float mGainOmega = 0.1f * mGainMu * mGainMu; - private float mOmegaRel = 0.005f; - private float mOmegaMid; + private float mSamplingPoint; //Sample Counter + private float mSampleCounterGain = 0.05f; //Sample Counter Gain + private float mSamplesPerSymbol; + private float mDetectedSamplesPerSymbol; + private float mDetectedSamplesPerSymbolGain = 0.1f * mSampleCounterGain * mSampleCounterGain; + private float mMaxDeviation = 0.005f; private int mDelayLinePointer = 0; - private float mSamplesPerSymbol; private int mTwiceSamplesPerSymbol; private RealInterpolator mInterpolator = new RealInterpolator(1.0f); + private SymbolDecisionData2 mSymbolDecisionData2; public InterpolatingSymbolBuffer(float samplesPerSymbol) { - mSamplesPerSymbol = samplesPerSymbol; - mMu = samplesPerSymbol; + mSamplingPoint = samplesPerSymbol; mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol); mDelayLineInphase = new float[2 * mTwiceSamplesPerSymbol]; mDelayLineQuadrature = new float[2 * mTwiceSamplesPerSymbol]; - mOmega = samplesPerSymbol; - mOmegaMid = samplesPerSymbol; + mDetectedSamplesPerSymbol = samplesPerSymbol; + mSamplesPerSymbol = samplesPerSymbol; + mSymbolDecisionData2 = new SymbolDecisionData2((int)samplesPerSymbol); } public void receive(Complex currentSample) { - mMu--; + mSymbolDecisionData2.receive(currentSample); + mSamplingPoint--; //Fill up the delay line to use with the interpolator mDelayLineInphase[mDelayLinePointer] = currentSample.inphase(); @@ -62,6 +63,7 @@ public class InterpolatingSymbolBuffer //Increment pointer and keep pointer in bounds mDelayLinePointer++; mDelayLinePointer = mDelayLinePointer % mTwiceSamplesPerSymbol; + } /** @@ -70,7 +72,7 @@ public class InterpolatingSymbolBuffer */ public void increaseSampleCounter(float samplesToAdd) { - mMu += samplesToAdd; + mSamplingPoint += samplesToAdd; } /** @@ -81,13 +83,13 @@ public class InterpolatingSymbolBuffer */ public void resetAndAdjust(float symbolTimingError) { - //mOmega is samples per symbol and is constrained to floating between +/- .005 of the nominal samples per + //mDetectedSamplesPerSymbol is samples per symbol and is constrained to floating between +/- .005 of the nominal samples per //symbol - mOmega = mOmega + mGainOmega * -symbolTimingError; - mOmega = mOmegaMid + clip(mOmega - mOmegaMid, mOmegaRel); + mDetectedSamplesPerSymbol = mDetectedSamplesPerSymbol + mDetectedSamplesPerSymbolGain * symbolTimingError; + mDetectedSamplesPerSymbol = mSamplesPerSymbol + clip(mDetectedSamplesPerSymbol - mSamplesPerSymbol, mMaxDeviation); //Add another symbol's worth of samples to the counter and adjust timing based on gardner error - increaseSampleCounter((mOmega + (mGainMu * -symbolTimingError))); + increaseSampleCounter((mDetectedSamplesPerSymbol + (mSampleCounterGain * symbolTimingError))); } /** @@ -95,7 +97,7 @@ public class InterpolatingSymbolBuffer */ public boolean hasSymbol() { - return mMu < 1.0f; + return mSamplingPoint < 1.0f; } /** @@ -105,7 +107,7 @@ public class InterpolatingSymbolBuffer public Complex getMiddleSample() { /* Calculate interpolated middle sample and current sample */ - mMiddleSample.setValues(getInphase(mMu), getQuadrature(mMu)); + mMiddleSample.setValues(getInphase(mSamplingPoint), getQuadrature(mSamplingPoint)); return mMiddleSample; } @@ -116,17 +118,18 @@ public class InterpolatingSymbolBuffer */ public Complex getCurrentSample() { - float half_omega = mOmega / 2.0f; - int half_sps = (int)Math.floor(half_omega); - float half_mu = mMu + half_omega - half_sps; + float halfDetectedSamplesPerSymbol = mDetectedSamplesPerSymbol / 2.0f; + int halfSamplingPointOffset = (int)Math.floor(halfDetectedSamplesPerSymbol); + float halfSamplingPoint = mSamplingPoint + halfDetectedSamplesPerSymbol - halfSamplingPointOffset; - if(half_mu > 1.0) + if(halfSamplingPoint > 1.0) { - half_mu -= 1.0; - half_sps += 1; + halfSamplingPoint -= 1.0; + halfSamplingPointOffset += 1; } - mCurrentSample.setValues(getInphase(half_mu, half_sps), getQuadrature(half_mu, half_sps)); + mCurrentSample.setValues(getInphase(halfSamplingPointOffset, halfSamplingPoint), + getQuadrature(halfSamplingPointOffset, halfSamplingPoint)); return mCurrentSample; } @@ -136,11 +139,10 @@ public class InterpolatingSymbolBuffer * be used to support an external eye-diagram chart. * @return symbol decision data. */ - public SymbolDecisionData getSymbolDecisionData() + public SymbolDecisionData2 getSymbolDecisionData() { - //TODO: I think that delay line pointer needs to have +4 added to it since the filter is 8 taps long - return new SymbolDecisionData(mDelayLineInphase, mDelayLineQuadrature, - mSamplesPerSymbol, mDelayLinePointer, mMu); + mSymbolDecisionData2.setSamplingPoint(mSamplingPoint); + return mSymbolDecisionData2; } /** @@ -155,12 +157,13 @@ public class InterpolatingSymbolBuffer /** * Returns the interpolated inphase value for the specified offset + * @param delayLineOffset offset into the inphase delay line buffer * @param interpolation into the buffer to calculate the interpolated sample * @return inphase value of the interpolated sample */ - public float getInphase(float interpolation, int indexOffet) + public float getInphase(int delayLineOffset, float interpolation) { - return mInterpolator.filter(mDelayLineInphase, mDelayLinePointer + indexOffet, interpolation); + return mInterpolator.filter(mDelayLineInphase, mDelayLinePointer + delayLineOffset, interpolation); } /** @@ -175,12 +178,13 @@ public class InterpolatingSymbolBuffer /** * Returns the interpolated quadrature value for the specified offset + * @param delayLineOffset offset into the quadrature delay line buffer * @param interpolation into the buffer to calculate the interpolated sample * @return quadrature value of the interpolated sample */ - public float getQuadrature(float interpolation, int indexOffset) + public float getQuadrature(int delayLineOffset, float interpolation) { - return mInterpolator.filter(mDelayLineQuadrature, mDelayLinePointer + indexOffset, interpolation); + return mInterpolator.filter(mDelayLineQuadrature, mDelayLinePointer + delayLineOffset, interpolation); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java index 38194543..bb06e223 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java @@ -34,7 +34,7 @@ public class QPSKDemodulator implements ComplexSampleListener private Listener mPLLErrorListener; private Listener mPLLFrequencyListener; private Listener mDibitListener; - private Listener mSymbolDecisionDataListener; + private Listener mSymbolDecisionDataListener; private Complex mPreviousSample = new Complex(0, 0); private Complex mPreviousMiddleSample = new Complex(0, 0); @@ -47,6 +47,7 @@ public class QPSKDemodulator implements ComplexSampleListener private float mSymbolTimingError; private double mSampleRate; + /** * Decoder for Quaternary Phase Shift Keying (QPSK) and Differential QPSK (DQPSK). This decoder uses both a * phase-locked loop and a gardner symbol timing error detector to automatically align to the incoming carrier @@ -129,6 +130,16 @@ public class QPSKDemodulator implements ComplexSampleListener mMiddleSymbol.normalize(); mCurrentSymbol.normalize(); + //Send to an external constellation symbol listener when registered + if(mSymbolListener != null) + { + mSymbolListener.receive(mCurrentSymbol); + } + + //We rotate the symbol from star pattern to polar pattern to facilitate phase error calculation +// mSymbolPhaseErrorCalculator.adjust(mCurrentSymbol); +// mSymbolPhaseErrorCalculator.adjust(mMiddleSymbol); + //Symbol timing error calculations mSymbolTimingError = mGardnerDetector.getError(mPreviousSymbol, mMiddleSymbol, mCurrentSymbol); mInterpolatingSymbolBuffer.resetAndAdjust(mSymbolTimingError); @@ -138,20 +149,12 @@ public class QPSKDemodulator implements ComplexSampleListener mPreviousMiddleSample.setValues(mMiddleSample); mPreviousSymbol.setValues(mCurrentSymbol); - //Send to an external constellation symbol listener when registered - if(mSymbolListener != null) - { - mSymbolListener.receive(mCurrentSymbol); - } - - //Adjust the current symbol as necessary (e.g. for differential encoding) - mSymbolPhaseErrorCalculator.adjust(mCurrentSymbol); //Calculate the phase error of the current symbol relative to the expected constellation and provide //feedback to the PLL mPhaseError = mSymbolPhaseErrorCalculator.getPhaseError(mCurrentSymbol); - mPhaseError = GardnerDetector.clip(mPhaseError, 0.2f); + mPhaseError = GardnerDetector.clip(mPhaseError, 0.15f); mPLL.adjust(mPhaseError); if(mPLLErrorListener != null) @@ -210,7 +213,7 @@ public class QPSKDemodulator implements ComplexSampleListener mPLLFrequencyListener = listener; } - public void setSymbolDecisionDataListener(Listener listener) + public void setSymbolDecisionDataListener(Listener listener) { mSymbolDecisionDataListener = listener; } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData2.java b/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData2.java new file mode 100644 index 00000000..e8bde425 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/psk/SymbolDecisionData2.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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.psk; + +import io.github.dsheirer.buffer.ComplexCircularBuffer; +import io.github.dsheirer.sample.complex.Complex; + +public class SymbolDecisionData2 +{ + private ComplexCircularBuffer mBuffer; + private float mSamplingPoint; + + public SymbolDecisionData2(int samplesPerSymbol) + { + mBuffer = new ComplexCircularBuffer(2 * samplesPerSymbol); + } + + public void receive(float inphase, float quadrature) + { + mBuffer.put(new Complex(inphase, quadrature)); + } + + public void receive(Complex complex) + { + mBuffer.put(complex.copy()); + } + + public void setSamplingPoint(float samplingPoint) + { + mSamplingPoint = samplingPoint; + } + + public float getSamplingPoint() + { + return mSamplingPoint; + } + + public Complex[] getDemodulated() + { + Complex[] samples = mBuffer.getAll(); + + int length = samples.length / 2; + + Complex[] demodulated = new Complex[length]; + + for(int x = 0; x < samples.length / 2; x++) + { + Complex copy = samples[length + x].copy(); + copy.multiply(samples[x].conjugate()); + demodulated[x] = copy; + } + + return demodulated; + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java index 22726f09..76aee9a7 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java @@ -192,7 +192,12 @@ public class CostasLoop implements IPhaseLockedLoop /* Limit frequency to +/- maximum loop frequency */ limitFrequency(); - mErrorBuffer.put(phaseError); +// checkLoopBandwidth(); + } + + private void checkLoopBandwidth() + { + mErrorBuffer.put(mLoopFrequency); double standardDeviation = mErrorBuffer.standardDeviation(); diff --git a/src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java b/src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java index a9f07ad8..d89133b2 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java @@ -22,9 +22,9 @@ public enum Tracking // FINE(150.0, 0.0001, 0.001), // LOCKED(200.0, 0.0, 0.0001); SEARCHING(100.0, 0.4, Math.PI), - COARSE(150.0, 0.2, 0.4), - FINE(175.0, 0.1, 0.2), - LOCKED(200.0, 0.0, 0.1); + COARSE(200.0, 0.2, 0.4), + FINE(400.0, 0.1, 0.2), + LOCKED(600.0, 0.0, 0.1); private double mLoopBandwidth; private double mThresholdMinimum; diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexSampleLineChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexSampleLineChart.java index 13f49d7d..e0dbae54 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexSampleLineChart.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexSampleLineChart.java @@ -37,14 +37,15 @@ public class ComplexSampleLineChart extends LineChart implements Listener> mISamples = FXCollections.observableArrayList(); private ObservableList> mQSamples = FXCollections.observableArrayList(); private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40); - public ComplexSampleLineChart(int length) + public ComplexSampleLineChart(int length, int samplesPerSymbol) { - super(new NumberAxis("Sample Number", 1, length, 2), + super(new NumberAxis("Demodulated Samples", 0, length, 10), new NumberAxis("Value", -1.0, 1.0, 0.25)); LineChart.Series iSampleSeries = new LineChart.Series<>("Inphase", mISamples); @@ -53,10 +54,15 @@ public class ComplexSampleLineChart extends LineChart implements Listener() { @@ -67,6 +73,7 @@ public class ComplexSampleLineChart extends LineChart implements Listener +public class EyeDiagramChart extends LineChart implements Listener { private final static Logger mLog = LoggerFactory.getLogger(EyeDiagramChart.class); @@ -38,9 +37,9 @@ public class EyeDiagramChart extends LineChart implements Listener> series = getSeries(mSeriesPointer++, length + 1); + ObservableList> iSeries = getSeries(mSeriesPointer++, length + 1); if(mSeriesPointer >= mSeriesCount) { mSeriesPointer = 0; } - int index = symbolDecisionData.getSampleIndex(); + ObservableList> qSeries = getSeries(mSeriesPointer++, length + 1); - Complex previous = new Complex(symbolDecisionData.getInphaseSamples()[index], - symbolDecisionData.getQuadratureSamples()[index]); - - for(int x = 1; x <= length; x++) + if(mSeriesPointer >= mSeriesCount) { - Complex current = new Complex(symbolDecisionData.getInphaseSamples()[index + x], - symbolDecisionData.getQuadratureSamples()[index + x]); + mSeriesPointer = 0; + } - double demodulated = FMDemodulator.demodulate(previous, current); - demodulated *= 6; + for(int x = 0; x < length; x++) + { + demodulated[x].normalize(); + Data iDataPoint = iSeries.get(x); + iDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint()); + double iValue = demodulated[x].inphase(); + iDataPoint.setYValue(iValue); - Data dataPoint = series.get(x); - dataPoint.setXValue((double)x - symbolDecisionData.getSampleIndexOffset()); - dataPoint.setYValue(demodulated); - - previous = current; + Data qDataPoint = qSeries.get(x); + qDataPoint.setXValue((double)x + 1 - symbolDecisionData.getSamplingPoint()); + double qValue = demodulated[x].quadrature(); + qDataPoint.setYValue(qValue); } } } diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SymbolChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SymbolChart.java index 70b9d4ce..6f99f042 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SymbolChart.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/SymbolChart.java @@ -56,7 +56,7 @@ public class SymbolChart extends ScatterChart implements Listener } ObservableList data = FXCollections.observableArrayList(); - data.add(new ScatterChart.Series("Current", mCurrentPoints)); + data.add(new ScatterChart.Series("Constellation", mCurrentPoints)); // data.add(new ScatterChart.Series("Previous", mPreviousPoints)); // data.add(new ScatterChart.Series("Oldest", mOldestPoints)); diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1Pane.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1Pane.java index c13f0a92..cb9eb617 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1Pane.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/decoder/P25Phase1Pane.java @@ -56,6 +56,8 @@ public class P25Phase1Pane extends DecoderPane getDecoder().setDEBUGPLLFrequencyListener(getPLLFrequencyLineChart()); getDecoder().setDEBUGSymbolDecisionDataListener(getEyeDiagramChart()); + HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS); + HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS); getChildren().addAll(getSampleChartBox(), getDecoderChartBox()); } @@ -65,6 +67,7 @@ public class P25Phase1Pane extends DecoderPane mLog.debug("Configuring for sample rate: " + sampleRate); mDecoder.setSampleRate(sampleRate); + getSampleLineChart().setSamplesPerSymbol((int)(sampleRate / 4800)); } private P25_C4FMDecoder2 getDecoder() @@ -87,6 +90,7 @@ public class P25Phase1Pane extends DecoderPane if(mDecoderChartBox == null) { mDecoderChartBox = new HBox(); + mDecoderChartBox.setMaxHeight(Double.MAX_VALUE); getSymbolChart().setMaxWidth(Double.MAX_VALUE); getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE); getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE); @@ -104,6 +108,7 @@ public class P25Phase1Pane extends DecoderPane if(mSampleChartBox == null) { mSampleChartBox = new HBox(); + mSampleChartBox.setMaxHeight(Double.MAX_VALUE); getSampleLineChart().setMaxWidth(Double.MAX_VALUE); getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE); HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS); @@ -118,7 +123,7 @@ public class P25Phase1Pane extends DecoderPane { if(mSampleLineChart == null) { - mSampleLineChart = new ComplexSampleLineChart(40); + mSampleLineChart = new ComplexSampleLineChart(100, 10); } return mSampleLineChart; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25_C4FMDecoder2.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25_C4FMDecoder2.java index f18ad3f3..b5ccec57 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25_C4FMDecoder2.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25_C4FMDecoder2.java @@ -26,7 +26,7 @@ import io.github.dsheirer.dsp.psk.IQPSKSymbolDecoder; import io.github.dsheirer.dsp.psk.ISymbolPhaseErrorCalculator; import io.github.dsheirer.dsp.psk.QPSKDemodulator; import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder; -import io.github.dsheirer.dsp.psk.SymbolDecisionData; +import io.github.dsheirer.dsp.psk.SymbolDecisionData2; import io.github.dsheirer.dsp.psk.pll.CostasLoop; import io.github.dsheirer.dsp.psk.pll.Tracking; import io.github.dsheirer.sample.Listener; @@ -64,7 +64,7 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen private Listener mDEBUGSymbolListener; private Listener mDEBUGPLLPhaseErrorListener; private Listener mDEBUGPLLFrequencyListener; - private Listener mDEBUGSymbolDecisionDataListener; + private Listener mDEBUGSymbolDecisionDataListener; public P25_C4FMDecoder2(AliasList aliasList) { @@ -121,7 +121,7 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen mQPSKDemodulator.setPLLFrequencyListener(listener); } - public void setDEBUGSymbolDecisionDataListener(Listener listener) + public void setDEBUGSymbolDecisionDataListener(Listener listener) { mDEBUGSymbolDecisionDataListener = listener; mQPSKDemodulator.setSymbolDecisionDataListener(listener); @@ -131,9 +131,9 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen public void receive(ComplexBuffer complexBuffer) { ComplexBuffer basebandFiltered = mBasebandFilter.filter(complexBuffer); -// ComplexBuffer symbolFiltered = mBasebandFilter.filter(basebandFiltered); + ComplexBuffer symbolFiltered = mSymbolFilter.filter(basebandFiltered); // ComplexBuffer amplified = mAGC.filter(symbolFiltered); - mQPSKDemodulator.receive(basebandFiltered); + mQPSKDemodulator.receive(symbolFiltered); // mQPSKDemodulator.receive(complexBuffer); }