#7 NXDN Decoder support. Redesigns tuner and channel frequency error management. (#2431)

Closes #7 

NXDN Decoder

* Implements: NXDN4800, NXDN9600, Type-C and Type-D decoding and trunk tracking.
* Redesigns tuner and channel frequency error management.
This commit is contained in:
Denny Sheirer
2026-07-08 03:44:08 -04:00
committed by GitHub
parent a053315675
commit 7945f0ce63
445 changed files with 30854 additions and 2053 deletions

View File

@@ -42,6 +42,7 @@ import io.github.dsheirer.vector.calibrate.sample.PackedSampleConverterCalibrati
import io.github.dsheirer.vector.calibrate.sample.UnpackedInterleavedSampleConverterCalibration;
import io.github.dsheirer.vector.calibrate.sample.UnpackedSampleConverterCalibration;
import io.github.dsheirer.vector.calibrate.sync.DMRSoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.sync.NXDNSoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.sync.P25P1SoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.window.WindowCalibration;
import java.util.ArrayList;
@@ -114,6 +115,7 @@ public class CalibrationManager
sInstance.add(new FmDemodulatorCalibration());
sInstance.add(new InterpolatorCalibration());
sInstance.add(new MagnitudeCalibration());
sInstance.add(new NXDNSoftSyncCalibration());
sInstance.add(new P25P1SoftSyncCalibration());
sInstance.add(new RealDcRemovalCalibration());
sInstance.add(new RealHalfBand11TapFilterCalibration());

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* Copyright (C) 2014-2026 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
@@ -48,6 +48,7 @@ public enum CalibrationType
INTERPOLATOR("Interpolator", 1),
MAGNITUDE("Magnitude", 1),
MIXER_COMPLEX("Complex Mixer", 1),
NXDN_SOFT_SYNC_DETECTOR("NXDN Soft Sync Detector", 2),
OSCILLATOR_REAL("Real Oscillator", 1),
P25P1_SOFT_SYNC_DETECTOR("P25 Phase 1 Soft Sync Detector", 1),
SAMPLE_PACKED_CONVERTER("Packed Sample Converter", 1),

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2026 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,7 +20,6 @@
package io.github.dsheirer.vector.calibrate.filter;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
import io.github.dsheirer.dsp.filter.fir.real.VectorRealFIRFilter128Bit;
@@ -75,7 +74,7 @@ public class FirFilterCalibration extends Calibration
mVector256 = new VectorRealFIRFilter256Bit(coefficients);
mVector512 = new VectorRealFIRFilter512Bit(coefficients);
}
catch(FilterDesignException fde)
catch(Exception fde)
{
mLog.error("Error creating sinc filter for real FIR filter calibration");
}

View File

@@ -0,0 +1,278 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.vector.calibrate.sync;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetector;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetectorScalar;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetectorVector128;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetectorVector256;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetectorVector512;
import io.github.dsheirer.module.decode.nxdn.layer1.sync.NXDNSoftSyncDetectorVector64;
import io.github.dsheirer.vector.calibrate.Calibration;
import io.github.dsheirer.vector.calibrate.CalibrationException;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
* NXDN Soft Sync Detector calibration
*/
public class NXDNSoftSyncCalibration 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 final NXDNSoftSyncDetector mScalarDetector = new NXDNSoftSyncDetectorScalar();
private final NXDNSoftSyncDetector mVectorDetector64 = new NXDNSoftSyncDetectorVector64();
private final NXDNSoftSyncDetector mVectorDetector128 = new NXDNSoftSyncDetectorVector128();
private final NXDNSoftSyncDetector mVectorDetector256 = new NXDNSoftSyncDetectorVector256();
private final NXDNSoftSyncDetector mVectorDetector512 = new NXDNSoftSyncDetectorVector512();
/**
* Constructs an instance
*/
public NXDNSoftSyncCalibration()
{
super(CalibrationType.NXDN_SOFT_SYNC_DETECTOR);
}
@Override public void calibrate() throws CalibrationException
{
float[] samples = getFloatSamples(BUFFER_SIZE);
Mean scalarMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testScalar(samples);
scalarMean.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
Mean vectorMean64 = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector64(samples);
vectorMean64.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult()));
Mean vectorMean128 = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector128(samples);
vectorMean128.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult()));
Mean vectorMean256 = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector256(samples);
vectorMean256.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult()));
Mean vectorMean512 = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector512(samples);
vectorMean512.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult()));
//Start tests
scalarMean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testScalar(samples);
scalarMean.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
vectorMean64.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector64(samples);
vectorMean64.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult()));
vectorMean128.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector128(samples);
vectorMean128.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult()));
vectorMean256.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector256(samples);
vectorMean256.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult()));
vectorMean512.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector512(samples);
vectorMean512.increment(score);
}
mLog.info("NXDN SOFT SYNC DETECTOR - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult()));
Implementation best = Implementation.SCALAR;
double bestScore = scalarMean.getResult();
if(vectorMean64.getResult() > bestScore)
{
bestScore = vectorMean64.getResult();
best = Implementation.VECTOR_SIMD_64;
}
if(vectorMean128.getResult() > bestScore)
{
bestScore = vectorMean128.getResult();
best = Implementation.VECTOR_SIMD_128;
}
if(vectorMean256.getResult() > bestScore)
{
bestScore = vectorMean256.getResult();
best = Implementation.VECTOR_SIMD_256;
}
if(vectorMean512.getResult() > bestScore)
{
best = Implementation.VECTOR_SIMD_512;
}
setImplementation(best);
mLog.info("NXDN SOFT SYNC DETECTOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation());
}
private long testScalar(float[] samples)
{
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
for(float sample : samples)
{
mScalarDetector.process(sample);
}
count++;
}
return count;
}
private long testVector64(float[] samples)
{
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
for(float sample : samples)
{
mVectorDetector64.process(sample);
}
count++;
}
return count;
}
private long testVector128(float[] samples)
{
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
for(float sample : samples)
{
mVectorDetector128.process(sample);
}
count++;
}
return count;
}
private long testVector256(float[] samples)
{
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
for(float sample : samples)
{
mVectorDetector256.process(sample);
}
count++;
}
return count;
}
private long testVector512(float[] samples)
{
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
for(float sample : samples)
{
mVectorDetector512.process(sample);
}
count++;
}
return count;
}
}