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);
}