diff --git a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java index 56841dcd..0bd3ade0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/decimate/DecimationFilterFactory.java @@ -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(); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/LinearInterpolator.java similarity index 53% rename from src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java rename to src/main/java/io/github/dsheirer/dsp/filter/interpolator/LinearInterpolator.java index 21f23fc8..15b0ccb7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/LinearInterpolator.java @@ -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); } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulator.java similarity index 84% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulator.java index b95f1ba7..ffb8fc67 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulator.java @@ -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++; + } } /** diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorFactory.java similarity index 64% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorFactory.java index 661cbcdc..d8253de5 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorFactory.java @@ -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 }; } } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorScalar.java similarity index 89% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorScalar.java index 9e6ba4a1..0781206a 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorScalar.java @@ -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); } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector128.java similarity index 91% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector128.java index 15a53092..391bda1a 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector128.java @@ -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 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); } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector256.java similarity index 90% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector256.java index a36cb25c..2c47304b 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector256.java @@ -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 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()]; diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector512.java similarity index 91% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector512.java index b1ab9698..185d8a80 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector512.java @@ -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 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); } diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector64.java similarity index 91% rename from src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java rename to src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector64.java index 63d09676..983de06d 100644 --- a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java +++ b/src/main/java/io/github/dsheirer/dsp/psk/demod/DifferentialDemodulatorVector64.java @@ -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 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); } diff --git a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java index 78efb2ff..6b9230b5 100644 --- a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java +++ b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java @@ -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. diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index c7e083ba..ef2dad4d 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -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 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); } }); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java index 161c98f3..4fd88e48 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoder.java @@ -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 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) diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java index 596ecff3..5f6831b2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java @@ -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()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java index d5b3c731..8ba6ae6a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java @@ -112,6 +112,15 @@ public class DMRMessageFramer implements Listener } } + /** + * 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; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java index 652d7ff8..eab5200a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java @@ -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]; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java index 5cc072c7..d2b25615 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java @@ -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); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java index 480cc020..109382f2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java @@ -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)); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java index 3293e5f7..15300b47 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java @@ -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; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java index 00120cea..0c3ad675 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java @@ -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(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java index b01f5edd..71b828a8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/ISyncResultsListener.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/ISyncResultsListener.java new file mode 100644 index 00000000..4057002b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/ISyncResultsListener.java @@ -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 + * **************************************************************************** + */ + +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); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncResultsViewer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncResultsViewer.java new file mode 100644 index 00000000..f2a0c3ed --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncResultsViewer.java @@ -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 + * **************************************************************************** + */ + +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); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncVisualizer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncVisualizer.java new file mode 100644 index 00000000..78f792d8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/visualizer/SyncVisualizer.java @@ -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 + * **************************************************************************** + */ + +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 mConstellationChart = new ScatterChart<>(mConstellationI, mConstellationQ); + private final LineChart 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 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 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 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 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); + }); + } +} diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java index fc8b35f6..2293b78d 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java @@ -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()) { diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java index a08fff71..6c7e57c7 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationType.java @@ -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), diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DifferentialDemodulatorCalibration.java similarity index 85% rename from src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java rename to src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DifferentialDemodulatorCalibration.java index faa973ee..56767ad2 100644 --- a/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java +++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DifferentialDemodulatorCalibration.java @@ -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 { diff --git a/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java b/src/test/java/io/github/dsheirer/dsp/psk/demod/DmrSoftSyncDetectorTest.java similarity index 90% rename from src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java rename to src/test/java/io/github/dsheirer/dsp/psk/demod/DmrSoftSyncDetectorTest.java index 5ad1fb5c..5ff6a840 100644 --- a/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java +++ b/src/test/java/io/github/dsheirer/dsp/psk/demod/DmrSoftSyncDetectorTest.java @@ -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.dsp.symbol.Dibit; import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector; @@ -60,11 +60,11 @@ public class DmrSoftSyncDetectorTest for(Dibit dibit: pattern.toDibits()) { symbol = toSymbol(dibit); - scalar.process(symbol); - vector64.process(symbol); - vector128.process(symbol); - vector256.process(symbol); - vector512.process(symbol); + scalar.processAndCalculate(symbol); + vector64.processAndCalculate(symbol); + vector128.processAndCalculate(symbol); + vector256.processAndCalculate(symbol); + vector512.processAndCalculate(symbol); } assertSame(scalar.getDetectedPattern(), pattern, "Scalar Detector Did Not Correctly Detect Pattern: " + pattern);