mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#2226 DMR Decoder adds an equalizer to adjust symbol constellation balance/skew and gain to optimize symbol quadrant alignment/mapping. Reduces sync detectors from 3 to 2 and sets minimum objective channel sample rate to 19.2 kHz. (#2245)
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -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
|
||||
@@ -42,6 +42,7 @@ public class DecimationFilterFactory
|
||||
switch(decimationRate)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
return new RealDecimateX0Filter();
|
||||
case 2:
|
||||
return new RealDecimateX2Filter();
|
||||
|
||||
@@ -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
|
||||
@@ -20,13 +20,10 @@
|
||||
package io.github.dsheirer.dsp.filter.interpolator;
|
||||
|
||||
/**
|
||||
* Linear interpolator for values in the range: -PI > 0 > PI with awareness of phase wrapping across the
|
||||
* (+/-) PI boundary.
|
||||
* Linear interpolator.
|
||||
*/
|
||||
public class PhaseAwareLinearInterpolator
|
||||
public class LinearInterpolator
|
||||
{
|
||||
public static final float TWO_PI = (float)(Math.PI * 2.0);
|
||||
|
||||
/**
|
||||
* Calculates an interpolated value between x1 and x2 at a linear position mu between 0.0 and 1.0
|
||||
* @param x1 first value
|
||||
@@ -36,39 +33,16 @@ public class PhaseAwareLinearInterpolator
|
||||
*/
|
||||
public static float calculate(float x1, float x2, float mu)
|
||||
{
|
||||
//Detect phase wrap at the +PI/-PI boundary ... ignore opposite side of the unit circle at 0 axis
|
||||
if(x1 * x2 < -Math.PI)
|
||||
if(mu < 0 || mu > 1)
|
||||
{
|
||||
if(x1 > 0)
|
||||
{
|
||||
float value = x1 + ((TWO_PI + x2 - x1) * mu);
|
||||
|
||||
if(value > Math.PI)
|
||||
{
|
||||
return -TWO_PI + value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = x1 + ((-TWO_PI + x2 - x1) * mu);
|
||||
|
||||
if(value < -Math.PI)
|
||||
{
|
||||
return TWO_PI + value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return x1 + ((x2 - x1) * mu);
|
||||
throw new IllegalArgumentException("mu [" + mu + "] must be between 0 and 1");
|
||||
}
|
||||
|
||||
return x1 + ((x2 - x1) * mu);
|
||||
}
|
||||
|
||||
public static float calculate(float x1, float x2, double mu)
|
||||
{
|
||||
return calculate(x1, x2, (float)mu);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,16 +17,16 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.Interpolator;
|
||||
import io.github.dsheirer.dsp.filter.interpolator.InterpolatorFactory;
|
||||
import io.github.dsheirer.dsp.fm.IDemodulator;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator base class
|
||||
* Differential demodulator base class
|
||||
*/
|
||||
public abstract class DQPSKDemodulator implements IDemodulator
|
||||
public abstract class DifferentialDemodulator implements IDemodulator
|
||||
{
|
||||
private int mSymbolRate;
|
||||
private float mSampleRate;
|
||||
@@ -43,7 +43,7 @@ public abstract class DQPSKDemodulator implements IDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulator(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulator(double sampleRate, int symbolRate)
|
||||
{
|
||||
mSampleRate = (float) sampleRate;
|
||||
mSymbolRate = symbolRate;
|
||||
@@ -51,6 +51,12 @@ public abstract class DQPSKDemodulator implements IDemodulator
|
||||
mMu = mSamplesPerSymbol % 1; //Fractional part of the samples per symbol rate
|
||||
mInterpolationOffset = (int) Math.floor(mSamplesPerSymbol) - 4; //Interpolate at the middle of 8x samples
|
||||
mBufferOverlap = (int) Math.floor(mSamplesPerSymbol) + 4;
|
||||
|
||||
while(mInterpolationOffset < 0)
|
||||
{
|
||||
mInterpolationOffset++;
|
||||
mBufferOverlap++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
@@ -26,11 +26,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating DQPSK demodulators
|
||||
* Factory for creating PSK differential demodulators
|
||||
*/
|
||||
public class DQPSKDemodulatorFactory
|
||||
public class DifferentialDemodulatorFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DQPSKDemodulatorFactory.class);
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DifferentialDemodulatorFactory.class);
|
||||
|
||||
/**
|
||||
* Creates the optimal demodulator using calibration data to select the optimal implementation from scalar and
|
||||
@@ -39,17 +39,17 @@ public class DQPSKDemodulatorFactory
|
||||
* @param symbolRate in Hertz
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static DQPSKDemodulator getDemodulator(double sampleRate, int symbolRate)
|
||||
public static DifferentialDemodulator getDemodulator(double sampleRate, int symbolRate)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DQPSK_DEMODULATOR);
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DIFFERENTIAL_DEMODULATOR);
|
||||
|
||||
return switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_64 -> new DQPSKDemodulatorVector64(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_128 -> new DQPSKDemodulatorVector128(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_256 -> new DQPSKDemodulatorVector256(sampleRate, symbolRate);
|
||||
case VECTOR_SIMD_512 -> new DQPSKDemodulatorVector512(sampleRate, symbolRate);
|
||||
default -> new DQPSKDemodulatorScalar(sampleRate, symbolRate); //Includes SCALAR case
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,25 +17,25 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator that uses scalar calculations for demodulating the sample stream.
|
||||
* Differential demodulator that uses scalar calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DQPSKDemodulatorScalar extends DQPSKDemodulator
|
||||
public class DifferentialDemodulatorScalar extends DifferentialDemodulator
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorScalar.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorScalar.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulatorScalar(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorScalar(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
@@ -27,11 +27,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator that uses Vector SIMD 128 calculations for demodulating the sample stream.
|
||||
* Differential demodulator that uses Vector SIMD 128 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DQPSKDemodulatorVector128 extends DQPSKDemodulator
|
||||
public class DifferentialDemodulatorVector128 extends DifferentialDemodulator
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector128.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector128.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ public class DQPSKDemodulatorVector128 extends DQPSKDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulatorVector128(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorVector128(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
@@ -25,9 +25,9 @@ import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator that uses Vector SIMD calculations for demodulating the sample stream.
|
||||
* Differential demodulator that uses Vector SIMD calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DQPSKDemodulatorVector256 extends DQPSKDemodulator
|
||||
public class DifferentialDemodulatorVector256 extends DifferentialDemodulator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class DQPSKDemodulatorVector256 extends DQPSKDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulatorVector256(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorVector256(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -68,7 +68,6 @@ public class DQPSKDemodulatorVector256 extends DQPSKDemodulator
|
||||
//Append new samples to the residual samples from the previous buffer.
|
||||
System.arraycopy(i, 0, mIBuffer, bufferOverlap, sampleLength);
|
||||
System.arraycopy(q, 0, mQBuffer, bufferOverlap, sampleLength);
|
||||
//mIDecoded, mQDecoded and mPhase will be filled below during the decoding process.
|
||||
|
||||
float[] interpolatedI = new float[VECTOR_SPECIES.length()];
|
||||
float[] interpolatedQ = new float[VECTOR_SPECIES.length()];
|
||||
@@ -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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
@@ -27,11 +27,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator that uses Vector SIMD 512 calculations for demodulating the sample stream.
|
||||
* Differential demodulator that uses Vector SIMD 512 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DQPSKDemodulatorVector512 extends DQPSKDemodulator
|
||||
public class DifferentialDemodulatorVector512 extends DifferentialDemodulator
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector512.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector512.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ public class DQPSKDemodulatorVector512 extends DQPSKDemodulator
|
||||
*
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulatorVector512(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorVector512(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -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
|
||||
@@ -17,7 +17,7 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.psk.dqpsk;
|
||||
package io.github.dsheirer.dsp.psk.demod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
@@ -27,11 +27,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DQPSK demodulator that uses Vector SIMD 64 calculations for demodulating the sample stream.
|
||||
* Differential demodulator that uses Vector SIMD 64 calculations for demodulating the sample stream.
|
||||
*/
|
||||
public class DQPSKDemodulatorVector64 extends DQPSKDemodulator
|
||||
public class DifferentialDemodulatorVector64 extends DifferentialDemodulator
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector64.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DifferentialDemodulatorVector64.class);
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ public class DQPSKDemodulatorVector64 extends DQPSKDemodulator
|
||||
* @param sampleRate in Hertz
|
||||
* @param symbolRate symbols per second
|
||||
*/
|
||||
public DQPSKDemodulatorVector64(double sampleRate, int symbolRate)
|
||||
public DifferentialDemodulatorVector64(double sampleRate, int symbolRate)
|
||||
{
|
||||
super(sampleRate, symbolRate);
|
||||
}
|
||||
@@ -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
|
||||
@@ -21,10 +21,10 @@ package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
public enum Dibit
|
||||
{
|
||||
D01_PLUS_3(false, true, 1, 4, 1, (float)(Math.PI / 4 * 3), (float)-Math.sin(Math.toRadians(45)), (float)Math.sin(Math.toRadians(45))),
|
||||
D00_PLUS_1(false, false, 0, 0, 0, (float)(Math.PI / 4.0), (float)Math.sin(Math.toRadians(45)), (float)Math.sin(Math.toRadians(45))),
|
||||
D10_MINUS_1(true, false, 2, 8, 2, (float)(Math.PI / -4.0), (float)Math.sin(Math.toRadians(45)), (float)-Math.sin(Math.toRadians(45))),
|
||||
D11_MINUS_3(true, true, 3, 12, 3, (float)(Math.PI / 4 * -3), (float)-Math.sin(Math.toRadians(45)), (float)-Math.sin(Math.toRadians(45)));
|
||||
D01_PLUS_3(false, true, 1, 4, 1, (float)((Math.PI / 4) * 3)),
|
||||
D00_PLUS_1(false, false, 0, 0, 0, (float)(Math.PI / 4)),
|
||||
D10_MINUS_1(true, false, 2, 8, 2, (float)(Math.PI / -4)),
|
||||
D11_MINUS_3(true, true, 3, 12, 3, (float)((Math.PI / 4) * -3));
|
||||
|
||||
private boolean mBit1;
|
||||
private boolean mBit2;
|
||||
@@ -32,8 +32,6 @@ public enum Dibit
|
||||
private int mHighValue;
|
||||
private int mValue;
|
||||
private float mPhase;
|
||||
private float mIdealI;
|
||||
private float mIdealQ;
|
||||
|
||||
/**
|
||||
* Dibit constructor.
|
||||
@@ -43,10 +41,8 @@ public enum Dibit
|
||||
* @param highValue integer value of the high order bit
|
||||
* @param value integer value combined of both bits.
|
||||
* @param phase optimal phase value for this symbol when transmitted.
|
||||
* @param idealI ideal inphase value for this symbol when transmitted.
|
||||
* @param idealQ ideal quadrature value for this symbol when transmitted.
|
||||
*/
|
||||
Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value, float phase, float idealI, float idealQ)
|
||||
Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value, float phase)
|
||||
{
|
||||
mBit1 = bit1;
|
||||
mBit2 = bit2;
|
||||
@@ -54,8 +50,6 @@ public enum Dibit
|
||||
mHighValue = highValue;
|
||||
mValue = value;
|
||||
mPhase = phase;
|
||||
mIdealI = idealI;
|
||||
mIdealQ = idealQ;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,24 +69,6 @@ public enum Dibit
|
||||
return mPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideal inphase value when this symbol is transmitted.
|
||||
* @return ideal I
|
||||
*/
|
||||
public float getIdealI()
|
||||
{
|
||||
return mIdealI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideal quadrature value when this symbol is transmitted.
|
||||
* @return ideal Q
|
||||
*/
|
||||
public float getIdealQ()
|
||||
{
|
||||
return mIdealQ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowable transition state 1 for trellis coded modulation. Indicates
|
||||
* the dibit that differs from this dibit in the LSB bit position.
|
||||
|
||||
@@ -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
|
||||
@@ -609,14 +609,14 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
|
||||
final Path captureFile = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().resolve(filename);
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
ThreadPool.CACHED.submit(() -> {
|
||||
try
|
||||
{
|
||||
ImageIO.write(image, "png", captureFile.toFile());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Couldn't write screen capture to file [" + captureFile.toString() + "]", e);
|
||||
mLog.error("Couldn't write screen capture to file [" + captureFile + "]", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,10 +21,13 @@ package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.decimate.DecimationFilterFactory;
|
||||
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.psk.dqpsk.DQPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorFactory;
|
||||
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.squelch.PowerMonitor;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
@@ -87,12 +90,16 @@ 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 DQPSKDemodulator mDemodulator;
|
||||
private DifferentialDemodulator mDemodulator;
|
||||
private final DMRMessageFramer mMessageFramer;
|
||||
private final DMRSoftSymbolProcessor mSymbolProcessor;
|
||||
private final DMRMessageProcessor mMessageProcessor;
|
||||
private IRealFilter mIBasebandFilter;
|
||||
private IRealFilter mQBasebandFilter;
|
||||
private IRealDecimationFilter mDecimationFilterI;
|
||||
private IRealDecimationFilter mDecimationFilterQ;
|
||||
private RealFIRFilter mRRCFilterI;
|
||||
private RealFIRFilter mRRCFilterQ;
|
||||
private final PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
private final CarrierOffsetProcessor mCarrierOffsetProcessor = new CarrierOffsetProcessor();
|
||||
|
||||
@@ -134,13 +141,40 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
}
|
||||
|
||||
mPowerMonitor.setSampleRate((int)sampleRate);
|
||||
mCarrierOffsetProcessor.setSampleRate(sampleRate);
|
||||
|
||||
mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
|
||||
mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
|
||||
mDemodulator = DQPSKDemodulatorFactory.getDemodulator(sampleRate, SYMBOL_RATE);
|
||||
|
||||
int decimation = 1;
|
||||
|
||||
//Identify decimation that gets us as close to 4.0 Samples Per Symbol as possible (19.2 kHz)
|
||||
while((sampleRate / decimation) >= 38400)
|
||||
{
|
||||
decimation *= 2;
|
||||
}
|
||||
|
||||
mDecimationFilterI = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
mDecimationFilterQ = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
|
||||
float decimatedSampleRate = (float)sampleRate / decimation;
|
||||
float rrcAlpha = Math.abs((float)(5760.0 / decimatedSampleRate));
|
||||
int symbolLength = (int)Math.floor((-44 * rrcAlpha) + 33);
|
||||
symbolLength += symbolLength % 2; //Make the symbol length even
|
||||
|
||||
if(symbolLength < 0)
|
||||
{
|
||||
symbolLength = 2;
|
||||
}
|
||||
|
||||
float[] taps = FilterFactory.getRootRaisedCosine(decimatedSampleRate / SYMBOL_RATE, symbolLength, rrcAlpha);
|
||||
mRRCFilterI = new RealFIRFilter(taps);
|
||||
mRRCFilterQ = new RealFIRFilter(taps);
|
||||
|
||||
mDemodulator = DifferentialDemodulatorFactory.getDemodulator(decimatedSampleRate, SYMBOL_RATE);
|
||||
mSymbolProcessor.setSamplesPerSymbol(mDemodulator.getSamplesPerSymbol());
|
||||
mMessageFramer.setListener(mMessageProcessor);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
mCarrierOffsetProcessor.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +184,7 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
@Override
|
||||
public void receive(ComplexSamples samples)
|
||||
{
|
||||
|
||||
mMessageFramer.setTimestamp(samples.timestamp());
|
||||
|
||||
float[] i = mIBasebandFilter.filter(samples.i());
|
||||
@@ -158,6 +193,12 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
//Process buffer for power measurements
|
||||
mPowerMonitor.process(i, q);
|
||||
|
||||
i = mDecimationFilterI.decimateReal(i);
|
||||
q = mDecimationFilterQ.decimateReal(q);
|
||||
|
||||
i = mRRCFilterI.filter(i);
|
||||
q = mRRCFilterQ.filter(q);
|
||||
|
||||
float[] demodulated = mDemodulator.demodulate(i, q);
|
||||
mSymbolProcessor.receive(demodulated);
|
||||
|
||||
@@ -312,19 +353,21 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
|
||||
// String directory = "D:\\DQPSK Equalizer Research\\"; //Windows
|
||||
String directory = "/media/denny/T9/DQPSK Equalizer Research/"; //Linux
|
||||
// String file = directory + "DMR_1_CAPPLUS.wav";
|
||||
String file = directory + "DMR_1_CAPPLUS.wav";
|
||||
// String file = directory + "DMR_2_CAPPLUS.wav";
|
||||
// String file = directory + "DMR_3_CAPPLUS.wav";
|
||||
// String file = directory + "20230819_064211_451250000_SaiaNet_Syracuse_Control_29_baseband.wav";
|
||||
// String file = directory + "20230819_064344_454575000_JPJ_Communications_(DMR)_Madison_Control_28_baseband.wav";
|
||||
// String file = directory + "DMR_DCDM_4_4_baseband_20220318_142716.wav";
|
||||
// String file = directory + "DMR_4_20241213_Saianet.wav";
|
||||
String file = directory + "DMR_5_20241217_031219_451425000_SaiaNet_Onondaga_SaiaNet_Control_1_baseband.wav";
|
||||
// String file = directory + "DMR_5_20241217_031219_451425000_SaiaNet_Onondaga_SaiaNet_Control_1_baseband.wav";
|
||||
// String file = directory + "DMR_6_20241217_031511_451425000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav";
|
||||
// String file = directory + "DMR_7_20241217_031651_451250000_SaiaNet_Onondaga_SaiaNet_Control_50_baseband.wav";
|
||||
// String file = directory + "DMR_8_20241217_031845_461662500_SaiaNet_(Tier_III)_Onondaga_Control_25_baseband.wav";
|
||||
// String file = directory + "DMR_9_CAPPLUS_encrypted_American_Airlines_Maricopa_Control_29_baseband.wav";
|
||||
// String file = directory + "DMR_10_CAP_ENCRYPTED_20241222_035408_935487500_American_Airlines_Maricopa_Control_1_baseband.wav";
|
||||
// String file = directory + "DMR_12_20250420_061639_451250000_SaiaNet_Onondaga_SaiaNet-Control_1_baseband.wav";
|
||||
// String file = directory + "DMR_17_20250516_053150_451425000_SaiaNet_Onondaga_SaiaNet-Control_1_baseband.wav";
|
||||
// String file = directory + "DMR_19_POLY_2CHAN_20250611_032038_451250000_SaiaNet_Onondaga_SaiaNet-Control_1_baseband.wav";
|
||||
|
||||
boolean autoReplay = false;
|
||||
|
||||
@@ -387,6 +430,11 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
boolean logIdles = false;
|
||||
boolean logEverything = true;
|
||||
|
||||
if(iMessage instanceof SyncLossMessage)
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
if(!logEverything && logFLC)
|
||||
{
|
||||
if(iMessage instanceof FullLCMessage)
|
||||
|
||||
@@ -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
|
||||
@@ -54,7 +54,7 @@ public class DMRHardSymbolProcessor
|
||||
Dibit ejected = mDibitDelayLine.insert(dibit);
|
||||
mMessageFramer.receive(ejected);
|
||||
|
||||
if(mSyncDetector.process(dibit))
|
||||
if(mSyncDetector.processAndDetect(dibit))
|
||||
{
|
||||
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
|
||||
}
|
||||
|
||||
@@ -112,6 +112,15 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the framer is assembling a burst and the active timeslot is assembling a voice superframe.
|
||||
*/
|
||||
public boolean isVoiceSuperFrame()
|
||||
{
|
||||
return mAssemblingBurst && ((mBufferAActive && mBufferAPattern.isVoicePattern()) ||
|
||||
(!mBufferAActive && mBufferBPattern.isVoicePattern()));
|
||||
}
|
||||
|
||||
private void dispatchBufferA()
|
||||
{
|
||||
mDibitCounter -= 144;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.PhaseAwareLinearInterpolator;
|
||||
import io.github.dsheirer.dsp.filter.interpolator.LinearInterpolator;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
|
||||
@@ -27,18 +27,22 @@ 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;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
|
||||
|
||||
/**
|
||||
* DMR soft symbol processor. Processes a stream of differentially decoded samples to align to the symbol timing and
|
||||
* symbol period and extract symbols from the sample stream.
|
||||
*
|
||||
* Uses three soft sync correlation detectors to achieve initial sync lock with a primary sync detector and two lagging
|
||||
* sync detectors sampling at 1/3 and 2/3 of the symbol period to ensure the sync is detected while only calculating at
|
||||
* each symbol period interval. Once a sync is detected, sync lock is declared and the lagging sync detectors are
|
||||
* disabled. Sync lock disables whenever the quantity of processed symbols exceeds one DMR burst length and the
|
||||
* lagging detectors re-enable.
|
||||
* Uses two soft sync correlation detectors to achieve initial fine sync with a primary sync detector and a lagging
|
||||
* sync detector sampling at 1/2 the symbol period to ensure the sync is detected while only calculating at each symbol
|
||||
* period interval. Once a sync is detected, fine sync is declared and the lagging sync detector is disabled. Fine sync
|
||||
* disables whenever the quantity of processed symbols exceeds one DMR burst length and the lagging detector
|
||||
* re-enables.
|
||||
*
|
||||
* The sync mode monitor processes the sync detections to identify the dominant sync mode for the channel and then
|
||||
* disables processing of the two unused sync modes (BASE STATION, MOBILE STATION, or DIRECT MODE) for the remainder
|
||||
@@ -50,39 +54,52 @@ import java.nio.ByteBuffer;
|
||||
* the message framer along with the sync detection.
|
||||
*
|
||||
* Buffer Structure & Size = 129 Dibits: [2 pad][12 CACH][54 Message Prefix][24 Sync][24 workspace][1 pad]
|
||||
*
|
||||
* The equalizer calculates balance and gain values to apply to the differentially decoded sample stream to optimize
|
||||
* the constellation skew and openness. The equalizer is updated at each quality sync detection and then the equalizer
|
||||
* values are applied across the entire burst through to the next sync detection. DMR sync patterns are made up of
|
||||
* equal quantities of +3 and -3 symbols which support the equalizer update approach.
|
||||
*/
|
||||
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 MAX_POSITIVE_SOFT_SYMBOL = Dibit.D01_PLUS_3.getIdealPhase();
|
||||
private static final float MAX_NEGATIVE_SOFT_SYMBOL = Dibit.D11_MINUS_3.getIdealPhase();
|
||||
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 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%
|
||||
private static final float SYMBOL_QUADRANT_BOUNDARY = (float)(Math.PI / 2.0);
|
||||
private static final float SYNC_DETECTION_THRESHOLD = 60;
|
||||
private static final float SYNC_OPTIMIZED_THRESHOLD = 80;
|
||||
private static final float SYNC_EQUALIZED_THRESHOLD = 100;
|
||||
private static final float TWO_PI = (float)(Math.PI * 2.0);
|
||||
private DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor();
|
||||
private DMRSoftSyncDetector mSyncDetector = DMRSoftSyncDetectorFactory.getDetector();
|
||||
private DMRSoftSyncDetector mSyncDetectorLag1 = DMRSoftSyncDetectorFactory.getDetector();
|
||||
private DMRSoftSyncDetector mSyncDetectorLag2 = DMRSoftSyncDetectorFactory.getDetector();
|
||||
private DMRSoftSyncDetector mSyncDetectorSecondary = DMRSoftSyncDetectorFactory.getDetector();
|
||||
private DibitToByteBufferAssembler mDibitAssembler = new DibitToByteBufferAssembler(300);
|
||||
//Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24)
|
||||
private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90);
|
||||
private DMRMessageFramer mMessageFramer;
|
||||
private boolean mSyncLock = false;
|
||||
private float mLaggingSyncOffset1;
|
||||
private float mLaggingSyncOffset2;
|
||||
private float mObservedSamplesPerSymbol;
|
||||
private float mSamplesPerSymbol;
|
||||
private float mMaxSamplesPerSymbol;
|
||||
private float mMinSamplesPerSymbol;
|
||||
private float mSamplePoint;
|
||||
private boolean mFineSync = false;
|
||||
private boolean mEqualizerInitialized = false;
|
||||
private double mNoiseStandardDeviationThreshold;
|
||||
private double mSecondarySyncOffset;
|
||||
private double mSamplesPerSymbol;
|
||||
private double mObservedSamplesPerSymbol;
|
||||
private double mSamplePoint;
|
||||
private float mEqualizerBalance = 0.0f;
|
||||
private float mEqualizerGain = 1.0f;
|
||||
private float mOptimizeFineIncrement;
|
||||
private float[] mBuffer;
|
||||
private int mBufferInterpolatorReservedRegion;
|
||||
private int mBufferLoadPointer;
|
||||
private int mBufferPointer;
|
||||
private int mBufferWorkspaceLength;
|
||||
private int mBufferInterpolatorReservedRegion;
|
||||
private int mSymbolsSinceLastSync = 0;
|
||||
private SyncResultsViewer mSyncResultsViewer;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -97,21 +114,17 @@ public class DMRSoftSymbolProcessor
|
||||
|
||||
mMessageFramer = messageFramer;
|
||||
mSyncModeMonitor.add(mSyncDetector);
|
||||
mSyncModeMonitor.add(mSyncDetectorLag1);
|
||||
mSyncModeMonitor.add(mSyncDetectorLag2);
|
||||
mSyncModeMonitor.add(mSyncDetectorSecondary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets base station sync detection mode. This disables detection for MOBILE and DIRECT sync patterns. This can
|
||||
* be used for traffic channels that are known to be part of a base station.
|
||||
* @param baseStationMode true for base mode only, false otherwise.
|
||||
* @param enabled true for base mode only, false otherwise.
|
||||
*/
|
||||
public void setBaseStationMode(boolean baseStationMode)
|
||||
public void setBaseStationMode(boolean enabled)
|
||||
{
|
||||
if(baseStationMode)
|
||||
{
|
||||
mSyncModeMonitor.setMode(DMRSyncDetectMode.BASE_ONLY);
|
||||
}
|
||||
mSyncModeMonitor.setMode(enabled ? DMRSyncDetectMode.BASE_ONLY : DMRSyncDetectMode.AUTOMATIC);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +175,33 @@ public class DMRSoftSymbolProcessor
|
||||
samplesPointer += copyLength;
|
||||
mBufferLoadPointer += copyLength;
|
||||
|
||||
for(int x = mBufferPointer; x < mBufferLoadPointer; x++)
|
||||
{
|
||||
//Unwrap phases
|
||||
if(mBuffer[x - 1] > 1.5f && mBuffer[x] < -1.5f)
|
||||
{
|
||||
mBuffer[x] += TWO_PI;
|
||||
}
|
||||
else if(mBuffer[x - 1] < -1.5f && mBuffer[x] > 1.5f)
|
||||
{
|
||||
mBuffer[x] -= TWO_PI;
|
||||
}
|
||||
|
||||
//Apply equalizer adjustments
|
||||
mBuffer[x] += mEqualizerBalance;
|
||||
mBuffer[x] *= mEqualizerGain;
|
||||
|
||||
//Allow the equalized buffer samples to exceed PI (3.14) by a small factor (3.5) to ensure the optimize
|
||||
// and equalizer update functions work correctly. The toSymbol() method will correctly map any symbols
|
||||
// that exceed +/- PI into the correct quadrant.
|
||||
mBuffer[x] = Math.min(mBuffer[x], MAXIMUM_POSITIVE_SAMPLE_PHASE);
|
||||
mBuffer[x] = Math.max(mBuffer[x], MAXIMUM_NEGATIVE_SAMPLE_PHASE);
|
||||
}
|
||||
|
||||
float softSymbol, primaryScore, secondaryScore;
|
||||
double secondary;
|
||||
int secondaryIntegral;
|
||||
|
||||
while(mBufferPointer < (mBufferLoadPointer - mBufferInterpolatorReservedRegion))
|
||||
{
|
||||
mBufferPointer++;
|
||||
@@ -169,16 +209,13 @@ public class DMRSoftSymbolProcessor
|
||||
|
||||
if(mSamplePoint < 1)
|
||||
{
|
||||
mSymbolsSinceLastSync++;
|
||||
|
||||
if(mSymbolsSinceLastSync > 144)
|
||||
{
|
||||
mSyncLock = false;
|
||||
mFineSync = false;
|
||||
}
|
||||
|
||||
float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[mBufferPointer],
|
||||
mBuffer[mBufferPointer + 1], mSamplePoint);
|
||||
|
||||
softSymbol = LinearInterpolator.calculate(mBuffer[mBufferPointer], mBuffer[mBufferPointer + 1], mSamplePoint);
|
||||
mSyncDetector.process(softSymbol);
|
||||
Dibit symbol = toSymbol(softSymbol);
|
||||
|
||||
//Store the symbol in the delay line and broadcast the delayed ejected symbol to the message framer
|
||||
@@ -186,52 +223,64 @@ public class DMRSoftSymbolProcessor
|
||||
Dibit ejected = mDibitDelayLine.insert(symbol);
|
||||
mMessageFramer.receive(ejected);
|
||||
mDibitAssembler.receive(ejected);
|
||||
mSymbolsSinceLastSync++;
|
||||
|
||||
float scorePrimary = mSyncDetector.process(softSymbol);
|
||||
|
||||
if(mSyncLock && scorePrimary > SYNC_DETECTION_THRESHOLD &&
|
||||
optimize(mSyncDetector.getDetectedPattern(), 0.0f))
|
||||
if(mFineSync)
|
||||
{
|
||||
mSymbolsSinceLastSync = 0;
|
||||
mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
|
||||
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
|
||||
if(mSymbolsSinceLastSync >= 144)
|
||||
{
|
||||
if(mMessageFramer.isVoiceSuperFrame())
|
||||
{
|
||||
mSymbolsSinceLastSync -= 144;
|
||||
}
|
||||
else
|
||||
{
|
||||
primaryScore = mSyncDetector.calculate();
|
||||
|
||||
//Debug - visualize the sync to show samples and symbol timing
|
||||
// if(primaryScore > SYNC_DETECTION_THRESHOLD)
|
||||
// {
|
||||
// visualizeSyncDetect(mSyncDetector.getDetectedPattern(), primaryScore, true);
|
||||
// }
|
||||
|
||||
if(primaryScore > SYNC_DETECTION_THRESHOLD && optimizeFine(mSyncDetector.getDetectedPattern()))
|
||||
{
|
||||
//Update the sync mode monitor and message framer
|
||||
mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
|
||||
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
|
||||
mSymbolsSinceLastSync = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFineSync = false;
|
||||
mSyncDetectorSecondary.reset();
|
||||
secondary = mBufferPointer + mSamplePoint - mSecondarySyncOffset;
|
||||
secondaryIntegral = (int)Math.floor(secondary);
|
||||
softSymbol = LinearInterpolator.calculate(mBuffer[secondaryIntegral], mBuffer[secondaryIntegral + 1], secondary - secondaryIntegral);
|
||||
mSyncDetectorSecondary.process(softSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float lag1 = mBufferPointer + mSamplePoint - mLaggingSyncOffset1;
|
||||
float lag2 = mBufferPointer + mSamplePoint - mLaggingSyncOffset2;
|
||||
int lagIntegral1 = (int)Math.floor(lag1);
|
||||
int lagIntegral2 = (int)Math.floor(lag2);
|
||||
float softSymbolLag1 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral1],
|
||||
mBuffer[lagIntegral1 + 1], lag1 - lagIntegral1);
|
||||
float softSymbolLag2 = PhaseAwareLinearInterpolator.calculate(mBuffer[lagIntegral2],
|
||||
mBuffer[lagIntegral2 + 1], lag2 - lagIntegral2);
|
||||
float scoreLag1 = mSyncDetectorLag1.process(softSymbolLag1);
|
||||
float scoreLag2 = mSyncDetectorLag2.process(softSymbolLag2);
|
||||
primaryScore = mSyncDetector.calculate();
|
||||
secondary = mBufferPointer + mSamplePoint - mSecondarySyncOffset;
|
||||
secondaryIntegral = (int)Math.floor(secondary);
|
||||
softSymbol = LinearInterpolator.calculate(mBuffer[secondaryIntegral], mBuffer[secondaryIntegral + 1], secondary - secondaryIntegral);
|
||||
secondaryScore = mSyncDetectorSecondary.processAndCalculate(softSymbol);
|
||||
|
||||
if(mSymbolsSinceLastSync > 1 && scoreLag1 > scorePrimary && scoreLag1 > scoreLag2 &&
|
||||
scoreLag1 > SYNC_DETECTION_THRESHOLD &&
|
||||
optimize(mSyncDetectorLag1.getDetectedPattern(), -mLaggingSyncOffset1))
|
||||
if(primaryScore > SYNC_DETECTION_THRESHOLD && optimizeCoarse(mSyncDetector.getDetectedPattern(), 0))
|
||||
{
|
||||
mSymbolsSinceLastSync = 0;
|
||||
mSyncModeMonitor.detected(mSyncDetectorLag1.getDetectedPattern());
|
||||
mMessageFramer.syncDetected(mSyncDetectorLag1.getDetectedPattern());
|
||||
}
|
||||
else if(mSymbolsSinceLastSync > 1 && scoreLag2 > scorePrimary &&
|
||||
scoreLag2 > SYNC_DETECTION_THRESHOLD &&
|
||||
optimize(mSyncDetectorLag2.getDetectedPattern(), -mLaggingSyncOffset2))
|
||||
{
|
||||
mSymbolsSinceLastSync = 0;
|
||||
mSyncModeMonitor.detected(mSyncDetectorLag2.getDetectedPattern());
|
||||
mMessageFramer.syncDetected(mSyncDetectorLag2.getDetectedPattern());
|
||||
}
|
||||
else if(scorePrimary > SYNC_DETECTION_THRESHOLD &&
|
||||
optimize(mSyncDetector.getDetectedPattern(), 0.0f))
|
||||
{
|
||||
mSymbolsSinceLastSync = 0;
|
||||
mSyncLock = true;
|
||||
mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
|
||||
mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
|
||||
mFineSync = true;
|
||||
mSymbolsSinceLastSync = 0;
|
||||
}
|
||||
else if(secondaryScore > SYNC_DETECTION_THRESHOLD && optimizeCoarse(mSyncDetectorSecondary.getDetectedPattern(), -mSecondarySyncOffset))
|
||||
{
|
||||
mMessageFramer.syncDetected(mSyncDetectorSecondary.getDetectedPattern());
|
||||
mFineSync = true;
|
||||
mSymbolsSinceLastSync = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +291,28 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the samples in the sample buffer that contain a detected sync pattern have a standard deviation
|
||||
* that is lower than the expected sample to sample deviation for a modulated signal. False detects against
|
||||
* noise tend to have a standard deviation that is 2x the expected value.
|
||||
* @param offset to the sample representing the final symbol in the detected sync pattern.
|
||||
* @return true if the standard deviation is more than expected.
|
||||
*/
|
||||
private boolean isNoisy(double offset)
|
||||
{
|
||||
StandardDeviation standardDeviation = new StandardDeviation();
|
||||
int start = (int)Math.floor(offset - (23 * mObservedSamplesPerSymbol));
|
||||
int end = (int)Math.ceil(offset);
|
||||
end = Math.min(end, mBuffer.length - 1);
|
||||
|
||||
for(int i = start; i < end; i++)
|
||||
{
|
||||
standardDeviation.increment(mBuffer[i] - mBuffer[i + 1]);
|
||||
}
|
||||
|
||||
return standardDeviation.getResult() > mNoiseStandardDeviationThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the symbol timing and symbol spacing to identify the best achievable sync correlation score and apply
|
||||
* those adjustments when the correlation score exceeds a positive sync detection threshold.
|
||||
@@ -250,32 +321,32 @@ public class DMRSoftSymbolProcessor
|
||||
* sync detector or an offset for the lagging sync detectors.
|
||||
* @return true if there is a positive sync detection.
|
||||
*/
|
||||
private boolean optimize(DMRSyncPattern pattern, float additionalOffset)
|
||||
private boolean optimizeCoarse(DMRSyncPattern pattern, double additionalOffset)
|
||||
{
|
||||
//Offset is the start of the first sample of the first symbol of the sync pattern calculated from the current
|
||||
//buffer pointer and sample point which should be the final sample of the final symbol of the detected sync.
|
||||
float offset = (mBufferPointer + mSamplePoint) + additionalOffset - (mObservedSamplesPerSymbol * 23);
|
||||
double offset = mBufferPointer + mSamplePoint + additionalOffset;
|
||||
|
||||
//Reject any sync detections where the sample:sample standard deviation exceeds the noise threshold.
|
||||
if(isNoisy(offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Find the optimal symbol timing
|
||||
float stepSize = mSyncLock ? (mObservedSamplesPerSymbol / 40.0f) : (mObservedSamplesPerSymbol / 10.0f);
|
||||
float stepSizeMin = 0.03f;
|
||||
float adjustment = 0.0f;
|
||||
float adjustmentMax = mObservedSamplesPerSymbol / 2.0f;
|
||||
float candidate = offset;
|
||||
double stepSize = mSamplesPerSymbol / 8.0; //Start at 1/8th of the samples per symbol
|
||||
double stepSizeMin = mOptimizeFineIncrement;
|
||||
double adjustment = 0.0;
|
||||
double adjustmentMax = mSamplesPerSymbol / 4.0;
|
||||
double candidate = offset;
|
||||
|
||||
int candidateIntegral = (int)Math.floor(candidate);
|
||||
float candidateFractional = candidate - candidateIntegral;
|
||||
float scoreCenter = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
float scoreCenter = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
|
||||
candidate = offset - stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
float scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
float scoreLeft = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
|
||||
candidate = offset + stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
float scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
float scoreRight = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
|
||||
while(stepSize > stepSizeMin && Math.abs(adjustment) <= adjustmentMax)
|
||||
{
|
||||
@@ -286,9 +357,7 @@ public class DMRSoftSymbolProcessor
|
||||
scoreCenter = scoreLeft;
|
||||
|
||||
candidate = offset + adjustment - stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
scoreLeft = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
}
|
||||
else if(scoreRight > scoreLeft && scoreRight > scoreCenter)
|
||||
{
|
||||
@@ -297,9 +366,7 @@ public class DMRSoftSymbolProcessor
|
||||
scoreCenter = scoreRight;
|
||||
|
||||
candidate = offset + adjustment + stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
scoreRight = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -308,14 +375,10 @@ public class DMRSoftSymbolProcessor
|
||||
if(stepSize > stepSizeMin)
|
||||
{
|
||||
candidate = offset + adjustment - stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
scoreLeft = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
|
||||
candidate = offset + adjustment + stepSize;
|
||||
candidateIntegral = (int)Math.floor(candidate);
|
||||
candidateFractional = candidate - candidateIntegral;
|
||||
scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
|
||||
scoreRight = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,11 +389,7 @@ public class DMRSoftSymbolProcessor
|
||||
return false;
|
||||
}
|
||||
|
||||
if(additionalOffset != 0.0)
|
||||
{
|
||||
adjustment += additionalOffset;
|
||||
}
|
||||
|
||||
adjustment += additionalOffset;
|
||||
mSamplePoint += adjustment;
|
||||
|
||||
while(mSamplePoint < 0)
|
||||
@@ -345,38 +404,27 @@ public class DMRSoftSymbolProcessor
|
||||
mBufferPointer++;
|
||||
}
|
||||
|
||||
//Adjust the observed samples per symbol using the timing error measured across one or two bursts when we're in
|
||||
//fine sync mode and the timing error is not excessive.
|
||||
if(mSyncLock && Math.abs(adjustment) < 0.5 && mSymbolsSinceLastSync > 143 && mSymbolsSinceLastSync < 289)
|
||||
{
|
||||
mObservedSamplesPerSymbol += (float)((double)adjustment / (double)mSymbolsSinceLastSync * 0.2);
|
||||
boolean resample = !mEqualizerInitialized || (Math.abs(adjustment) > 0.25);
|
||||
|
||||
if(mObservedSamplesPerSymbol > mMaxSamplesPerSymbol)
|
||||
{
|
||||
mObservedSamplesPerSymbol = mMaxSamplesPerSymbol;
|
||||
}
|
||||
else if(mObservedSamplesPerSymbol < mMinSamplesPerSymbol)
|
||||
{
|
||||
mObservedSamplesPerSymbol = mMinSamplesPerSymbol;
|
||||
}
|
||||
}
|
||||
updateEqualizer(pattern);
|
||||
// visualizeSyncDetect(pattern, scoreCenter, true);
|
||||
|
||||
//If the timing error adjustment is high enough, resample the symbols. Otherwise, overwrite the captured sync
|
||||
//pattern in the delay buffer with the detected sync pattern to eliminate any sync bit errors.
|
||||
if(Math.abs(adjustment) > 0.05)
|
||||
//If the equalizer was just initialized or the timing error adjustment is high enough, resample the symbols.
|
||||
// Otherwise, overwrite the captured sync pattern in the delay buffer with the detected sync pattern to
|
||||
// eliminate any sync bit errors.
|
||||
if(resample)
|
||||
{
|
||||
float resampleStart = mBufferPointer + mSamplePoint;
|
||||
resampleStart -= (89 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols
|
||||
int resampleStartIntegral;
|
||||
double resamplePointer = mBufferPointer + mSamplePoint;
|
||||
resamplePointer -= (89 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols
|
||||
int integral;
|
||||
|
||||
for(int x = 0; x < 66; x++)
|
||||
{
|
||||
resampleStartIntegral = (int)Math.floor(resampleStart);
|
||||
integral = (int)Math.floor(resamplePointer);
|
||||
|
||||
if(resampleStartIntegral >= 0)
|
||||
if(integral >= 0)
|
||||
{
|
||||
float resampledSoftSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[resampleStartIntegral],
|
||||
mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral);
|
||||
float resampledSoftSymbol = LinearInterpolator.calculate(mBuffer[integral], mBuffer[integral + 1], resamplePointer - integral);
|
||||
mDibitDelayLine.insert(toSymbol(resampledSoftSymbol));
|
||||
}
|
||||
else
|
||||
@@ -385,7 +433,7 @@ public class DMRSoftSymbolProcessor
|
||||
mDibitDelayLine.insert(Dibit.D01_PLUS_3);
|
||||
}
|
||||
|
||||
resampleStart += mObservedSamplesPerSymbol;
|
||||
resamplePointer += mObservedSamplesPerSymbol;
|
||||
}
|
||||
|
||||
//We don't need to resample the sync region ... just use the actual sync dibit values.
|
||||
@@ -404,27 +452,256 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the sync correlation score for sync pattern at the specified offsets and symbol timing.
|
||||
* @param bufferPointer to the start of the samples in the soft symbol buffer
|
||||
* @param fractional position to interpolate within the 8 samples starting at the buffer pointer.
|
||||
* Performs fine-grained symbol timing optimization of +/- .4% of the samples per symbol as needed.
|
||||
* @param pattern that was detected
|
||||
* @return true if detection score exceeds the optimized and equalized threshold for a quality sync detect.
|
||||
*/
|
||||
private boolean optimizeFine(DMRSyncPattern pattern)
|
||||
{
|
||||
//Offset is the sample for the last symbol of the detected sync pattern.
|
||||
double offset = mBufferPointer + mSamplePoint;
|
||||
float currentScore = score(offset, mObservedSamplesPerSymbol, pattern);
|
||||
double candidate = offset - mOptimizeFineIncrement;
|
||||
float candidateScore = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
boolean adjusted = false;
|
||||
|
||||
if(candidateScore > currentScore)
|
||||
{
|
||||
mSamplePoint -= mOptimizeFineIncrement;
|
||||
adjusted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidate = offset + mOptimizeFineIncrement;
|
||||
candidateScore = score(candidate, mObservedSamplesPerSymbol, pattern);
|
||||
|
||||
if(candidateScore > currentScore)
|
||||
{
|
||||
mSamplePoint += mOptimizeFineIncrement;
|
||||
adjusted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(adjusted)
|
||||
{
|
||||
while(mSamplePoint < 0)
|
||||
{
|
||||
mSamplePoint++;
|
||||
mBufferPointer--;
|
||||
}
|
||||
|
||||
while(mSamplePoint > 1)
|
||||
{
|
||||
mSamplePoint--;
|
||||
mBufferPointer++;
|
||||
}
|
||||
}
|
||||
|
||||
if(candidateScore > SYNC_OPTIMIZED_THRESHOLD)
|
||||
{
|
||||
updateEqualizer(pattern);
|
||||
}
|
||||
|
||||
return candidateScore > SYNC_EQUALIZED_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the equalizer balance and gain when the sync pattern is detected in the sample buffer. This method
|
||||
* resamples the symbols and compares each soft symbol to the ideal symbol phase to develop average error
|
||||
* measurements for balance and gain. On initial sync detection, the equalizer settings are applied to the
|
||||
* samples in the buffer allowing the symbols to be resampled during coarse sync acquisition.
|
||||
* @param pattern that was detected.
|
||||
*/
|
||||
private void updateEqualizer(DMRSyncPattern pattern)
|
||||
{
|
||||
float[] symbols = pattern.toSymbols();
|
||||
double resampleStart = mBufferPointer + mSamplePoint;
|
||||
int resampleStartIntegral = (int)Math.floor(resampleStart);
|
||||
float resampledSoftSymbol = LinearInterpolator.calculate(mBuffer[resampleStartIntegral],
|
||||
mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral);
|
||||
float balanceAccumulator = resampledSoftSymbol - symbols[23];
|
||||
float gainAccumulator = Math.abs(symbols[23]) - Math.abs(resampledSoftSymbol);
|
||||
|
||||
resampleStart -= (23 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols
|
||||
resampleStartIntegral = (int)Math.floor(resampleStart);
|
||||
|
||||
for(int x = 0; x < 23; x++)
|
||||
{
|
||||
if(resampleStartIntegral >= 0)
|
||||
{
|
||||
resampledSoftSymbol = LinearInterpolator.calculate(mBuffer[resampleStartIntegral],
|
||||
mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral);
|
||||
balanceAccumulator += resampledSoftSymbol - symbols[x];
|
||||
gainAccumulator += Math.abs(symbols[x]) - Math.abs(resampledSoftSymbol);
|
||||
}
|
||||
|
||||
resampleStart += mObservedSamplesPerSymbol;
|
||||
resampleStartIntegral = (int)Math.floor(resampleStart);
|
||||
}
|
||||
|
||||
//Average the accumulated error over 24 sync symbols
|
||||
balanceAccumulator /= -24.0f;
|
||||
gainAccumulator /= (24.0f * Dibit.D01_PLUS_3.getIdealPhase());
|
||||
|
||||
if(mEqualizerInitialized)
|
||||
{
|
||||
//Limit equalizer adjustments at each sync after the initial equalizer setup.
|
||||
mEqualizerBalance += (balanceAccumulator * EQUALIZER_LOOP_GAIN);
|
||||
mEqualizerGain += (gainAccumulator * EQUALIZER_LOOP_GAIN);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEqualizerBalance += balanceAccumulator;
|
||||
mEqualizerGain += gainAccumulator;
|
||||
}
|
||||
|
||||
//Constrain balance to +/- PI/4
|
||||
mEqualizerBalance = Math.min(mEqualizerBalance, MAXIMUM_EQUALIZER_BALANCE);
|
||||
mEqualizerBalance = Math.max(mEqualizerBalance, -MAXIMUM_EQUALIZER_BALANCE);
|
||||
|
||||
//Constrain gain between 1.0f and 1.35f
|
||||
mEqualizerGain = Math.min(mEqualizerGain, MAXIMUM_EQUALIZER_GAIN);
|
||||
mEqualizerGain = Math.max(mEqualizerGain, 1.0f);
|
||||
|
||||
if(!mEqualizerInitialized)
|
||||
{
|
||||
//Apply the initial gain settings to the samples in the buffer so that the symbols can be resampled.
|
||||
for(int x = 0; x < mBufferPointer; x++)
|
||||
{
|
||||
mBuffer[x] = (mBuffer[x] + mEqualizerBalance) * mEqualizerGain;
|
||||
}
|
||||
|
||||
mEqualizerInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug method to visualize the contents of the sample buffer.
|
||||
*/
|
||||
public void visualizeBufferContents()
|
||||
{
|
||||
//This will block until the viewer is constructed and showing.
|
||||
if(mSyncResultsViewer == null)
|
||||
{
|
||||
mSyncResultsViewer = new SyncResultsViewer();
|
||||
}
|
||||
|
||||
int offset = 3;
|
||||
int length = (int)Math.ceil(mObservedSamplesPerSymbol * 90) + (2 * offset);
|
||||
int end = mBufferPointer + offset;
|
||||
int start = end - length;
|
||||
float[] symbols = new float[90];
|
||||
float[] samples = Arrays.copyOfRange(mBuffer, start, end);
|
||||
|
||||
float[] intervals = new float[90];
|
||||
|
||||
int adjust = mBufferPointer - length + offset;
|
||||
double pointer = mBufferPointer + mSamplePoint - adjust;
|
||||
|
||||
for(int x = 89; x >= 0; x--)
|
||||
{
|
||||
intervals[x] = (float)pointer;
|
||||
pointer -= mObservedSamplesPerSymbol;
|
||||
}
|
||||
|
||||
float[] sync = new float[90];
|
||||
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);
|
||||
|
||||
try
|
||||
{
|
||||
countDownLatch.await();
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug method to visualize the contents of the sync detection for the samples and symbols and symbol timing.
|
||||
* @param pattern that was detected.
|
||||
* @param score from the sync detector
|
||||
* @param primary sync detector (true) or secondary (false)
|
||||
*/
|
||||
public void visualizeSyncDetect(DMRSyncPattern pattern, float score, boolean primary)
|
||||
{
|
||||
//This will block until the viewer is constructed and showing.
|
||||
if(mSyncResultsViewer == null)
|
||||
{
|
||||
mSyncResultsViewer = new SyncResultsViewer();
|
||||
}
|
||||
|
||||
int offset = 3;
|
||||
int length = (int)Math.ceil(mObservedSamplesPerSymbol * 23) + (2 * offset);
|
||||
int end = mBufferPointer + offset;
|
||||
int start = end - length;
|
||||
float[] symbols = new float[24];
|
||||
float[] samples = Arrays.copyOfRange(mBuffer, start, end);
|
||||
|
||||
float[] intervals = new float[24];
|
||||
|
||||
int adjust = mBufferPointer - length + offset;
|
||||
double pointer = mBufferPointer + mSamplePoint - adjust;
|
||||
|
||||
double symbolPointer = mBufferPointer + mSamplePoint - (mObservedSamplesPerSymbol * 23);
|
||||
int symbolIntegral = (int)Math.floor(symbolPointer);
|
||||
double mu = symbolPointer - symbolIntegral;
|
||||
|
||||
for(int x = 0; x < 24; x++)
|
||||
{
|
||||
symbols[x] = LinearInterpolator.calculate(mBuffer[symbolIntegral], mBuffer[symbolIntegral + 1], mu);
|
||||
symbolPointer += mObservedSamplesPerSymbol;
|
||||
symbolIntegral = (int)Math.floor(symbolPointer);
|
||||
mu = symbolPointer - symbolIntegral;
|
||||
}
|
||||
|
||||
for(int x = 23; x >= 0; x--)
|
||||
{
|
||||
intervals[x] = (float)pointer;
|
||||
pointer -= mObservedSamplesPerSymbol;
|
||||
}
|
||||
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
mSyncResultsViewer.receive(symbols, pattern.toSymbols(), samples, intervals,
|
||||
pattern + " Score: " + score + (primary ? " PRIMARY" : " SECONDARY"), countDownLatch);
|
||||
|
||||
try
|
||||
{
|
||||
countDownLatch.await();
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the sync correlation score for sync pattern ending at the specified offset and samples per symbol interval.
|
||||
* @param offset to the final symbol in the soft symbol buffer
|
||||
* @param samplesPerSymbol spacing to test for.
|
||||
* @param pattern to correlate against.
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float score(int bufferPointer, float fractional, float samplesPerSymbol, DMRSyncPattern pattern)
|
||||
public float score(double offset, double samplesPerSymbol, DMRSyncPattern pattern)
|
||||
{
|
||||
float score = 0;
|
||||
float[] symbols = pattern.toSymbols();
|
||||
int integral, maxPointer = mBuffer.length - 1;
|
||||
float softSymbol = 0.0f;
|
||||
int maxPointer = mBuffer.length - 1;
|
||||
float softSymbol;
|
||||
|
||||
double pointer = offset - (samplesPerSymbol * 23.0);
|
||||
int bufferPointer = (int)Math.floor(pointer);
|
||||
double fractional = pointer - bufferPointer;
|
||||
|
||||
float score = 0;
|
||||
|
||||
for(int x = 0; x < 24; x++)
|
||||
{
|
||||
if(bufferPointer < maxPointer)
|
||||
{
|
||||
softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional);
|
||||
softSymbol = Math.min(softSymbol, MAX_POSITIVE_SOFT_SYMBOL);
|
||||
softSymbol = Math.max(softSymbol, MAX_NEGATIVE_SOFT_SYMBOL);
|
||||
softSymbol = LinearInterpolator.calculate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -432,10 +709,9 @@ public class DMRSoftSymbolProcessor
|
||||
}
|
||||
|
||||
score += softSymbol * symbols[x];
|
||||
fractional += samplesPerSymbol;
|
||||
integral = (int)Math.floor(fractional);
|
||||
bufferPointer += integral;
|
||||
fractional -= integral;
|
||||
pointer += samplesPerSymbol;
|
||||
bufferPointer = (int)Math.floor(pointer);
|
||||
fractional = pointer - bufferPointer;
|
||||
}
|
||||
|
||||
return score;
|
||||
@@ -448,14 +724,14 @@ public class DMRSoftSymbolProcessor
|
||||
public void setSamplesPerSymbol(float samplesPerSymbol)
|
||||
{
|
||||
mSamplesPerSymbol = samplesPerSymbol;
|
||||
mMaxSamplesPerSymbol = samplesPerSymbol * (1.0f + SAMPLES_PER_SYMBOL_ALLOWABLE_DEVIATION);
|
||||
mMinSamplesPerSymbol = samplesPerSymbol * (1.0f - SAMPLES_PER_SYMBOL_ALLOWABLE_DEVIATION);
|
||||
float maxSamplesPerSymbol = samplesPerSymbol * (1.0f + SAMPLES_PER_SYMBOL_ALLOWABLE_DEVIATION);
|
||||
mOptimizeFineIncrement = samplesPerSymbol * .004f; //Adjust at .4%
|
||||
//Protected region for score() method to avoid IndexOutOfBounds = Max Adjustment + Max Step Size for Max SPS
|
||||
mBufferInterpolatorReservedRegion = (int)Math.ceil((mMaxSamplesPerSymbol / 2.0f) + (mMaxSamplesPerSymbol / 10.0f));
|
||||
mBufferInterpolatorReservedRegion = (int)Math.ceil((maxSamplesPerSymbol / 2.0f) + (maxSamplesPerSymbol / 10.0f));
|
||||
mObservedSamplesPerSymbol = mSamplesPerSymbol;
|
||||
mNoiseStandardDeviationThreshold = Dibit.D01_PLUS_3.getIdealPhase() * 2 / mSamplesPerSymbol * 1.2; //120% of optimal
|
||||
mSamplePoint = mSamplesPerSymbol;
|
||||
mLaggingSyncOffset1 = mSamplesPerSymbol / 3;
|
||||
mLaggingSyncOffset2 = mLaggingSyncOffset1 * 2;
|
||||
mSecondarySyncOffset = mSamplesPerSymbol / 2.0;
|
||||
mBufferWorkspaceLength = (int)Math.ceil(BUFFER_WORKSPACE_LENGTH_DIBITS * mSamplesPerSymbol);
|
||||
int bufferLength = (int)(Math.ceil(BUFFER_LENGTH_DIBITS * mSamplesPerSymbol));
|
||||
mBuffer = new float[bufferLength];
|
||||
|
||||
@@ -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
|
||||
@@ -75,4 +75,23 @@ public class DibitDelayLine
|
||||
mPointer %= mLength;
|
||||
}
|
||||
}
|
||||
|
||||
public void log()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int pointer = mPointer;
|
||||
for(int i = 0; i < mDelayLine.length; i++)
|
||||
{
|
||||
Dibit dibit = mDelayLine[pointer];
|
||||
sb.append(dibit.getBit1() ? "1" : "0");
|
||||
sb.append(dibit.getBit2() ? "1" : "0");
|
||||
sb.append(" ");
|
||||
|
||||
pointer++;
|
||||
pointer = pointer % mLength;
|
||||
}
|
||||
|
||||
System.out.println(sb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@ public class DMRDataMessageFactory
|
||||
*/
|
||||
private static CorrectedBinaryMessage getPayload(CorrectedBinaryMessage message)
|
||||
{
|
||||
CorrectedBinaryMessage descrambled = extract(message);
|
||||
return BPTC_196_96.extract(descrambled);
|
||||
CorrectedBinaryMessage extracted = extract(message);
|
||||
return BPTC_196_96.extract(extracted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +321,7 @@ public class DMRDataMessageFactory
|
||||
{
|
||||
extracted.add(message.get(i));
|
||||
}
|
||||
for(int i = 190; i < 190 + 98; i++)
|
||||
for(int i = 190; i < 288; i++)
|
||||
{
|
||||
extracted.add(message.get(i));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -29,6 +29,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
private static final long SYNC_MASK = 0xFFFFFFFFFFFFL;
|
||||
private static final int MAXIMUM_BIT_ERROR = 5;
|
||||
private long mValue;
|
||||
private int mDelta;
|
||||
private DMRSyncPattern mDetectedPattern = DMRSyncPattern.UNKNOWN;
|
||||
|
||||
/**
|
||||
@@ -44,18 +45,41 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the dibit.
|
||||
* Count of bit errors for a detected sync pattern.
|
||||
* @return delta bit error count.
|
||||
*/
|
||||
public int getDelta()
|
||||
{
|
||||
return mDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the dibit into the queue and detect any DMR sync pattern.
|
||||
* @param dibit to process
|
||||
* @return true if a sync pattern is detected.
|
||||
*/
|
||||
public boolean process(Dibit dibit)
|
||||
public boolean processAndDetect(Dibit dibit)
|
||||
{
|
||||
process(dibit);
|
||||
return detect();
|
||||
}
|
||||
|
||||
public void process(Dibit dibit)
|
||||
{
|
||||
mValue = (Long.rotateLeft(mValue, 2) & SYNC_MASK) + dibit.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the received sequence of dibits for sync pattern match. The detected pattern is at getDetectedPattern().
|
||||
* @return true if a sync pattern is detected.
|
||||
*/
|
||||
public boolean detect()
|
||||
{
|
||||
int bitCount = Long.bitCount(mValue ^ DMRSyncPattern.BASE_STATION_DATA.getPattern());
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -63,6 +87,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,6 +95,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,6 +103,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,6 +111,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -91,6 +119,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -98,6 +127,7 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,9 +135,11 @@ public class DMRHardSyncDetector extends DMRSyncDetector
|
||||
if(bitCount <= MAXIMUM_BIT_ERROR)
|
||||
{
|
||||
mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
|
||||
mDelta = bitCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
mDelta = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -65,29 +65,32 @@ public abstract class DMRSoftSyncDetector extends DMRSyncDetector
|
||||
|
||||
/**
|
||||
* Worker method to be implemented by the subclass implementation.
|
||||
* @return best sync correlation score where the best detected pattern is available at
|
||||
* @see DmrSyncDetector#getDetectedPattern() method.
|
||||
* @return best sync correlation score where the best detected pattern is available at getDetectedPattern().
|
||||
*/
|
||||
protected abstract float calculate();
|
||||
public abstract float calculate();
|
||||
|
||||
/**
|
||||
* Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
|
||||
* symbols that include this recent soft symbol.
|
||||
*
|
||||
* @param dibitSymbol to process that represents the soft demodulated symbol value in phase angle/radians.
|
||||
* @return correlation score.
|
||||
*/
|
||||
public float process(float dibitSymbol)
|
||||
public void process(float dibitSymbol)
|
||||
{
|
||||
//Constrain symbol to maximum positive or negative value to limit noisy symbols.
|
||||
dibitSymbol = Math.min(dibitSymbol, MAX_POSITIVE);
|
||||
dibitSymbol = Math.max(dibitSymbol, MAX_NEGATIVE);
|
||||
|
||||
mSymbols[mSymbolPointer] = dibitSymbol;
|
||||
mSymbols[mSymbolPointer + 24] = dibitSymbol;
|
||||
mSymbolPointer++;
|
||||
mSymbolPointer %= 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the symbol and calculate the sync detection score.
|
||||
* @param symbol to process
|
||||
* @return sync detection score.
|
||||
*/
|
||||
public float processAndCalculate(float symbol)
|
||||
{
|
||||
process(symbol);
|
||||
return calculate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Interface for receiving sync detection results from a demodulated I/Q stream
|
||||
*/
|
||||
public interface ISyncResultsListener
|
||||
{
|
||||
/**
|
||||
* Receive results
|
||||
* @param symbols for the detected sync
|
||||
* @param sync pattern symbols (ideal)
|
||||
* @param samples demodulated samples
|
||||
* @param syncIntervals for timing of each symbol in the samples array
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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 io.github.dsheirer.util.SwingUtils;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
/**
|
||||
* Utility for viewing sync detection results
|
||||
*/
|
||||
public class SyncResultsViewer implements ISyncResultsListener
|
||||
{
|
||||
private ISyncResultsListener mListener;
|
||||
|
||||
public SyncResultsViewer()
|
||||
{
|
||||
initUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* View sync detection results.
|
||||
* @param symbols that were detected
|
||||
* @param samples for the symbols
|
||||
* @param sync that was detected
|
||||
* @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)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(symbols, samples, sync, syncIntervals, label, release);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the viewer UI
|
||||
*/
|
||||
private void initUI()
|
||||
{
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final JFXPanel fxPanel = new JFXPanel();
|
||||
Platform.runLater(() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
SyncVisualizer syncVisualizer = new SyncVisualizer();
|
||||
mListener = syncVisualizer;
|
||||
Scene scene = new Scene(syncVisualizer, 1400, 1000);
|
||||
fxPanel.setScene(scene);
|
||||
fxPanel.setVisible(true);
|
||||
|
||||
JFrame frame = new JFrame();
|
||||
frame.setTitle("Sync Results Viewer");
|
||||
frame.setContentPane(fxPanel);
|
||||
frame.setSize(1400, 1000);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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,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,7 +25,7 @@ import io.github.dsheirer.preference.calibration.VectorCalibrationPreference;
|
||||
import io.github.dsheirer.vector.calibrate.airspy.AirspySampleConverterCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.airspy.AirspyUnpackedInterleavedCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.demodulator.DqpskDemodulatorCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.demodulator.DifferentialDemodulatorCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.demodulator.FmDemodulatorCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration;
|
||||
import io.github.dsheirer.vector.calibrate.filter.RealDcRemovalCalibration;
|
||||
@@ -108,7 +108,7 @@ public class CalibrationManager
|
||||
sInstance.add(new ComplexOscillatorCalibration());
|
||||
sInstance.add(new ComplexMixerCalibration());
|
||||
sInstance.add(new DMRSoftSyncCalibration());
|
||||
sInstance.add(new DqpskDemodulatorCalibration());
|
||||
sInstance.add(new DifferentialDemodulatorCalibration());
|
||||
sInstance.add(new FirFilterCalibration());
|
||||
sInstance.add(new FmDemodulatorCalibration());
|
||||
sInstance.add(new InterpolatorCalibration());
|
||||
@@ -281,7 +281,7 @@ public class CalibrationManager
|
||||
{
|
||||
CalibrationManager manager = getInstance();
|
||||
// manager.reset();
|
||||
manager.reset(CalibrationType.DQPSK_DEMODULATOR);
|
||||
manager.reset(CalibrationType.DIFFERENTIAL_DEMODULATOR);
|
||||
|
||||
if(!manager.isCalibrated())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -38,7 +38,7 @@ public enum CalibrationType
|
||||
AM_DEMODULATOR("AM Demodulator", 1),
|
||||
DC_REMOVAL_REAL("Real DC Removal Filter", 1),
|
||||
DMR_SOFT_SYNC_DETECTOR("DMR Soft Sync Detector", 1),
|
||||
DQPSK_DEMODULATOR("DQPSK Demodulator", 1),
|
||||
DIFFERENTIAL_DEMODULATOR("DQPSK Demodulator", 1),
|
||||
FILTER_FIR("FIR Filter", 1),
|
||||
FILTER_HALF_BAND_REAL_11_TAP("Real Half-Band Decimation Filter - 11 Tap", 1),
|
||||
FILTER_HALF_BAND_REAL_15_TAP("Real Half-Band Decimation Filter - 15 Tap", 1),
|
||||
|
||||
@@ -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
|
||||
@@ -19,12 +19,12 @@
|
||||
|
||||
package io.github.dsheirer.vector.calibrate.demodulator;
|
||||
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorScalar;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector128;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector256;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector512;
|
||||
import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorVector64;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorScalar;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorVector128;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorVector256;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorVector512;
|
||||
import io.github.dsheirer.dsp.psk.demod.DifferentialDemodulatorVector64;
|
||||
import io.github.dsheirer.vector.calibrate.Calibration;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationException;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
@@ -32,26 +32,26 @@ import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||
|
||||
/**
|
||||
* Calibrates DQPSK demodulator options
|
||||
* Calibrates differential demodulator options
|
||||
*/
|
||||
public class DqpskDemodulatorCalibration extends Calibration
|
||||
public class DifferentialDemodulatorCalibration extends Calibration
|
||||
{
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
private static final int ITERATION_DURATION_MS = 1000;
|
||||
private static final int WARMUP_ITERATIONS = 5;
|
||||
private static final int TEST_ITERATIONS = 5;
|
||||
private DQPSKDemodulator mScalarDemodulator = new DQPSKDemodulatorScalar(50000.0, 4800);
|
||||
private final DQPSKDemodulator mVectorDemodulator64 = new DQPSKDemodulatorVector64(50000.0, 4800);
|
||||
private final DQPSKDemodulator mVectorDemodulator128 = new DQPSKDemodulatorVector128(50000.0, 4800);
|
||||
private final DQPSKDemodulator mVectorDemodulator256 = new DQPSKDemodulatorVector256(50000.0, 4800);
|
||||
private final DQPSKDemodulator mVectorDemodulator512 = new DQPSKDemodulatorVector512(50000.0, 4800);
|
||||
private DifferentialDemodulator mScalarDemodulator = new DifferentialDemodulatorScalar(50000.0, 4800);
|
||||
private final DifferentialDemodulator mVectorDemodulator64 = new DifferentialDemodulatorVector64(50000.0, 4800);
|
||||
private final DifferentialDemodulator mVectorDemodulator128 = new DifferentialDemodulatorVector128(50000.0, 4800);
|
||||
private final DifferentialDemodulator mVectorDemodulator256 = new DifferentialDemodulatorVector256(50000.0, 4800);
|
||||
private final DifferentialDemodulator mVectorDemodulator512 = new DifferentialDemodulatorVector512(50000.0, 4800);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DqpskDemodulatorCalibration()
|
||||
public DifferentialDemodulatorCalibration()
|
||||
{
|
||||
super(CalibrationType.DQPSK_DEMODULATOR);
|
||||
super(CalibrationType.DIFFERENTIAL_DEMODULATOR);
|
||||
}
|
||||
|
||||
@Override public void calibrate() throws CalibrationException
|
||||
@@ -273,7 +273,7 @@ public class DqpskDemodulatorCalibration extends Calibration
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
DqpskDemodulatorCalibration calibration = new DqpskDemodulatorCalibration();
|
||||
DifferentialDemodulatorCalibration calibration = new DifferentialDemodulatorCalibration();
|
||||
|
||||
try
|
||||
{
|
||||
Reference in New Issue
Block a user