diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java
index 50326b13..2dfd2e21 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDemodulator.java
@@ -29,9 +29,10 @@ public class DQPSKDemodulator implements ComplexSampleListener
private final static Logger mLog = LoggerFactory.getLogger(DQPSKDemodulator.class);
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
protected IPhaseLockedLoop mPLL;
- protected IQPSKSymbolDecoder mDQPSKSymbolDecoder;
+ protected IQPSKSymbolDecoder mSymbolDecoder;
private ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator;
- private DQPSKTimingErrorDetector mDQPSKTimingErrorDetector = new DQPSKTimingErrorDetector();
+ private DQPSKTimingErrorDetector mSymbolTimingErrorDetector = new DQPSKTimingErrorDetector();
+ private DQPSKSymbolEvaluator mSymbolEvaluator = new DQPSKSymbolEvaluator();
private Complex mReceivedSample = new Complex(0, 0);
private Complex mPreviousPrecedingSample = new Complex(0, 0);
@@ -61,7 +62,7 @@ public class DQPSKDemodulator implements ComplexSampleListener
{
mPLL = phaseLockedLoop;
mSymbolPhaseErrorCalculator = symbolPhaseErrorCalculator;
- mDQPSKSymbolDecoder = symbolDecoder;
+ mSymbolDecoder = symbolDecoder;
mInterpolatingSampleBuffer = interpolatingSampleBuffer;
}
@@ -127,8 +128,10 @@ public class DQPSKDemodulator implements ComplexSampleListener
mPrecedingSymbol.normalize();
mCurrentSymbol.normalize();
+ mSymbolEvaluator.setSymbol(mCurrentSymbol);
+
//Symbol timing error calculation
- mSymbolTimingError = mDQPSKTimingErrorDetector.getError(mPrecedingSymbol, mCurrentSymbol);
+ mSymbolTimingError = mSymbolTimingErrorDetector.getError(mPrecedingSymbol, mCurrentSymbol);
mSymbolTimingError = clip(mSymbolTimingError, 0.5f);
mInterpolatingSampleBuffer.resetAndAdjust(mSymbolTimingError);
@@ -141,11 +144,13 @@ public class DQPSKDemodulator implements ComplexSampleListener
//feedback to the PLL
mPhaseError = mSymbolPhaseErrorCalculator.getPhaseError(mCurrentSymbol);
+ mLog.debug("Phase error - ORIG:" + mPhaseError + " NEW:" + mSymbolEvaluator.getPhaseError());
+
mPLL.adjust(mPhaseError);
if(mDibitListener != null)
{
- mDibitListener.receive(mDQPSKSymbolDecoder.decode(mCurrentSymbol));
+ mDibitListener.receive(mSymbolDecoder.decode(mCurrentSymbol));
}
}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java
new file mode 100644
index 00000000..3ae2d214
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKSymbolEvaluator.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * 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.dsp.symbol.Dibit;
+import io.github.dsheirer.sample.complex.Complex;
+
+public class DQPSKSymbolEvaluator implements IDQPSKSymbolEvaluator
+{
+ private static final Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * Math.PI / 4.0);
+ private static final Complex ROTATE_FROM_PLUS_45 = Complex.fromAngle(-1.0 * Math.PI / 4.0);
+ private static final Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * Math.PI / 4.0);
+ private static final Complex ROTATE_FROM_MINUS_135 = Complex.fromAngle(3.0 * Math.PI / 4.0);
+
+ private Complex mSymbolError = new Complex(0,0);
+ private float mPhaseError = 0.0f;
+ private float mTimingError = 0.0f;
+ private float mTimingErrorSign = 1.0f;
+ private Dibit mSymbolDecision = Dibit.D00_PLUS_1;
+
+ public DQPSKSymbolEvaluator()
+ {
+ }
+
+ /**
+ * Sets the symbol to be evaluated and calculates the symbol decision and phase and timing errors of the symbol
+ * relative to the four QPSK reference symbol locations. After invoking this method, you can access the phase and
+ * timing errors and the symbol decision via their respective accessor methods.
+ *
+ * Phase and timing error values are calculated by determining the symbol's quadrant and multiplying the symbol
+ * by the complex conjugate of the reference symbol for that quadrant and then deriving the radian angle error
+ * value. Both phase and timing error use this angle error, however the timing error is corrected with the
+ * appropriate sign so that the error value indicates the correct error direction.
+ *
+ * @param complex symbol to be evaluated
+ */
+ @Override
+ public void setSymbol(Complex complex)
+ {
+ mSymbolError.setValues(complex);
+
+ if(mSymbolError.quadrature() > 0.0f)
+ {
+ if(mSymbolError.inphase() > 0.0f)
+ {
+ mSymbolDecision = Dibit.D00_PLUS_1;
+ mSymbolError.multiply(ROTATE_FROM_PLUS_45);
+ }
+ else
+ {
+ mSymbolDecision = Dibit.D01_PLUS_3;
+ mSymbolError.multiply(ROTATE_FROM_PLUS_135);
+ }
+
+ mTimingErrorSign = 1.0f;
+ }
+ else
+ {
+ if(mSymbolError.inphase() > 0.0f)
+ {
+ mSymbolDecision = Dibit.D10_MINUS_1;
+ mSymbolError.multiply(ROTATE_FROM_MINUS_45);
+ }
+ else
+ {
+ mSymbolDecision = Dibit.D11_MINUS_3;
+ mSymbolError.multiply(ROTATE_FROM_MINUS_135);
+ }
+
+ mTimingErrorSign = -1.0f;
+ }
+
+ //Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
+ //arctan of the error angle relative to 0 radians and this provides our error value
+ mPhaseError = -mSymbolError.quadrature();
+
+ //Timing error is the same as phase error with the sign corrected according to the symbol's hemisphere
+ mTimingError = mPhaseError * mTimingErrorSign;
+ }
+
+ /**
+ * Phase error of the symbol relative to the nearest reference symbol.
+ * @return phase error in radians of distance from the reference symbol.
+ */
+ @Override
+ public float getPhaseError()
+ {
+ return mPhaseError;
+ }
+
+ /**
+ * Timing error of the symbol relative to the nearest reference symbol.
+ *
+ * @return timing error in radians of angular distance from the reference symbol recognizing that the symbol
+ * originates at zero radians and rotates toward the intended reference symbol, therefore the error value indicates
+ * if the symbol was sampled early (-) or late (+) relative to the reference symbol.
+ */
+ @Override
+ public float getTimingError()
+ {
+ return mTimingError;
+ }
+
+ /**
+ * Reference symbol that is closest to the transmitted/sampled symbol.
+ */
+ @Override
+ public Dibit getSymbolDecision()
+ {
+ return mSymbolDecision;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java
new file mode 100644
index 00000000..da650c53
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/IDQPSKSymbolEvaluator.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.dsp.symbol.Dibit;
+import io.github.dsheirer.sample.complex.Complex;
+
+
+public interface IDQPSKSymbolEvaluator
+{
+ /**
+ * Sets the sampled symbol to be evaluated. Note: the symbol's gain value MUST be normalized to the unit circle.
+ * @param complex symbol
+ */
+ void setSymbol(Complex complex);
+
+ /**
+ * Phase error of the symbol relative to the closest reference symbol
+ * @return angular phase error in radians
+ */
+ float getPhaseError();
+
+ /**
+ * Timing error of the symbol relative to the closest reference symbol
+ * @return angular timing error in radians where a positive value indicates early sampling and sample timing needs
+ * to be increased and a negative value indicates late sampling where sample timing needs to be decreased.
+ */
+ float getTimingError();
+
+ /**
+ * Dibit decision indicating the reference symbol location closest to the symbol.
+ * @return symbol decision
+ */
+ Dibit getSymbolDecision();
+}