mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
39 - Adds P25/LSM support to the DemodulatorViewerFX application and
adds Feed forward gain support to the C4FM DQPSK demodulator which seems to improve output from the interpolating sample buffer at each symbol period. Updates the symbol timing gain for C4FM to decrease the gain a bit since the feed forward gain is providing a better interpolated symbol.
This commit is contained in:
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,16 @@ import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator<Dibit>
|
||||
{
|
||||
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<Dibit>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<ComplexBuffer>
|
||||
public class ComplexPhaseLineChart extends LineChart implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class);
|
||||
|
||||
@@ -81,7 +81,7 @@ public class ComplexPhaseLineChart extends LineChart implements Listener<Complex
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
|
||||
@@ -95,6 +95,8 @@ public class ComplexPhaseLineChart extends LineChart implements Listener<Complex
|
||||
mComplexCircularBuffer.put(copy);
|
||||
}
|
||||
|
||||
complexBuffer.decrementUserCount();
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ public class EyeDiagramChart extends LineChart implements Listener<SymbolDecisio
|
||||
private int mSeriesPointer;
|
||||
private int mSeriesLength;
|
||||
|
||||
public EyeDiagramChart(int seriesCount)
|
||||
public EyeDiagramChart(int seriesCount, String legend)
|
||||
{
|
||||
super(new NumberAxis("Symbol Timing (5 = center)", 1.0, 10.0, 1.0),
|
||||
super(new NumberAxis("Symbol Timing (" + legend + ")", 1.0, 10.0, 1.0),
|
||||
new NumberAxis("Value", -1.25, 1.25, 0.25));
|
||||
|
||||
mSeriesCount = seriesCount * 2;
|
||||
|
||||
@@ -19,7 +19,7 @@ import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.dsp.fm.FMDemodulator;
|
||||
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.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
@@ -27,7 +27,7 @@ import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FMDemodulatedLineChart extends LineChart implements Listener<ComplexBuffer>
|
||||
public class FMDemodulatedLineChart extends LineChart implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatedLineChart.class);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class FMDemodulatedLineChart extends LineChart implements Listener<Comple
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
|
||||
@@ -97,6 +97,8 @@ public class FMDemodulatedLineChart extends LineChart implements Listener<Comple
|
||||
mPreviousSample = sample;
|
||||
updateChart(demodulated);
|
||||
}
|
||||
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
private void updateChart(double value)
|
||||
|
||||
@@ -156,7 +156,7 @@ public class P25Phase1C4FMPane extends DecoderPane
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10);
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
|
||||
@@ -156,7 +156,7 @@ public class P25Phase1LSMPane extends DecoderPane
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10);
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Middle: 3-4 / Symbol: 8-9");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
@@ -34,11 +35,12 @@ import java.util.Map;
|
||||
public class P25DecoderC4FM extends P25Decoder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderC4FM.class);
|
||||
protected static final float SYMBOL_TIMING_GAIN = 0.1f;
|
||||
protected static final float SAMPLE_COUNTER_GAIN = 0.5f;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
|
||||
private Map<Double,float[]> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<Double,float[]> 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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
|
||||
private Listener<ReusableComplexBuffer> 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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user