diff --git a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java index 64cb1d0f..6c28556e 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/fir/complex/ComplexFIRFilter.java @@ -23,7 +23,7 @@ import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; public class ComplexFIRFilter extends FIRFilter { - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Complex FIR Filter"); private RealFIRFilter mIFilter; private RealFIRFilter mQFilter; @@ -84,13 +84,13 @@ public class ComplexFIRFilter extends FIRFilter */ public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer) { - ReusableComplexBuffer filteredBuffer = mReusableBufferQueue.getBuffer(originalBuffer.getSampleLength()); + ReusableComplexBuffer filteredBuffer = mReusableBufferQueue.getBuffer(originalBuffer.getSamples().length); filteredBuffer.setTimestamp(originalBuffer.getTimestamp()); float[] samples = originalBuffer.getSamples(); float[] filteredSamples = filteredBuffer.getSamples(); - for(int x = 0; x < originalBuffer.getSampleLength(); x += 2) + for(int x = 0; x < originalBuffer.getSamples().length; x += 2) { filteredSamples[x] = filterInphase(samples[x]); filteredSamples[x + 1] = filterQuadrature(samples[x + 1]); diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java index 560655b4..13c54eda 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/ComplexFeedForwardGainControl.java @@ -29,7 +29,7 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener private ComplexSampleListener mListener; private FloatCircularBuffer mEnvelopeHistory; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ComplexFeedForwardGainControl"); private float mMaxEnvelope = 0.0f; private float mGain = 1.0f; @@ -155,11 +155,36 @@ public class ComplexFeedForwardGainControl implements ComplexSampleListener */ public ReusableComplexBuffer filter(ReusableComplexBuffer buffer) { - float[] filtered = filter(buffer.getSamples()); + float[] samples = buffer.getSamples(); - ReusableComplexBuffer filteredBuffer = mReusableBufferQueue.getBuffer(filtered.length); + ReusableComplexBuffer filteredBuffer = mReusableBufferQueue.getBuffer(samples.length); + filteredBuffer.setTimestamp(buffer.getTimestamp()); - filteredBuffer.reloadFrom(filtered, buffer.getTimestamp()); + float[] filtered = filteredBuffer.getSamples(); + + mMaxEnvelope = MINIMUM_ENVELOPE; + + float currentEnvelope; + + for(int x = 0; x < samples.length; x += 2) + { + currentEnvelope = Complex.envelope(samples[x], samples[x + 1]); + + if(currentEnvelope > mMaxEnvelope) + { + mMaxEnvelope = currentEnvelope; + } + } + + adjustGain(); + + for(int x = 0; x < samples.length; x += 2) + { + filtered[x] = samples[x] * mGain; + filtered[x + 1] = samples[x + 1] * mGain; + } + + buffer.decrementUserCount(); filteredBuffer.incrementUserCount(); return filteredBuffer; diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedSymbolEvaluator.java index 077010dc..61c82abc 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedSymbolEvaluator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedSymbolEvaluator.java @@ -25,7 +25,7 @@ public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator 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 Complex mEvaluationSymbol = new Complex(0, 0); private float mPhaseError = 0.0f; private float mTimingError = 0.0f; private float mTimingErrorPolarity = 1.0f; @@ -59,46 +59,46 @@ public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator */ public void setSymbol(Complex preceding, Complex current) { - mSymbolError.setValues(current); + mEvaluationSymbol.setValues(current); - if(mSymbolError.quadrature() > 0.0f) + if(mEvaluationSymbol.quadrature() > 0.0f) { - if(mSymbolError.inphase() > 0.0f) + if(mEvaluationSymbol.inphase() > 0.0f) { mSymbolDecision = Dibit.D00_PLUS_1; mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f); - mSymbolError.multiply(ROTATE_FROM_PLUS_45); + mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_45); } else { mSymbolDecision = Dibit.D01_PLUS_3; mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f); - mSymbolError.multiply(ROTATE_FROM_PLUS_135); + mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_135); } } else { - if(mSymbolError.inphase() > 0.0f) + if(mEvaluationSymbol.inphase() > 0.0f) { mSymbolDecision = Dibit.D10_MINUS_1; mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f); - mSymbolError.multiply(ROTATE_FROM_MINUS_45); + mEvaluationSymbol.multiply(ROTATE_FROM_MINUS_45); } else { mSymbolDecision = Dibit.D11_MINUS_3; mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f); - mSymbolError.multiply(ROTATE_FROM_MINUS_135); + mEvaluationSymbol.multiply(ROTATE_FROM_MINUS_135); } } //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(); + mPhaseError = -mEvaluationSymbol.quadrature(); //Timing error is the same as phase error with the sign corrected according to the vector's angular rotation - mTimingError = mSymbolError.quadrature() * mTimingErrorPolarity; + mTimingError = mEvaluationSymbol.quadrature() * mTimingErrorPolarity; } /** diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerSymbolEvaluator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerSymbolEvaluator.java index e54ae90a..e2e1b544 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerSymbolEvaluator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKGardnerSymbolEvaluator.java @@ -20,10 +20,16 @@ import io.github.dsheirer.sample.complex.Complex; public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator { + 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 float mPhaseError = 0.0f; private float mTimingError = 0.0f; private Dibit mSymbolDecision = Dibit.D00_PLUS_1; private Complex mPreviousSymbol = new Complex(0, 0); + private Complex mEvaluationSymbol = new Complex(0, 0); /** * Differential QPSK Decision-directed symbol phase and timing error detector and symbol decision slicer. @@ -53,40 +59,48 @@ public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator */ public void setSymbols(Complex middle, Complex current) { - /* Gardner timing error calculations */ + //Gardner timing error calculation float errorInphase = (mPreviousSymbol.inphase() - current.inphase()) * middle.inphase(); float errorQuadrature = (mPreviousSymbol.quadrature() - current.quadrature()) * middle.quadrature(); - mTimingError = normalize(errorInphase + errorQuadrature, 1.0f); + mTimingError = normalize(errorInphase + errorQuadrature, .3f); - //Calculate phase error and determine the transmitted dibit from the symbol - if(Math.abs(current.inphase()) > Math.abs(current.quadrature())) + //Store the current symbol to use in the next symbol calculation + mPreviousSymbol.setValues(current); + + //Phase error and symbol decision calculations ... + mEvaluationSymbol.setValues(current); + + if(mEvaluationSymbol.quadrature() > 0.0f) { - if(current.inphase() > 0) + if(mEvaluationSymbol.inphase() > 0.0f) { - mSymbolDecision = Dibit.D10_MINUS_1; - mPhaseError = -current.quadrature(); + mSymbolDecision = Dibit.D00_PLUS_1; + mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_45); } else { mSymbolDecision = Dibit.D01_PLUS_3; - mPhaseError = current.quadrature(); + mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_135); } + } else { - if(current.quadrature() > 0) + if(mEvaluationSymbol.inphase() > 0.0f) { - mSymbolDecision = Dibit.D00_PLUS_1; - mPhaseError = current.inphase(); + mSymbolDecision = Dibit.D10_MINUS_1; + mEvaluationSymbol.multiply(ROTATE_FROM_MINUS_45); } else { mSymbolDecision = Dibit.D11_MINUS_3; - mPhaseError = -current.inphase(); + mEvaluationSymbol.multiply(ROTATE_FROM_MINUS_135); } } - mPreviousSymbol.setValues(current); + //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 = -mEvaluationSymbol.quadrature(); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java index 5b64f338..37ea8fe9 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java @@ -32,7 +32,7 @@ public class InterpolatingSampleBuffer private int mTwiceSamplesPerSymbol; private float mSamplingPoint; - private float mSampleCounterGain = 1.0f; + private float mSampleCounterGain = 0.5f; private float mDetectedSamplesPerSymbol; private float mDetectedSamplesPerSymbolGain = 0.1f * mSampleCounterGain * mSampleCounterGain; private float mMaximumSamplesPerSymbol; @@ -43,9 +43,9 @@ public class InterpolatingSampleBuffer /** * Buffer to store complex sample data and produce interpolated samples. * @param samplesPerSymbol - * @param symbolTimingGain for the symbol timing error adjustments + * @param sampleCounterGain for the symbol timing error adjustments */ - public InterpolatingSampleBuffer(float samplesPerSymbol, float symbolTimingGain) + public InterpolatingSampleBuffer(float samplesPerSymbol, float sampleCounterGain) { mSamplingPoint = samplesPerSymbol; mDetectedSamplesPerSymbol = samplesPerSymbol; @@ -54,7 +54,9 @@ public class InterpolatingSampleBuffer mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol); mDelayLineInphase = new float[2 * mTwiceSamplesPerSymbol]; mDelayLineQuadrature = new float[2 * mTwiceSamplesPerSymbol]; - mDetectedSamplesPerSymbolGain = symbolTimingGain * mSampleCounterGain * mSampleCounterGain; + + mSampleCounterGain = sampleCounterGain; + mDetectedSamplesPerSymbolGain = 0.1f * mSampleCounterGain * mSampleCounterGain; } /** diff --git a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexPhaseLineChart.java b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexPhaseLineChart.java index 68d86efb..3d7694a2 100644 --- a/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexPhaseLineChart.java +++ b/src/main/java/io/github/dsheirer/instrument/gui/viewer/chart/ComplexPhaseLineChart.java @@ -19,7 +19,7 @@ import io.github.dsheirer.buffer.ComplexCircularBuffer; import io.github.dsheirer.dsp.gain.ComplexGain; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.Complex; -import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; @@ -31,7 +31,7 @@ import javafx.scene.chart.NumberAxis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ComplexPhaseLineChart extends LineChart implements Listener +public class ComplexPhaseLineChart extends LineChart implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class); @@ -81,7 +81,7 @@ public class ComplexPhaseLineChart extends LineChart implements Listener +public class FMDemodulatedLineChart extends LineChart implements Listener { private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatedLineChart.class); @@ -73,7 +73,7 @@ public class FMDemodulatedLineChart extends LineChart implements Listener mBasebandFilters = new HashMap<>(); private ComplexFIRFilter mBasebandFilter; @@ -61,7 +63,7 @@ public class P25DecoderC4FM extends P25Decoder mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); - mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN); + mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer); @@ -79,10 +81,13 @@ public class P25DecoderC4FM extends P25Decoder public void receive(ReusableComplexBuffer reusableComplexBuffer) { //User accounting of the incoming buffer is handled by the filter - ReusableComplexBuffer filtered = filter(reusableComplexBuffer); + ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer); + + //User accounting of the incoming buffer is handled by the gain filter + ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered); //User accounting of the filtered buffer is handled by the demodulator - mQPSKDemodulator.receive(filtered); + mQPSKDemodulator.receive(gainApplied); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java index c7b329bc..46762450 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderC4FMInstrumented.java @@ -50,9 +50,6 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM { ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); - //Increment user count before we hand to another listener or return from this method - filtered.incrementUserCount(); - if(mFilteredSymbolListener != null) { filtered.incrementUserCount(); @@ -70,7 +67,7 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM super.setSampleRate(sampleRate); InterpolatingSampleBufferInstrumented instrumentedBuffer = - new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN); + new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); mInterpolatingSampleBuffer = instrumentedBuffer; DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java index 74a8cceb..1fd4c683 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSM.java @@ -34,7 +34,7 @@ import java.util.Map; public class P25DecoderLSM extends P25Decoder { private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class); - protected static final float SYMBOL_TIMING_GAIN = 0.1f; + protected static final float SAMPLE_COUNTER_GAIN = 0.5f; private Map mBasebandFilters = new HashMap<>(); private ComplexFIRFilter mBasebandFilter; @@ -67,7 +67,7 @@ public class P25DecoderLSM extends P25Decoder mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f); mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate()); - mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN); + mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); mQPSKDemodulator = new DQPSKGardnerDemodulator(mCostasLoop, mInterpolatingSampleBuffer); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java index 179d3503..41292d32 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecoderLSMInstrumented.java @@ -52,9 +52,6 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM { ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer); - //Increment user count before we hand to another listener or return from this method - filtered.incrementUserCount(); - if(mFilteredSymbolListener != null) { filtered.incrementUserCount(); @@ -71,7 +68,7 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM { super.setSampleRate(sampleRate); - InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN); + InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN); mInterpolatingSampleBuffer = instrumentedBuffer; DQPSKGardnerDemodulatorInstrumented instrumented = new DQPSKGardnerDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate()); diff --git a/src/main/java/io/github/dsheirer/sample/complex/Complex.java b/src/main/java/io/github/dsheirer/sample/complex/Complex.java index 93c15627..f61f45a5 100644 --- a/src/main/java/io/github/dsheirer/sample/complex/Complex.java +++ b/src/main/java/io/github/dsheirer/sample/complex/Complex.java @@ -248,12 +248,35 @@ public class Complex implements Serializable } /** - * Returns the vector length to 1 (unit circle), - * avoiding square root multiplication + * Maintains the vector magnitude close to 1.0 (unit circle) when the magnitude is already + * close to 1.0, avoiding square root multiplication. If you need a faster way to set the + * magnitude to close to 1.0 without the square root of normalize(), use fastNormalize2(). */ public void fastNormalize() { - multiply((float)(1.95f - magnitudeSquared())); + multiply((float)(1.9999f - magnitudeSquared())); + } + + /** + * Sets vector magnitude to close to 1.0f without the magnitude square root operation + */ + public void normalizeFast() + { + float inphaseAbsolute = Math.abs(inphase()); + float quadratureAbsolute = Math.abs(quadrature()); + + float gain; + + if(inphaseAbsolute > quadratureAbsolute) + { + gain = 1.0f / (inphaseAbsolute + (0.4f * quadratureAbsolute)); + } + else + { + gain = 1.0f / (quadratureAbsolute + (0.4f * inphaseAbsolute)); + } + + multiply(gain); } public float left() @@ -434,17 +457,4 @@ public class Complex implements Serializable return quadratureAbsolute + (0.4f * inphaseAbsolute); } } - - public static void main(String[] args) - { - Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * Math.PI / 4.0); - Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * Math.PI / 4.0); - mLog.debug("REF: " + ROTATE_FROM_MINUS_45.toString()); - - Complex symbol = new Complex(0.7665009f, -0.6422432f); - mLog.debug("SYM: " + symbol.toString()); - - symbol.multiply(ROTATE_FROM_MINUS_45); - mLog.debug("DER: " + symbol.toString()); - } } diff --git a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java index 0abc2824..256a53f3 100644 --- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java @@ -45,7 +45,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil private Listener mListener; private AudioInputStream mInputStream; private File mFile; - private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("Complex Wave Source"); public ComplexWaveSource(File file) throws IOException {