mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
C4FM now uses SIMD differential decoding and soft sync detection. LSM updated with soft sync detector and retains original Gardner error detector. Adds new QPSK symbol view in the now playing channel tab. Restores PLL error driven tuner Auto-PPM feature. Deletes the demod viewer GUI application and all instrumented decoders Updates DMR to send soft symbols to new symbol view in Now Playing. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -243,6 +243,16 @@ public class BinaryMessage extends BitSet
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current size of the message indicating the quantity of bits that have been added to this message. This value
|
||||
* is less than or equal to the size() value.
|
||||
* @return current size in bits.
|
||||
*/
|
||||
public int currentSize()
|
||||
{
|
||||
return mPointer;
|
||||
}
|
||||
|
||||
public void setSize(int size)
|
||||
{
|
||||
mSize = size;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -69,6 +69,16 @@ public record IntField(int start, int end)
|
||||
return new IntField(start, (start + 3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility constructor method for a field with one four bits of length.
|
||||
* @param start index (inclusive)
|
||||
* @return constructed bit field.
|
||||
*/
|
||||
public static IntField length6(int start)
|
||||
{
|
||||
return new IntField(start, (start + 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility constructor method for a field with one octet of length.
|
||||
* @param start index (inclusive)
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
|
||||
* comparision against the incoming bit stream.
|
||||
@@ -39,6 +35,7 @@ import java.util.List;
|
||||
* of the Long.rotate methods will wrap the MSB around to the LSB and
|
||||
* corrupt the matching value.
|
||||
*/
|
||||
@Deprecated //remove after P25P2 decoder is updated
|
||||
public class MultiSyncPatternMatcher
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MultiSyncPatternMatcher.class);
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 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.
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
@Deprecated //Remove after P25P2 decoder is updated.
|
||||
public class SoftSyncDetector implements ISyncProcessor
|
||||
{
|
||||
private ISyncDetectListener mListener;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package io.github.dsheirer.dsp.filter.design;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
@@ -61,7 +62,26 @@ public class FilterViewer extends Application
|
||||
int filterLength = 31;
|
||||
WindowType windowType = WindowType.BLACKMAN_HARRIS_7;
|
||||
|
||||
float[] taps = FilterFactory.getHighPass(8000, 3000, filterLength, windowType);
|
||||
FIRFilterSpecification specification = FIRFilterSpecification
|
||||
.lowPassBuilder()
|
||||
.sampleRate(12500)
|
||||
.passBandCutoff(5200)
|
||||
.passBandAmplitude(1.0).passBandRipple(0.01) //.01
|
||||
.stopBandAmplitude(0.0).stopBandStart(6500) //6500
|
||||
.stopBandRipple(0.01).build();
|
||||
|
||||
float[] taps = null;
|
||||
|
||||
try
|
||||
{
|
||||
taps = FilterFactory.getTaps(specification);
|
||||
}
|
||||
catch(Exception fde) //FilterDesignException
|
||||
{
|
||||
System.out.println("Error");
|
||||
}
|
||||
|
||||
// float[] taps = FilterFactory.getHighPass(8000, 3000, filterLength, windowType);
|
||||
// float[] taps = FilterFactory.getRootRaisedCosine(50000.0 / 4800.0, 20, 0.2f);
|
||||
|
||||
// taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.Interpolator;
|
||||
import io.github.dsheirer.dsp.filter.interpolator.InterpolatorFactory;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator<Dibit>
|
||||
{
|
||||
protected DQPSKDecisionDirectedSymbolEvaluator mSymbolEvaluator = new DQPSKDecisionDirectedSymbolEvaluator();
|
||||
private Complex mPreviousPrecedingSample = new Complex(0, 0);
|
||||
private Complex mPreviousCurrentSample = new Complex(0, 0);
|
||||
private Complex mPrecedingSample = new Complex(0, 0);
|
||||
private Complex mCurrentSample = new Complex(0, 0);
|
||||
private Complex mPrecedingSymbol = new Complex(0, 0);
|
||||
protected Complex mCurrentSymbol = new Complex(0, 0);
|
||||
|
||||
private final Interpolator mInterpolator = InterpolatorFactory.getInterpolator();
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("+#0.00000;-#0.00000");
|
||||
private float[] mI = new float[15];
|
||||
private float[] mQ = new float[15];
|
||||
|
||||
/**
|
||||
* Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and
|
||||
* a decision-directed DQPSK symbol timing and phase error detector to automatically align to the incoming carrier
|
||||
* frequency and to adjust for any changes in symbol timing.
|
||||
*
|
||||
* This detector is optimized for constant amplitude DQPSK symbols like C4FM.
|
||||
*
|
||||
* @param phaseLockedLoop for tracking carrier frequency error
|
||||
* @param interpolatingSampleBuffer to hold samples for interpolating a symbol
|
||||
*/
|
||||
public DQPSKDecisionDirectedDemodulator(IPhaseLockedLoop phaseLockedLoop, InterpolatingSampleBuffer interpolatingSampleBuffer)
|
||||
{
|
||||
super(interpolatingSampleBuffer, phaseLockedLoop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
super.receive(inphase, quadrature);
|
||||
|
||||
System.arraycopy(mI, 1, mI, 0, mI.length - 1);
|
||||
System.arraycopy(mQ, 1, mQ, 0, mQ.length - 1);
|
||||
|
||||
//Rotate the sample and add to buffer
|
||||
Complex current = new Complex(inphase, quadrature);
|
||||
current.normalizeFast();
|
||||
current.multiply(getPLL().getCurrentVector());
|
||||
mI[mI.length - 1] = current.inphase();
|
||||
mQ[mQ.length - 1] = current.quadrature();
|
||||
float iTemp = mInterpolator.filter(mI, 6, .416f);
|
||||
float qTemp = mInterpolator.filter(mQ, 6, .416f);
|
||||
float iSymbol = (mI[0] * iTemp) - (-mQ[0] * qTemp);
|
||||
float qSymbol = (mI[0] * qTemp) + (iTemp * -mQ[0]);
|
||||
float radians = (float)Math.atan2(iSymbol, qSymbol);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(DECIMAL_FORMAT.format(radians)).append(",");
|
||||
sb.append(DECIMAL_FORMAT.format(((CostasLoop)getPLL()).getLoopFrequency()));
|
||||
// System.out.println(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a symbol from the interpolating buffer
|
||||
*/
|
||||
protected void calculateSymbol()
|
||||
{
|
||||
//Get preceding sample and an interpolated current sample from the interpolating buffer
|
||||
mPrecedingSample = getInterpolatingSampleBuffer().getPrecedingSample();
|
||||
mCurrentSample = getInterpolatingSampleBuffer().getCurrentSample();
|
||||
|
||||
//Differential decode preceding and current symbols by calculating the angular rotation between the previous and
|
||||
//current samples (current sample x complex conjugate of previous sample).
|
||||
|
||||
//Note: preceding symbol is a preceding measurement of the current symbol that is simply used as a reference
|
||||
//point to determine vector rotation to the current symbol -- it is not the true predecessor symbol
|
||||
mPrecedingSymbol.setInphase(Complex.multiplyInphase(mPrecedingSample.inphase(), mPrecedingSample.quadrature(),
|
||||
mPreviousPrecedingSample.inphase(), -mPreviousPrecedingSample.quadrature()));
|
||||
mPrecedingSymbol.setQuadrature(Complex.multiplyQuadrature(mPrecedingSample.inphase(), mPrecedingSample.quadrature(),
|
||||
mPreviousPrecedingSample.inphase(), -mPreviousPrecedingSample.quadrature()));
|
||||
|
||||
mCurrentSymbol.setInphase(Complex.multiplyInphase(mCurrentSample.inphase(), mCurrentSample.quadrature(),
|
||||
mPreviousCurrentSample.inphase(), -mPreviousCurrentSample.quadrature()));
|
||||
mCurrentSymbol.setQuadrature(Complex.multiplyQuadrature(mCurrentSample.inphase(), mCurrentSample.quadrature(),
|
||||
mPreviousCurrentSample.inphase(), -mPreviousCurrentSample.quadrature()));
|
||||
|
||||
//Set gain to unity before we calculate the error value
|
||||
mPrecedingSymbol.normalize();
|
||||
mCurrentSymbol.normalize();
|
||||
|
||||
//Apply symbols to evaluator to calculate phase and timing error and make a symbol decision
|
||||
mSymbolEvaluator.setSymbol(mPrecedingSymbol, mCurrentSymbol);
|
||||
|
||||
//Update the symbol timing error
|
||||
getInterpolatingSampleBuffer().resetAndAdjust(mSymbolEvaluator.getTimingError());
|
||||
|
||||
//Update the costas loop (PLL) with any measured phase error
|
||||
// getPLL().adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f));
|
||||
|
||||
//Store current samples/symbols to use for the next symbol period
|
||||
mPreviousPrecedingSample.setValues(mPrecedingSample);
|
||||
mPreviousCurrentSample.setValues(mCurrentSample);
|
||||
|
||||
broadcast(mSymbolEvaluator.getSymbolDecision());
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class DQPSKDecisionDirectedDemodulatorInstrumented extends DQPSKDecisionDirectedDemodulator
|
||||
{
|
||||
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||
private Listener<Double> mSamplesPerSymbolListener;
|
||||
private Listener<Complex> mComplexSymbolListener;
|
||||
private Listener<Double> mPLLErrorListener;
|
||||
private Listener<Double> mPLLFrequencyListener;
|
||||
private Listener<ComplexSamples> mFilteredGainAppliedComplexBufferListener;
|
||||
private double mSampleRate;
|
||||
|
||||
/**
|
||||
* Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and
|
||||
* a custom DQPSK symbol timing error detector to automatically align to the incoming carrier frequency and to
|
||||
* adjust for any changes in symbol timing.
|
||||
*
|
||||
* This detector is optimized for constant amplitude DQPSK symbols like C4FM.
|
||||
*
|
||||
* @param phaseLockedLoop for tracking carrier frequency error
|
||||
* @param interpolatingSampleBuffer
|
||||
* @param sampleRate of the incoming complex sample stream
|
||||
*/
|
||||
public DQPSKDecisionDirectedDemodulatorInstrumented(IPhaseLockedLoop phaseLockedLoop,
|
||||
InterpolatingSampleBufferInstrumented interpolatingSampleBuffer,
|
||||
double sampleRate)
|
||||
{
|
||||
super(phaseLockedLoop, interpolatingSampleBuffer);
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
if(mFilteredGainAppliedComplexBufferListener != null)
|
||||
{
|
||||
mFilteredGainAppliedComplexBufferListener.receive(complexSamples);
|
||||
}
|
||||
|
||||
super.receive(complexSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent class symbol calculation to capture eye diagram data
|
||||
*/
|
||||
protected void calculateSymbol()
|
||||
{
|
||||
//Eye diagram listener
|
||||
if(mSymbolDecisionDataListener != null)
|
||||
{
|
||||
mSymbolDecisionDataListener.receive(
|
||||
((InterpolatingSampleBufferInstrumented)getInterpolatingSampleBuffer()).getSymbolDecisionData());
|
||||
}
|
||||
|
||||
super.calculateSymbol();
|
||||
|
||||
if(mSamplesPerSymbolListener != null)
|
||||
{
|
||||
mSamplesPerSymbolListener.receive((double)getInterpolatingSampleBuffer().getSamplingPoint());
|
||||
}
|
||||
|
||||
//Send to an external constellation symbol listener when registered
|
||||
if(mComplexSymbolListener != null)
|
||||
{
|
||||
mComplexSymbolListener.receive(mCurrentSymbol);
|
||||
}
|
||||
|
||||
if(mPLLErrorListener != null)
|
||||
{
|
||||
mPLLErrorListener.receive((double)mSymbolEvaluator.getPhaseError());
|
||||
}
|
||||
|
||||
if(mPLLFrequencyListener != null)
|
||||
{
|
||||
double loopFrequency = ((CostasLoop)getPLL()).getLoopFrequency();
|
||||
|
||||
loopFrequency *= mSampleRate / (2.0 * FastMath.PI);
|
||||
|
||||
mPLLFrequencyListener.receive(loopFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive symbol decision data to produce an eye diagram.
|
||||
*/
|
||||
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||
{
|
||||
mSymbolDecisionDataListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||
{
|
||||
mSamplesPerSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive decoded QPSK symbols
|
||||
*/
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mComplexSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setPLLErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLErrorListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLFrequencyListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator<Dibit>
|
||||
{
|
||||
private static final Complex ROTATE_FROM_PLUS_135 = Complex.fromAngle(-3.0 * FastMath.PI / 4.0);
|
||||
private static final Complex ROTATE_FROM_PLUS_45 = Complex.fromAngle(-1.0 * FastMath.PI / 4.0);
|
||||
private static final Complex ROTATE_FROM_MINUS_45 = Complex.fromAngle(1.0 * FastMath.PI / 4.0);
|
||||
private static final Complex ROTATE_FROM_MINUS_135 = Complex.fromAngle(3.0 * FastMath.PI / 4.0);
|
||||
|
||||
private Complex mEvaluationSymbol = new Complex(0, 0);
|
||||
private float mPhaseError = 0.0f;
|
||||
private float mTimingError = 0.0f;
|
||||
private float mTimingErrorPolarity = 1.0f;
|
||||
private Dibit mSymbolDecision = Dibit.D00_PLUS_1;
|
||||
|
||||
/**
|
||||
* Differential QPSK Decision-directed symbol phase and timing error detector and symbol decision slicer.
|
||||
*
|
||||
* Symbol decision is based on the closest reference quadrant for the sampled symbol.
|
||||
*
|
||||
* Phase and timing error are calculated as the angular distance of the sampled symbol from the reference
|
||||
* symbol. Timing error is further corrected to compensate for clock-wise or counter-clock-wise vector rotation
|
||||
* relative to the immediately preceding symbol.
|
||||
*/
|
||||
public DQPSKDecisionDirectedSymbolEvaluator()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preceding and current symbols to be evaluated for phase and timing errors and to determine the
|
||||
* transmitted symbol relative to the closest reference symbol. 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 first determining the symbol and then calculating the phase
|
||||
* and timing errors relative to the reference symbol. The timing error is corrected with the appropriate sign
|
||||
* relative to the angular vector rotation so that the error value indicates the correct error direction.
|
||||
*
|
||||
* @param preceding differential-decoded sample/symbol used to detect rotation - does NOT need to be interpolated
|
||||
* since it's only a reference
|
||||
* @param current symbol to be evaluated - interpolated
|
||||
*/
|
||||
public void setSymbol(Complex preceding, Complex current)
|
||||
{
|
||||
mEvaluationSymbol.setValues(current);
|
||||
|
||||
if(mEvaluationSymbol.quadrature() > 0.0f)
|
||||
{
|
||||
if(mEvaluationSymbol.inphase() > 0.0f)
|
||||
{
|
||||
mSymbolDecision = Dibit.D00_PLUS_1;
|
||||
mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f);
|
||||
mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_45);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSymbolDecision = Dibit.D01_PLUS_3;
|
||||
mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f);
|
||||
mEvaluationSymbol.multiply(ROTATE_FROM_PLUS_135);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mEvaluationSymbol.inphase() > 0.0f)
|
||||
{
|
||||
mSymbolDecision = Dibit.D10_MINUS_1;
|
||||
mTimingErrorPolarity = (preceding.quadrature() > current.quadrature() ? 1.0f : -1.0f);
|
||||
mEvaluationSymbol.multiply(ROTATE_FROM_MINUS_45);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSymbolDecision = Dibit.D11_MINUS_3;
|
||||
mTimingErrorPolarity = (preceding.quadrature() < current.quadrature() ? 1.0f : -1.0f);
|
||||
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
|
||||
float errorNormalized = normalize(mEvaluationSymbol.quadrature(), 0.3f);
|
||||
|
||||
mPhaseError = -errorNormalized;
|
||||
|
||||
//Timing error is the same as phase error with the sign corrected according to the vector's angular rotation
|
||||
mTimingError = errorNormalized * mTimingErrorPolarity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains value to the range of ( -maximum <> maximum )
|
||||
*/
|
||||
private static float clip(float value, float maximum)
|
||||
{
|
||||
if(value > maximum)
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
else if(value < -maximum)
|
||||
{
|
||||
return -maximum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains timing error to +/- the maximum value and corrects any
|
||||
* floating point invalid numbers
|
||||
*/
|
||||
private float normalize(float error, float maximum)
|
||||
{
|
||||
if(Float.isNaN(error))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return clip(error, maximum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class DQPSKGardnerDemodulatorInstrumented extends DQPSKGardnerDemodulator
|
||||
{
|
||||
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||
private Listener<Double> mSamplesPerSymbolListener;
|
||||
private Listener<Complex> mComplexSymbolListener;
|
||||
private Listener<Double> mPLLErrorListener;
|
||||
private Listener<Double> mPLLFrequencyListener;
|
||||
private Listener<ComplexSamples> mFilteredGainAppliedComplexBufferListener;
|
||||
private double mSampleRate;
|
||||
|
||||
/**
|
||||
* Implements a Differential QPSK demodulator using a Costas Loop (PLL) and a Gardner timing error detector.
|
||||
*
|
||||
* This decoder is optimized for P25 Linear Simulcast Modulation (LSM).
|
||||
*
|
||||
* @param phaseLockedLoop for tracking carrier frequency error
|
||||
* @param interpolatingSampleBuffer to hold samples for interpolating a symbol
|
||||
*/
|
||||
public DQPSKGardnerDemodulatorInstrumented(IPhaseLockedLoop phaseLockedLoop,
|
||||
InterpolatingSampleBuffer interpolatingSampleBuffer,
|
||||
double sampleRate)
|
||||
{
|
||||
super(phaseLockedLoop, interpolatingSampleBuffer);
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
if(mFilteredGainAppliedComplexBufferListener != null)
|
||||
{
|
||||
mFilteredGainAppliedComplexBufferListener.receive(complexSamples);
|
||||
}
|
||||
|
||||
super.receive(complexSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent class symbol calculation to capture eye diagram data
|
||||
*/
|
||||
protected void calculateSymbol()
|
||||
{
|
||||
//Eye diagram listener
|
||||
if(mSymbolDecisionDataListener != null)
|
||||
{
|
||||
mSymbolDecisionDataListener.receive(
|
||||
((InterpolatingSampleBufferInstrumented)getInterpolatingSampleBuffer()).getSymbolDecisionData());
|
||||
}
|
||||
|
||||
super.calculateSymbol();
|
||||
|
||||
if(mSamplesPerSymbolListener != null)
|
||||
{
|
||||
mSamplesPerSymbolListener.receive((double)getInterpolatingSampleBuffer().getSamplingPoint());
|
||||
}
|
||||
|
||||
//Send to an external constellation symbol listener when registered
|
||||
if(mComplexSymbolListener != null)
|
||||
{
|
||||
mComplexSymbolListener.receive(mCurrentSymbol);
|
||||
}
|
||||
|
||||
if(mPLLErrorListener != null)
|
||||
{
|
||||
mPLLErrorListener.receive((double)mSymbolEvaluator.getPhaseError());
|
||||
}
|
||||
|
||||
if(mPLLFrequencyListener != null)
|
||||
{
|
||||
double loopFrequency = ((CostasLoop)getPLL()).getLoopFrequency();
|
||||
|
||||
loopFrequency *= mSampleRate / (2.0 * FastMath.PI);
|
||||
|
||||
mPLLFrequencyListener.receive(loopFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive symbol decision data to produce an eye diagram.
|
||||
*/
|
||||
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||
{
|
||||
mSymbolDecisionDataListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||
{
|
||||
mSamplesPerSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive decoded QPSK symbols
|
||||
*/
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mComplexSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setPLLErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLErrorListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLFrequencyListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive complex sample buffers that have been filtered with automatic gain control applied
|
||||
*/
|
||||
public void setFilteredGainAppliedComplexBufferListener(Listener<ComplexSamples> listener)
|
||||
{
|
||||
mFilteredGainAppliedComplexBufferListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class GardnerDetector
|
||||
{
|
||||
private float mErrorInphase;
|
||||
private float mErrorQuadrature;
|
||||
private float mError;
|
||||
|
||||
/**
|
||||
* Gardner detector for recovering symbol timing error.
|
||||
*/
|
||||
public GardnerDetector()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the symbol timing error by evaluating the previous, middle and current symbol instants.
|
||||
*
|
||||
* @param previous symbol
|
||||
* @param middle symbol (ie midway between the previous and current)
|
||||
* @param current symbol
|
||||
* @return symbol timing error
|
||||
*/
|
||||
public float getError(Complex previous, Complex middle, Complex current)
|
||||
{
|
||||
mErrorInphase = (FastMath.abs(previous.inphase()) - FastMath.abs(current.inphase())) * FastMath.abs(middle.inphase());
|
||||
mErrorQuadrature = (FastMath.abs(previous.quadrature()) - FastMath.abs(current.quadrature())) * FastMath.abs(middle.quadrature());
|
||||
mError = normalize(mErrorInphase + mErrorQuadrature, 1.0f);
|
||||
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains timing error to +/- the maximum value and corrects any floating point invalid numbers
|
||||
*/
|
||||
public static float normalize(float error, float maximum)
|
||||
{
|
||||
if(Float.isNaN(error))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return clip(error, maximum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains value to the range of ( -maximum <> maximum )
|
||||
*/
|
||||
public static float clip(float value, float maximum)
|
||||
{
|
||||
if(value > maximum)
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
else if(value < -maximum)
|
||||
{
|
||||
return -maximum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public interface IQPSKSymbolDecoder
|
||||
{
|
||||
/**
|
||||
* Decodes the symbol and converts it into a Dibit
|
||||
* @param symbol to decode
|
||||
* @return decoded dibit
|
||||
*/
|
||||
Dibit decode(Complex symbol);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public interface ISymbolPhaseErrorCalculator
|
||||
{
|
||||
/**
|
||||
* Calculates the phase error of the symbol relative to an alignment for the constellation
|
||||
* @param symbol to calculate error
|
||||
* @return error value
|
||||
*/
|
||||
float getPhaseError(Complex symbol);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InterpolatingSampleBufferInstrumented extends InterpolatingSampleBuffer
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(InterpolatingSampleBufferInstrumented.class);
|
||||
private SymbolDecisionData mSymbolDecisionData;
|
||||
private ComplexSampleListener mSampleListener;
|
||||
private int mBufferLength;
|
||||
|
||||
public InterpolatingSampleBufferInstrumented(float samplesPerSymbol, float symbolTimingGain)
|
||||
{
|
||||
super(samplesPerSymbol, symbolTimingGain);
|
||||
mBufferLength = (int) FastMath.ceil(samplesPerSymbol);
|
||||
mSymbolDecisionData = new SymbolDecisionData(mBufferLength);
|
||||
}
|
||||
|
||||
public void receive(Complex sample)
|
||||
{
|
||||
super.receive(sample);
|
||||
mSymbolDecisionData.receive(sample);
|
||||
|
||||
if(mSampleListener != null)
|
||||
{
|
||||
mSampleListener.receive(sample.inphase(), sample.quadrature());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of the interpolating buffer and the current buffer index and symbol decision offset. This data can
|
||||
* be used to support an external eye-diagram chart.
|
||||
* @return symbol decision data.
|
||||
*/
|
||||
public SymbolDecisionData getSymbolDecisionData()
|
||||
{
|
||||
for(int x = mDelayLinePointer; x < mDelayLinePointer + mBufferLength; x++)
|
||||
{
|
||||
mSymbolDecisionData.receive(mDelayLineInphase[x], mDelayLineQuadrature[x]);
|
||||
}
|
||||
|
||||
mSymbolDecisionData.setSamplingPoint(getSamplingPoint());
|
||||
return mSymbolDecisionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive samples being sent to this buffer. Note: these samples have
|
||||
* already been corrected by the PLL, so this provides an ideal tap point for PLL corrected samples.
|
||||
* @param listener to receive samples.
|
||||
*/
|
||||
public void setSampleListener(ComplexSampleListener listener)
|
||||
{
|
||||
mSampleListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class LSMConstellationDecoder implements IQPSKSymbolDecoder
|
||||
{
|
||||
@Override
|
||||
public Dibit decode(Complex symbol)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class QPSKStarSlicer implements IQPSKSymbolDecoder, Listener<Complex>
|
||||
{
|
||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||
|
||||
/**
|
||||
* Slices a Complex sample representing a phase shifted symbol according to the following constellation pattern:
|
||||
*
|
||||
* 00 +3 | 01 +1
|
||||
* ------|------
|
||||
* 10 -1 | 11 -1
|
||||
*/
|
||||
public QPSKStarSlicer()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
mBroadcaster = null;
|
||||
}
|
||||
|
||||
public void addListener(Listener<Dibit> listener)
|
||||
{
|
||||
mBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<Dibit> listener)
|
||||
{
|
||||
mBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Complex sample)
|
||||
{
|
||||
mBroadcaster.broadcast(decode(sample));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dibit decode(Complex sample)
|
||||
{
|
||||
if(sample.inphase() > 0)
|
||||
{
|
||||
return sample.quadrature() > 0 ? Dibit.D00_PLUS_1 : Dibit.D10_MINUS_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sample.quadrature() > 0 ? Dibit.D01_PLUS_3 : Dibit.D11_MINUS_3;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class QPSKSymbolDecoder implements Listener<Complex>, IQPSKSymbolDecoder
|
||||
{
|
||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||
|
||||
/**
|
||||
* Slices a ComplexSample representing a phase shifted symbol according to
|
||||
* the following constellation pattern:
|
||||
*
|
||||
* \ 00 /
|
||||
* \ /
|
||||
* 01 \/ 10
|
||||
* /\
|
||||
* / \
|
||||
* / 11 \
|
||||
*/
|
||||
public QPSKSymbolDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
mBroadcaster = null;
|
||||
}
|
||||
|
||||
public void addListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
public void removeListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Complex complex )
|
||||
{
|
||||
mBroadcaster.broadcast( decode( complex ) );
|
||||
}
|
||||
|
||||
public Dibit decode(Complex complex )
|
||||
{
|
||||
if( FastMath.abs( complex.inphase() ) > FastMath.abs( complex.quadrature() ) )
|
||||
{
|
||||
if( complex.inphase() > 0 )
|
||||
{
|
||||
return Dibit.D10_MINUS_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Dibit.D01_PLUS_3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( complex.quadrature() > 0 )
|
||||
{
|
||||
return Dibit.D00_PLUS_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Dibit.D11_MINUS_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Calculates the phase error for a QPSK polar-orientation constellation
|
||||
*/
|
||||
public class QPSKSymbolPhaseErrorCalculator implements ISymbolPhaseErrorCalculator
|
||||
{
|
||||
private float mPhaseError;
|
||||
|
||||
@Override
|
||||
public float getPhaseError(Complex symbol)
|
||||
{
|
||||
mPhaseError = 0.0f;
|
||||
|
||||
if(FastMath.abs(symbol.inphase()) > FastMath.abs(symbol.quadrature()))
|
||||
{
|
||||
if(symbol.inphase() > 0)
|
||||
{
|
||||
mPhaseError = -symbol.quadrature();
|
||||
}
|
||||
else
|
||||
{
|
||||
mPhaseError = symbol.quadrature();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(symbol.quadrature() > 0)
|
||||
{
|
||||
mPhaseError = symbol.inphase();
|
||||
}
|
||||
else
|
||||
{
|
||||
mPhaseError = -symbol.inphase();
|
||||
}
|
||||
}
|
||||
|
||||
return mPhaseError;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class SymbolDecisionData
|
||||
{
|
||||
//45 degrees rotation to orient the symbol to a polar axis to make error calculation easy/efficient
|
||||
public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(FastMath.PI / 4.0d);
|
||||
|
||||
private ComplexCircularBuffer mBuffer;
|
||||
private float mSamplingPoint;
|
||||
private float mSamplesPerSymbol;
|
||||
|
||||
/**
|
||||
* Circular buffer for capturing two symbols worth of sample data for use in instrumentation.
|
||||
* @param samplesPerSymbol to size the buffer
|
||||
*/
|
||||
public SymbolDecisionData(float samplesPerSymbol)
|
||||
{
|
||||
mSamplesPerSymbol = samplesPerSymbol;
|
||||
mBuffer = new ComplexCircularBuffer(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the complex sample in the buffer
|
||||
*/
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
mBuffer.put(new Complex(inphase, quadrature));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the complex sample in the buffer
|
||||
*/
|
||||
public void receive(Complex complex)
|
||||
{
|
||||
mBuffer.put(complex.copy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fractional sampling point for the current symbol to use in interpolating the current symbol.
|
||||
*/
|
||||
public void setSamplingPoint(float samplingPoint)
|
||||
{
|
||||
mSamplingPoint = samplingPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fractional sampling point
|
||||
*/
|
||||
public float getSamplingPoint()
|
||||
{
|
||||
return mSamplingPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw samples
|
||||
*/
|
||||
public Complex[] getSamples()
|
||||
{
|
||||
return mBuffer.getAll();
|
||||
}
|
||||
|
||||
public Complex[] getSamples(int length)
|
||||
{
|
||||
return mBuffer.get(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of (FM) demodulated samples representing the current symbol where each current sample is demodulated against
|
||||
* the previous symbol's sample to produce the differential demodulated sample. Samples are in time order.
|
||||
*
|
||||
* Note: differential decoding is on an integral samples-per-symbol basis and does not use fractional interpolation.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.psk;
|
||||
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public enum SymbolInversion
|
||||
{
|
||||
DEGREES_90_PLUS(FastMath.PI / 2.0),
|
||||
DEGREES_90_MINUS(FastMath.PI / -2.0),
|
||||
DEGREES_180(FastMath.PI);
|
||||
|
||||
private double mAngleRadians;
|
||||
|
||||
SymbolInversion(double angleRadians)
|
||||
{
|
||||
mAngleRadians = angleRadians;
|
||||
}
|
||||
|
||||
public double getError()
|
||||
{
|
||||
return mAngleRadians;
|
||||
}
|
||||
}
|
||||
@@ -22,34 +22,30 @@ package io.github.dsheirer.dsp.psk.demod;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating PSK differential demodulators
|
||||
*/
|
||||
public class DifferentialDemodulatorFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DifferentialDemodulatorFactory.class);
|
||||
|
||||
/**
|
||||
* Creates the optimal demodulator using calibration data to select the optimal implementation from scalar and
|
||||
* vector options, or defaults to the scalar implementation if the calibration wasn't executed.
|
||||
* Creates the optimal float output demodulator using calibration data to select the optimal implementation from
|
||||
* scalar and vector options, or defaults to the scalar implementation if the calibration wasn't executed.
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate in Hertz
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static DifferentialDemodulator getDemodulator(double sampleRate, int symbolRate)
|
||||
public static DifferentialDemodulatorFloat getFloatDemodulator(double sampleRate, int symbolRate)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DIFFERENTIAL_DEMODULATOR);
|
||||
|
||||
return switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_64 -> new DifferentialDemodulatorVector64(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_128 -> new DifferentialDemodulatorVector128(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_256 -> new DifferentialDemodulatorVector256(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_512 -> new DifferentialDemodulatorVector512(sampleRate, symbolRate);
|
||||
default -> new DifferentialDemodulatorScalar(sampleRate, symbolRate); //Includes SCALAR case
|
||||
case VECTOR_SIMD_64 -> new DifferentialDemodulatorFloatVector64(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_128 -> new DifferentialDemodulatorFloatVector128(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_256 -> new DifferentialDemodulatorFloatVector256(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_512 -> new DifferentialDemodulatorFloatVector512(sampleRate, symbolRate);
|
||||
default -> new DifferentialDemodulatorFloatScalar(sampleRate, symbolRate); //Includes SCALAR case
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import io.github.dsheirer.dsp.fm.IDemodulator;
|
||||
/**
|
||||
* Differential demodulator base class
|
||||
*/
|
||||
public abstract class DifferentialDemodulator implements IDemodulator
|
||||
public abstract class DifferentialDemodulatorFloat implements IDemodulator
|
||||
{
|
||||
private int mSymbolRate;
|
||||
private float mSampleRate;
|
||||
@@ -37,14 +37,14 @@ public abstract class DifferentialDemodulator implements IDemodulator
|
||||
protected int mBufferOverlap;
|
||||
protected int mInterpolationOffset;
|
||||
protected final Interpolator mInterpolator = InterpolatorFactory.getInterpolator();
|
||||
private DifferentialDemodulatorScalar mScalar;
|
||||
private DifferentialDemodulatorFloatScalar mScalar;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulator(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloat(double sampleRate, int symbolRate)
|
||||
{
|
||||
mSampleRate = (float) sampleRate;
|
||||
mSymbolRate = symbolRate;
|
||||
@@ -64,11 +64,11 @@ public abstract class DifferentialDemodulator implements IDemodulator
|
||||
* Scalar implementation for fallback when the sample buffer size is not compatible with the vector implementation.
|
||||
* @return scalar implementation lazy instantiated when needed.
|
||||
*/
|
||||
protected DifferentialDemodulatorScalar getScalarImplementation()
|
||||
protected DifferentialDemodulatorFloatScalar getScalarImplementation()
|
||||
{
|
||||
if(mScalar == null)
|
||||
{
|
||||
mScalar = new DifferentialDemodulatorScalar(mSampleRate, mSymbolRate);
|
||||
mScalar = new DifferentialDemodulatorFloatScalar(mSampleRate, mSymbolRate);
|
||||
}
|
||||
|
||||
return mScalar;
|
||||
@@ -26,16 +26,16 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Differential demodulator that uses scalar calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DifferentialDemodulatorScalar extends DifferentialDemodulator
|
||||
public class DifferentialDemodulatorFloatScalar extends DifferentialDemodulatorFloat
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorScalar.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorFloatScalar.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulatorScalar(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloatScalar(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -29,9 +29,9 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Differential demodulator that uses Vector SIMD 128 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DifferentialDemodulatorVector128 extends DifferentialDemodulator
|
||||
public class DifferentialDemodulatorFloatVector128 extends DifferentialDemodulatorFloat
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector128.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorFloatVector128.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ public class DifferentialDemodulatorVector128 extends DifferentialDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulatorVector128(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloatVector128(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import jdk.incubator.vector.VectorSpecies;
|
||||
/**
|
||||
* Differential demodulator that uses Vector SIMD calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DifferentialDemodulatorVector256 extends DifferentialDemodulator
|
||||
public class DifferentialDemodulatorFloatVector256 extends DifferentialDemodulatorFloat
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class DifferentialDemodulatorVector256 extends DifferentialDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulatorVector256(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloatVector256(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -29,9 +29,9 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Differential demodulator that uses Vector SIMD 512 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DifferentialDemodulatorVector512 extends DifferentialDemodulator
|
||||
public class DifferentialDemodulatorFloatVector512 extends DifferentialDemodulatorFloat
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector512.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorFloatVector512.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ public class DifferentialDemodulatorVector512 extends DifferentialDemodulator
|
||||
*
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulatorVector512(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloatVector512(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -29,9 +29,9 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Differential demodulator that uses Vector SIMD 64 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DifferentialDemodulatorVector64 extends DifferentialDemodulator
|
||||
public class DifferentialDemodulatorFloatVector64 extends DifferentialDemodulatorFloat
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector64.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorFloatVector64.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ public class DifferentialDemodulatorVector64 extends DifferentialDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DifferentialDemodulatorVector64(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorFloatVector64(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Monitors decode sync events to adaptively control PLL loop gain.
|
||||
*/
|
||||
public class AdaptivePLLBandwidthMonitor implements ISyncDetectListener, IFrequencyErrorProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AdaptivePLLBandwidthMonitor.class);
|
||||
|
||||
private static final int MAX_SYNC_COUNT = PLLBandwidth.BW_200.getRangeEnd();
|
||||
private CostasLoop mCostasLoop;
|
||||
private FeedbackDecoder mFeedbackDecoder;
|
||||
private PLLBandwidth mPLLBandwidth = PLLBandwidth.BW_400;
|
||||
private int mSyncCount;
|
||||
|
||||
/**
|
||||
* Constructs an adaptive monitor to monitor the sync state of a decoder and adjust the gain
|
||||
* level of the costas loop accordingly.
|
||||
*
|
||||
* @param costasLoop to receive adaptive gain updates.
|
||||
* @param feedbackDecoder to rebroadcast frequency error source events
|
||||
*/
|
||||
public AdaptivePLLBandwidthMonitor(CostasLoop costasLoop, FeedbackDecoder feedbackDecoder)
|
||||
{
|
||||
mCostasLoop = costasLoop;
|
||||
mCostasLoop.setPLLBandwidth(mPLLBandwidth);
|
||||
mCostasLoop.setFrequencyErrorProcessor(this);
|
||||
mFeedbackDecoder = feedbackDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync detection event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mSyncCount++;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync loss event. Updates the running sync count and updates the PLL gain level.
|
||||
*/
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
mSyncCount -= 2;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the gain state for a sync detect or sync loss
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
if(mSyncCount < 0)
|
||||
{
|
||||
mSyncCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(mSyncCount > MAX_SYNC_COUNT)
|
||||
{
|
||||
mSyncCount = MAX_SYNC_COUNT;
|
||||
return;
|
||||
}
|
||||
|
||||
PLLBandwidth gain = PLLBandwidth.fromSyncCount(mSyncCount);
|
||||
|
||||
//Only update the costas loop if the gain value changes
|
||||
if(gain != mPLLBandwidth)
|
||||
{
|
||||
mPLLBandwidth = gain;
|
||||
mCostasLoop.setPLLBandwidth(mPLLBandwidth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the monitor and commands the PLL to use the highest gain level.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mSyncCount = 0;
|
||||
mPLLBandwidth = PLLBandwidth.BW_400;
|
||||
mCostasLoop.setPLLBandwidth(mPLLBandwidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processFrequencyError(long frequencyError)
|
||||
{
|
||||
//Only rebroadcast the costas loop frequency error if the gain level has progressed above
|
||||
//a bandwidth of 300, meaning that we have received at least 4 sync events
|
||||
if(mPLLBandwidth != PLLBandwidth.BW_400 && mPLLBandwidth != PLLBandwidth.BW_300)
|
||||
{
|
||||
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Monitors decode sync events to adaptively control frequency correction broadcasts.
|
||||
*/
|
||||
@Deprecated //Remove after P25P2 decoder gets updated
|
||||
public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFrequencyErrorProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FrequencyCorrectionSyncMonitor.class);
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
|
||||
/**
|
||||
* Utility for calculating phase inverted sync pattern derivatives for QPSK constellations
|
||||
*/
|
||||
public class QPSKPhaseInversionCalculator
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
|
||||
//DMR sync patterns
|
||||
// String[] syncs = {"755FD7DF75F7", "DFF57D75DF5D", "7F7D5DD57DFD", "D5D7F77FD757", "77D55F7DFD77",
|
||||
// "5D577F7757FF", "F7FDD5DDFD55", "7DFFD5F55D5F", "D7557F5FF7F5", "DD7FF5D757DD"};
|
||||
|
||||
//P25 Phase 2 sync pattern
|
||||
String[] syncs = {"575D57F7FF"};
|
||||
|
||||
//P25 Phase 1 sync pattern
|
||||
// String[] syncs = {"5575F5FF77FF"};
|
||||
|
||||
|
||||
for(String sync: syncs)
|
||||
{
|
||||
StringBuilder sbNorm = new StringBuilder();
|
||||
StringBuilder sbP90 = new StringBuilder();
|
||||
StringBuilder sbM90 = new StringBuilder();
|
||||
StringBuilder sb180 = new StringBuilder();
|
||||
|
||||
for(char letter: sync.toCharArray())
|
||||
{
|
||||
int value;
|
||||
|
||||
if(Character.isAlphabetic(letter))
|
||||
{
|
||||
value = letter - 55;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = letter - 48;
|
||||
}
|
||||
|
||||
sbNorm.append(letter);
|
||||
sbP90.append(rotate(value, Dibit.Rotation.PLUS90));
|
||||
sbM90.append(rotate(value, Dibit.Rotation.MINUS90));
|
||||
sb180.append(rotate(value, Dibit.Rotation.INVERT));
|
||||
}
|
||||
|
||||
System.out.println("NORMAL: " + sync);
|
||||
System.out.println(" +90: " + sbP90);
|
||||
System.out.println(" -90: " + sbM90);
|
||||
System.out.println(" 180: " + sb180);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the hexadecimal value to an integer, decomposes it into high and low order dibits, rotates the dibits
|
||||
* in the specified direction, and recombines the rotated dibits into a 4-bit value, returned as a hex character.
|
||||
* @param value to rotate
|
||||
* @param direction direction
|
||||
* @return rotated hex character
|
||||
*/
|
||||
public static String rotate(int value, Dibit.Rotation direction)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dibit dibitHigh = getHighDibit(value);
|
||||
Dibit dibitLow = getLowDibit(value);
|
||||
return getHex(dibitHigh.rotate(direction), dibitLow.rotate(direction));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the bit values from the two dibits and converts the integer value to a hex character
|
||||
* @param d1 containing the high order bits
|
||||
* @param d2 containing the low order bits
|
||||
* @return string hex character representing the value of these dibits combined
|
||||
*/
|
||||
public static String getHex(Dibit d1, Dibit d2)
|
||||
{
|
||||
int value = d1.getHighValue() + d2.getLowValue();
|
||||
return Integer.toHexString(value).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dibit that represents the bits in the 0x8 and 0x4 bit positions of the value
|
||||
* @param value
|
||||
* @return a dibit
|
||||
*/
|
||||
public static Dibit getHighDibit(int value)
|
||||
{
|
||||
return Dibit.fromValue((value >> 2) & 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dibit that represents the bits in the 0x2 and 0x1 bit positions of the value
|
||||
* @param value
|
||||
* @return a dibit
|
||||
*/
|
||||
public static Dibit getLowDibit(int value)
|
||||
{
|
||||
return Dibit.fromValue(value & 3);
|
||||
}
|
||||
}
|
||||
@@ -34,14 +34,14 @@ public class PowerMonitor
|
||||
private int mPowerLevelBroadcastCount = 0;
|
||||
private int mPowerLevelBroadcastThreshold;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
private SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1f);
|
||||
private final SinglePoleIirFilter mPowerFilter = new SinglePoleIirFilter(0.1f);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public PowerMonitor()
|
||||
{
|
||||
mPowerLevelBroadcastThreshold = 25000; //Based on a default sample rate of 50 kHz
|
||||
mPowerLevelBroadcastThreshold = 12500; //Based on a default sample rate of 25 kHz
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,36 +54,25 @@ public class PowerMonitor
|
||||
mPowerLevelBroadcastThreshold = sampleRate / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||
* threshold value.
|
||||
* @param inphase complex sample component
|
||||
* @param quadrature complex sample component
|
||||
*/
|
||||
public void process(float inphase, float quadrature)
|
||||
{
|
||||
mPowerLevelBroadcastCount++;
|
||||
|
||||
if(mPowerLevelBroadcastCount > mPowerLevelBroadcastThreshold)
|
||||
{
|
||||
mPowerFilter.filter(inphase * inphase + quadrature * quadrature);
|
||||
}
|
||||
|
||||
if(mPowerLevelBroadcastCount > (mPowerLevelBroadcastThreshold + 10))
|
||||
{
|
||||
mPowerLevelBroadcastCount = 0;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPowerFilter.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes I&Q complex baseband sample buffers.
|
||||
*/
|
||||
public void process(float[] i, float[] q)
|
||||
{
|
||||
for(int x = 0; x < i.length; x++)
|
||||
mPowerLevelBroadcastCount += i.length;
|
||||
|
||||
if(mPowerLevelBroadcastCount > mPowerLevelBroadcastThreshold && i.length >= 10)
|
||||
{
|
||||
process(i[x], q[x]);
|
||||
double iSquared, qSquared;
|
||||
for(int x = 0; x < 10; x++)
|
||||
{
|
||||
iSquared = Math.pow(i[x], 2.0f);
|
||||
qSquared = Math.pow(q[x], 2.0f);
|
||||
mPowerFilter.filter((float)(iSquared + qSquared));
|
||||
}
|
||||
|
||||
mPowerLevelBroadcastCount -= mPowerLevelBroadcastThreshold;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPowerFilter.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -265,15 +265,24 @@ public enum Dibit
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
/**
|
||||
* Number of bit errors between this dibit and the expected dibit argument.
|
||||
* @param dibit to compare
|
||||
* @return 0, 1 or 2 bit errors
|
||||
*/
|
||||
public int getBitErrorFrom(Dibit dibit)
|
||||
{
|
||||
byte a = (byte)0x1B;
|
||||
int xor = getValue() ^ dibit.getValue();
|
||||
|
||||
Dibit d1 = Dibit.parse(a, 0);
|
||||
Dibit d2 = Dibit.parse(a, 1);
|
||||
Dibit d3 = Dibit.parse(a, 2);
|
||||
Dibit d4 = Dibit.parse(a, 3);
|
||||
if(xor == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(xor == 3)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
System.out.println("1: " + d1 + " 2:" + d2 + " 3:" + d3 + " 4:" + d4);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Circular buffer for storing and accessing dibits.
|
||||
*/
|
||||
public class DibitDelayBuffer
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DibitDelayBuffer.class);
|
||||
|
||||
protected Dibit[] mBuffer;
|
||||
protected int mPointer;
|
||||
|
||||
/**
|
||||
* Constructs a dibit delay buffer of the specified length
|
||||
*/
|
||||
public DibitDelayBuffer(int length)
|
||||
{
|
||||
Validate.isTrue(length > 0, "Dibit buffer length must be greater than 0");
|
||||
mBuffer = new Dibit[length];
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer and preloads the buffer with all zeros
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
for(int x = 0; x < mBuffer.length; x++)
|
||||
{
|
||||
mBuffer[x] = Dibit.D00_PLUS_1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of this circular buffer
|
||||
*/
|
||||
public int length()
|
||||
{
|
||||
return mBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dibit at the specified index
|
||||
* @param index to get
|
||||
* @return dibit at the specified index
|
||||
*/
|
||||
public Dibit get(int index)
|
||||
{
|
||||
if(index < 0 || index >= mBuffer.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
|
||||
}
|
||||
|
||||
return mBuffer[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dibit at the specified index
|
||||
* @param index of the dibit
|
||||
* @param dibit to load at the specified index
|
||||
*/
|
||||
public void set(int index, Dibit dibit)
|
||||
{
|
||||
if(index < 0 || index >= mBuffer.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
|
||||
}
|
||||
|
||||
mBuffer[index] = dibit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered view of the internal circular buffer contents.
|
||||
*/
|
||||
public Dibit[] getBuffer()
|
||||
{
|
||||
Dibit[] transferBuffer = new Dibit[mBuffer.length];
|
||||
|
||||
int transferBufferPointer = 0;
|
||||
int bufferPointer = mPointer;
|
||||
|
||||
while(transferBufferPointer < transferBuffer.length)
|
||||
{
|
||||
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return transferBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a view of the buffer from the starting index to the specified length. Note: if the requested
|
||||
* length exceeds the length of this buffer, the contents will be unwrapped from the circular buffer and repeated.
|
||||
* @param start of the view where 0 is the first element.
|
||||
* @param length of the dibit view
|
||||
* @return an array of dibits
|
||||
*/
|
||||
public Dibit[] getBuffer(int start, int length)
|
||||
{
|
||||
Dibit[] transferBuffer = new Dibit[length];
|
||||
|
||||
int transferBufferPointer = 0;
|
||||
int bufferPointer = (mPointer + start) % mBuffer.length;
|
||||
|
||||
while(transferBufferPointer < transferBuffer.length)
|
||||
{
|
||||
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return transferBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a corrected binary message from the dibit buffer
|
||||
*
|
||||
* @param start dibit index where 0 is the oldest dibit and the newest dibit is buffer length - 1
|
||||
* @param dibitLength number of dibits to include in the message, making the message length = dibitLength * 2
|
||||
* @return corrected binary message
|
||||
*/
|
||||
public CorrectedBinaryMessage getMessage(int start, int dibitLength)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(dibitLength * 2);
|
||||
|
||||
int dibitCount = 0;
|
||||
int bufferPointer = (mPointer + start) % mBuffer.length;
|
||||
|
||||
try
|
||||
{
|
||||
while(dibitCount < dibitLength)
|
||||
{
|
||||
Dibit dibit = mBuffer[bufferPointer++];
|
||||
message.add(dibit.getBit1());
|
||||
message.add(dibit.getBit2());
|
||||
dibitCount++;
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(BitSetFullException e)
|
||||
{
|
||||
//This shouldn't happen
|
||||
mLog.error("Bit set full ??", e);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to view the buffer contents with the dibits decoded as an array of zeros and ones.
|
||||
*/
|
||||
public int[] getBufferAsArray()
|
||||
{
|
||||
Dibit[] dibits = getBuffer();
|
||||
|
||||
int[] bits = new int[dibits.length * 2];
|
||||
|
||||
for(int x = 0; x < dibits.length; x++)
|
||||
{
|
||||
if(dibits[x].getBit1())
|
||||
{
|
||||
bits[x * 2] = 1;
|
||||
}
|
||||
if(dibits[x].getBit2())
|
||||
{
|
||||
bits[x * 2 + 1] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the dibit into the internal circular buffer, overwriting the oldest dibit.
|
||||
*/
|
||||
public void put(Dibit dibit)
|
||||
{
|
||||
mBuffer[mPointer++] = dibit;
|
||||
|
||||
if(mPointer >= mBuffer.length)
|
||||
{
|
||||
mPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the dibit into the internal circular buffer, overwriting and returning the
|
||||
* oldest dibit.
|
||||
*/
|
||||
public Dibit getAndPut(Dibit dibit)
|
||||
{
|
||||
Dibit toReturn = mBuffer[mPointer];
|
||||
put(dibit);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the contents of the buffer
|
||||
*/
|
||||
public void log()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int counter = 0;
|
||||
int pointer = mPointer;
|
||||
|
||||
while(counter < mBuffer.length)
|
||||
{
|
||||
sb.append(mBuffer[pointer].getBit1() ? "1" : "0");
|
||||
sb.append(mBuffer[pointer++].getBit2() ? "1" : "0");
|
||||
|
||||
if(pointer >= mBuffer.length)
|
||||
{
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
mLog.debug("BUFFER: " + sb + " Length:" + mBuffer.length);
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,8 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -27,9 +26,8 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class DibitDelayLine
|
||||
{
|
||||
private Dibit[] mDelayLine;
|
||||
protected Dibit[] mDelayLine;
|
||||
public int mPointer;
|
||||
private int mLength;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -37,7 +35,6 @@ public class DibitDelayLine
|
||||
*/
|
||||
public DibitDelayLine(int length)
|
||||
{
|
||||
mLength = length;
|
||||
mDelayLine = new Dibit[length];
|
||||
Arrays.fill(mDelayLine, Dibit.D00_PLUS_1);
|
||||
}
|
||||
@@ -51,7 +48,7 @@ public class DibitDelayLine
|
||||
{
|
||||
Dibit ejected = mDelayLine[mPointer];
|
||||
mDelayLine[mPointer++] = dibit;
|
||||
mPointer %= mLength;
|
||||
mPointer %= mDelayLine.length;
|
||||
return ejected;
|
||||
}
|
||||
|
||||
@@ -72,10 +69,44 @@ public class DibitDelayLine
|
||||
for(Dibit dibit: dibits)
|
||||
{
|
||||
mDelayLine[mPointer++] = dibit;
|
||||
mPointer %= mLength;
|
||||
mPointer %= mDelayLine.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the pointer to correct a dibit stuff/delete.
|
||||
* @param offset to adjust.
|
||||
*/
|
||||
public void adjustPointer(int offset)
|
||||
{
|
||||
mPointer += offset;
|
||||
|
||||
if(mPointer < 0)
|
||||
{
|
||||
mPointer += mDelayLine.length;
|
||||
}
|
||||
else if(mPointer >= mDelayLine.length)
|
||||
{
|
||||
mPointer -= mDelayLine.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the most recently stored Dibit.
|
||||
*/
|
||||
public Dibit getLast()
|
||||
{
|
||||
int pointer = mPointer - 1;
|
||||
|
||||
if(pointer < 0)
|
||||
{
|
||||
pointer += mDelayLine.length;
|
||||
}
|
||||
|
||||
return mDelayLine[pointer];
|
||||
}
|
||||
|
||||
public void log()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -89,7 +120,7 @@ public class DibitDelayLine
|
||||
sb.append(" ");
|
||||
|
||||
pointer++;
|
||||
pointer = pointer % mLength;
|
||||
pointer = pointer % mDelayLine.length;
|
||||
}
|
||||
|
||||
System.out.println(sb);
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BCH_63_16_11 extends ReedSolomon_63_P25
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BCH_63_16_11.class);
|
||||
|
||||
/**
|
||||
* BCH( 63,16,11) decoder
|
||||
*/
|
||||
public BCH_63_16_11()
|
||||
{
|
||||
super(63, 41);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs error detection and correction on the first 63 bits of the
|
||||
* message argument. If the message is correctable, only the first 16 bits
|
||||
* (information bits) are corrected.
|
||||
*
|
||||
* @return - true = success, false = failure
|
||||
*/
|
||||
public BinaryMessage correctNID(BinaryMessage message)
|
||||
{
|
||||
CRC status = CRC.PASSED;
|
||||
|
||||
int[] original = message.toReverseIntegerArray(0, 62);
|
||||
int[] corrected = new int[63];
|
||||
|
||||
boolean irrecoverableErrors = decode(original, corrected);
|
||||
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
message.setCRC(CRC.FAILED_CRC);
|
||||
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
int index = 63 - x - 1;
|
||||
|
||||
if(corrected[index] != original[index])
|
||||
{
|
||||
status = CRC.CORRECTED;
|
||||
|
||||
if(corrected[index] == 1)
|
||||
{
|
||||
message.set(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.clear(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.setCRC(status);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String orig = "0010011000000011010010100000000110000111110011101010001010110000";
|
||||
String error = "0001010100000011010010100000000110000111100011001010001010110000";
|
||||
|
||||
BinaryMessage errorMessage = BinaryMessage.load(error);
|
||||
|
||||
BCH_63_16_11 bch = new BCH_63_16_11();
|
||||
|
||||
mLog.debug("ORIG:" + orig);
|
||||
mLog.debug(" ERR:" + errorMessage.toString());
|
||||
mLog.debug(" ");
|
||||
|
||||
errorMessage = bch.correctNID(errorMessage);
|
||||
|
||||
mLog.debug(" CRC: " + errorMessage.getCRC().name());
|
||||
mLog.debug("CORR:" + errorMessage.toString());
|
||||
mLog.debug("ORIG:" + orig);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -27,7 +27,7 @@ public class ReedSolomon_63_P25 extends BerlekempMassey
|
||||
/**
|
||||
* APCO-25 Reed-Solomon code is generated from a Galois Field(2^6) by the polynomial: a6 + a1 + a0.
|
||||
* In binary, this is expressed as: 1000011 which is reversed to big-endian format for this algorithm
|
||||
* See: TIA 102-BAAA paragraph 5.9 Reed-Solomon Code Generator Matrices
|
||||
* See: TIA 102-BAAA paragraph 4.9 Reed-Solomon Code Generator Matrices
|
||||
*/
|
||||
public static final int[] P25_GENERATOR_POLYNOMIAL = { 1, 1, 0, 0, 0, 0, 1 };
|
||||
|
||||
|
||||
@@ -324,6 +324,13 @@ public abstract class BCH
|
||||
/* add a^(k*2^i)(z^(2^i) mod f) and compute (z^(2^i) mod f)^2 */
|
||||
for (j = z.mDegree; j >= 0; j--)
|
||||
{
|
||||
if(2 * j >= z.mC.length)
|
||||
{
|
||||
//Fail ... set degree to 0 to signal fail
|
||||
outTK.mDegree = 0;
|
||||
return outTK;
|
||||
}
|
||||
|
||||
outTK.mC[j] ^= z.mC[j];
|
||||
z.mC[2 * j] = gf_sqr(z.mC[j]);
|
||||
z.mC[2 * j + 1] = 0;
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 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.
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument;
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
|
||||
{
|
||||
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";
|
||||
@@ -23,7 +23,9 @@ import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
|
||||
import io.github.dsheirer.gui.power.SignalPowerView;
|
||||
import io.github.dsheirer.gui.squelch.NoiseSquelchView;
|
||||
import io.github.dsheirer.gui.symbol.SymbolView;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.module.decode.PrimaryDecoder;
|
||||
import io.github.dsheirer.module.decode.am.AMDecoder;
|
||||
import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
|
||||
@@ -46,13 +48,14 @@ import java.awt.EventQueue;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.net.URL;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -70,7 +73,7 @@ import javax.swing.event.MouseInputAdapter;
|
||||
*/
|
||||
public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ChannelSpectrumPanel.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelSpectrumPanel.class);
|
||||
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||
private final PlaylistManager mPlaylistManager;
|
||||
private ProcessingChain mProcessingChain;
|
||||
@@ -80,13 +83,14 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
private final FrequencyOverlayPanel mFrequencyOverlayPanel;
|
||||
private final SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||
private final SpinnerNumberModel mNoiseFloorSpinnerModel;
|
||||
private final JLabel mEstimatedCarrierOffsetFrequencyLabel;
|
||||
private final JLabel mEstimatedCarrierOffsetFrequencyValueLabel;
|
||||
private boolean mPanelVisible = false;
|
||||
private boolean mDftProcessing = false;
|
||||
private final NoiseSquelchView mNoiseSquelchView;
|
||||
private final SignalPowerView mSignalPowerView;
|
||||
private final SymbolView mSymbolView = new SymbolView();
|
||||
private final JFXPanel mNoiseSquelchPanel;
|
||||
private final JFXPanel mSymbolPanel;
|
||||
private JSplitPane mSplitPane;
|
||||
|
||||
/**
|
||||
@@ -103,13 +107,10 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
|
||||
|
||||
JPanel labelPanel = new JPanel();
|
||||
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][][]", ""));
|
||||
labelPanel.add(new JLabel("Channel Spectrum"));
|
||||
labelPanel.setLayout(new MigLayout("insets 2", "[grow,fill][grow,fill,left][right][][]", ""));
|
||||
labelPanel.add(new JLabel("Channel Spectrum "));
|
||||
|
||||
mEstimatedCarrierOffsetFrequencyLabel = new JLabel("Carrier Offset:");
|
||||
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(false);
|
||||
labelPanel.add(mEstimatedCarrierOffsetFrequencyLabel);
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel = new JLabel("0 Hz");
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel = new JLabel(getPaddedCarrierOffsetLabel(0));
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(false);
|
||||
labelPanel.add(mEstimatedCarrierOffsetFrequencyValueLabel);
|
||||
|
||||
@@ -120,7 +121,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
});
|
||||
JSpinner noiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
|
||||
labelPanel.add(noiseFloorSpinner);
|
||||
labelPanel.add(new JLabel("Spectral Display Noise Floor"));
|
||||
labelPanel.add(new JLabel("Noise Floor"));
|
||||
|
||||
JButton logIndexesButton = new JButton("Log Settings");
|
||||
logIndexesButton.addActionListener(e -> {
|
||||
@@ -144,8 +145,8 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
sb.append(" Polyphase Indexes: ").append(indexes);
|
||||
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
|
||||
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
|
||||
mLog.info(sb.toString());
|
||||
mLog.info("Output Processor: " + pcs.getStateDescription());
|
||||
LOGGER.info(sb.toString());
|
||||
LOGGER.info("Output Processor: " + pcs.getStateDescription());
|
||||
}
|
||||
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
|
||||
{
|
||||
@@ -153,11 +154,11 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
|
||||
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
|
||||
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
|
||||
mLog.info(sb.toString());
|
||||
LOGGER.info(sb.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
|
||||
LOGGER.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -194,18 +195,32 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
fftPanel.add(layeredPanel);
|
||||
|
||||
mNoiseSquelchPanel = new JFXPanel();
|
||||
mSymbolPanel = new JFXPanel();
|
||||
|
||||
//Spin noise squelch panel construction off onto the JavafX UI thread.
|
||||
Platform.runLater(() -> {
|
||||
Scene scene = new Scene(mNoiseSquelchView);
|
||||
mNoiseSquelchPanel.setScene(scene);
|
||||
Scene scene2 = new Scene(mSymbolView);
|
||||
URL resource = getClass().getResource("/sdrtrunk_style.css");
|
||||
|
||||
if(resource != null)
|
||||
{
|
||||
scene2.getStylesheets().add(resource.toExternalForm());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
|
||||
}
|
||||
|
||||
mSymbolPanel.setScene(scene2);
|
||||
});
|
||||
|
||||
mSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
mSplitPane.add(fftPanel, JSplitPane.LEFT);
|
||||
mSplitPane.add(mNoiseSquelchPanel, JSplitPane.RIGHT);
|
||||
mSplitPane.setDividerLocation(0.5);
|
||||
add(mSplitPane);
|
||||
// mSplitPane.setDividerLocation(0.5);
|
||||
|
||||
mSampleStreamTapModule.setListener(mComplexDftProcessor);
|
||||
DFTResultsConverter DFTResultsConverter = new ComplexDecibelConverter();
|
||||
@@ -214,6 +229,13 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
mSpectrumPanel.clearSpectrum();
|
||||
}
|
||||
|
||||
private String getPaddedCarrierOffsetLabel(long value)
|
||||
{
|
||||
String paddedValue = StringUtils.leftPad(String.valueOf(value), 5);
|
||||
String paddedText = StringUtils.rightPad(paddedValue + " Hz", 20);
|
||||
return "Carrier Offset: " + paddedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
|
||||
* and turn off the FFT processor when it's not.
|
||||
@@ -229,6 +251,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
mPanelVisible = visible;
|
||||
updateFFTProcessing();
|
||||
mNoiseSquelchView.setShowing(visible);
|
||||
mSymbolView.setShowing(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,10 +306,8 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
|
||||
//has to apply to move the signal to center/baseband
|
||||
EventQueue.invokeLater(() -> {
|
||||
String formattedValue = NumberFormat.getInstance().format(carrierOffsetFrequency);
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setText(formattedValue + " Hz");
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setText(getPaddedCarrierOffsetLabel(carrierOffsetFrequency));
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(true);
|
||||
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(true);
|
||||
});
|
||||
|
||||
mFrequencyOverlayPanel.setEstimatedCarrierOffsetFrequency(carrierOffsetFrequency);
|
||||
@@ -306,8 +327,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
*/
|
||||
private void reset()
|
||||
{
|
||||
mEstimatedCarrierOffsetFrequencyLabel.setEnabled(false);
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setText("0 Hz");
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setText(getPaddedCarrierOffsetLabel(0));
|
||||
mEstimatedCarrierOffsetFrequencyValueLabel.setEnabled(false);
|
||||
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
|
||||
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
|
||||
@@ -321,10 +341,13 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
@Override
|
||||
public void receive(ProcessingChain processingChain)
|
||||
{
|
||||
//Disconnect the FFT panel
|
||||
//Disconnect the previous processing chain.
|
||||
if(mProcessingChain != null)
|
||||
{
|
||||
mNoiseSquelchView.setController(null);
|
||||
mSignalPowerView.setProcessingChain(null);
|
||||
mSymbolView.removeSymbolProvider();
|
||||
mSymbolView.setProtocol("");
|
||||
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
||||
mProcessingChain.removeModule(mSampleStreamTapModule);
|
||||
}
|
||||
@@ -341,34 +364,19 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
PrimaryDecoder primaryDecoder = mProcessingChain.getPrimaryDecoder();
|
||||
if(primaryDecoder instanceof NBFMDecoder nbfmDecoder)
|
||||
{
|
||||
Component rightComponent = mSplitPane.getRightComponent();
|
||||
|
||||
if(rightComponent != mNoiseSquelchPanel)
|
||||
{
|
||||
mSplitPane.remove(rightComponent);
|
||||
mSplitPane.setRightComponent(mNoiseSquelchPanel);
|
||||
}
|
||||
|
||||
setRightComponent(mNoiseSquelchPanel);
|
||||
mNoiseSquelchView.setController(nbfmDecoder);
|
||||
mSignalPowerView.setProcessingChain(null);
|
||||
}
|
||||
else if(primaryDecoder instanceof AMDecoder)
|
||||
{
|
||||
Component rightComponent = mSplitPane.getRightComponent();
|
||||
|
||||
if(rightComponent != mSignalPowerView)
|
||||
{
|
||||
mSplitPane.remove(rightComponent);
|
||||
mSplitPane.setRightComponent(mSignalPowerView);
|
||||
}
|
||||
|
||||
mNoiseSquelchView.setController(null);
|
||||
setRightComponent(mSignalPowerView);
|
||||
mSignalPowerView.setProcessingChain(mProcessingChain);
|
||||
}
|
||||
else
|
||||
else if(primaryDecoder instanceof FeedbackDecoder feedbackDecoder)
|
||||
{
|
||||
mNoiseSquelchView.setController(null);
|
||||
mSignalPowerView.setProcessingChain(null);
|
||||
setRightComponent(mSymbolPanel);
|
||||
mSymbolView.setSymbolProvider(feedbackDecoder);
|
||||
mSymbolView.setProtocol(feedbackDecoder.getProtocolDescription());
|
||||
}
|
||||
|
||||
mProcessingChain.addModule(mSampleStreamTapModule);
|
||||
@@ -392,15 +400,25 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mNoiseSquelchView.setController(null);
|
||||
mSignalPowerView.setProcessingChain(null);
|
||||
}
|
||||
|
||||
updateFFTProcessing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the component on the right side of the split pane.
|
||||
* @param component to show.
|
||||
*/
|
||||
private void setRightComponent(Component component)
|
||||
{
|
||||
Component rightComponent = mSplitPane.getRightComponent();
|
||||
|
||||
if(rightComponent != component)
|
||||
{
|
||||
mSplitPane.remove(rightComponent);
|
||||
mSplitPane.setRightComponent(component);
|
||||
mSplitPane.setDividerLocation(0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor for source event stream to capture power level and squelch related source events.
|
||||
@@ -410,7 +428,7 @@ public class ChannelSpectrumPanel extends JPanel implements Listener<ProcessingC
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_CARRIER_OFFSET_FREQUENCY)
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED)
|
||||
{
|
||||
updateEstimatedCarrierOffsetFrequency(sourceEvent.getValue().longValue());
|
||||
}
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Application for viewing and working with demodulators and recording files.
|
||||
*/
|
||||
public class DemodulatorViewerFX extends Application
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DemodulatorViewerFX.class);
|
||||
|
||||
private static final String PREFERENCE_FILE_OPEN_DEFAULT_FOLDER = "default.folder";
|
||||
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(DemodulatorViewerFX.class);
|
||||
private Stage mStage;
|
||||
private MenuBar mMenuBar;
|
||||
private RecentFilesMenu mRecentFilesMenu;
|
||||
private ViewerDesktop mViewerDesktop;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception
|
||||
{
|
||||
mStage = primaryStage;
|
||||
setTitle();
|
||||
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.setTop(getMenuBar());
|
||||
borderPane.setCenter(getViewerDesktop());
|
||||
|
||||
Scene scene = new Scene(borderPane, 1800, 900);
|
||||
|
||||
mStage.setScene(scene);
|
||||
mStage.show();
|
||||
}
|
||||
|
||||
private void setTitle()
|
||||
{
|
||||
mStage.setTitle("Demodulator Viewer FX");
|
||||
}
|
||||
|
||||
private void setTitle(DecoderType decoderType)
|
||||
{
|
||||
if(decoderType != null)
|
||||
{
|
||||
mStage.setTitle("Demodulator Viewer FX - " + decoderType.getDisplayString());
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle();
|
||||
}
|
||||
}
|
||||
|
||||
private void setTitle(P25P1Decoder.Modulation modulation)
|
||||
{
|
||||
if(modulation != null)
|
||||
{
|
||||
mStage.setTitle("Demodulator Viewer FX - P25 " + modulation.getLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
setTitle();
|
||||
}
|
||||
}
|
||||
|
||||
private ViewerDesktop getViewerDesktop()
|
||||
{
|
||||
if(mViewerDesktop == null)
|
||||
{
|
||||
mViewerDesktop = new ViewerDesktop();
|
||||
}
|
||||
|
||||
return mViewerDesktop;
|
||||
}
|
||||
|
||||
private MenuBar getMenuBar()
|
||||
{
|
||||
if(mMenuBar == null)
|
||||
{
|
||||
mMenuBar = new MenuBar();
|
||||
|
||||
Menu fileMenu = new Menu("File");
|
||||
MenuItem openMenuItem = new MenuItem("Open ...");
|
||||
openMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open an I/Q recording file");
|
||||
|
||||
String directory = mPreferences.get(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, null);
|
||||
|
||||
if(directory != null)
|
||||
{
|
||||
fileChooser.setInitialDirectory(new File(directory));
|
||||
}
|
||||
|
||||
File file = fileChooser.showOpenDialog(mStage);
|
||||
|
||||
if(file != null)
|
||||
{
|
||||
getViewerDesktop().load(file);
|
||||
getRecentFilesMenu().add(file);
|
||||
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem closeMenuItem = new MenuItem("Close");
|
||||
closeMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
getViewerDesktop().close();
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem exitMenuItem = new MenuItem("Exit");
|
||||
exitMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
Platform.exit();
|
||||
}
|
||||
});
|
||||
|
||||
fileMenu.getItems().addAll(openMenuItem, getRecentFilesMenu(), closeMenuItem, new SeparatorMenuItem(),
|
||||
exitMenuItem);
|
||||
|
||||
mMenuBar.getMenus().add(fileMenu);
|
||||
|
||||
Menu decoderMenu = new Menu("Decoder");
|
||||
|
||||
for(DecoderType decoderType: DecoderType.values())
|
||||
{
|
||||
if(DecoderPaneFactory.isSupported(decoderType))
|
||||
{
|
||||
if(decoderType == DecoderType.P25_PHASE1)
|
||||
{
|
||||
MenuItem c4fmDecoderMenuItem = new MenuItem("P25 Phase 1 C4FM");
|
||||
|
||||
c4fmDecoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.C4FM);
|
||||
setTitle(decoderType);
|
||||
}
|
||||
});
|
||||
|
||||
decoderMenu.getItems().add(c4fmDecoderMenuItem);
|
||||
|
||||
MenuItem lsmDecoderMenuItem = new MenuItem("P25 Phase 1 LSM");
|
||||
|
||||
lsmDecoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
getViewerDesktop().setP25Phase1Decoder(P25P1Decoder.Modulation.CQPSK);
|
||||
setTitle(decoderType);
|
||||
}
|
||||
});
|
||||
|
||||
decoderMenu.getItems().add(lsmDecoderMenuItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuItem decoderMenuItem = new MenuItem(decoderType.getDisplayString());
|
||||
|
||||
decoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
getViewerDesktop().setDecoder(decoderType);
|
||||
setTitle(decoderType);
|
||||
}
|
||||
});
|
||||
|
||||
decoderMenu.getItems().add(decoderMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mMenuBar.getMenus().add(decoderMenu);
|
||||
}
|
||||
|
||||
return mMenuBar;
|
||||
}
|
||||
|
||||
private RecentFilesMenu getRecentFilesMenu()
|
||||
{
|
||||
if(mRecentFilesMenu == null)
|
||||
{
|
||||
mRecentFilesMenu = new RecentFilesMenu("Recent","demodulator.viewer", 5);
|
||||
mRecentFilesMenu.setFileSelectionListener(new RecentFilesMenu.IFileSelectionListener()
|
||||
{
|
||||
@Override
|
||||
public void fileSelected(File file)
|
||||
{
|
||||
getViewerDesktop().load(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mRecentFilesMenu;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.IControllableFileSource;
|
||||
import io.github.dsheirer.source.IFrameLocationListener;
|
||||
import io.github.dsheirer.source.wave.ComplexWaveSource;
|
||||
import io.github.dsheirer.source.wave.RealWaveSource;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.javafx.IconNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PlaybackController extends HBox implements IFrameLocationListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PlaybackController.class);
|
||||
|
||||
private Button mRewindButton;
|
||||
private TextField mPlaybackPositionText;
|
||||
private Button mPlay1Button;
|
||||
private Button mPlay10Button;
|
||||
private Button mPlay30Button;
|
||||
private Button mPlay100Button;
|
||||
private Button mPlay1000Button;
|
||||
private Button mPlay2000Button;
|
||||
private HBox mControlsBox;
|
||||
private Label mFileLabel;
|
||||
private EventHandler<ActionEvent> mPlayEventHandler;
|
||||
private AbstractDecoderPane mSampleRateListener;
|
||||
|
||||
private IControllableFileSource mControllableFileSource;
|
||||
private Broadcaster<float[]> mRealBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<INativeBuffer> mNativeBufferBroadcaster = new Broadcaster<>();
|
||||
|
||||
public PlaybackController()
|
||||
{
|
||||
super(10);
|
||||
|
||||
getChildren().addAll(getControlsBox(), getFileLabel());
|
||||
|
||||
disableControls();
|
||||
}
|
||||
|
||||
public void setSampleRateListener(AbstractDecoderPane decoderPane)
|
||||
{
|
||||
mSampleRateListener = decoderPane;
|
||||
|
||||
if(mSampleRateListener != null && mControllableFileSource != null)
|
||||
{
|
||||
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File file)
|
||||
{
|
||||
if(file != null && file.isFile())
|
||||
{
|
||||
if(ComplexWaveSource.supports(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
mControllableFileSource = new ComplexWaveSource(file);
|
||||
mControllableFileSource.setListener(this);
|
||||
((ComplexWaveSource)mControllableFileSource).setListener(mNativeBufferBroadcaster);
|
||||
mControllableFileSource.open();
|
||||
|
||||
if(mSampleRateListener != null)
|
||||
{
|
||||
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||
}
|
||||
}
|
||||
catch(UnsupportedAudioFileException e)
|
||||
{
|
||||
mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
|
||||
ButtonType.OK);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
|
||||
enableControls();
|
||||
}
|
||||
else if(RealWaveSource.supports(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
mControllableFileSource = new RealWaveSource(file);
|
||||
mControllableFileSource.setListener(this);
|
||||
((RealWaveSource)mControllableFileSource).setListener(mRealBufferBroadcaster);
|
||||
mControllableFileSource.open();
|
||||
|
||||
if(mSampleRateListener != null)
|
||||
{
|
||||
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||
}
|
||||
}
|
||||
catch(UnsupportedAudioFileException e)
|
||||
{
|
||||
mLog.error("Unsupported Audio File Type [" + (file != null ? file.getAbsolutePath() : "null") + "]");
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "The file type is unsupported.", ButtonType.OK);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error opening file [" + (file != null ? file.getAbsolutePath() : "null") + "]", ioe);
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "There was an error opening the file",
|
||||
ButtonType.OK);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
|
||||
enableControls();
|
||||
}
|
||||
|
||||
getFileLabel().setText(file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if(mControllableFileSource != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mControllableFileSource.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error closing file", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
disableControls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener to receive real buffers from this playback
|
||||
*/
|
||||
public void addRealListener(Listener<float[]> listener)
|
||||
{
|
||||
mRealBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving real buffers from this playback
|
||||
*/
|
||||
public void removeRealListener(Listener<float[]> listener)
|
||||
{
|
||||
mRealBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener to receive complex buffers from this playback
|
||||
*/
|
||||
public void addComplexListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mNativeBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving compex buffers from this playback
|
||||
*/
|
||||
public void removeComplexListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mNativeBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all of the playback controls to the specified enabled state.
|
||||
*/
|
||||
private void disableControls()
|
||||
{
|
||||
getRewindButton().setDisable(true);
|
||||
getPlaybackPositionText().setDisable(true);
|
||||
getPlay1Button().setDisable(true);
|
||||
getPlay10Button().setDisable(true);
|
||||
getPlay30Button().setDisable(true);
|
||||
getPlay100Button().setDisable(true);
|
||||
getPlay1000Button().setDisable(true);
|
||||
getPlay2000Button().setDisable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all of the playback controls to the specified enabled state.
|
||||
*/
|
||||
private void enableControls()
|
||||
{
|
||||
getRewindButton().setDisable(false);
|
||||
getPlaybackPositionText().setDisable(false);
|
||||
getPlay1Button().setDisable(false);
|
||||
getPlay10Button().setDisable(false);
|
||||
getPlay30Button().setDisable(false);
|
||||
getPlay100Button().setDisable(false);
|
||||
getPlay1000Button().setDisable(false);
|
||||
getPlay2000Button().setDisable(false);
|
||||
}
|
||||
|
||||
private HBox getControlsBox()
|
||||
{
|
||||
if(mControlsBox == null)
|
||||
{
|
||||
mControlsBox = new HBox();
|
||||
|
||||
mControlsBox.getChildren().addAll(getRewindButton(), getPlaybackPositionText(), getPlay1Button(),
|
||||
getPlay10Button(), getPlay30Button(), getPlay100Button(), getPlay1000Button(), getPlay2000Button());
|
||||
}
|
||||
|
||||
return mControlsBox;
|
||||
}
|
||||
|
||||
|
||||
private Button getRewindButton()
|
||||
{
|
||||
if(mRewindButton == null)
|
||||
{
|
||||
mRewindButton = new Button();
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_BACKWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mRewindButton.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mRewindButton;
|
||||
}
|
||||
|
||||
private Button getPlay1Button()
|
||||
{
|
||||
if(mPlay1Button == null)
|
||||
{
|
||||
mPlay1Button = new Button("1");
|
||||
mPlay1Button.setUserData(1);
|
||||
mPlay1Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay1Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay1Button;
|
||||
}
|
||||
|
||||
private Button getPlay10Button()
|
||||
{
|
||||
if(mPlay10Button == null)
|
||||
{
|
||||
mPlay10Button = new Button("10");
|
||||
mPlay10Button.setUserData(10);
|
||||
mPlay10Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay10Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay10Button;
|
||||
}
|
||||
|
||||
private Button getPlay30Button()
|
||||
{
|
||||
if(mPlay30Button == null)
|
||||
{
|
||||
mPlay30Button = new Button("30");
|
||||
mPlay30Button.setUserData(30);
|
||||
mPlay30Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay30Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay30Button;
|
||||
}
|
||||
|
||||
private Button getPlay100Button()
|
||||
{
|
||||
if(mPlay100Button == null)
|
||||
{
|
||||
mPlay100Button = new Button("100");
|
||||
mPlay100Button.setUserData(100);
|
||||
mPlay100Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay100Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay100Button;
|
||||
}
|
||||
|
||||
private Button getPlay1000Button()
|
||||
{
|
||||
if(mPlay1000Button == null)
|
||||
{
|
||||
mPlay1000Button = new Button("1000");
|
||||
mPlay1000Button.setUserData(1000);
|
||||
mPlay1000Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay1000Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay1000Button;
|
||||
}
|
||||
|
||||
private Button getPlay2000Button()
|
||||
{
|
||||
if(mPlay2000Button == null)
|
||||
{
|
||||
mPlay2000Button = new Button("2000");
|
||||
mPlay2000Button.setUserData(2000);
|
||||
mPlay2000Button.setOnAction(getPlaybackEventHandler());
|
||||
IconNode iconNode = new IconNode(FontAwesome.FAST_FORWARD);
|
||||
iconNode.setIconSize(10);
|
||||
mPlay2000Button.setGraphic(iconNode);
|
||||
}
|
||||
|
||||
return mPlay2000Button;
|
||||
}
|
||||
|
||||
private EventHandler<ActionEvent> getPlaybackEventHandler()
|
||||
{
|
||||
if(mPlayEventHandler == null)
|
||||
{
|
||||
mPlayEventHandler = new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
int count = (int)((Button)event.getSource()).getUserData();
|
||||
|
||||
if(mControllableFileSource != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mControllableFileSource.next(count, true);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error while playing samples");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return mPlayEventHandler;
|
||||
}
|
||||
|
||||
private TextField getPlaybackPositionText()
|
||||
{
|
||||
if(mPlaybackPositionText == null)
|
||||
{
|
||||
mPlaybackPositionText = new TextField("0");
|
||||
mPlaybackPositionText.setAlignment(Pos.CENTER);
|
||||
mPlaybackPositionText.setPrefWidth(80);
|
||||
}
|
||||
|
||||
return mPlaybackPositionText;
|
||||
}
|
||||
|
||||
private Label getFileLabel()
|
||||
{
|
||||
if(mFileLabel == null)
|
||||
{
|
||||
mFileLabel = new Label();
|
||||
}
|
||||
|
||||
return mFileLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frameLocationUpdated(int location)
|
||||
{
|
||||
getPlaybackPositionText().setText(String.valueOf(location));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frameLocationReset()
|
||||
{
|
||||
mLog.info("Frame location reset ... ignoring?");
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
|
||||
import io.github.dsheirer.gui.instrument.decoder.DecoderPaneFactory;
|
||||
import io.github.dsheirer.gui.instrument.decoder.RealDecoderPane;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ViewerDesktop extends BorderPane
|
||||
{
|
||||
private PlaybackController mPlaybackController;
|
||||
private AbstractDecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
|
||||
|
||||
public ViewerDesktop()
|
||||
{
|
||||
setBottom(getPlaybackController());
|
||||
setCenter(getDecoderPane());
|
||||
}
|
||||
|
||||
public void load(File file)
|
||||
{
|
||||
close();
|
||||
getPlaybackController().load(file);
|
||||
}
|
||||
|
||||
public void setDecoder(DecoderType decoderType)
|
||||
{
|
||||
setDecoderPane(DecoderPaneFactory.getDecoderPane(decoderType));
|
||||
}
|
||||
|
||||
public void setP25Phase1Decoder(P25P1Decoder.Modulation modulation)
|
||||
{
|
||||
setDecoderPane(DecoderPaneFactory.getP25P1DecoderPane(modulation));
|
||||
}
|
||||
|
||||
private void setDecoderPane(AbstractDecoderPane decoderPane)
|
||||
{
|
||||
if(getDecoderPane() instanceof RealDecoderPane)
|
||||
{
|
||||
getPlaybackController().removeRealListener(getDecoderPane());
|
||||
}
|
||||
else
|
||||
{
|
||||
getPlaybackController().removeComplexListener(getDecoderPane());
|
||||
}
|
||||
getChildren().remove(getDecoderPane());
|
||||
mDecoderPane = decoderPane;
|
||||
setCenter(getDecoderPane());
|
||||
|
||||
if(getDecoderPane() instanceof RealDecoderPane)
|
||||
{
|
||||
getPlaybackController().addRealListener(getDecoderPane());
|
||||
}
|
||||
else
|
||||
{
|
||||
getPlaybackController().addComplexListener(getDecoderPane());
|
||||
}
|
||||
getPlaybackController().setSampleRateListener(getDecoderPane());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder pane
|
||||
*/
|
||||
public AbstractDecoderPane getDecoderPane()
|
||||
{
|
||||
if(mDecoderPane == null)
|
||||
{
|
||||
mDecoderPane = DecoderPaneFactory.getDefaultPane();
|
||||
}
|
||||
|
||||
return mDecoderPane;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
getPlaybackController().close();
|
||||
}
|
||||
|
||||
private PlaybackController getPlaybackController()
|
||||
{
|
||||
if(mPlaybackController == null)
|
||||
{
|
||||
mPlaybackController = new PlaybackController();
|
||||
}
|
||||
|
||||
return mPlaybackController;
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AFSK1200SampleBufferChart extends LineChart
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200SampleBufferChart.class);
|
||||
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
|
||||
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSamplePoints = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSamplePointSeries = new Series<>("Pointers", mSamplePoints);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSymbolSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mPreviousSymbol = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mPreviousSymbolSeries = new Series<>("Previous Symbol", mPreviousSymbol);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mZeroCrossing = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
|
||||
|
||||
private IInstrumentedAFSK1200Decoder mIInstrumentedAFSK1200Decoder;
|
||||
|
||||
public AFSK1200SampleBufferChart(IInstrumentedAFSK1200Decoder decoder, int length)
|
||||
{
|
||||
super(new NumberAxis("Samples", 0, length, 5),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series> observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
|
||||
mSymbolSamplesSeries, mPreviousSymbolSeries, mZeroCrossingSeries);
|
||||
|
||||
setData(observableList);
|
||||
|
||||
mIInstrumentedAFSK1200Decoder = decoder;
|
||||
decoder.getBufferCountProperty().addListener(new BufferChangeListener());
|
||||
decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
}
|
||||
|
||||
public void setBuffer(boolean[] buffer)
|
||||
{
|
||||
while(mSamples.size() < buffer.length)
|
||||
{
|
||||
Data<Integer,Float> sample = new Data<>(mSamples.size(), 0.0f);
|
||||
mSamples.add(sample);
|
||||
|
||||
//Turn off the data point marker symbols - yes I know this is inefficient
|
||||
for(Data data: mSampleSeries.getData())
|
||||
{
|
||||
StackPane stackPane = (StackPane)data.getNode();
|
||||
stackPane.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
for(int x = 0; x < buffer.length; x++)
|
||||
{
|
||||
Data<Integer,Float> sample = mSamples.get(x);
|
||||
|
||||
sample.setYValue(buffer[x] ? 0.5f : -0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
setBuffer(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLine());
|
||||
|
||||
while(mPreviousSymbol.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.55);
|
||||
mPreviousSymbol.add(sample);
|
||||
}
|
||||
|
||||
while(mZeroCrossing.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.65);
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
while(mSamplePoints.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.75);
|
||||
mSamplePoints.add(sample);
|
||||
}
|
||||
|
||||
while(mSymbolSamples.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.85);
|
||||
mSymbolSamples.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
mSamplePoints.get(0).setXValue(pointer1);
|
||||
mSamplePoints.get(1).setXValue(pointer2);
|
||||
|
||||
float samplesPerSymbol = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
|
||||
boolean symbol = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getLastSymbol();
|
||||
int symbolStart = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSymbolStart();
|
||||
int symbolEnd = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getSymbolEnd();
|
||||
|
||||
float start = pointer1 + samplesPerSymbol - 1;
|
||||
float end = symbolEnd;
|
||||
|
||||
mSymbolSamples.get(0).setXValue(start);
|
||||
mSymbolSamples.get(1).setXValue(end);
|
||||
|
||||
|
||||
mPreviousSymbol.get(0).setXValue(symbolStart);
|
||||
mPreviousSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
mPreviousSymbol.get(1).setXValue(symbolEnd);
|
||||
mPreviousSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
while(mZeroCrossing.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.65);
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
int detectedZeroCrossing = ((Number)newValue).intValue();
|
||||
|
||||
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
// reference--;
|
||||
//
|
||||
// float start = reference - zeroCrossingIdeal;
|
||||
// float end = reference - detectedZeroCrossing;
|
||||
//
|
||||
// mZeroCrossing.get(0).setXValue(start);
|
||||
// mZeroCrossing.get(1).setXValue(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AFSK1200ZeroCrossingErrorDetectorChart extends LineChart
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200ZeroCrossingErrorDetectorChart.class);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mCurrentSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mPreviousSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mDetected = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mDetectedSeries = new Series<>("Detected", mDetected);
|
||||
|
||||
private IInstrumentedAFSK1200Decoder mIInstrumentedAFSK1200Decoder;
|
||||
|
||||
public AFSK1200ZeroCrossingErrorDetectorChart(IInstrumentedAFSK1200Decoder decoder, int length)
|
||||
{
|
||||
super(new NumberAxis("Samples", 0, length, 5),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series> observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
|
||||
mPreviousSampleSeries, mDetectedSeries);
|
||||
setData(observableList);
|
||||
|
||||
mIInstrumentedAFSK1200Decoder = decoder;
|
||||
decoder.getAFSK1200Decoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
decoder.getBufferCountProperty().addListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
updateBuffer();
|
||||
}
|
||||
});
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(x, 0.0f);
|
||||
mCurrentSamples.add(sample);
|
||||
Data<Number,Number> sample2 = new Data<>(x, 0.0f);
|
||||
mPreviousSamples.add(sample2);
|
||||
}
|
||||
|
||||
Data<Number,Number> detected1 = new Data<>(5, -0.25f);
|
||||
mDetected.add(detected1);
|
||||
Data<Number,Number> detected2 = new Data<>(5, -0.75f);
|
||||
mDetected.add(detected2);
|
||||
}
|
||||
|
||||
private void updateBuffer()
|
||||
{
|
||||
boolean[] samples = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
float error = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getError();
|
||||
|
||||
boolean[] samples = mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
}
|
||||
|
||||
if(error == 0.0f)
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(0.0f);
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(mIInstrumentedAFSK1200Decoder.getAFSK1200Decoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ComplexSampleLineChart extends LineChart implements Listener<ComplexSamples>, ComplexSampleListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexSampleLineChart.class);
|
||||
|
||||
private ComplexCircularBuffer mComplexCircularBuffer;
|
||||
private ComplexCircularBuffer mDemodulationCircularBuffer;
|
||||
private ObservableList<XYChart.Data<Integer,Float>> mISamples = FXCollections.observableArrayList();
|
||||
private ObservableList<XYChart.Data<Integer,Float>> mQSamples = FXCollections.observableArrayList();
|
||||
private IntegerProperty mLengthProperty = new SimpleIntegerProperty(40);
|
||||
|
||||
public ComplexSampleLineChart(String label, int length, int samplesPerSymbol)
|
||||
{
|
||||
super(new NumberAxis(label, 0, length, 10),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
LineChart.Series<Integer,Float> iSampleSeries = new LineChart.Series<>("Inphase", mISamples);
|
||||
LineChart.Series<Integer,Float> qSampleSeries = new LineChart.Series<>("Quadrature", mQSamples);
|
||||
ObservableList<XYChart.Series<Integer,Float>> observableList =
|
||||
FXCollections.observableArrayList(iSampleSeries, qSampleSeries);
|
||||
|
||||
setData(observableList);
|
||||
init(length, samplesPerSymbol);
|
||||
}
|
||||
|
||||
public void setSamplesPerSymbol(int samplesPerSymbol)
|
||||
{
|
||||
mDemodulationCircularBuffer = new ComplexCircularBuffer(samplesPerSymbol);
|
||||
}
|
||||
|
||||
private void init(int length, int samplesPerSymbol)
|
||||
{
|
||||
mLengthProperty.addListener(new ChangeListener<Number>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
|
||||
{
|
||||
mComplexCircularBuffer = new ComplexCircularBuffer(newValue.intValue());
|
||||
}
|
||||
});
|
||||
mComplexCircularBuffer = new ComplexCircularBuffer(length);
|
||||
mDemodulationCircularBuffer = new ComplexCircularBuffer(samplesPerSymbol);
|
||||
length().setValue(length);
|
||||
|
||||
for(int x = 1; x <= length; x++)
|
||||
{
|
||||
mISamples.add(new Data(x, 0.0f));
|
||||
mQSamples.add(new Data(x, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the x-axis for displaying samples
|
||||
*/
|
||||
public IntegerProperty length()
|
||||
{
|
||||
return mLengthProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples samples)
|
||||
{
|
||||
for(int x = 0; x < samples.i().length; x++)
|
||||
{
|
||||
receive(samples.i()[x], samples.q()[x]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
Complex sample = new Complex(i,q);
|
||||
sample.normalize();
|
||||
Complex previous = mDemodulationCircularBuffer.get(sample.copy());
|
||||
sample.multiply(previous.conjugate());
|
||||
mComplexCircularBuffer.put(sample);
|
||||
updateChart();
|
||||
}
|
||||
|
||||
private void updateChart()
|
||||
{
|
||||
Complex[] samples = mComplexCircularBuffer.getAll();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Complex sample = samples[x];
|
||||
|
||||
if(sample != null)
|
||||
{
|
||||
sample.normalize();
|
||||
mISamples.get(x).setYValue(sample.inphase());
|
||||
mQSamples.get(x).setYValue(sample.quadrature());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DecodedSymbolChart extends LineChart implements Listener<Boolean>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DecodedSymbolChart.class);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSymbols = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSymbolSeries = new Series<>("Samples", mSymbols);
|
||||
private boolean[] mSymbolBuffer;
|
||||
private int mSymbolBufferPointer;
|
||||
|
||||
public DecodedSymbolChart(int length)
|
||||
{
|
||||
super(new NumberAxis("Symbols", 0, length, 5),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series> observableList = FXCollections.observableArrayList(mSymbolSeries);
|
||||
setData(observableList);
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
Data<Number,Number> symbol = new Data<>(x, -0.5f);
|
||||
mSymbols.add(symbol);
|
||||
}
|
||||
|
||||
mSymbolBuffer = new boolean[length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Boolean symbol)
|
||||
{
|
||||
mSymbolBuffer[mSymbolBufferPointer++] = symbol;
|
||||
mSymbolBufferPointer %= mSymbolBuffer.length;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
private void update()
|
||||
{
|
||||
for(int x = 0; x < mSymbolBuffer.length; x++)
|
||||
{
|
||||
Data<Number,Number> symbol = mSymbols.get(x);
|
||||
|
||||
int offset = x + mSymbolBufferPointer;
|
||||
offset %= mSymbolBuffer.length;
|
||||
|
||||
symbol.setYValue(mSymbolBuffer[offset] ? 0.5f : -0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ValueAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DoubleLineChart extends LineChart implements Listener<Double>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DoubleLineChart.class);
|
||||
|
||||
private DoubleCircularBuffer mDoubleCircularBuffer;
|
||||
private ObservableList<Data<Integer,Double>> mPhaseValues = FXCollections.observableArrayList();
|
||||
|
||||
public DoubleLineChart(String seriesLabel, double min, double max, double tick, int length)
|
||||
{
|
||||
super(new NumberAxis(1, length, 2), new NumberAxis(min, max, tick));
|
||||
|
||||
Series<Integer,Double> phaseSeries = new Series<>(seriesLabel, mPhaseValues);
|
||||
ObservableList<Series<Integer,Double>> observableList = FXCollections.observableArrayList(phaseSeries);
|
||||
|
||||
setData(observableList);
|
||||
init(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum displayable value for the y axis
|
||||
* @param min
|
||||
*/
|
||||
public void setMin(double min)
|
||||
{
|
||||
((ValueAxis)getYAxis()).setLowerBound(min);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum displayable value for the y axis
|
||||
* @param max
|
||||
*/
|
||||
public void setMax(double max)
|
||||
{
|
||||
((ValueAxis)getYAxis()).setUpperBound(max);
|
||||
}
|
||||
|
||||
private void init(int length)
|
||||
{
|
||||
mDoubleCircularBuffer = new DoubleCircularBuffer(length);
|
||||
|
||||
for(int x = 1; x <= length; x++)
|
||||
{
|
||||
mPhaseValues.add(new Data(x, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Double phase)
|
||||
{
|
||||
mDoubleCircularBuffer.put(phase);
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
private void updateChart()
|
||||
{
|
||||
double[] values = mDoubleCircularBuffer.getAll();
|
||||
|
||||
for(int x = 0; x < values.length; x++)
|
||||
{
|
||||
mPhaseValues.get(x).setYValue(values[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EyeDiagramChart extends LineChart implements Listener<SymbolDecisionData>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(EyeDiagramChart.class);
|
||||
|
||||
private ObservableList<Series<Double,Float>> mData = FXCollections.observableArrayList();
|
||||
private int mSeriesCount;
|
||||
private int mSeriesPointer;
|
||||
private int mSeriesLength;
|
||||
|
||||
public EyeDiagramChart(int seriesCount, String legend)
|
||||
{
|
||||
super(new NumberAxis("Sample Point = 3", -1.0, 7.0, 1.0),
|
||||
new NumberAxis("Radians", -5.0, 5.0, 0.5));
|
||||
|
||||
mSeriesCount = seriesCount;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
for(int x = 0; x < mSeriesCount; x++)
|
||||
{
|
||||
ObservableList<Data<Double,Double>> series = FXCollections.observableArrayList();
|
||||
mData.add(new Series(series));
|
||||
}
|
||||
|
||||
setData(mData);
|
||||
}
|
||||
|
||||
private ObservableList<Data<Double,Float>> getSeries(int series, int length)
|
||||
{
|
||||
ObservableList<Data<Double,Float>> seriesData = mData.get(series).getData();
|
||||
|
||||
while(seriesData.size() > length)
|
||||
{
|
||||
seriesData.remove(0);
|
||||
}
|
||||
|
||||
while(seriesData.size() < length)
|
||||
{
|
||||
seriesData.add(new Data<>(0.0,0.0f));
|
||||
}
|
||||
|
||||
return seriesData;
|
||||
}
|
||||
|
||||
private void checkChartLength(int length)
|
||||
{
|
||||
if(mSeriesLength != length)
|
||||
{
|
||||
((NumberAxis)getXAxis()).setUpperBound(length);
|
||||
mSeriesLength = length;
|
||||
}
|
||||
}
|
||||
|
||||
private float[] conditionData(Complex[] samples)
|
||||
{
|
||||
Float previousAngle = null;
|
||||
float[] corrected = new float[samples.length];
|
||||
|
||||
int phaseRolloverIndex = -1;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
corrected[x] = samples[x].angle();
|
||||
|
||||
if(x > 0 && (FastMath.abs(corrected[x] - corrected[x - 1]) > FastMath.PI))
|
||||
{
|
||||
phaseRolloverIndex = x;
|
||||
}
|
||||
}
|
||||
|
||||
if(phaseRolloverIndex >= 0)
|
||||
{
|
||||
for(int x = 0; x < phaseRolloverIndex; x++)
|
||||
{
|
||||
if(corrected[x] > 0)
|
||||
{
|
||||
corrected[x] -= 2 * FastMath.PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
corrected[x] += 2 * FastMath.PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return corrected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SymbolDecisionData symbolDecisionData)
|
||||
{
|
||||
Complex[] samples = symbolDecisionData.getSamples();
|
||||
int length = samples.length;
|
||||
|
||||
ObservableList<Data<Double,Float>> iSeries = getSeries(mSeriesPointer++, length);
|
||||
// checkChartLength(length);
|
||||
|
||||
if(mSeriesPointer >= mSeriesCount)
|
||||
{
|
||||
mSeriesPointer = 0;
|
||||
}
|
||||
|
||||
float[] angles = conditionData(samples);
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Double,Float> iDataPoint = iSeries.get(x);
|
||||
Complex sample = samples[x];
|
||||
iDataPoint.setXValue((double)x - symbolDecisionData.getSamplingPoint());
|
||||
iDataPoint.setYValue(angles[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
public interface IInstrumentedAFSK1200Decoder
|
||||
{
|
||||
SimpleIntegerProperty getBufferCountProperty();
|
||||
|
||||
AFSK1200DecoderInstrumented getAFSK1200Decoder();
|
||||
|
||||
void setMessageListener(Listener<IMessage> listener);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LTRNetSampleBufferChart extends LineChart
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LTRNetSampleBufferChart.class);
|
||||
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
|
||||
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSamplePoints = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSamplePointSeries = new Series<>("Pointers", mSamplePoints);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSymbolSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSymbolSamplesSeries = new Series<>("Next Symbol Samples", mSymbolSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mSymbol = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mSymbolSeries = new Series<>("Previous Symbol", mSymbol);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mZeroCrossing = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mZeroCrossingSeries = new Series<>("Zero Crossing", mZeroCrossing);
|
||||
|
||||
private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
|
||||
|
||||
public LTRNetSampleBufferChart(LTRNetDecoderInstrumented decoder, int length)
|
||||
{
|
||||
super(new NumberAxis("Samples", 0, length, 5),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series> observableList = FXCollections.observableArrayList(mSampleSeries, mSamplePointSeries,
|
||||
mSymbolSamplesSeries, mSymbolSeries, mZeroCrossingSeries);
|
||||
|
||||
setData(observableList);
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
decoder.bufferCount.addListener(new BufferChangeListener());
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
}
|
||||
|
||||
public void setBuffer(boolean[] buffer)
|
||||
{
|
||||
while(mSamples.size() < buffer.length)
|
||||
{
|
||||
Data<Integer,Float> sample = new Data<>(mSamples.size(), 0.0f);
|
||||
mSamples.add(sample);
|
||||
|
||||
//Turn off the data point marker symbols - yes I know this is inefficient
|
||||
for(Data data: mSampleSeries.getData())
|
||||
{
|
||||
StackPane stackPane = (StackPane)data.getNode();
|
||||
stackPane.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
for(int x = 0; x < buffer.length; x++)
|
||||
{
|
||||
Data<Integer,Float> sample = mSamples.get(x);
|
||||
|
||||
sample.setYValue(buffer[x] ? 0.5f : -0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||
|
||||
while(mSymbol.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.55);
|
||||
mSymbol.add(sample);
|
||||
}
|
||||
|
||||
while(mZeroCrossing.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.65);
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
while(mSamplePoints.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.75);
|
||||
mSamplePoints.add(sample);
|
||||
}
|
||||
|
||||
while(mSymbolSamples.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.85);
|
||||
mSymbolSamples.add(sample);
|
||||
}
|
||||
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
// mSamplePoints.get(0).setXValue(pointer1);
|
||||
// mSamplePoints.get(1).setXValue(pointer2);
|
||||
|
||||
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
|
||||
// float start = pointer1 + samplesRemaining;
|
||||
// float end = start + samplesPerSymbol;
|
||||
|
||||
// mSymbolSamples.get(0).setXValue(start);
|
||||
// mSymbolSamples.get(1).setXValue(end);
|
||||
|
||||
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||
|
||||
// mSymbol.get(0).setXValue(symbolStart);
|
||||
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
// mSymbol.get(1).setXValue(symbolEnd);
|
||||
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
while(mZeroCrossing.size() < 2)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(0.0, -0.65);
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||
//
|
||||
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
// reference--;
|
||||
//
|
||||
// float start = reference - zeroCrossingIdeal;
|
||||
// float end = reference - detectedZeroCrossing;
|
||||
//
|
||||
// mZeroCrossing.get(0).setXValue(start);
|
||||
// mZeroCrossing.get(1).setXValue(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PhaseLineChart extends LineChart implements Listener<Double>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PhaseLineChart.class);
|
||||
|
||||
private DoubleCircularBuffer mDoubleCircularBuffer;
|
||||
private ObservableList<Data<Integer,Double>> mPhaseValues = FXCollections.observableArrayList();
|
||||
|
||||
public PhaseLineChart(int length)
|
||||
{
|
||||
super(new NumberAxis("Time", 1, length, 2),
|
||||
new NumberAxis("Phase", -0.8, 0.8, 0.1));
|
||||
|
||||
Series<Integer,Double> phaseSeries = new Series<>("PLL Phase Error", mPhaseValues);
|
||||
ObservableList<Series<Integer,Double>> observableList = FXCollections.observableArrayList(phaseSeries);
|
||||
|
||||
setData(observableList);
|
||||
init(length);
|
||||
}
|
||||
|
||||
private void init(int length)
|
||||
{
|
||||
mDoubleCircularBuffer = new DoubleCircularBuffer(length);
|
||||
|
||||
for(int x = 1; x <= length; x++)
|
||||
{
|
||||
mPhaseValues.add(new Data(x, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Double phase)
|
||||
{
|
||||
mDoubleCircularBuffer.put(phase);
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
private void updateChart()
|
||||
{
|
||||
double[] values = mDoubleCircularBuffer.getAll();
|
||||
|
||||
for(int x = 0; x < values.length; x++)
|
||||
{
|
||||
mPhaseValues.get(x).setYValue(values[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.FloatCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RealSampleLineChart extends LineChart implements Listener<float[]>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
|
||||
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
|
||||
private Series<Integer,Float> mSampleSeries = new Series<>("Samples", mSamples);
|
||||
private FloatCircularBuffer mRealCircularBuffer;
|
||||
|
||||
public RealSampleLineChart(int length, double tickUnit)
|
||||
{
|
||||
super(new NumberAxis("Samples", 0, length, tickUnit),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series<Integer,Float>> observableList = FXCollections.observableArrayList(mSampleSeries);
|
||||
|
||||
setData(observableList);
|
||||
|
||||
init(length);
|
||||
}
|
||||
|
||||
private void init(int length)
|
||||
{
|
||||
mRealCircularBuffer = new FloatCircularBuffer(length);
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
Data<Integer,Float> sample = new Data<>(x, 0.0f);
|
||||
mSamples.add(sample);
|
||||
}
|
||||
|
||||
//Turn off the data point marker symbols
|
||||
for(XYChart.Data data: mSampleSeries.getData())
|
||||
{
|
||||
StackPane stackPane = (StackPane)data.getNode();
|
||||
stackPane.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] buffer)
|
||||
{
|
||||
for(float sample: buffer)
|
||||
{
|
||||
mRealCircularBuffer.put(sample);
|
||||
}
|
||||
|
||||
float[] bufferSamples = mRealCircularBuffer.getAll();
|
||||
|
||||
for(int x = 0; x < bufferSamples.length; x++)
|
||||
{
|
||||
Data<Integer,Float> sample = mSamples.get(x);
|
||||
sample.setYValue(bufferSamples[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import eu.hansolo.fx.charts.ChartType;
|
||||
import eu.hansolo.fx.charts.PolarChart;
|
||||
import eu.hansolo.fx.charts.XYPane;
|
||||
import eu.hansolo.fx.charts.data.XYChartItem;
|
||||
import eu.hansolo.fx.charts.series.XYSeries;
|
||||
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SampleXYChart extends PolarChart<XYChartItem> implements Listener<ComplexSamples>
|
||||
{
|
||||
private static ObservableList<XYChartItem> sChartItemList = FXCollections.observableArrayList();
|
||||
|
||||
static
|
||||
{
|
||||
sChartItemList.add(new XYChartItem(1, 1));
|
||||
sChartItemList.add(new XYChartItem(-1, -1));
|
||||
}
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SampleXYChart.class);
|
||||
private ComplexCircularBuffer mCircularBuffer;
|
||||
private int mSampleCount;
|
||||
|
||||
public SampleXYChart(int sampleCount, String title)
|
||||
{
|
||||
super(new XYPane<>(new XYSeries<>(sChartItemList, ChartType.POLAR)));
|
||||
mSampleCount = sampleCount;
|
||||
mCircularBuffer = new ComplexCircularBuffer(mSampleCount);
|
||||
|
||||
getXYPane().setLowerBoundX(0);
|
||||
getXYPane().setUpperBoundX(1.0);
|
||||
getXYPane().setLowerBoundY(0);
|
||||
getXYPane().setUpperBoundY(1.0);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples buffer)
|
||||
{
|
||||
for(int x = 0; x < buffer.i().length; x++)
|
||||
{
|
||||
mCircularBuffer.put(new Complex(buffer.i()[x], buffer.q()[x]));
|
||||
}
|
||||
|
||||
Complex[] complexSamples = mCircularBuffer.getAll();
|
||||
|
||||
XYSeries<XYChartItem> series = getXYPane().getListOfSeries().get(0);
|
||||
series.setStroke(Color.BLUE);
|
||||
|
||||
series.getItems().clear();
|
||||
|
||||
for (Complex sample : complexSamples) {
|
||||
series.getItems().add(new XYChartItem(sample.polarAngleDegrees(), sample.magnitude(), Color.BLUE));
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public class SamplesPerSymbolChart extends DoubleLineChart
|
||||
{
|
||||
private double mFractionalSamplesPerSymbol;
|
||||
private double mPreviousSamplesPerSymbol;
|
||||
|
||||
public SamplesPerSymbolChart(int length, double samplesPerSymbol)
|
||||
{
|
||||
super("Samples Per Symbol", -1.0, 1.0, 0.1, length);
|
||||
setSamplesPerSymbol(samplesPerSymbol);
|
||||
}
|
||||
|
||||
public void setSamplesPerSymbol(double samplesPerSymbol)
|
||||
{
|
||||
mFractionalSamplesPerSymbol = samplesPerSymbol - ((int)samplesPerSymbol);
|
||||
mPreviousSamplesPerSymbol = samplesPerSymbol;
|
||||
|
||||
ObservableList<Series> seriesList = getData();
|
||||
seriesList.get(0).setName("Samples Per Symbol:" + samplesPerSymbol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Double samplesPerSymbol)
|
||||
{
|
||||
double delta = samplesPerSymbol - mPreviousSamplesPerSymbol - mFractionalSamplesPerSymbol;
|
||||
|
||||
while(delta < (-1.0 + mFractionalSamplesPerSymbol))
|
||||
{
|
||||
delta++;
|
||||
}
|
||||
|
||||
while(delta > 1.0)
|
||||
{
|
||||
delta--;
|
||||
}
|
||||
|
||||
super.receive(delta);
|
||||
|
||||
mPreviousSamplesPerSymbol = samplesPerSymbol;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SymbolChart extends ScatterChart implements Listener<Complex>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SymbolChart.class);
|
||||
|
||||
private ObservableList<ScatterChart.Data> mCurrentPoints = FXCollections.observableArrayList();
|
||||
// private ObservableList<ScatterChart.Data> mPreviousPoints = FXCollections.observableArrayList();
|
||||
// private ObservableList<ScatterChart.Data> mOldestPoints = FXCollections.observableArrayList();
|
||||
private int mDataPointer;
|
||||
private int mHistory;
|
||||
|
||||
/**
|
||||
* Symbol chart for plotting a series of constellation symbols over time. Maintains three history sets for
|
||||
* current, previous, and oldest symbols.
|
||||
*
|
||||
* @param history size for each of the history sets
|
||||
*/
|
||||
public SymbolChart(int history)
|
||||
{
|
||||
super(new NumberAxis("I", -1.1, 1.1, 0.2),
|
||||
new NumberAxis("Q", -1.1, 1.1, 0.2));
|
||||
|
||||
mHistory = history;
|
||||
|
||||
for(int x = 0; x < mHistory; x++)
|
||||
{
|
||||
mCurrentPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
// mPreviousPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
// mOldestPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
}
|
||||
|
||||
ObservableList<XYChart.Series> data = FXCollections.observableArrayList();
|
||||
data.add(new ScatterChart.Series("Constellation", mCurrentPoints));
|
||||
// data.add(new ScatterChart.Series("Previous", mPreviousPoints));
|
||||
// data.add(new ScatterChart.Series("Oldest", mOldestPoints));
|
||||
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Complex complex)
|
||||
{
|
||||
//Transfer previous to oldest
|
||||
// XYChart.Data oldest = mOldestPoints.get(mDataPointer);
|
||||
// XYChart.Data previous = mPreviousPoints.get(mDataPointer);
|
||||
XYChart.Data current = mCurrentPoints.get(mDataPointer);
|
||||
|
||||
// oldest.setXValue(previous.getXValue());
|
||||
// oldest.setYValue(previous.getYValue());
|
||||
//
|
||||
// previous.setXValue(current.getXValue());
|
||||
// previous.setYValue(current.getYValue());
|
||||
|
||||
current.setXValue(complex.inphase());
|
||||
current.setYValue(complex.quadrature());
|
||||
|
||||
mDataPointer++;
|
||||
|
||||
if(mDataPointer >= mHistory)
|
||||
{
|
||||
mDataPointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ZeroCrossingErrorDetectorChart.class);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mCurrentSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mCurrentSampleSeries = new Series<>("Samples", mCurrentSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mPreviousSamples = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mPreviousSampleSeries = new Series<>("Samples", mPreviousSamples);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mIdeal = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mIdealSeries = new Series<>("Ideal", mIdeal);
|
||||
|
||||
private ObservableList<Data<Number,Number>> mDetected = FXCollections.observableArrayList();
|
||||
private Series<Number,Number> mDetectedSeries = new Series<>("Detected", mDetected);
|
||||
|
||||
private LTRNetDecoderInstrumented mLTRNetDecoderInstrumented;
|
||||
|
||||
public ZeroCrossingErrorDetectorChart(LTRNetDecoderInstrumented decoder, int length)
|
||||
{
|
||||
super(new NumberAxis("Samples", 0, length, 5),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
ObservableList<Series> observableList = FXCollections.observableArrayList(mCurrentSampleSeries,
|
||||
mPreviousSampleSeries, mIdealSeries, mDetectedSeries);
|
||||
setData(observableList);
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
decoder.bufferCount.addListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
updateBuffer();
|
||||
}
|
||||
});
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = new Data<>(x, 0.0f);
|
||||
mCurrentSamples.add(sample);
|
||||
Data<Number,Number> sample2 = new Data<>(x, 0.0f);
|
||||
mPreviousSamples.add(sample2);
|
||||
}
|
||||
|
||||
Data<Number,Number> ideal1 = new Data<>(5, -0.1f);
|
||||
mIdeal.add(ideal1);
|
||||
Data<Number,Number> ideal2 = new Data<>(5, -0.9f);
|
||||
mIdeal.add(ideal2);
|
||||
|
||||
Data<Number,Number> detected1 = new Data<>(5, -0.25f);
|
||||
mDetected.add(detected1);
|
||||
Data<Number,Number> detected2 = new Data<>(5, -0.75f);
|
||||
mDetected.add(detected2);
|
||||
}
|
||||
|
||||
private void updateBuffer()
|
||||
{
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
// }
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||
//
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
// }
|
||||
//
|
||||
// Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
// Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
//
|
||||
// if(error == 0.0f)
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(0.0f);
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// detected2.setXValue(0.0f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.AFSK1200SampleBufferChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.AFSK1200ZeroCrossingErrorDetectorChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DecodedSymbolChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AbstractAFSK1200Pane extends RealDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAFSK1200Pane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private RealSampleLineChart mRealSampleLineChart;
|
||||
private HBox mSymbolDecoderBox;
|
||||
private AFSK1200SampleBufferChart mSampleBufferChart;
|
||||
private AFSK1200ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart;
|
||||
private DecodedSymbolChart mDecodedSymbolChart;
|
||||
|
||||
private IInstrumentedAFSK1200Decoder mDecoder;
|
||||
|
||||
public AbstractAFSK1200Pane(DecoderType decoderType)
|
||||
{
|
||||
super(decoderType);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
//This is force-cast without type checking ....
|
||||
addListener((Listener<float[]>) getDecoder());
|
||||
|
||||
addListener(getSampleLineChart());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS);
|
||||
|
||||
VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS);
|
||||
|
||||
getSampleChartBox().setMaxWidth(Double.MAX_VALUE);
|
||||
getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox());
|
||||
|
||||
getDecoder().getAFSK1200Decoder().addListener(getDecodedSymbolChart());
|
||||
|
||||
getDecoder().setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug((message.isValid() ? "PASS " : "FAIL ") + message.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract IInstrumentedAFSK1200Decoder getDecoder();
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getDecodedSymbolChart().setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getDecodedSymbolChart(), Priority.ALWAYS);
|
||||
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getDecodedSymbolChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private RealSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mRealSampleLineChart == null)
|
||||
{
|
||||
mRealSampleLineChart = new RealSampleLineChart(200, (10.0));
|
||||
}
|
||||
|
||||
return mRealSampleLineChart;
|
||||
}
|
||||
|
||||
private HBox getSymbolDecoderBox()
|
||||
{
|
||||
if(mSymbolDecoderBox == null)
|
||||
{
|
||||
mSymbolDecoderBox = new HBox();
|
||||
mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
getSampleBufferChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getZeroCrossingErrorDetectorChart().setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getZeroCrossingErrorDetectorChart(), Priority.ALWAYS);
|
||||
|
||||
mSymbolDecoderBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart());
|
||||
}
|
||||
|
||||
return mSymbolDecoderBox;
|
||||
}
|
||||
|
||||
private AFSK1200SampleBufferChart getSampleBufferChart()
|
||||
{
|
||||
if(mSampleBufferChart == null)
|
||||
{
|
||||
mSampleBufferChart = new AFSK1200SampleBufferChart(getDecoder(), 106);
|
||||
}
|
||||
|
||||
return mSampleBufferChart;
|
||||
}
|
||||
|
||||
private AFSK1200ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart()
|
||||
{
|
||||
if(mZeroCrossingErrorDetectorChart == null)
|
||||
{
|
||||
mZeroCrossingErrorDetectorChart = new AFSK1200ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
getDecoder().getAFSK1200Decoder().getErrorDetector().getBuffer().length);
|
||||
}
|
||||
|
||||
return mZeroCrossingErrorDetectorChart;
|
||||
}
|
||||
|
||||
private DecodedSymbolChart getDecodedSymbolChart()
|
||||
{
|
||||
if(mDecodedSymbolChart == null)
|
||||
{
|
||||
mDecodedSymbolChart = new DecodedSymbolChart(50);
|
||||
}
|
||||
|
||||
return mDecodedSymbolChart;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class ComplexDecoderPane extends AbstractDecoderPane<INativeBuffer>
|
||||
{
|
||||
private Broadcaster<INativeBuffer> mBufferBroadcaster = new Broadcaster();
|
||||
private DecoderType mDecoderType;
|
||||
|
||||
public ComplexDecoderPane(DecoderType decoderType)
|
||||
{
|
||||
mDecoderType = decoderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Package private - used by factory class to create an empty pane
|
||||
*/
|
||||
ComplexDecoderPane()
|
||||
{
|
||||
getChildren().add(new Text("Please select a decoder from the menu"));
|
||||
}
|
||||
|
||||
@Override
|
||||
SampleType getSampleType()
|
||||
{
|
||||
return SampleType.COMPLEX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(INativeBuffer nativeBuffer)
|
||||
{
|
||||
mBufferBroadcaster.broadcast(nativeBuffer);
|
||||
}
|
||||
|
||||
public void addListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class DecoderPaneFactory
|
||||
{
|
||||
private static final EnumSet<DecoderType> SUPPORTED_DECODER_TYPES = EnumSet.of(
|
||||
DecoderType.P25_PHASE1,
|
||||
DecoderType.P25_PHASE2,
|
||||
DecoderType.FLEETSYNC2,
|
||||
DecoderType.LJ_1200,
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.TAIT_1200);
|
||||
|
||||
/**
|
||||
* Creates a decoder pane for the decoder type
|
||||
*/
|
||||
public static AbstractDecoderPane getDecoderPane(DecoderType decoderType)
|
||||
{
|
||||
switch(decoderType)
|
||||
{
|
||||
case FLEETSYNC2:
|
||||
return new Fleetsync2Pane();
|
||||
case LJ_1200:
|
||||
return new LJ1200Pane();
|
||||
case LTR_NET:
|
||||
return new LTRNetPane();
|
||||
case MDC1200:
|
||||
return new MDC1200Pane();
|
||||
case MPT1327:
|
||||
return new MPT1327Pane();
|
||||
case TAIT_1200:
|
||||
return new Tait1200Pane();
|
||||
case P25_PHASE1:
|
||||
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
|
||||
case P25_PHASE2:
|
||||
return new P25Phase2HDQPSKPane();
|
||||
}
|
||||
|
||||
return getDefaultPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a decoder pane for the P25 decoder type
|
||||
*/
|
||||
public static AbstractDecoderPane getP25P1DecoderPane(P25P1Decoder.Modulation modulation)
|
||||
{
|
||||
switch(modulation)
|
||||
{
|
||||
case C4FM:
|
||||
return new P25Phase1C4FMPane();
|
||||
case CQPSK:
|
||||
return new P25Phase1LSMPane();
|
||||
}
|
||||
|
||||
return getDefaultPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty decoder pane
|
||||
*/
|
||||
public static AbstractDecoderPane getDefaultPane()
|
||||
{
|
||||
return new ComplexDecoderPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the decoder type is supported by this factory
|
||||
*/
|
||||
public static boolean isSupported(DecoderType decoderType)
|
||||
{
|
||||
return SUPPORTED_DECODER_TYPES.contains(decoderType);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderInstrumented;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Fleetsync2Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Fleetsync2Pane.class);
|
||||
|
||||
private Fleetsync2DecoderInstrumented mDecoder;
|
||||
|
||||
public Fleetsync2Pane()
|
||||
{
|
||||
super(DecoderType.FLEETSYNC2);
|
||||
}
|
||||
|
||||
protected Fleetsync2DecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new Fleetsync2DecoderInstrumented();
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.lj1200.LJ1200DecoderInstrumented;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LJ1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LJ1200Pane.class);
|
||||
|
||||
private LJ1200DecoderInstrumented mDecoder;
|
||||
|
||||
public LJ1200Pane()
|
||||
{
|
||||
super(DecoderType.LJ_1200);
|
||||
}
|
||||
|
||||
protected LJ1200DecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new LJ1200DecoderInstrumented();
|
||||
mDecoder.getTowerMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mLog.debug("Tower Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
mDecoder.getTransponderMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mLog.debug("Transponder Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.ZeroCrossingErrorDetectorChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.MessageDirection;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LTRNetPane extends RealDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LTRNetPane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private RealSampleLineChart mRealSampleLineChart;
|
||||
private HBox mSymbolDecoderBox;
|
||||
private LTRNetSampleBufferChart mLTRNetSampleBufferChart;
|
||||
private ZeroCrossingErrorDetectorChart mZeroCrossingErrorDetectorChart;
|
||||
|
||||
private LTRNetDecoderInstrumented mDecoder;
|
||||
|
||||
public LTRNetPane()
|
||||
{
|
||||
super(DecoderType.LTR_NET);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(getDecoder());
|
||||
addListener(getSampleLineChart());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSymbolDecoderBox(), Priority.ALWAYS);
|
||||
|
||||
VBox.setVgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
VBox.setVgrow(getSymbolDecoderBox(), Priority.ALWAYS);
|
||||
|
||||
getSampleChartBox().setMaxWidth(Double.MAX_VALUE);
|
||||
getSymbolDecoderBox().setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
getChildren().addAll(getSampleChartBox(), getSymbolDecoderBox());
|
||||
|
||||
}
|
||||
|
||||
private LTRNetDecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
DecodeConfigLTRNet config = new DecodeConfigLTRNet();
|
||||
config.setMessageDirection(MessageDirection.OSW);
|
||||
mDecoder = new LTRNetDecoderInstrumented(config);
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private RealSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mRealSampleLineChart == null)
|
||||
{
|
||||
mRealSampleLineChart = new RealSampleLineChart(500, (8000.0 / 300.0));
|
||||
}
|
||||
|
||||
return mRealSampleLineChart;
|
||||
}
|
||||
|
||||
private HBox getSymbolDecoderBox()
|
||||
{
|
||||
if(mSymbolDecoderBox == null)
|
||||
{
|
||||
mSymbolDecoderBox = new HBox();
|
||||
mSymbolDecoderBox.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
getSampleBufferChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleBufferChart(), Priority.ALWAYS);
|
||||
|
||||
mSampleChartBox.getChildren().addAll(getSampleBufferChart(), getZeroCrossingErrorDetectorChart());
|
||||
}
|
||||
|
||||
return mSymbolDecoderBox;
|
||||
}
|
||||
|
||||
private LTRNetSampleBufferChart getSampleBufferChart()
|
||||
{
|
||||
if(mLTRNetSampleBufferChart == null)
|
||||
{
|
||||
mLTRNetSampleBufferChart = new LTRNetSampleBufferChart(getDecoder(), 106);
|
||||
}
|
||||
|
||||
return mLTRNetSampleBufferChart;
|
||||
}
|
||||
|
||||
private ZeroCrossingErrorDetectorChart getZeroCrossingErrorDetectorChart()
|
||||
{
|
||||
if(mZeroCrossingErrorDetectorChart == null)
|
||||
{
|
||||
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||
}
|
||||
|
||||
return mZeroCrossingErrorDetectorChart;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.mdc1200.MDCDecoderInstrumented;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MDC1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MDC1200Pane.class);
|
||||
|
||||
private MDCDecoderInstrumented mDecoder;
|
||||
|
||||
public MDC1200Pane()
|
||||
{
|
||||
super(DecoderType.MDC1200);
|
||||
}
|
||||
|
||||
protected MDCDecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new MDCDecoderInstrumented();
|
||||
mDecoder.getMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mLog.debug("Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
mLog.debug("Sync Lost!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.mpt1327.MPT1327DecoderInstrumented;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MPT1327Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MPT1327Pane.class);
|
||||
|
||||
private MPT1327DecoderInstrumented mDecoder;
|
||||
|
||||
public MPT1327Pane()
|
||||
{
|
||||
super(DecoderType.MPT1327);
|
||||
}
|
||||
|
||||
protected MPT1327DecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new MPT1327DecoderInstrumented();
|
||||
mDecoder.getControlMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
// mLog.debug("Control Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
mDecoder.getTrafficMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
// mLog.debug("Traffic Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FMInstrumented;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class P25Phase1C4FMPane extends ComplexDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1C4FMPane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private ComplexSampleLineChart mSampleLineChart;
|
||||
private EyeDiagramChart mEyeDiagramChart;
|
||||
private HBox mDecoderChartBox;
|
||||
private SymbolChart mSymbolChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private DoubleLineChart mSamplesPerSymbolLineChart;
|
||||
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
|
||||
private P25P1DecoderC4FMInstrumented mDecoder = new P25P1DecoderC4FMInstrumented();
|
||||
|
||||
public P25Phase1C4FMPane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE1);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(nativeBuffer ->
|
||||
{
|
||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
||||
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
getDecoder().receive(iterator.next());
|
||||
}
|
||||
});
|
||||
|
||||
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||
getDecoder().setComplexSymbolListener(getSymbolChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
|
||||
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
|
||||
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
|
||||
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
|
||||
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
|
||||
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
double samplesPerSymbol = sampleRate / 4800.0;
|
||||
|
||||
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
|
||||
}
|
||||
|
||||
private P25P1DecoderC4FMInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolChart()
|
||||
{
|
||||
if(mSymbolChart == null)
|
||||
{
|
||||
mSymbolChart = new SymbolChart(10);
|
||||
}
|
||||
|
||||
return mSymbolChart;
|
||||
}
|
||||
|
||||
private HBox getDecoderChartBox()
|
||||
{
|
||||
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);
|
||||
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
|
||||
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
|
||||
getPLLFrequencyLineChart());
|
||||
}
|
||||
|
||||
return mDecoderChartBox;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
|
||||
getSamplesPerSymbolLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mSampleLineChart == null)
|
||||
{
|
||||
mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mSampleLineChart;
|
||||
}
|
||||
|
||||
private EyeDiagramChart getEyeDiagramChart()
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getPLLFrequencyLineChart()
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getSamplesPerSymbolLineChart()
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSMInstrumented;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class P25Phase1LSMPane extends ComplexDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
|
||||
|
||||
private HBox mTopChartBox;
|
||||
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
|
||||
private EyeDiagramChart mEyeDiagramChart;
|
||||
private HBox mBottomChartBox;
|
||||
private SymbolChart mSymbolConstellationChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
|
||||
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
|
||||
private P25P1DecoderLSMInstrumented mDecoder = new P25P1DecoderLSMInstrumented();
|
||||
|
||||
public P25Phase1LSMPane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE1);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(nativeBuffer ->
|
||||
{
|
||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
||||
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
getDecoder().receive(iterator.next());
|
||||
}
|
||||
});
|
||||
|
||||
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
|
||||
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
|
||||
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
|
||||
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
|
||||
|
||||
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
|
||||
getChildren().addAll(getTopChartBox(), getBottomChartBox());
|
||||
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
double samplesPerSymbol = sampleRate / 4800.0;
|
||||
|
||||
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int) samplesPerSymbol);
|
||||
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
|
||||
}
|
||||
|
||||
private P25P1DecoderLSMInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolConstellationChart()
|
||||
{
|
||||
if(mSymbolConstellationChart == null)
|
||||
{
|
||||
mSymbolConstellationChart = new SymbolChart(10);
|
||||
}
|
||||
|
||||
return mSymbolConstellationChart;
|
||||
}
|
||||
|
||||
private HBox getBottomChartBox()
|
||||
{
|
||||
if(mBottomChartBox == null)
|
||||
{
|
||||
mBottomChartBox = new HBox();
|
||||
mBottomChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSymbolConstellationChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
|
||||
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
|
||||
getPLLFrequencyLineChart());
|
||||
}
|
||||
|
||||
return mBottomChartBox;
|
||||
}
|
||||
|
||||
private HBox getTopChartBox()
|
||||
{
|
||||
if(mTopChartBox == null)
|
||||
{
|
||||
mTopChartBox = new HBox();
|
||||
mTopChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getDifferentialDemodulatedSamplesChartBox().setMaxWidth(Double.MAX_VALUE);
|
||||
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
|
||||
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
|
||||
getSamplesPerSymbolLineChart());
|
||||
}
|
||||
|
||||
return mTopChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
|
||||
{
|
||||
if(mDifferentialDemodulatedSamplesChartBox == null)
|
||||
{
|
||||
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart("Raw Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mDifferentialDemodulatedSamplesChartBox;
|
||||
}
|
||||
|
||||
private EyeDiagramChart getEyeDiagramChart()
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getPLLFrequencyLineChart()
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -2500, 2500, 200, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
}
|
||||
|
||||
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SampleXYChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSKInstrumented;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class P25Phase2HDQPSKPane extends ComplexDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25Phase2HDQPSKPane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private HBox mDecoderChartBox;
|
||||
|
||||
private ComplexSampleLineChart mSampleLineChartRaw;
|
||||
private ComplexSampleLineChart mSampleLineChartPllCorrected;
|
||||
private EyeDiagramChart mEyeDiagramChart;
|
||||
private DoubleLineChart mSamplesPerSymbolLineChart;
|
||||
|
||||
private SymbolChart mSymbolChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private SampleXYChart mSampleXYChart;
|
||||
|
||||
|
||||
private Broadcaster<ComplexSamples> mFilteredBufferBroadcaster = new Broadcaster();
|
||||
private P25P2DecoderHDQPSKInstrumented mDecoder = new P25P2DecoderHDQPSKInstrumented(new DecodeConfigP25Phase2());
|
||||
|
||||
public P25Phase2HDQPSKPane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE2);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(nativeBuffer ->
|
||||
{
|
||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
||||
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
getDecoder().receive(iterator.next());
|
||||
}
|
||||
});
|
||||
|
||||
getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
|
||||
|
||||
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||
getDecoder().setComplexSymbolListener(getSymbolChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
|
||||
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
|
||||
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
|
||||
|
||||
//Listen for raw (uncorrected) samples
|
||||
mFilteredBufferBroadcaster.addListener(getSampleLineChartRaw());
|
||||
|
||||
//Listen for PLL corrected samples
|
||||
getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
|
||||
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
|
||||
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
double samplesPerSymbol = sampleRate / 4800.0;
|
||||
|
||||
getSampleLineChartRaw().setSamplesPerSymbol((int) samplesPerSymbol);
|
||||
getDecoder().getSampleBuffer().setSampleListener(getSampleLineChartPllCorrected());
|
||||
getDecoder().getDemodulator().setFilteredGainAppliedComplexBufferListener(getSampleXYChart());
|
||||
}
|
||||
|
||||
private P25P2DecoderHDQPSKInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolChart()
|
||||
{
|
||||
if(mSymbolChart == null)
|
||||
{
|
||||
mSymbolChart = new SymbolChart(10);
|
||||
}
|
||||
|
||||
return mSymbolChart;
|
||||
}
|
||||
|
||||
private HBox getDecoderChartBox()
|
||||
{
|
||||
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);
|
||||
getSampleXYChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSampleXYChart(), Priority.ALWAYS);
|
||||
mDecoderChartBox.getChildren().addAll(getPLLPhaseErrorLineChart(), getPLLFrequencyLineChart(),
|
||||
getSymbolChart(), getSampleXYChart());
|
||||
}
|
||||
|
||||
return mDecoderChartBox;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSampleLineChartRaw().setMaxWidth(Double.MAX_VALUE);
|
||||
getSampleLineChartPllCorrected().setMaxWidth(Double.MAX_VALUE);
|
||||
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChartRaw(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSampleLineChartPllCorrected(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
|
||||
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChartRaw(), getSampleLineChartPllCorrected(),
|
||||
getEyeDiagramChart(), getSamplesPerSymbolLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChartRaw()
|
||||
{
|
||||
if(mSampleLineChartRaw == null)
|
||||
{
|
||||
mSampleLineChartRaw = new ComplexSampleLineChart("Raw Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mSampleLineChartRaw;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChartPllCorrected()
|
||||
{
|
||||
if(mSampleLineChartPllCorrected == null)
|
||||
{
|
||||
mSampleLineChartPllCorrected = new ComplexSampleLineChart("PLL Corrected Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mSampleLineChartPllCorrected;
|
||||
}
|
||||
|
||||
private EyeDiagramChart getEyeDiagramChart()
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getPLLFrequencyLineChart()
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
}
|
||||
|
||||
private SampleXYChart getSampleXYChart()
|
||||
{
|
||||
if(mSampleXYChart == null)
|
||||
{
|
||||
mSampleXYChart = new SampleXYChart(100, "Samples");
|
||||
}
|
||||
|
||||
return mSampleXYChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getSamplesPerSymbolLineChart()
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 8.0, 10.0, 0.1, 40);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class RealDecoderPane extends AbstractDecoderPane<float[]>
|
||||
{
|
||||
private Broadcaster<float[]> mBufferBroadcaster = new Broadcaster();
|
||||
private DecoderType mDecoderType;
|
||||
|
||||
public RealDecoderPane(DecoderType decoderType)
|
||||
{
|
||||
mDecoderType = decoderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Package private - used by factory class to create an empty pane
|
||||
*/
|
||||
RealDecoderPane()
|
||||
{
|
||||
getChildren().add(new Text("Please select a decoder from the menu"));
|
||||
}
|
||||
|
||||
@Override
|
||||
SampleType getSampleType()
|
||||
{
|
||||
return SampleType.COMPLEX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] buffer)
|
||||
{
|
||||
mBufferBroadcaster.broadcast(buffer);
|
||||
}
|
||||
|
||||
public void addListener(Listener<float[]> listener)
|
||||
{
|
||||
mBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<float[]> listener)
|
||||
{
|
||||
mBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.tait.Tait1200DecoderInstrumented;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Tait1200Pane extends AbstractAFSK1200Pane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Tait1200Pane.class);
|
||||
|
||||
private Tait1200DecoderInstrumented mDecoder;
|
||||
|
||||
public Tait1200Pane()
|
||||
{
|
||||
super(DecoderType.TAIT_1200);
|
||||
}
|
||||
|
||||
protected Tait1200DecoderInstrumented getDecoder()
|
||||
{
|
||||
if(mDecoder == null)
|
||||
{
|
||||
mDecoder = new Tait1200DecoderInstrumented();
|
||||
mDecoder.getANIMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mLog.debug("ANI Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
mDecoder.getGPSMessageFramer().setSyncDetectListener(new ISyncDetectListener()
|
||||
{
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
{
|
||||
mLog.debug("GPS Sync Detected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDecoder;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -27,7 +27,7 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
|
||||
import io.github.dsheirer.module.log.EventLogType;
|
||||
import io.github.dsheirer.module.log.config.EventLogConfiguration;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
@@ -332,7 +332,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
||||
DecodeConfigP25Phase1 decodeConfig = (DecodeConfigP25Phase1)config;
|
||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||
if(decodeConfig.getModulation() == P25P1Decoder.Modulation.C4FM)
|
||||
if(decodeConfig.getModulation() == Modulation.C4FM)
|
||||
{
|
||||
getC4FMToggleButton().setSelected(true);
|
||||
getLSMToggleButton().setSelected(false);
|
||||
@@ -366,7 +366,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
||||
|
||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||
config.setModulation(getC4FMToggleButton().isSelected() ? P25P1Decoder.Modulation.C4FM : P25P1Decoder.Modulation.CQPSK);
|
||||
config.setModulation(getC4FMToggleButton().isSelected() ? Modulation.C4FM : Modulation.CQPSK);
|
||||
getItem().setDecodeConfiguration(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -26,7 +26,7 @@ import io.github.dsheirer.module.decode.DecoderFactory;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.rrapi.type.Category;
|
||||
@@ -36,6 +36,7 @@ import io.github.dsheirer.rrapi.type.SubCategory;
|
||||
import io.github.dsheirer.service.radioreference.RadioReference;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.text.DecimalFormat;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.HPos;
|
||||
@@ -53,8 +54,6 @@ import javafx.util.Duration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class FrequencyEditor extends VBox
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(FrequencyEditor.class);
|
||||
@@ -421,7 +420,7 @@ public class FrequencyEditor extends VBox
|
||||
}
|
||||
else if(decodeConfiguration instanceof DecodeConfigP25Phase1)
|
||||
{
|
||||
((DecodeConfigP25Phase1)decodeConfiguration).setModulation(P25P1Decoder.Modulation.C4FM);
|
||||
((DecodeConfigP25Phase1)decodeConfiguration).setModulation(Modulation.C4FM);
|
||||
}
|
||||
channel.setDecodeConfiguration(decodeConfiguration);
|
||||
return channel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -30,7 +30,7 @@ import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.Modulation;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
@@ -257,7 +257,7 @@ public class SiteEditor extends GridPane
|
||||
{
|
||||
if(p1config instanceof DecodeConfigP25Phase1)
|
||||
{
|
||||
((DecodeConfigP25Phase1)p1config).setModulation(P25P1Decoder.Modulation.CQPSK);
|
||||
((DecodeConfigP25Phase1)p1config).setModulation(Modulation.CQPSK);
|
||||
}
|
||||
}
|
||||
return p1config;
|
||||
|
||||
@@ -22,6 +22,7 @@ package io.github.dsheirer.gui.squelch;
|
||||
import io.github.dsheirer.dsp.squelch.INoiseSquelchController;
|
||||
import io.github.dsheirer.dsp.squelch.NoiseSquelch;
|
||||
import io.github.dsheirer.dsp.squelch.NoiseSquelchState;
|
||||
import io.github.dsheirer.gui.symbol.ChannelView;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
@@ -72,7 +73,7 @@ import org.slf4j.LoggerFactory;
|
||||
* 1 for an operating range of 1-6. The close slider uses a range of 0-5 that is combined with the open slider for a
|
||||
* range of (open + 0) to (open + 5).
|
||||
*/
|
||||
public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState>
|
||||
public class NoiseSquelchView extends ChannelView implements Listener<NoiseSquelchState>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NoiseSquelchView.class);
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
|
||||
@@ -129,7 +130,6 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
|
||||
private Label mHysteresisValueLabel;
|
||||
private Button mResetButton;
|
||||
|
||||
private boolean mShowing = false;
|
||||
private boolean mControlsUpdated = true;
|
||||
|
||||
/**
|
||||
@@ -235,9 +235,10 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
|
||||
* Sets this view as showing and starts the chart update timer, or sets this view as hidden and stops the update timer
|
||||
* @param showing to indicate if this view is selected by the user and showing.
|
||||
*/
|
||||
@Override
|
||||
public void setShowing(boolean showing)
|
||||
{
|
||||
mShowing = showing;
|
||||
super.setShowing(showing);
|
||||
updateTimer();
|
||||
}
|
||||
|
||||
@@ -258,12 +259,12 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
|
||||
*/
|
||||
private synchronized void updateTimer()
|
||||
{
|
||||
if(mShowing && mController != null && mTimerFuture == null)
|
||||
if(isShowing() && mController != null && mTimerFuture == null)
|
||||
{
|
||||
//Start the timer
|
||||
mTimerFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::updateChart, 0, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else if((!mShowing || mController == null) && mTimerFuture != null)
|
||||
else if((!isShowing() || mController == null) && mTimerFuture != null)
|
||||
{
|
||||
cancelTimer();
|
||||
}
|
||||
@@ -604,7 +605,7 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
|
||||
*/
|
||||
private void updateControllerNoise()
|
||||
{
|
||||
if(mController != null && mShowing)
|
||||
if(mController != null && isShowing())
|
||||
{
|
||||
float open = getOpenNoiseThresholdFromSliderValue();
|
||||
float close = getCloseNoiseThresholdFromSliderValue();
|
||||
@@ -669,7 +670,7 @@ public class NoiseSquelchView extends VBox implements Listener<NoiseSquelchState
|
||||
*/
|
||||
private void updateControllerHysteresis()
|
||||
{
|
||||
if(mController != null && mShowing)
|
||||
if(mController != null && isShowing())
|
||||
{
|
||||
int open = (int)getOpenHysteresisSlider().getValue();
|
||||
int close = (int)getCloseHysteresisSlider().getValue() + open;
|
||||
|
||||
55
src/main/java/io/github/dsheirer/gui/symbol/ChannelView.java
Normal file
55
src/main/java/io/github/dsheirer/gui/symbol/ChannelView.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.symbol;
|
||||
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Base class for channel viewer panel
|
||||
*/
|
||||
public class ChannelView extends VBox
|
||||
{
|
||||
private boolean mShowing = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*/
|
||||
public ChannelView()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this panel is currently showing.
|
||||
* @return true if showing.
|
||||
*/
|
||||
protected boolean isShowing()
|
||||
{
|
||||
return mShowing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the showing state to indicate when the user has selected to show this panel.
|
||||
* @param showing true or false to disable the panel.
|
||||
*/
|
||||
public void setShowing(boolean showing)
|
||||
{
|
||||
mShowing = showing;
|
||||
}
|
||||
}
|
||||
133
src/main/java/io/github/dsheirer/gui/symbol/SymbolView.java
Normal file
133
src/main/java/io/github/dsheirer/gui/symbol/SymbolView.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.symbol;
|
||||
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.Arrays;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Chart display for received QPSK symbols
|
||||
*/
|
||||
public class SymbolView extends ChannelView implements Listener<Float>
|
||||
{
|
||||
private static final int SYMBOL_DISPLAY_COUNT = 4800;
|
||||
private static final int SYMBOL_QUEUE_SIZE = SYMBOL_DISPLAY_COUNT / 40;
|
||||
private final float[] mSymbolQueue = new float[SYMBOL_QUEUE_SIZE];
|
||||
private int mSymbolQueuePointer = 0;
|
||||
private int mChartSymbolPointer = 0;
|
||||
private final NumberAxis mSymbolTiming = new NumberAxis();
|
||||
private final NumberAxis mSymbolPhase = new NumberAxis(-Math.PI, Math.PI, Math.PI / 2);
|
||||
private final XYChart.Series<Number, Number> mSymbolSeries = new XYChart.Series<>();
|
||||
private final ScatterChart<Number, Number> mSymbolChart = new ScatterChart<>(mSymbolTiming, mSymbolPhase);
|
||||
private FeedbackDecoder mFeedbackDecoder;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public SymbolView()
|
||||
{
|
||||
mSymbolSeries.setName("xx Demodulated Symbols");
|
||||
|
||||
for(int x = 0; x < SYMBOL_DISPLAY_COUNT; x++)
|
||||
{
|
||||
mSymbolSeries.getData().add(new XYChart.Data<>(x, 0));
|
||||
}
|
||||
|
||||
mSymbolChart.getData().add(mSymbolSeries);
|
||||
mSymbolChart.setMaxHeight(Double.MAX_VALUE);
|
||||
mSymbolChart.setMaxWidth(Double.MAX_VALUE);
|
||||
mSymbolChart.setAnimated(false);
|
||||
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
|
||||
getChildren().add(mSymbolChart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the chart axis label with the currently displayed protocol
|
||||
* @param protocol that is being processed/displayed.
|
||||
*/
|
||||
public void setProtocol(String protocol)
|
||||
{
|
||||
Platform.runLater(() -> mSymbolSeries.setName(protocol + " Demodulated Symbols"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the decoder with this view and this view then registers as a symbol listener on the decoder.
|
||||
* @param feedbackDecoder for this view
|
||||
*/
|
||||
public void setSymbolProvider(FeedbackDecoder feedbackDecoder)
|
||||
{
|
||||
removeSymbolProvider();
|
||||
|
||||
mFeedbackDecoder = feedbackDecoder;
|
||||
|
||||
if(mFeedbackDecoder != null)
|
||||
{
|
||||
mFeedbackDecoder.setSymbolListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes current symbol provider and unregisters this view from that provider.
|
||||
*/
|
||||
public void removeSymbolProvider()
|
||||
{
|
||||
if(mFeedbackDecoder != null)
|
||||
{
|
||||
mFeedbackDecoder.removeSymbolListener();
|
||||
}
|
||||
|
||||
mFeedbackDecoder = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Float symbol)
|
||||
{
|
||||
if(isShowing())
|
||||
{
|
||||
mSymbolQueue[mSymbolQueuePointer++] = symbol;
|
||||
|
||||
//Flush the queued symbols to the chart when the queue is full
|
||||
if(mSymbolQueuePointer == SYMBOL_QUEUE_SIZE)
|
||||
{
|
||||
float[] symbolDataCopy = Arrays.copyOf(mSymbolQueue, mSymbolQueue.length);
|
||||
|
||||
//Execute the chart data update on the JavaFX UI thread
|
||||
Platform.runLater(() ->
|
||||
{
|
||||
for(float v : symbolDataCopy)
|
||||
{
|
||||
mSymbolSeries.getData().get(mChartSymbolPointer++).setYValue(v);
|
||||
mChartSymbolPointer %= SYMBOL_DISPLAY_COUNT;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
mSymbolQueuePointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.channel.state.SingleChannelState;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
@@ -164,8 +165,9 @@ public class P25P1Viewer extends VBox
|
||||
|
||||
ThreadPool.CACHED.submit(() -> {
|
||||
List<MessagePackage> messagePackages = new ArrayList<>();
|
||||
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
|
||||
P25P1MessageFramer messageFramer = new P25P1MessageFramer();
|
||||
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
|
||||
|
||||
Broadcaster<IMessage> messageBroadcaster = new Broadcaster<>();
|
||||
messageFramer.setListener(messageBroadcaster);
|
||||
messageBroadcaster.addListener(messageProcessor);
|
||||
@@ -229,7 +231,13 @@ public class P25P1Viewer extends VBox
|
||||
while(reader.hasNext())
|
||||
{
|
||||
ByteBuffer buffer = reader.next();
|
||||
messageFramer.receive(buffer);
|
||||
for(byte value : buffer.array())
|
||||
{
|
||||
for(int x = 0; x <= 3; x++)
|
||||
{
|
||||
messageFramer.processWithHardSyncDetect(Dibit.parse(value, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ioe)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -16,17 +16,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import javafx.scene.layout.VBox;
|
||||
package io.github.dsheirer.gui.viewer.symbol;
|
||||
|
||||
public abstract class AbstractDecoderPane<T> extends VBox implements Listener<T>
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public interface ISymbolResultsListener
|
||||
{
|
||||
abstract SampleType getSampleType();
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
}
|
||||
void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
|
||||
double[] points, float[] symbols, CountDownLatch release);
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.viewer.symbol;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.LinearInterpolator;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* JavaFX viewer application for visualizing sample buffers and detected sync patterns
|
||||
*/
|
||||
public class SymbolViewPanel extends VBox implements ISymbolResultsListener
|
||||
{
|
||||
private static final double TWO_PI = Math.PI * 2.0;
|
||||
private final NumberAxis mRawTiming = new NumberAxis();
|
||||
private final NumberAxis mRawValue = new NumberAxis(-1.1, 1.1, .1);
|
||||
private final NumberAxis mDemodulatedTiming = new NumberAxis();
|
||||
private final NumberAxis mDemodulatedValue = new NumberAxis(-1.1, 1.1, .1);
|
||||
private final NumberAxis mSymbolTiming = new NumberAxis();
|
||||
private final NumberAxis mSymbolValue = new NumberAxis(-Math.PI, Math.PI, Math.PI / 4.0);
|
||||
private final LineChart<Number, Number> mRawChart = new LineChart<>(mRawTiming, mRawValue);
|
||||
private final LineChart<Number, Number> mDemodulatedChart = new LineChart<>(mDemodulatedTiming, mDemodulatedValue);
|
||||
private final LineChart<Number, Number> mSymbolChart = new LineChart<>(mSymbolTiming, mSymbolValue);
|
||||
private final Button mReleaseButton = new Button("Next Symbol");
|
||||
private final Label mDisplayText = new Label();
|
||||
private CountDownLatch mRelease;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public SymbolViewPanel()
|
||||
{
|
||||
setPadding(new Insets(5));
|
||||
mRawTiming.setLabel("Raw Sample Timing");
|
||||
mRawValue.setLabel("Raw Sample Value (+/- 1.0)");
|
||||
mDemodulatedTiming.setLabel("Demodulated Sample Timing");
|
||||
mDemodulatedValue.setLabel("Demodulated Sample Value (+/- 1.0)");
|
||||
mSymbolTiming.setLabel("Symbol Timing");
|
||||
mSymbolValue.setLabel("Symbol Value (+/- PI)");
|
||||
|
||||
mReleaseButton.setOnAction(e -> {
|
||||
if(mRelease != null)
|
||||
{
|
||||
mRelease.countDown();
|
||||
mReleaseButton.setDisable(true);
|
||||
}
|
||||
});
|
||||
|
||||
mRawChart.setAnimated(false);
|
||||
mDemodulatedChart.setAnimated(false);
|
||||
mSymbolChart.setAnimated(false);
|
||||
|
||||
VBox.setVgrow(mRawChart, Priority.ALWAYS);
|
||||
VBox.setVgrow(mDemodulatedChart, Priority.ALWAYS);
|
||||
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
|
||||
|
||||
HBox buttons = new HBox();
|
||||
buttons.setAlignment(Pos.BASELINE_LEFT);
|
||||
buttons.setSpacing(10);
|
||||
buttons.getChildren().addAll(mReleaseButton, mDisplayText);
|
||||
VBox.setVgrow(buttons, Priority.NEVER);
|
||||
|
||||
getChildren().addAll(buttons, mRawChart, mDemodulatedChart, mSymbolChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
|
||||
double[] points, float[] symbolSequence, CountDownLatch release)
|
||||
{
|
||||
mRelease = release;
|
||||
mReleaseButton.setDisable(release == null);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
XYChart.Series<Number, Number> rawSeriesI = new XYChart.Series<>();
|
||||
rawSeriesI.setName("Raw Samples");
|
||||
XYChart.Series<Number, Number> rawSeriesQ = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> rawTiming = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> rawDecisionI = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> rawDecisionQ = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> demodulatedSeriesI = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> demodulatedSeriesQ = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> demodulatedTiming = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> demodulatedDecisionI = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> demodulatedDecisionQ = new XYChart.Series<>();
|
||||
|
||||
XYChart.Series<Number, Number> symbolsRaw = new XYChart.Series<>();
|
||||
symbolsRaw.setName("Symbols Raw");
|
||||
XYChart.Series<Number, Number> symbolsPLL = new XYChart.Series<>();
|
||||
symbolsPLL.setName("Symbols PLL");
|
||||
XYChart.Series<Number, Number> symbolTiming = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolP3 = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolP1 = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolM1 = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolM3 = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolsWithPLL = new XYChart.Series<>();
|
||||
XYChart.Series<Number, Number> symbolsWithoutPLL = new XYChart.Series<>();
|
||||
|
||||
float pllI = (float)Math.cos(pll);
|
||||
float pllQ = (float)Math.sin(pll);
|
||||
float pllTemp = 0;
|
||||
|
||||
double spsMax = Math.ceil(samplesPerSymbol);
|
||||
double fractional = spsMax - samplesPerSymbol;
|
||||
for(int x = 0; x < rawI.length; x++)
|
||||
{
|
||||
rawSeriesI.getData().add(new XYChart.Data<>(x, rawI[x] * sampleGain));
|
||||
rawSeriesQ.getData().add(new XYChart.Data<>(x, rawQ[x] * sampleGain));
|
||||
|
||||
if(x >= spsMax)
|
||||
{
|
||||
int offset = x - (int)spsMax;
|
||||
float previousI = LinearInterpolator.calculate(rawI[offset] * sampleGain, rawI[offset + 1] * sampleGain, fractional);
|
||||
float previousQ = LinearInterpolator.calculate(rawQ[offset] * sampleGain, rawQ[offset + 1] * sampleGain, fractional);
|
||||
float currentI = rawI[x] * sampleGain;
|
||||
float currentQ = rawQ[x] * sampleGain;
|
||||
float demodulatedI = (previousI * currentI) - (-previousQ * currentQ);
|
||||
float demodulatedQ = (previousI * currentQ) + (-previousQ * currentI);
|
||||
|
||||
//Rotate by the PLL
|
||||
pllTemp = (demodulatedI * pllI) - (demodulatedQ * pllQ);
|
||||
demodulatedQ = (demodulatedQ * pllI) + (demodulatedI * pllQ);
|
||||
demodulatedI = pllTemp;
|
||||
|
||||
demodulatedSeriesI.getData().add(new XYChart.Data<>(x, demodulatedI));
|
||||
demodulatedSeriesQ.getData().add(new XYChart.Data<>(x, demodulatedQ));
|
||||
symbolsRaw.getData().add(new XYChart.Data<>(x, Math.atan2(demodulatedQ, demodulatedI)));
|
||||
// symbolsPLL.getData().add(new XYChart.Data<>(x, normalize(Math.atan2(demodulatedQ, demodulatedI) + pll)));
|
||||
}
|
||||
}
|
||||
|
||||
rawTiming.getData().add(new XYChart.Data<>(0, -1));
|
||||
demodulatedTiming.getData().add(new XYChart.Data<>(0, -1));
|
||||
symbolTiming.getData().add(new XYChart.Data<>(0, -Math.PI));
|
||||
|
||||
for(int x = 0; x < 3; x++)
|
||||
{
|
||||
rawTiming.getData().add(new XYChart.Data<>(points[x], -1));
|
||||
rawTiming.getData().add(new XYChart.Data<>(points[x], 1));
|
||||
rawTiming.getData().add(new XYChart.Data<>(points[x], -1));
|
||||
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], -1));
|
||||
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], 1));
|
||||
demodulatedTiming.getData().add(new XYChart.Data<>(points[x], -1));
|
||||
symbolTiming.getData().add(new XYChart.Data<>(points[x], -Math.PI));
|
||||
symbolTiming.getData().add(new XYChart.Data<>(points[x], Math.PI));
|
||||
symbolTiming.getData().add(new XYChart.Data<>(points[x], -Math.PI));
|
||||
}
|
||||
|
||||
rawTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -1));
|
||||
demodulatedTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -1));
|
||||
symbolTiming.getData().add(new XYChart.Data<>(rawI.length - 1, -Math.PI));
|
||||
|
||||
rawDecisionI.getData().add(new XYChart.Data<>(points[0], points[3]));
|
||||
rawDecisionI.getData().add(new XYChart.Data<>(points[1], points[4]));
|
||||
rawDecisionI.getData().add(new XYChart.Data<>(points[2], points[5]));
|
||||
|
||||
rawDecisionQ.getData().add(new XYChart.Data<>(points[0], points[6]));
|
||||
rawDecisionQ.getData().add(new XYChart.Data<>(points[1], points[7]));
|
||||
rawDecisionQ.getData().add(new XYChart.Data<>(points[2], points[8]));
|
||||
|
||||
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[0], points[9]));
|
||||
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[1], points[10]));
|
||||
demodulatedDecisionI.getData().add(new XYChart.Data<>(points[2], points[11]));
|
||||
|
||||
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[0], points[12]));
|
||||
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[1], points[13]));
|
||||
demodulatedDecisionQ.getData().add(new XYChart.Data<>(points[2], points[14]));
|
||||
|
||||
symbolP3.getData().add(new XYChart.Data<>(0, 3 * Math.PI / 4));
|
||||
symbolP1.getData().add(new XYChart.Data<>(0, Math.PI / 4));
|
||||
symbolM1.getData().add(new XYChart.Data<>(0, -Math.PI / 4));
|
||||
symbolM3.getData().add(new XYChart.Data<>(0, -3 * Math.PI / 4));
|
||||
symbolP3.getData().add(new XYChart.Data<>(rawI.length - 1, 3 * Math.PI / 4));
|
||||
symbolP1.getData().add(new XYChart.Data<>(rawI.length - 1, Math.PI / 4));
|
||||
symbolM1.getData().add(new XYChart.Data<>(rawI.length - 1, -Math.PI / 4));
|
||||
symbolM3.getData().add(new XYChart.Data<>(rawI.length - 1, -3 * Math.PI / 4));
|
||||
|
||||
symbolsWithPLL.getData().add((new XYChart.Data<>(points[0], symbolSequence[0])));
|
||||
// symbolsWithPLL.getData().add((new XYChart.Data<>(points[1], symbolSequence[1])));
|
||||
symbolsWithPLL.getData().add((new XYChart.Data<>(points[2], symbolSequence[2])));
|
||||
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[0], normalize(symbolSequence[0] - pll))));
|
||||
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[1], normalize(symbolSequence[1] - pll))));
|
||||
// symbolsWithoutPLL.getData().add((new XYChart.Data<>(points[2], normalize(symbolSequence[2] - pll))));
|
||||
|
||||
mRawChart.getData().clear();
|
||||
mRawChart.getData().add(rawSeriesI);
|
||||
mRawChart.getData().add(rawSeriesQ);
|
||||
mRawChart.getData().add(rawTiming);
|
||||
mRawChart.getData().add(rawDecisionI);
|
||||
mRawChart.getData().add(rawDecisionQ);
|
||||
mDemodulatedChart.getData().clear();
|
||||
mDemodulatedChart.getData().add(demodulatedSeriesI);
|
||||
mDemodulatedChart.getData().add(demodulatedSeriesQ);
|
||||
mDemodulatedChart.getData().add(demodulatedTiming);
|
||||
mDemodulatedChart.getData().add(demodulatedDecisionI);
|
||||
mDemodulatedChart.getData().add(demodulatedDecisionQ);
|
||||
mSymbolChart.getData().clear();
|
||||
mSymbolChart.getData().add(symbolsRaw);
|
||||
// mSymbolChart.getData().add(symbolsPLL);
|
||||
mSymbolChart.getData().add(symbolTiming);
|
||||
mSymbolChart.getData().add(symbolP3);
|
||||
mSymbolChart.getData().add(symbolP1);
|
||||
mSymbolChart.getData().add(symbolM1);
|
||||
mSymbolChart.getData().add(symbolM3);
|
||||
mSymbolChart.getData().add(symbolsWithPLL);
|
||||
// mSymbolChart.getData().add(symbolsWithoutPLL);
|
||||
mDisplayText.setText("PLL: " + pll + " Sample Gain: " + sampleGain);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the value in radians to the range of +/-(2 * PI)
|
||||
* @param value to normalize
|
||||
* @return normalized value.
|
||||
*/
|
||||
private static double normalize(double value)
|
||||
{
|
||||
if(value > Math.PI)
|
||||
{
|
||||
return value - TWO_PI;
|
||||
}
|
||||
else if(value < -Math.PI)
|
||||
{
|
||||
return value + TWO_PI;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.viewer.symbol;
|
||||
|
||||
import io.github.dsheirer.util.SwingUtils;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
/**
|
||||
* Utility for viewing sync detection results
|
||||
*/
|
||||
public class SymbolViewerFX implements ISymbolResultsListener
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SymbolViewerFX.class.getName());
|
||||
private ISymbolResultsListener mListener;
|
||||
|
||||
public SymbolViewerFX()
|
||||
{
|
||||
initUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(double samplesPerSymbol, float[] rawI, float[] rawQ, float sampleGain, float pll,
|
||||
double[] points, float[] symbols, CountDownLatch releasee)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(samplesPerSymbol, rawI, rawQ, sampleGain, pll, points, symbols, releasee);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the viewer UI
|
||||
*/
|
||||
private void initUI()
|
||||
{
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final JFXPanel fxPanel = new JFXPanel();
|
||||
Platform.runLater(() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
SymbolViewPanel viewer = new SymbolViewPanel();
|
||||
mListener = viewer;
|
||||
Scene scene = new Scene(viewer, 1400, 1000);
|
||||
|
||||
// URL resource = getClass().getResource("/sdrtrunk_style.css");
|
||||
//
|
||||
// if(resource != null)
|
||||
// {
|
||||
// scene.getStylesheets().add(resource.toExternalForm());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
|
||||
// }
|
||||
|
||||
fxPanel.setScene(scene);
|
||||
fxPanel.setVisible(true);
|
||||
|
||||
JFrame frame = new JFrame();
|
||||
frame.setTitle("Symbol Results Viewer");
|
||||
frame.setContentPane(fxPanel);
|
||||
frame.setSize(1400, 1400);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLocationRelativeTo(null);
|
||||
SwingUtils.run(() -> {
|
||||
frame.setVisible(true);
|
||||
latch.countDown();
|
||||
});
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
latch.await();
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.sync.visualizer;
|
||||
package io.github.dsheirer.gui.viewer.sync;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@@ -32,8 +32,17 @@ public interface ISyncResultsListener
|
||||
* @param sync pattern symbols (ideal)
|
||||
* @param samples demodulated samples
|
||||
* @param syncIntervals for timing of each symbol in the samples array
|
||||
* @param equalizerBalance balance for the equalizer
|
||||
* @param equalizerGain gain for the equalizer
|
||||
* @param label to display in the UI
|
||||
* @param release that pauses execution until the user acknowledges the displayed sync results. Decrement the latch to release.
|
||||
*/
|
||||
void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, String label, CountDownLatch release);
|
||||
void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, float equalizerBalance,
|
||||
float equalizerGain, String label, CountDownLatch release);
|
||||
|
||||
/**
|
||||
* Post a symbol value to the listener
|
||||
* @param symbol value in range +/- PI
|
||||
*/
|
||||
void symbol(float symbol);
|
||||
}
|
||||
@@ -17,13 +17,15 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.sync.visualizer;
|
||||
package io.github.dsheirer.gui.viewer.sync;
|
||||
|
||||
import io.github.dsheirer.util.SwingUtils;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
@@ -32,6 +34,7 @@ import javax.swing.JFrame;
|
||||
*/
|
||||
public class SyncResultsViewer implements ISyncResultsListener
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SyncResultsViewer.class.getName());
|
||||
private ISyncResultsListener mListener;
|
||||
|
||||
public SyncResultsViewer()
|
||||
@@ -42,18 +45,28 @@ public class SyncResultsViewer implements ISyncResultsListener
|
||||
/**
|
||||
* View sync detection results.
|
||||
* @param symbols that were detected
|
||||
* @param samples for the symbols
|
||||
* @param sync that was detected
|
||||
* @param samples for the symbols
|
||||
* @param syncIntervals symbol timing interval pointers into the I/Q sample arrays
|
||||
* @param label to display to the user
|
||||
* @param release latch to release via the UI when done viewing these results
|
||||
*/
|
||||
@Override
|
||||
public void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, String label, CountDownLatch release)
|
||||
public void receive(float[] symbols, float[] sync, float[] samples, float[] syncIntervals, float equalizerBalance,
|
||||
float equalizerGain, String label, CountDownLatch release)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(symbols, samples, sync, syncIntervals, label, release);
|
||||
mListener.receive(symbols, samples, sync, syncIntervals, equalizerBalance, equalizerGain, label, release);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbol(float symbol)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.symbol(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,14 +82,27 @@ public class SyncResultsViewer implements ISyncResultsListener
|
||||
Platform.setImplicitExit(false);
|
||||
SyncVisualizer syncVisualizer = new SyncVisualizer();
|
||||
mListener = syncVisualizer;
|
||||
|
||||
Scene scene = new Scene(syncVisualizer, 1400, 1000);
|
||||
|
||||
// URL resource = getClass().getResource("/sdrtrunk_style.css");
|
||||
//
|
||||
// if(resource != null)
|
||||
// {
|
||||
// scene.getStylesheets().add(resource.toExternalForm());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOGGER.warn("Can't find stylesheet resource for sdrtrunk");
|
||||
// }
|
||||
|
||||
fxPanel.setScene(scene);
|
||||
fxPanel.setVisible(true);
|
||||
|
||||
JFrame frame = new JFrame();
|
||||
frame.setTitle("Sync Results Viewer");
|
||||
frame.setContentPane(fxPanel);
|
||||
frame.setSize(1400, 1000);
|
||||
frame.setSize(1400, 1400);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLocationRelativeTo(null);
|
||||
SwingUtils.run(() -> {
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.viewer.sync;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* JavaFX viewer application for visualizing sample buffers and detected sync patterns
|
||||
*/
|
||||
public class SyncVisualizer extends VBox implements ISyncResultsListener
|
||||
{
|
||||
private static final int SYMBOL_CHART_SERIES_COUNT = 2000;
|
||||
private static final float SYMBOL = (float) (Math.PI / 4.0);
|
||||
private final NumberAxis mConstellationI = new NumberAxis();
|
||||
private final NumberAxis mConstellationQ = new NumberAxis();
|
||||
private final NumberAxis mSampleTiming = new NumberAxis();
|
||||
private final NumberAxis mSamplePhase = new NumberAxis(-3.5, 3.5, .5);
|
||||
private final NumberAxis mSymbolTiming = new NumberAxis();
|
||||
private final NumberAxis mSymbolPhase = new NumberAxis(-Math.PI, Math.PI, Math.PI / 2);
|
||||
private final XYChart.Series<Number, Number> mSymbolSeries = new XYChart.Series<>();
|
||||
private final XYChart.Series<Number, Number> mSymbolSeriesMarker = new XYChart.Series<>();
|
||||
private final ScatterChart<Number, Number> mConstellationChart = new ScatterChart<>(mConstellationI, mConstellationQ);
|
||||
private final LineChart<Number, Number> mSampleChart = new LineChart<>(mSampleTiming, mSamplePhase);
|
||||
private final ScatterChart<Number, Number> mSymbolChart = new ScatterChart<>(mSymbolTiming, mSymbolPhase);
|
||||
|
||||
private final NumberAxis mEqualizerHistoryAxis = new NumberAxis();
|
||||
private final NumberAxis mEqualizerValueAxis = new NumberAxis(-1.5, 1.5, .1);
|
||||
private final LineChart<Number, Number> mEqualizerChart = new LineChart<>(mEqualizerHistoryAxis, mEqualizerValueAxis);
|
||||
private int mEqualizerPointer = 0;
|
||||
private int mEqualizerHistorySize = 40;
|
||||
private float[] mEqualizerBalanceHistory = new float[mEqualizerHistorySize];
|
||||
private float[] mEqualizerGainHistory = new float[mEqualizerHistorySize];
|
||||
|
||||
private final Button mReleaseButton = new Button("Next Sync");
|
||||
private final Label mPatternLabel = new Label();
|
||||
private CountDownLatch mRelease;
|
||||
private int mSymbolPointer = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public SyncVisualizer()
|
||||
{
|
||||
setPadding(new Insets(5));
|
||||
mConstellationI.setLabel("Constellation - Inphase");
|
||||
mConstellationQ.setLabel("Quadrature");
|
||||
mSampleTiming.setLabel("Sample Timing");
|
||||
mSamplePhase.setLabel("Sample Phase (+/- PI)");
|
||||
|
||||
mReleaseButton.setOnAction(e -> {
|
||||
if(mRelease != null)
|
||||
{
|
||||
mRelease.countDown();
|
||||
mReleaseButton.setDisable(true);
|
||||
}
|
||||
});
|
||||
|
||||
mConstellationChart.setPrefSize(400, 400);
|
||||
mConstellationChart.setAnimated(false);
|
||||
mSampleChart.setAnimated(false);
|
||||
mSymbolChart.setAnimated(false);
|
||||
mSymbolChart.setId("symbol-chart");
|
||||
mEqualizerChart.setAnimated(false);
|
||||
|
||||
mEqualizerValueAxis.setLabel("Value");
|
||||
mEqualizerHistoryAxis.setLabel("Equalizer");
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.getChildren().add(mConstellationChart);
|
||||
HBox.setHgrow(mEqualizerChart, Priority.ALWAYS);
|
||||
hbox.getChildren().add(mEqualizerChart);
|
||||
// VBox.setVgrow(hbox, Priority.ALWAYS);
|
||||
|
||||
VBox.setVgrow(mSampleChart, Priority.ALWAYS);
|
||||
VBox.setVgrow(mSymbolChart, Priority.ALWAYS);
|
||||
|
||||
HBox buttons = new HBox();
|
||||
buttons.setAlignment(Pos.BASELINE_LEFT);
|
||||
buttons.setSpacing(10);
|
||||
buttons.getChildren().addAll(mReleaseButton, mPatternLabel);
|
||||
VBox.setVgrow(buttons, Priority.NEVER);
|
||||
|
||||
//Load the symbol chart with symbols
|
||||
mSymbolSeries.setName("Symbols");
|
||||
for(int x = 0; x < SYMBOL_CHART_SERIES_COUNT; x++)
|
||||
{
|
||||
mSymbolSeries.getData().add(new XYChart.Data<>(x, 0));
|
||||
}
|
||||
mSymbolChart.getData().add(mSymbolSeries);
|
||||
float pi_4 = (float)Math.PI / 4f;
|
||||
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, 3f * pi_4));
|
||||
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, pi_4));
|
||||
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, 0));
|
||||
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, -pi_4));
|
||||
mSymbolSeriesMarker.getData().add(new XYChart.Data<>(0, -3 * pi_4));
|
||||
mSymbolChart.getData().add(mSymbolSeriesMarker);
|
||||
getChildren().addAll(buttons, hbox, mSampleChart, mSymbolChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbol(float symbol)
|
||||
{
|
||||
for(XYChart.Data<Number, Number> data : mSymbolSeriesMarker.getData())
|
||||
{
|
||||
data.setXValue(mSymbolPointer);
|
||||
}
|
||||
mSymbolSeries.getData().get(mSymbolPointer++).setYValue(symbol);
|
||||
mSymbolPointer %= SYMBOL_CHART_SERIES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] symbols, float[] samples, float[] sync, float[] syncIntervals, float equalizerBalance, float equalizerGain, String label, CountDownLatch latch)
|
||||
{
|
||||
mRelease = latch;
|
||||
mReleaseButton.setDisable(latch == null);
|
||||
|
||||
mEqualizerBalanceHistory[mEqualizerPointer] = equalizerBalance;
|
||||
mEqualizerGainHistory[mEqualizerPointer++] = (equalizerGain - 1.0f);
|
||||
mEqualizerPointer %= mEqualizerHistorySize;
|
||||
//Clear the next/upcoming values to give visual indication of where we're at.
|
||||
mEqualizerBalanceHistory[mEqualizerPointer] = 0f;
|
||||
mEqualizerGainHistory[mEqualizerPointer] = 0f;
|
||||
|
||||
Platform.runLater(() -> {
|
||||
XYChart.Series<Number, Number> constellationIdeal = new XYChart.Series<>();
|
||||
constellationIdeal.setName("Ideal");
|
||||
float PI_4 = (float) (Math.PI / 4);
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, -PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, -PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(1, 0));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-1, 0));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(0, 1));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(0, -1));
|
||||
|
||||
mConstellationChart.getData().clear();
|
||||
mConstellationChart.getData().add(constellationIdeal);
|
||||
|
||||
XYChart.Series<Number, Number> constellation = new XYChart.Series<>();
|
||||
constellation.setName("Decoded");
|
||||
|
||||
for(int x = 0; x < symbols.length; x++)
|
||||
{
|
||||
constellation.getData().add(new XYChart.Data<>(Math.cos(symbols[x]), Math.sin(symbols[x])));
|
||||
}
|
||||
|
||||
mConstellationChart.getData().add(constellation);
|
||||
|
||||
XYChart.Series<Number, Number> sampleSeries = new XYChart.Series<>();
|
||||
sampleSeries.setName("Demodulated Samples");
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
sampleSeries.getData().add(new XYChart.Data<>(x, samples[x]));
|
||||
}
|
||||
|
||||
mSampleChart.getData().clear();
|
||||
mSampleChart.getData().add(sampleSeries);
|
||||
|
||||
XYChart.Series<Number, Number> patternSeries = new XYChart.Series<>();
|
||||
patternSeries.setName("Ideal Sync Pattern");
|
||||
|
||||
for(int x = 0; x < sync.length; x++)
|
||||
{
|
||||
patternSeries.getData().add(new XYChart.Data<>(syncIntervals[x], sync[x]));
|
||||
}
|
||||
|
||||
mSampleChart.getData().add(patternSeries);
|
||||
|
||||
mPatternLabel.setText(label);
|
||||
mEqualizerChart.getData().clear();
|
||||
|
||||
XYChart.Series<Number, Number> balanceSeries = new XYChart.Series<>();
|
||||
balanceSeries.setName("Balance");
|
||||
XYChart.Series<Number, Number> gainSeries = new XYChart.Series<>();
|
||||
gainSeries.setName("Gain (Value minus 1.0)");
|
||||
|
||||
for(int x = 0; x < mEqualizerHistorySize; x++)
|
||||
{
|
||||
balanceSeries.getData().add(new XYChart.Data<>(x, mEqualizerBalanceHistory[x]));
|
||||
gainSeries.getData().add(new XYChart.Data<>(x, mEqualizerGainHistory[x]));
|
||||
}
|
||||
|
||||
mEqualizerChart.getData().add(balanceSeries);
|
||||
mEqualizerChart.getData().add(gainSeries);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.message;
|
||||
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Empty message to convey that a number of dropped samples produced a shortfall of expected bits. This can result
|
||||
* from either the tuner dropping the samples, or from a decoder dropping samples due to timing adjustments.
|
||||
*
|
||||
* This message supports periodic updates to user to indicate that the USB bus and/or the tuner may be causing dropped
|
||||
* samples.
|
||||
*/
|
||||
public class DroppedSamplesMessage extends Message
|
||||
{
|
||||
private int mBitsDropped;
|
||||
private Protocol mProtocol;
|
||||
private int mTimeslot = 0;
|
||||
|
||||
/**
|
||||
* Constructs a dropped samples message.
|
||||
*
|
||||
* @param timestamp of the message
|
||||
* @param bitsDropped for the expected message length
|
||||
* @param timeslot that lost the sync
|
||||
*/
|
||||
public DroppedSamplesMessage(long timestamp, int bitsDropped, Protocol protocol, int timeslot)
|
||||
{
|
||||
super(timestamp);
|
||||
mBitsDropped = bitsDropped;
|
||||
mProtocol = protocol;
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a dropped samples message.
|
||||
*
|
||||
* @param timestamp of the message
|
||||
* @param bitsDropped for the expected message length
|
||||
*/
|
||||
public DroppedSamplesMessage(long timestamp, int bitsDropped, Protocol protocol)
|
||||
{
|
||||
this(timestamp, bitsDropped, protocol, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeslot that has dropped bits
|
||||
* @return timeslot or a default of 0
|
||||
*/
|
||||
@Override
|
||||
public int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the number of bits that were detected to be dropped
|
||||
*/
|
||||
public int getBitsDropped()
|
||||
{
|
||||
return mBitsDropped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "<-> DROPPED SAMPLES (POSSIBLY) DETECTED -- COULD NOT ACCOUNT FOR [" + getBitsDropped() + "] SYMBOL BITS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
@@ -177,9 +177,9 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
||||
{
|
||||
for(Module module : mModules)
|
||||
{
|
||||
if(module instanceof PrimaryDecoder primaryDecoder)
|
||||
if(module instanceof PrimaryDecoder decoder)
|
||||
{
|
||||
return primaryDecoder;
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode;
|
||||
@@ -30,9 +27,16 @@ import io.github.dsheirer.source.SourceEvent;
|
||||
* Decoder that provides feedback to signal source(s) via SourceEvent broadcasts and supports registering source
|
||||
* event listeners.
|
||||
*/
|
||||
public abstract class FeedbackDecoder extends Decoder implements ISourceEventProvider
|
||||
public abstract class FeedbackDecoder extends PrimaryDecoder implements ISourceEventProvider
|
||||
{
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
private Listener<Float> mSymbolListener;
|
||||
|
||||
/**
|
||||
* Protocol description suitable for display in the user interface
|
||||
* @return description
|
||||
*/
|
||||
public abstract String getProtocolDescription();
|
||||
|
||||
/**
|
||||
* Registers the listener to receive source events from this decoder
|
||||
@@ -62,4 +66,43 @@ public abstract class FeedbackDecoder extends Decoder implements ISourceEventPro
|
||||
mSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current phase-locked loop (PLL) measurement and sends to the parent tuner for self correction.
|
||||
* @param pllError as measured in the decoder.
|
||||
*/
|
||||
public void processPLLError(float pllError, int baudRate)
|
||||
{
|
||||
long frequencyError = (long)(baudRate / 2.0 * (pllError / Math.PI));
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(frequencyError, "Decoder measured error sync locked"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive demodulated symbols.
|
||||
* @param listener to receive symbols.
|
||||
*/
|
||||
public void setSymbolListener(Listener<Float> listener)
|
||||
{
|
||||
mSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any registered symbol listener
|
||||
*/
|
||||
public void removeSymbolListener()
|
||||
{
|
||||
mSymbolListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the demodulated symbol to a symbol listener.
|
||||
* @param symbol in range 0 to +/- PI radians
|
||||
*/
|
||||
public void broadcast(float symbol)
|
||||
{
|
||||
if(mSymbolListener != null)
|
||||
{
|
||||
mSymbolListener.receive(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,27 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode;
|
||||
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Primary decoder adds the following functionality over the basic decoder:
|
||||
*
|
||||
* - Provides audio squelch control
|
||||
* Base class for primary decoder
|
||||
*/
|
||||
|
||||
public abstract class PrimaryDecoder extends Decoder implements ISquelchStateProvider
|
||||
public abstract class PrimaryDecoder extends Decoder
|
||||
{
|
||||
protected Listener<SquelchStateEvent> mSquelchStateListener;
|
||||
|
||||
protected DecodeConfiguration mDecodeConfiguration;
|
||||
|
||||
public PrimaryDecoder(DecodeConfiguration config)
|
||||
{
|
||||
mDecodeConfiguration = config;
|
||||
}
|
||||
|
||||
public DecodeConfiguration getDecodeConfiguration()
|
||||
{
|
||||
return mDecodeConfiguration;
|
||||
}
|
||||
|
||||
public void broadcast(SquelchStateEvent state)
|
||||
{
|
||||
if(mSquelchStateListener != null)
|
||||
{
|
||||
mSquelchStateListener.receive(state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
|
||||
{
|
||||
mSquelchStateListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode;
|
||||
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Primary decoder that adds the following functionality over the basic decoder:
|
||||
*
|
||||
* - Provides audio squelch control
|
||||
*/
|
||||
public abstract class SquelchControlDecoder extends PrimaryDecoder implements ISquelchStateProvider
|
||||
{
|
||||
protected Listener<SquelchStateEvent> mSquelchStateListener;
|
||||
|
||||
protected DecodeConfiguration mDecodeConfiguration;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param config for the decoder
|
||||
*/
|
||||
public SquelchControlDecoder(DecodeConfiguration config)
|
||||
{
|
||||
mDecodeConfiguration = config;
|
||||
}
|
||||
|
||||
public DecodeConfiguration getDecodeConfiguration()
|
||||
{
|
||||
return mDecodeConfiguration;
|
||||
}
|
||||
|
||||
public void broadcast(SquelchStateEvent state)
|
||||
{
|
||||
if(mSquelchStateListener != null)
|
||||
{
|
||||
mSquelchStateListener.receive(state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
|
||||
{
|
||||
mSquelchStateListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import io.github.dsheirer.dsp.squelch.NoiseSquelch;
|
||||
import io.github.dsheirer.dsp.squelch.NoiseSquelchState;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.PrimaryDecoder;
|
||||
import io.github.dsheirer.module.decode.SquelchControlDecoder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.IComplexSamplesListener;
|
||||
@@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* AM decoder module with integrated squelch control
|
||||
*/
|
||||
public class AMDecoder extends PrimaryDecoder implements ISourceEventListener, ISourceEventProvider,
|
||||
public class AMDecoder extends SquelchControlDecoder implements ISourceEventListener, ISourceEventProvider,
|
||||
IComplexSamplesListener, Listener<ComplexSamples>, IRealBufferProvider,
|
||||
IDecoderStateEventProvider, INoiseSquelchController
|
||||
{
|
||||
|
||||
@@ -26,14 +26,14 @@ import io.github.dsheirer.dsp.filter.decimate.IRealDecimationFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorFactory;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorFloat;
|
||||
import io.github.dsheirer.dsp.squelch.PowerMonitor;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
import io.github.dsheirer.module.carrier.CarrierOffsetProcessor;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||
@@ -82,7 +82,7 @@ import org.slf4j.LoggerFactory;
|
||||
* The DMRMessageProcessor processes messages from the message framer to extract and reassemble link control and
|
||||
* embedded link control parameters.
|
||||
*/
|
||||
public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener,
|
||||
public class DMRDecoder extends FeedbackDecoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener,
|
||||
ISourceEventProvider, Listener<ComplexSamples>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DMRDecoder.class);
|
||||
@@ -90,7 +90,7 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
private static final float MAXIMUM_CARRIER_OFFSET = 5000.0f; //Threshold for retuning the signal.
|
||||
private static final Map<Double,float[]> BASEBAND_FILTERS = new HashMap<>();
|
||||
private DifferentialDemodulator mDemodulator;
|
||||
private DifferentialDemodulatorFloat mDemodulator;
|
||||
private final DMRMessageFramer mMessageFramer;
|
||||
private final DMRSoftSymbolProcessor mSymbolProcessor;
|
||||
private final DMRMessageProcessor mMessageProcessor;
|
||||
@@ -111,7 +111,7 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
{
|
||||
DMRCrcMaskManager crcMaskManager = new DMRCrcMaskManager(config.getIgnoreCRCChecksums());
|
||||
mMessageFramer = new DMRMessageFramer(crcMaskManager);
|
||||
mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
|
||||
mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer, this);
|
||||
mMessageProcessor = new DMRMessageProcessor(config, crcMaskManager);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
setSampleRate(25000.0);
|
||||
@@ -171,7 +171,7 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
mRRCFilterI = new RealFIRFilter(taps);
|
||||
mRRCFilterQ = new RealFIRFilter(taps);
|
||||
|
||||
mDemodulator = DifferentialDemodulatorFactory.getDemodulator(decimatedSampleRate, SYMBOL_RATE);
|
||||
mDemodulator = DifferentialDemodulatorFactory.getFloatDemodulator(decimatedSampleRate, SYMBOL_RATE);
|
||||
mSymbolProcessor.setSamplesPerSymbol(mDemodulator.getSamplesPerSymbol());
|
||||
mMessageFramer.setListener(mMessageProcessor);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
@@ -293,6 +293,12 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
return this::process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocolDescription()
|
||||
{
|
||||
return "DMR C4FM";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source event listener to receive source events from this decoder.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.dsp.symbol.DibitDelayLine;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRHardSyncDetector;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
@@ -21,13 +21,15 @@ package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.LinearInterpolator;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.dsp.symbol.DibitDelayLine;
|
||||
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
|
||||
import io.github.dsheirer.gui.viewer.sync.SyncResultsViewer;
|
||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncDetectMode;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncModeMonitor;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.visualizer.SyncResultsViewer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
@@ -65,9 +67,10 @@ public class DMRSoftSymbolProcessor
|
||||
private static final int BUFFER_PROTECTED_REGION_DIBITS = 92;
|
||||
private static final int BUFFER_WORKSPACE_LENGTH_DIBITS = 25; //This can be adjusted for efficiency
|
||||
private static final int BUFFER_LENGTH_DIBITS = BUFFER_PROTECTED_REGION_DIBITS + BUFFER_WORKSPACE_LENGTH_DIBITS;
|
||||
private static final float EQUALIZER_LOOP_GAIN = 0.3f;
|
||||
private static final float MAXIMUM_EQUALIZER_BALANCE = (float)(Math.PI / 4.0);
|
||||
private static final float MAXIMUM_EQUALIZER_GAIN = 1.35f;
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
private static final float EQUALIZER_LOOP_GAIN = 0.15f;
|
||||
private static final float MAXIMUM_EQUALIZER_BALANCE = (float)(Math.PI / 3.0);
|
||||
private static final float MAXIMUM_EQUALIZER_GAIN = 1.25f;
|
||||
private static final float MAXIMUM_POSITIVE_SAMPLE_PHASE = 3.5f;
|
||||
private static final float MAXIMUM_NEGATIVE_SAMPLE_PHASE = -3.5f;
|
||||
private static final float SAMPLES_PER_SYMBOL_ALLOWABLE_DEVIATION = 0.005f; //.5%
|
||||
@@ -100,12 +103,13 @@ public class DMRSoftSymbolProcessor
|
||||
private int mBufferWorkspaceLength;
|
||||
private int mSymbolsSinceLastSync = 0;
|
||||
private SyncResultsViewer mSyncResultsViewer;
|
||||
private FeedbackDecoder mFeedbackDecoder;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param messageFramer to receive demodulated symbols/dibits and sync detect notifications
|
||||
*/
|
||||
public DMRSoftSymbolProcessor(DMRMessageFramer messageFramer)
|
||||
public DMRSoftSymbolProcessor(DMRMessageFramer messageFramer, FeedbackDecoder feedbackDecoder)
|
||||
{
|
||||
if(messageFramer == null)
|
||||
{
|
||||
@@ -113,6 +117,7 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
|
||||
mMessageFramer = messageFramer;
|
||||
mFeedbackDecoder = feedbackDecoder;
|
||||
mSyncModeMonitor.add(mSyncDetector);
|
||||
mSyncModeMonitor.add(mSyncDetectorSecondary);
|
||||
}
|
||||
@@ -215,6 +220,7 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
|
||||
softSymbol = LinearInterpolator.calculate(mBuffer[mBufferPointer], mBuffer[mBufferPointer + 1], mSamplePoint);
|
||||
mFeedbackDecoder.broadcast(softSymbol);
|
||||
mSyncDetector.process(softSymbol);
|
||||
Dibit symbol = toSymbol(softSymbol);
|
||||
|
||||
@@ -559,6 +565,8 @@ public class DMRSoftSymbolProcessor
|
||||
mEqualizerBalance = Math.min(mEqualizerBalance, MAXIMUM_EQUALIZER_BALANCE);
|
||||
mEqualizerBalance = Math.max(mEqualizerBalance, -MAXIMUM_EQUALIZER_BALANCE);
|
||||
|
||||
mFeedbackDecoder.processPLLError(mEqualizerBalance, SYMBOL_RATE);
|
||||
|
||||
//Constrain gain between 1.0f and 1.35f
|
||||
mEqualizerGain = Math.min(mEqualizerGain, MAXIMUM_EQUALIZER_GAIN);
|
||||
mEqualizerGain = Math.max(mEqualizerGain, 1.0f);
|
||||
@@ -608,7 +616,8 @@ public class DMRSoftSymbolProcessor
|
||||
Arrays.fill(sync, -1.57f); //Cutoff between -1 & -1 to show symbol timings
|
||||
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
mSyncResultsViewer.receive(symbols, sync, samples, intervals, "BUFFER CONTENTS", countDownLatch);
|
||||
mSyncResultsViewer.receive(symbols, sync, samples, intervals, mEqualizerBalance, mEqualizerGain,
|
||||
"BUFFER CONTENTS", countDownLatch);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -665,7 +674,7 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
mSyncResultsViewer.receive(symbols, pattern.toSymbols(), samples, intervals,
|
||||
mSyncResultsViewer.receive(symbols, pattern.toSymbols(), samples, intervals, mEqualizerBalance, mEqualizerGain,
|
||||
pattern + " Score: " + score + (primary ? " PRIMARY" : " SECONDARY"), countDownLatch);
|
||||
|
||||
try
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.sync.visualizer;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* JavaFX viewer application for visualizing sample buffers and detected sync patterns
|
||||
*/
|
||||
public class SyncVisualizer extends VBox implements ISyncResultsListener
|
||||
{
|
||||
private static final float SYMBOL = (float)(Math.PI / 4.0);
|
||||
private final NumberAxis mConstellationI = new NumberAxis();
|
||||
private final NumberAxis mConstellationQ = new NumberAxis();
|
||||
private final NumberAxis mSampleTiming = new NumberAxis();
|
||||
private final NumberAxis mSamplePhase = new NumberAxis();
|
||||
private final ScatterChart<Number,Number> mConstellationChart = new ScatterChart<>(mConstellationI, mConstellationQ);
|
||||
private final LineChart<Number,Number> mSampleChart = new LineChart<>(mSampleTiming, mSamplePhase);
|
||||
private final Button mReleaseButton = new Button("Next Sync");
|
||||
private final Label mPatternLabel = new Label();
|
||||
private CountDownLatch mRelease;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public SyncVisualizer()
|
||||
{
|
||||
setPadding(new Insets(5));
|
||||
mConstellationI.setLabel("Inphase");
|
||||
mConstellationQ.setLabel("Quadrature");
|
||||
mSampleTiming.setLabel("Sample Timing");
|
||||
mSamplePhase.setLabel("Sample Phase (+/- PI)");
|
||||
|
||||
mReleaseButton.setOnAction(e ->
|
||||
{
|
||||
if(mRelease != null)
|
||||
{
|
||||
mRelease.countDown();
|
||||
mReleaseButton.setDisable(true);
|
||||
}
|
||||
});
|
||||
|
||||
mConstellationChart.setPrefSize(400, 400);
|
||||
HBox hbox = new HBox();
|
||||
hbox.getChildren().add(mConstellationChart);
|
||||
// VBox.setVgrow(hbox, Priority.ALWAYS);
|
||||
VBox.setVgrow(mSampleChart, Priority.ALWAYS);
|
||||
|
||||
HBox buttons = new HBox();
|
||||
buttons.setAlignment(Pos.BASELINE_CENTER);
|
||||
buttons.setSpacing(10);
|
||||
buttons.getChildren().addAll(mReleaseButton, mPatternLabel);
|
||||
VBox.setVgrow(buttons, Priority.NEVER);
|
||||
|
||||
getChildren().addAll(hbox, mSampleChart, buttons);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] symbols, float[] samples, float[] sync, float[] syncIntervals, String label, CountDownLatch latch)
|
||||
{
|
||||
mRelease = latch;
|
||||
mReleaseButton.setDisable(latch == null);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
XYChart.Series<Number,Number> constellationIdeal = new XYChart.Series<>();
|
||||
constellationIdeal.setName("Constellation Ideal");
|
||||
float PI_4 = (float)(Math.PI / 4);
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(PI_4, -PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-PI_4, -PI_4));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(1, 0));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(-1, 0));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(0, 1));
|
||||
constellationIdeal.getData().add(new XYChart.Data<>(0, -1));
|
||||
|
||||
mConstellationChart.getData().clear();
|
||||
mConstellationChart.getData().add(constellationIdeal);
|
||||
|
||||
XYChart.Series<Number,Number> constellation = new XYChart.Series<>();
|
||||
constellation.setName("Constellation");
|
||||
|
||||
for(int x = 0; x < symbols.length; x++)
|
||||
{
|
||||
constellation.getData().add(new XYChart.Data<>(Math.cos(symbols[x]), Math.sin(symbols[x])));
|
||||
}
|
||||
|
||||
mConstellationChart.getData().add(constellation);
|
||||
|
||||
|
||||
XYChart.Series<Number,Number> sampleSeries = new XYChart.Series<>();
|
||||
sampleSeries.setName("Demodulated Samples");
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
sampleSeries.getData().add(new XYChart.Data<>(x, samples[x]));
|
||||
}
|
||||
|
||||
mSampleChart.getData().clear();
|
||||
mSampleChart.getData().add(sampleSeries);
|
||||
|
||||
XYChart.Series<Number,Number> patternSeries = new XYChart.Series<>();
|
||||
patternSeries.setName("Ideal Sync Pattern");
|
||||
|
||||
for(int x = 0; x < sync.length; x++)
|
||||
{
|
||||
patternSeries.getData().add(new XYChart.Data<>(syncIntervals[x], sync[x]));
|
||||
}
|
||||
|
||||
mSampleChart.getData().add(patternSeries);
|
||||
|
||||
mPatternLabel.setText(label);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.fleetsync2;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Instrumented version of the AFSK-1200 decoder for use with DemodulatorViewerFX
|
||||
*/
|
||||
public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements IInstrumentedAFSK1200Decoder
|
||||
{
|
||||
public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
|
||||
|
||||
public Fleetsync2DecoderInstrumented()
|
||||
{
|
||||
super(new AFSK1200DecoderInstrumented(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleIntegerProperty getBufferCountProperty()
|
||||
{
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
public AFSK1200DecoderInstrumented getAFSK1200Decoder()
|
||||
{
|
||||
return (AFSK1200DecoderInstrumented)getDecoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] realBuffer)
|
||||
{
|
||||
super.receive(realBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.lj1200;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Instrumented version of the AFSK-1200 decoder for use with DemodulatorViewerFX
|
||||
*/
|
||||
public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrumentedAFSK1200Decoder
|
||||
{
|
||||
public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
|
||||
|
||||
public LJ1200DecoderInstrumented()
|
||||
{
|
||||
super(new AFSK1200DecoderInstrumented(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleIntegerProperty getBufferCountProperty()
|
||||
{
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
public AFSK1200DecoderInstrumented getAFSK1200Decoder()
|
||||
{
|
||||
return (AFSK1200DecoderInstrumented)getDecoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] realBuffer)
|
||||
{
|
||||
super.receive(realBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.mdc1200;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Instrumented version of the AFSK-1200 decoder for use with DemodulatorViewerFX
|
||||
*/
|
||||
public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedAFSK1200Decoder
|
||||
{
|
||||
public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
|
||||
|
||||
public MDCDecoderInstrumented()
|
||||
{
|
||||
super(new AFSK1200DecoderInstrumented(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleIntegerProperty getBufferCountProperty()
|
||||
{
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
public AFSK1200DecoderInstrumented getAFSK1200Decoder()
|
||||
{
|
||||
return (AFSK1200DecoderInstrumented)getDecoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] realBuffer)
|
||||
{
|
||||
super.receive(realBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.mpt1327;
|
||||
|
||||
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
|
||||
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Instrumented version of the AFSK-1200 decoder for use with DemodulatorViewerFX
|
||||
*/
|
||||
public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstrumentedAFSK1200Decoder
|
||||
{
|
||||
public SimpleIntegerProperty bufferCount = new SimpleIntegerProperty();
|
||||
|
||||
public MPT1327DecoderInstrumented()
|
||||
{
|
||||
super(new AFSK1200DecoderInstrumented(true), Sync.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleIntegerProperty getBufferCountProperty()
|
||||
{
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
public AFSK1200DecoderInstrumented getAFSK1200Decoder()
|
||||
{
|
||||
return (AFSK1200DecoderInstrumented)getDecoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] realBuffer)
|
||||
{
|
||||
super.receive(realBuffer);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import io.github.dsheirer.dsp.squelch.NoiseSquelch;
|
||||
import io.github.dsheirer.dsp.squelch.NoiseSquelchState;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.PrimaryDecoder;
|
||||
import io.github.dsheirer.module.decode.SquelchControlDecoder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.IComplexSamplesListener;
|
||||
@@ -51,7 +51,7 @@ import org.slf4j.LoggerFactory;
|
||||
* audio to Noise Squelch. Squelch operates on the noise level with open and close thresholds to pass low-noise audio
|
||||
* and block high-noise audio. Audio is filtered and resampled to 8 kHz for downstream consumers.
|
||||
*/
|
||||
public class NBFMDecoder extends PrimaryDecoder implements ISourceEventListener, IComplexSamplesListener,
|
||||
public class NBFMDecoder extends SquelchControlDecoder implements ISourceEventListener, IComplexSamplesListener,
|
||||
Listener<ComplexSamples>, IRealBufferProvider, IDecoderStateEventProvider, INoiseSquelchController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NBFMDecoder.class);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase1;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
* C4FM slicer to convert the output stream of the C4FMSymbolFilter into a
|
||||
* stream of C4FM symbols.
|
||||
*
|
||||
* Supports registering listener(s) to receive normal and/or inverted symbol
|
||||
* output streams.
|
||||
*/
|
||||
public class C4FMSlicer implements RealSampleListener
|
||||
{
|
||||
private static final float THRESHOLD = 2.0f;
|
||||
|
||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mBroadcaster.dispose();
|
||||
mBroadcaster = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for receiving output from the C4FMSymbolFilter. Slices
|
||||
* (converts) the filtered sample value into a C4FMSymbol decision.
|
||||
*/
|
||||
@Override
|
||||
public void receive( float sample )
|
||||
{
|
||||
if( sample > 0.0 )
|
||||
{
|
||||
if( sample >= THRESHOLD )
|
||||
{
|
||||
dispatch( Dibit.D01_PLUS_3 );
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch( Dibit.D00_PLUS_1 );
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( sample > -THRESHOLD )
|
||||
{
|
||||
dispatch( Dibit.D10_MINUS_1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch( Dibit.D11_MINUS_3 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the symbol decision to any registered listeners
|
||||
*/
|
||||
private void dispatch( Dibit symbol )
|
||||
{
|
||||
mBroadcaster.receive( symbol );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive the normal (non-inverted) C4FM symbol
|
||||
* stream.
|
||||
*/
|
||||
public void addListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener
|
||||
*/
|
||||
public void removeListener( Listener<Dibit> listener )
|
||||
{
|
||||
mBroadcaster.removeListener( listener );
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -33,7 +33,7 @@ public class DecodeConfigP25Phase1 extends DecodeConfigP25
|
||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
|
||||
|
||||
private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
|
||||
private Modulation mModulation = Modulation.C4FM;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -49,12 +49,12 @@ public class DecodeConfigP25Phase1 extends DecodeConfigP25
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "modulation")
|
||||
public P25P1Decoder.Modulation getModulation()
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return mModulation;
|
||||
}
|
||||
|
||||
public void setModulation(P25P1Decoder.Modulation modulation)
|
||||
public void setModulation(Modulation modulation)
|
||||
{
|
||||
mModulation = modulation;
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 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 <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.p25.phase1;
|
||||
|
||||
/**
|
||||
* Listener interface to be notified each time a P25 sync pattern and data unit has been detected
|
||||
* and the data unit is correct after error detection and correction and/or when the sync has been lost.
|
||||
*/
|
||||
public interface IP25P1DataUnitDetectListener
|
||||
{
|
||||
/**
|
||||
* Indicates that a P25 sync has been detected and a P25 data unit was successfully decoded.
|
||||
* @param dataUnitID that was contained in the detected NID
|
||||
* @param nac or Network Access Code that was contained in the detected NID
|
||||
* @param discardedDibits prior to detecting the P25 sync pattern
|
||||
* @param bitErrors detected and corrected from both the sync pattern and the NID.
|
||||
* @param correctedNid bits corrected by the BCH error correction code (temporary until message parsers are updated
|
||||
*/
|
||||
void dataUnitDetected(P25P1DataUnitID dataUnitID, int nac, int bitErrors, int discardedDibits, int[] correctedNid);
|
||||
|
||||
/**
|
||||
* Indicates that sync has been lost on the dibit stream
|
||||
*
|
||||
* @param bitsProcessed since the last sync detect
|
||||
*/
|
||||
void syncLost(int bitsProcessed);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -15,21 +14,36 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase1;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
public interface C4FMFrameListener
|
||||
/**
|
||||
* P25 Phase 1 Modulations enumeration.
|
||||
*/
|
||||
public enum Modulation
|
||||
{
|
||||
/**
|
||||
* Listener interface to receive the output of the C4FMMessageFramer.
|
||||
*
|
||||
* @param buffer - framed message without the sync pattern
|
||||
* @param inverted - flag indicating if the message was received inverted
|
||||
*/
|
||||
public void receive( BinaryMessage buffer, boolean inverted );
|
||||
CQPSK("Simulcast (LSM)"),
|
||||
C4FM("Normal (C4FM)");
|
||||
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param label pretty display
|
||||
*/
|
||||
Modulation(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user