diff --git a/build.gradle b/build.gradle
index 48509d72..6c94270e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -164,6 +164,15 @@ application {
}
}
+test {
+ if(os.isWindows()) {
+ jvmArgs = jvmArgsWindows
+ }
+ else {
+ jvmArgs = jvmArgsLinux
+ }
+}
+
jar {
manifest {
attributes (
diff --git a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
index 341798e7..5d887c89 100644
--- a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
+++ b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java
@@ -281,6 +281,20 @@ public class BinaryMessage extends BitSet
}
}
+ /**
+ * Adds bit1 and bit2 to this bitset.
+ *
+ * Note: this method does not provide overfill protection.
+ *
+ * @param bit1 to add.
+ * @param bit2 to add.
+ */
+ public void add(boolean bit1, boolean bit2)
+ {
+ set(mPointer++, bit1);
+ set(mPointer++, bit2);
+ }
+
public String toString()
{
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java
index 21d31990..fc754be6 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/FilterFactory.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,15 +56,14 @@ import io.github.dsheirer.dsp.window.WindowType;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
+import java.text.DecimalFormat;
+import java.util.Set;
+import java.util.TreeSet;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.DecimalFormat;
-import java.util.Set;
-import java.util.TreeSet;
-
/**
* Utility methods for designing various types of filters.
*/
@@ -627,11 +626,13 @@ public class FilterFactory
* @param samplesPerSymbol - number of samples per symbol
* @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size
* @param alpha - roll-off factor.
+ * @param gain of the filter
* @return - filter coefficients
*/
public static float[] getRootRaisedCosine(double samplesPerSymbol, int symbolCount, float alpha)
{
- int taps = (int)(samplesPerSymbol * symbolCount);
+ samplesPerSymbol /= 2;
+ int taps = (int)Math.round(samplesPerSymbol * symbolCount);
float scale = 0;
@@ -639,47 +640,47 @@ public class FilterFactory
for(int x = 0; x < taps; x++)
{
- float index = (float)x - ((float)taps / 2.0f);
+ double index = x - (taps / 2.0f);
- float x1 = (float)FastMath.PI * index / (float)samplesPerSymbol;
- float x2 = 4.0f * alpha * index / (float)samplesPerSymbol;
- float x3 = x2 * x2 - 1.0f;
+ double x1 = FastMath.PI * index / samplesPerSymbol;
+ double x2 = 4.0 * alpha * index / samplesPerSymbol;
+ double x3 = x2 * x2 - 1.0;
- float numerator, denominator;
+ double numerator, denominator;
if(FastMath.abs(x3) >= 0.000001)
{
if(x != taps / 2)
{
- numerator = (float)FastMath.cos((1.0 + alpha) * x1) +
- (float)FastMath.sin((1.0f - alpha) * x1) / (4.0f * alpha * index / (float)samplesPerSymbol);
+ numerator = FastMath.cos((1.0 + alpha) * x1) +
+ FastMath.sin((1.0 - alpha) * x1) / (4.0 * alpha * index / samplesPerSymbol);
}
else
{
- numerator = (float)FastMath.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)FastMath.PI / (4.0f * alpha);
+ numerator = FastMath.cos((1.0 + alpha) * x1) + (1.0 - alpha) * FastMath.PI / (4.0 * alpha);
}
- denominator = x3 * (float)FastMath.PI;
+ denominator = x3 * FastMath.PI;
}
else
{
- if(alpha == 1.0f)
+ if(alpha == 1.0)
{
coefficients[x] = -1.0f;
continue;
}
- x3 = (1.0f - alpha) * x1;
- x2 = (1.0f + alpha) * x1;
+ x3 = (1.0 - alpha) * x1;
+ x2 = (1.0 + alpha) * x1;
- numerator = (float)(FastMath.sin(x2) * (1.0f + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI *
- (double)samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * (double)samplesPerSymbol *
- (double)samplesPerSymbol / (4.0 * alpha * index * index));
+ numerator = (FastMath.sin(x2) * (1.0 + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI *
+ samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * samplesPerSymbol *
+ samplesPerSymbol / (4.0 * alpha * index * index));
- denominator = (float)(-32.0 * FastMath.PI * alpha * alpha * index / (double)samplesPerSymbol);
+ denominator = -32.0 * FastMath.PI * alpha * alpha * index / samplesPerSymbol;
}
- coefficients[x] = 4.0f * alpha * numerator / denominator;
+ coefficients[x] = (float)(4.0 * alpha * numerator / denominator);
scale += coefficients[x];
}
@@ -1210,13 +1211,20 @@ public class FilterFactory
{
DecimalFormat df = new DecimalFormat("0.000000");
- int length = 15;
+ int length = 49;
- float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING);
-
- for(int x = 0; x < length; x++)
+ try
{
- mLog.debug("Tap: " + x + " Value: " + df.format(taps[x]));
+ float [] taps = FilterFactory.getSinc(0.5, length, WindowType.NONE);
+
+ for(int x = 0; x < length; x++)
+ {
+ mLog.debug("Tap: " + x + " Value: " + df.format(taps[x]));
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
}
mLog.debug("Done!");
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
index a587b659..7dd7ac32 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
-import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import io.github.dsheirer.dsp.window.WindowType;
import javafx.application.Application;
import javafx.scene.Scene;
@@ -62,9 +61,10 @@ public class FilterViewer extends Application
int filterLength = 47;
WindowType windowType = WindowType.HAMMING;
- float[] taps = FilterFactory.getHalfBand(filterLength, windowType);
+// float[] taps = FilterFactory.getHalfBand(filterLength, windowType);
+ float[] taps = FilterFactory.getRootRaisedCosine(50000.0 / 4800.0, 20, 0.2f);
- taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
+// taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
if(taps == null)
{
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java
index a0b1e8e4..295e8edd 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/Interpolator.java
@@ -1,25 +1,24 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.dsp.filter.interpolator;
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2015 Dennis Sheirer
- *
- * Copyright (C) 2002,2012 Free Software Foundation, Inc.
- * Ported to java from gnuradio - interpolator_taps.h
- *
- * 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
- ******************************************************************************/
public abstract class Interpolator
{
public static final int NTAPS = 8;
@@ -158,4 +157,19 @@ public abstract class Interpolator
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
+
+ /**
+ * Calculates an interpolated value from eight samples that start at the offset into the sample array. The
+ * interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and
+ * offset+4. The mu argument is translated into an index position between 0 and 128, where a mu of 0.0 will be
+ * converted to index zero and will be equal to the sample at index offset+3 and a mu of 1.0 will be equal to
+ * the sample at offset+4. All mu values between 0.0 and 1.0 will be converted to a 1 - 127 index and will
+ * produce an approximated value from among 127 interpolated sample points between indexes offset+3 and offset+4.
+ *
+ * @param samples - sample array of length at least offset + 7
+ * @param offset into the sample array for the start of the 8-value sequence
+ * @param mu - interpolated sample position between 0.0 and 1.0
+ * @return - interpolated sample value
+ */
+ public abstract float filter(float[] samples, int offset, float mu);
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java
new file mode 100644
index 00000000..f738b6c5
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorFactory.java
@@ -0,0 +1,57 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.filter.interpolator;
+
+import io.github.dsheirer.vector.calibrate.CalibrationManager;
+import io.github.dsheirer.vector.calibrate.CalibrationType;
+import io.github.dsheirer.vector.calibrate.Implementation;
+
+/**
+ * Factory for creating an optimal scalar or vector (SIMD) interpolator implementation
+ */
+public class InterpolatorFactory
+{
+ /**
+ * Selects and instantiates the best interpolator version, scalar or vector, based on previous calibration.
+ * @return interpolator
+ */
+ public static Interpolator getInterpolator()
+ {
+ Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.INTERPOLATOR);
+ return getInterpolator(implementation);
+ }
+
+ /**
+ * Instantiates the specified interpolator implementation.
+ * @param implementation to construct.
+ * @return interpolator.
+ */
+ public static Interpolator getInterpolator(Implementation implementation)
+ {
+ return switch(implementation)
+ {
+ case VECTOR_SIMD_64 -> new InterpolatorVector64();
+ case VECTOR_SIMD_128 -> new InterpolatorVector128();
+ case VECTOR_SIMD_256, VECTOR_SIMD_512 -> new InterpolatorVector256(); //Vector 512 not supported
+ case SCALAR, UNCALIBRATED -> new InterpolatorScalar();
+ default -> throw new IllegalArgumentException("Unknown implementation type: " + implementation);
+ };
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java
similarity index 72%
rename from src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java
rename to src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java
index 00a64f80..73215e97 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/RealInterpolator.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorScalar.java
@@ -1,17 +1,35 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.dsp.filter.interpolator;
import io.github.dsheirer.sample.complex.Complex;
+import java.text.DecimalFormat;
+import java.util.Arrays;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.DecimalFormat;
-import java.util.Arrays;
-
-public class RealInterpolator extends Interpolator
+public class InterpolatorScalar extends Interpolator
{
- private final static Logger mLog = LoggerFactory.getLogger(RealInterpolator.class);
+ private final static Logger mLog = LoggerFactory.getLogger(InterpolatorScalar.class);
private float mGain;
@@ -21,11 +39,16 @@ public class RealInterpolator extends Interpolator
*
* @param gain to apply to the interpolated sample
*/
- public RealInterpolator(float gain)
+ public InterpolatorScalar(float gain)
{
mGain = gain;
}
+ public InterpolatorScalar()
+ {
+ this(1.0f);
+ }
+
/**
* Calculates an interpolated value from eight samples that start at the offset into the sample array. The
* interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and
@@ -68,7 +91,7 @@ public class RealInterpolator extends Interpolator
public static void main(String[] args)
{
- RealInterpolator interpolator = new RealInterpolator(1.0f);
+ InterpolatorScalar interpolator = new InterpolatorScalar(1.0f);
DecimalFormat decimalFormat = new DecimalFormat("0.0000");
double TWO_PI = FastMath.PI * 2.0;
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java
new file mode 100644
index 00000000..0c54160b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector128.java
@@ -0,0 +1,49 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.filter.interpolator;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * Vector interpolator for 128 bit SIMD instructions.
+ */
+public class InterpolatorVector128 extends VectorInterpolator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
+
+ @Override
+ protected float vectorFilter(float[] samples, int offset, int index)
+ {
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector tapsVector, sampleVector;
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ return accumulator.reduceLanes(VectorOperators.ADD);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java
new file mode 100644
index 00000000..6b137a63
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector256.java
@@ -0,0 +1,40 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.filter.interpolator;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * Vector interpolator for 256 bit SIMD instructions.
+ */
+public class InterpolatorVector256 extends VectorInterpolator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
+
+ @Override
+ protected float vectorFilter(float[] samples, int offset, int index)
+ {
+ FloatVector tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
+ FloatVector sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
+ return sampleVector.mul(tapsVector).reduceLanes(VectorOperators.ADD);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java
new file mode 100644
index 00000000..a97b4dd1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/InterpolatorVector64.java
@@ -0,0 +1,57 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.filter.interpolator;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * Vector interpolator for 64 bit SIMD instructions.
+ */
+public class InterpolatorVector64 extends VectorInterpolator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
+
+ @Override
+ protected float vectorFilter(float[] samples, int offset, int index)
+ {
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector tapsVector, sampleVector;
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 2);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 2);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 6);
+ sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 6);
+ accumulator = tapsVector.fma(sampleVector, accumulator);
+
+ return accumulator.reduceLanes(VectorOperators.ADD);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java
new file mode 100644
index 00000000..21f23fc8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/PhaseAwareLinearInterpolator.java
@@ -0,0 +1,74 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+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.
+ */
+public class PhaseAwareLinearInterpolator
+{
+ 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
+ * @param x2 second value
+ * @param mu offset between first and second values in range 0.0 to 1.0
+ * @return interpolated value.
+ */
+ 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(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);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java
new file mode 100644
index 00000000..a7f813b2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/filter/interpolator/VectorInterpolator.java
@@ -0,0 +1,50 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2023 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.filter.interpolator;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Base vector interpolator implementation.
+ */
+public abstract class VectorInterpolator extends Interpolator
+{
+ @Override
+ public float filter(float[] samples, int offset, float mu)
+ {
+ /* Ensure we have enough samples in the array */
+ Validate.isTrue(samples.length >= offset + 7, "Offset [" + offset + "] must be 7 less than length[" + samples.length + "]");
+
+ //Identify the filter bank that corresponds to mu. Note: since we're not loading the TAPS in reverse order,
+ //we select the inverse tap index that has the mirrored set of taps, by subtracting from 127.
+ int index = 127 - (int)(NSTEPS * mu);
+ return vectorFilter(samples, offset, index);
+ }
+
+ /**
+ * Perform vectorized interpolation to find the interpolated value from the samples where the 8-value sequence
+ * starts at the offset into the array, using the filter coefficients specified by the index value.
+ * @param samples to interpolate
+ * @param offset to the start of the 8-value sequence
+ * @param index into the TAPS coefficients array.
+ * @return interpolated value.
+ */
+ protected abstract float vectorFilter(float[] samples, int offset, int index);
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java
index 7eebc342..08b30d52 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/DQPSKDecisionDirectedDemodulator.java
@@ -1,23 +1,30 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
package io.github.dsheirer.dsp.psk;
+import io.github.dsheirer.dsp.filter.interpolator.Interpolator;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorFactory;
+import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.sample.complex.Complex;
+import java.text.DecimalFormat;
public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator
{
@@ -29,6 +36,11 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator
private Complex mPrecedingSymbol = new Complex(0, 0);
protected Complex mCurrentSymbol = new Complex(0, 0);
+ private final Interpolator mInterpolator = InterpolatorFactory.getInterpolator();
+ private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("+#0.00000;-#0.00000");
+ private float[] mI = new float[15];
+ private float[] mQ = new float[15];
+
/**
* Decoder for Differential Quaternary Phase Shift Keying (DQPSK). This decoder uses both a Costas Loop (PLL) and
* a decision-directed DQPSK symbol timing and phase error detector to automatically align to the incoming carrier
@@ -44,6 +56,32 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator
super(interpolatingSampleBuffer, phaseLockedLoop);
}
+ @Override
+ public void receive(float inphase, float quadrature)
+ {
+ super.receive(inphase, quadrature);
+
+ System.arraycopy(mI, 1, mI, 0, mI.length - 1);
+ System.arraycopy(mQ, 1, mQ, 0, mQ.length - 1);
+
+ //Rotate the sample and add to buffer
+ Complex current = new Complex(inphase, quadrature);
+ current.normalizeFast();
+ current.multiply(getPLL().getCurrentVector());
+ mI[mI.length - 1] = current.inphase();
+ mQ[mQ.length - 1] = current.quadrature();
+ float iTemp = mInterpolator.filter(mI, 6, .416f);
+ float qTemp = mInterpolator.filter(mQ, 6, .416f);
+ float iSymbol = (mI[0] * iTemp) - (-mQ[0] * qTemp);
+ float qSymbol = (mI[0] * qTemp) + (iTemp * -mQ[0]);
+ float radians = (float)Math.atan2(iSymbol, qSymbol);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(DECIMAL_FORMAT.format(radians)).append(",");
+ sb.append(DECIMAL_FORMAT.format(((CostasLoop)getPLL()).getLoopFrequency()));
+// System.out.println(sb);
+ }
+
/**
* Calculates a symbol from the interpolating buffer
*/
@@ -79,7 +117,7 @@ public class DQPSKDecisionDirectedDemodulator extends PSKDemodulator
getInterpolatingSampleBuffer().resetAndAdjust(mSymbolEvaluator.getTimingError());
//Update the costas loop (PLL) with any measured phase error
- getPLL().adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f));
+// getPLL().adjust(clip(mSymbolEvaluator.getPhaseError(), 0.5f));
//Store current samples/symbols to use for the next symbol period
mPreviousPrecedingSample.setValues(mPrecedingSample);
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
index 6d52579c..91fc440f 100644
--- a/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
+++ b/src/main/java/io/github/dsheirer/dsp/psk/InterpolatingSampleBuffer.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,7 +18,7 @@
*/
package io.github.dsheirer.dsp.psk;
-import io.github.dsheirer.dsp.filter.interpolator.RealInterpolator;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorScalar;
import io.github.dsheirer.sample.complex.Complex;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
@@ -45,7 +45,7 @@ public class InterpolatingSampleBuffer
private float mMaximumSamplesPerSymbol;
private float mMinimumSamplesPerSymbol;
- private RealInterpolator mInterpolator = new RealInterpolator(1.0f);
+ private InterpolatorScalar mInterpolator = new InterpolatorScalar(1.0f);
/**
* Buffer to store complex sample data and produce interpolated samples.
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java
new file mode 100644
index 00000000..b95f1ba7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulator.java
@@ -0,0 +1,64 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+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
+ */
+public abstract class DQPSKDemodulator implements IDemodulator
+{
+ private int mSymbolRate;
+ private float mSampleRate;
+ private float mSamplesPerSymbol;
+ protected float[] mIBuffer = new float[20]; //Initial size 20 for array copy, but gets resized on first buffer
+ protected float[] mQBuffer = new float[20];
+ protected float mMu;
+ protected int mBufferOverlap;
+ protected int mInterpolationOffset;
+ protected final Interpolator mInterpolator = InterpolatorFactory.getInterpolator();
+
+ /**
+ * Constructor
+ * @param sampleRate in Hertz
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulator(double sampleRate, int symbolRate)
+ {
+ mSampleRate = (float) sampleRate;
+ mSymbolRate = symbolRate;
+ mSamplesPerSymbol = mSampleRate / mSymbolRate;
+ 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;
+ }
+
+ /**
+ * Calculated samples per symbol.
+ * @return samples per symbol.
+ */
+ public float getSamplesPerSymbol()
+ {
+ return mSamplesPerSymbol;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java
new file mode 100644
index 00000000..661cbcdc
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorFactory.java
@@ -0,0 +1,55 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import io.github.dsheirer.vector.calibrate.CalibrationManager;
+import io.github.dsheirer.vector.calibrate.CalibrationType;
+import io.github.dsheirer.vector.calibrate.Implementation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for creating DQPSK demodulators
+ */
+public class DQPSKDemodulatorFactory
+{
+ private static final Logger mLog = LoggerFactory.getLogger(DQPSKDemodulatorFactory.class);
+
+ /**
+ * Creates the optimal demodulator using calibration data to select the optimal implementation from scalar and
+ * vector options, or defaults to the scalar implementation if the calibration wasn't executed.
+ * @param sampleRate in Hertz
+ * @param symbolRate in Hertz
+ * @return demodulator instance
+ */
+ public static DQPSKDemodulator getDemodulator(double sampleRate, int symbolRate)
+ {
+ Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DQPSK_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
+ };
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java
new file mode 100644
index 00000000..9e6ba4a1
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorScalar.java
@@ -0,0 +1,89 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DQPSK demodulator that uses scalar calculations for demodulating the sample stream.
+ */
+public class DQPSKDemodulatorScalar extends DQPSKDemodulator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorScalar.class);
+
+ /**
+ * Constructor
+ *
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulatorScalar(double sampleRate, int symbolRate)
+ {
+ super(sampleRate, symbolRate);
+ }
+
+ @Override
+ public float[] demodulate(float[] i, float[] q)
+ {
+ int sampleLength = i.length;
+ int bufferOverlap = mBufferOverlap;
+ float mu = mMu;
+
+ //Copy previous buffer residual samples to beginning of buffer.
+ System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap);
+ System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap);
+
+ //Resize I/Q buffers if necessary
+ int requiredBufferLength = sampleLength + bufferOverlap;
+ if(mIBuffer.length != requiredBufferLength)
+ {
+ mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength);
+ mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength);
+ }
+
+ //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[] decodedPhases = new float[i.length];
+ float iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ;
+
+ //Differential demodulation.
+ for(int x = 0; x < sampleLength; x++)
+ {
+ iPrevious = mIBuffer[x];
+ qPreviousConjugate = -mQBuffer[x]; //Complex Conjugate
+
+ int offset = mInterpolationOffset + x;
+ iCurrent = mInterpolator.filter(mIBuffer, offset, mu);
+ qCurrent = mInterpolator.filter(mQBuffer, offset, mu);
+
+ //Multiply current complex sample by the complex conjugate of previous complex sample.
+ differentialI = (iPrevious * iCurrent) - (qPreviousConjugate * qCurrent);
+ differentialQ = (iPrevious * qCurrent) + (iCurrent * qPreviousConjugate);
+
+ decodedPhases[x] = (float)Math.atan2(differentialQ, differentialI);
+ }
+
+ return decodedPhases;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java
new file mode 100644
index 00000000..15a53092
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector128.java
@@ -0,0 +1,108 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import java.util.Arrays;
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DQPSK demodulator that uses Vector SIMD 128 calculations for demodulating the sample stream.
+ */
+public class DQPSKDemodulatorVector128 extends DQPSKDemodulator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector128.class);
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
+
+ /**
+ * Constructor
+ *
+ * @param sampleRate in Hertz
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulatorVector128(double sampleRate, int symbolRate)
+ {
+ super(sampleRate, symbolRate);
+ }
+
+ @Override
+ public float[] demodulate(float[] i, float[] q)
+ {
+ int sampleLength = i.length;
+ int bufferOverlap = mBufferOverlap;
+ float mu = mMu;
+
+ //Copy previous buffer residual samples to beginning of buffer.
+ System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap);
+ System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap);
+
+ //Resize I/Q buffers if necessary
+ int requiredBufferLength = sampleLength + bufferOverlap;
+ if(mIBuffer.length != requiredBufferLength)
+ {
+ if(i.length % VECTOR_SPECIES.length() != 0)
+ {
+ throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length());
+ }
+ mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength);
+ mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength);
+ }
+
+ //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()];
+ float[] decodedPhases = new float[i.length];
+ FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ;
+
+ //Differential demodulation.
+ for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length())
+ {
+ iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x);
+ qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate
+
+ int offset = mInterpolationOffset + x;
+ int index;
+ for(int y = 0; y < VECTOR_SPECIES.length(); y++)
+ {
+ index = offset + y;
+ interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu);
+ interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu);
+ }
+ iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0);
+ qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0);
+
+ //Multiply current complex sample by the complex conjugate of previous complex sample.
+ differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent));
+ differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate));
+
+ //Calculate phase angles using Arc Tangent and export to the decoded phases array.
+ differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x);
+ }
+
+ return decodedPhases;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java
new file mode 100644
index 00000000..a36cb25c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector256.java
@@ -0,0 +1,105 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import java.util.Arrays;
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * DQPSK demodulator that uses Vector SIMD calculations for demodulating the sample stream.
+ */
+public class DQPSKDemodulatorVector256 extends DQPSKDemodulator
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
+
+ /**
+ * Constructor
+ *
+ * @param sampleRate in Hertz
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulatorVector256(double sampleRate, int symbolRate)
+ {
+ super(sampleRate, symbolRate);
+ }
+
+ @Override
+ public float[] demodulate(float[] i, float[] q)
+ {
+ int sampleLength = i.length;
+ int bufferOverlap = mBufferOverlap;
+ float mu = mMu;
+
+ //Copy previous buffer residual samples to beginning of buffer.
+ System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap);
+ System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap);
+
+ //Resize I/Q buffers if necessary
+ int requiredBufferLength = sampleLength + bufferOverlap;
+ if(mIBuffer.length != requiredBufferLength)
+ {
+ if(i.length % VECTOR_SPECIES.length() != 0)
+ {
+ throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length());
+ }
+ mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength);
+ mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength);
+ }
+
+ //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()];
+ float[] decodedPhases = new float[sampleLength];
+ FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ;
+
+ //Differential demodulation.
+ for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length())
+ {
+ iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x);
+ qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate
+
+ int offset = mInterpolationOffset + x;
+ int index;
+ for(int y = 0; y < VECTOR_SPECIES.length(); y++)
+ {
+ index = offset + y;
+ interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu);
+ interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu);
+ }
+ iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0);
+ qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0);
+
+ //Multiply current complex sample by the complex conjugate of previous complex sample.
+ differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent));
+ differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate));
+
+ //Calculate phase angles using Arc Tangent and export to the decoded phases array.
+ differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x);
+ }
+
+ return decodedPhases;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java
new file mode 100644
index 00000000..b1ab9698
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector512.java
@@ -0,0 +1,106 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import java.util.Arrays;
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DQPSK demodulator that uses Vector SIMD 512 calculations for demodulating the sample stream.
+ */
+public class DQPSKDemodulatorVector512 extends DQPSKDemodulator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector512.class);
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
+
+ /**
+ * Constructor
+ *
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulatorVector512(double sampleRate, int symbolRate)
+ {
+ super(sampleRate, symbolRate);
+ }
+
+ @Override
+ public float[] demodulate(float[] i, float[] q)
+ {
+ int sampleLength = i.length;
+ int bufferOverlap = mBufferOverlap;
+ float mu = mMu;
+
+ //Copy previous buffer residual samples to beginning of buffer.
+ System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap);
+ System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap);
+
+ //Resize I/Q buffers if necessary
+ int requiredBufferLength = sampleLength + bufferOverlap;
+ if(mIBuffer.length != requiredBufferLength)
+ {
+ if(i.length % VECTOR_SPECIES.length() != 0)
+ {
+ throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length());
+ }
+ mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength);
+ mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength);
+ }
+
+ //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);
+
+ float[] interpolatedI = new float[VECTOR_SPECIES.length()];
+ float[] interpolatedQ = new float[VECTOR_SPECIES.length()];
+ float[] decodedPhases = new float[sampleLength];
+ FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ;
+
+ //Differential demodulation.
+ for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length())
+ {
+ iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x);
+ qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate
+
+ int offset = mInterpolationOffset + x;
+ int index;
+ for(int y = 0; y < VECTOR_SPECIES.length(); y++)
+ {
+ index = offset + y;
+ interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu);
+ interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu);
+ }
+ iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0);
+ qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0);
+
+ //Multiply current complex sample by the complex conjugate of previous complex sample.
+ differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent));
+ differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate));
+
+ //Calculate phase angles using Arc Tangent and export to the decoded phases array.
+ differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x);
+ }
+
+ return decodedPhases;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java
new file mode 100644
index 00000000..63d09676
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/dsp/psk/dqpsk/DQPSKDemodulatorVector64.java
@@ -0,0 +1,108 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import java.util.Arrays;
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DQPSK demodulator that uses Vector SIMD 64 calculations for demodulating the sample stream.
+ */
+public class DQPSKDemodulatorVector64 extends DQPSKDemodulator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(DQPSKDemodulatorVector64.class);
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
+
+ /**
+ * Constructor
+ *
+ * @param sampleRate in Hertz
+ * @param symbolRate symbols per second
+ */
+ public DQPSKDemodulatorVector64(double sampleRate, int symbolRate)
+ {
+ super(sampleRate, symbolRate);
+ }
+
+ @Override
+ public float[] demodulate(float[] i, float[] q)
+ {
+ int sampleLength = i.length;
+ int bufferOverlap = mBufferOverlap;
+ float mu = mMu;
+
+ //Copy previous buffer residual samples to beginning of buffer.
+ System.arraycopy(mIBuffer, mIBuffer.length - bufferOverlap, mIBuffer, 0, bufferOverlap);
+ System.arraycopy(mQBuffer, mQBuffer.length - bufferOverlap, mQBuffer, 0, bufferOverlap);
+
+ //Resize I/Q buffers if necessary
+ int requiredBufferLength = sampleLength + bufferOverlap;
+ if(mIBuffer.length != requiredBufferLength)
+ {
+ if(i.length % VECTOR_SPECIES.length() != 0)
+ {
+ throw new IllegalArgumentException("Buffer length must be a multiple of " + VECTOR_SPECIES.length());
+ }
+ mIBuffer = Arrays.copyOf(mIBuffer, requiredBufferLength);
+ mQBuffer = Arrays.copyOf(mQBuffer, requiredBufferLength);
+ }
+
+ //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()];
+ float[] decodedPhases = new float[i.length];
+ FloatVector iPrevious, qPreviousConjugate, iCurrent, qCurrent, differentialI, differentialQ;
+
+ //Differential demodulation.
+ for(int x = 0; x < sampleLength; x += VECTOR_SPECIES.length())
+ {
+ iPrevious = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, x);
+ qPreviousConjugate = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x).neg(); //Complex Conjugate
+
+ int offset = mInterpolationOffset + x;
+ int index;
+ for(int y = 0; y < VECTOR_SPECIES.length(); y++)
+ {
+ index = offset + y;
+ interpolatedI[y] = mInterpolator.filter(mIBuffer, index, mu);
+ interpolatedQ[y] = mInterpolator.filter(mQBuffer, index, mu);
+ }
+ iCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedI, 0);
+ qCurrent = FloatVector.fromArray(VECTOR_SPECIES, interpolatedQ, 0);
+
+ //Multiply current complex sample by the complex conjugate of previous complex sample.
+ differentialI = iPrevious.mul(iCurrent).sub(qPreviousConjugate.mul(qCurrent));
+ differentialQ = iPrevious.mul(qCurrent).add(iCurrent.mul(qPreviousConjugate));
+
+ //Calculate phase angles using Arc Tangent and export to the decoded phases array.
+ differentialQ.lanewise(VectorOperators.ATAN2, differentialI).intoArray(decodedPhases, x);
+ }
+
+ return decodedPhases;
+ }
+}
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 8c13b300..78efb2ff 100644
--- a/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java
+++ b/src/main/java/io/github/dsheirer/dsp/symbol/Dibit.java
@@ -1,47 +1,61 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2020 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * 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.dsp.symbol;
public enum Dibit
{
- D01_PLUS_3(false, true, 1, 4, 1),
- D00_PLUS_1(false, false, 0, 0, 0),
- D10_MINUS_1(true, false, 2, 8, 2),
- D11_MINUS_3(true, true, 3, 12, 3);
+ 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)));
private boolean mBit1;
private boolean mBit2;
private int mLowValue;
private int mHighValue;
private int mValue;
+ private float mPhase;
+ private float mIdealI;
+ private float mIdealQ;
- private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value)
+ /**
+ * Dibit constructor.
+ * @param bit1 boolean value
+ * @param bit2 boolean value
+ * @param lowValue integer value of the low order bit
+ * @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)
{
mBit1 = bit1;
mBit2 = bit2;
mLowValue = lowValue;
mHighValue = highValue;
mValue = value;
+ mPhase = phase;
+ mIdealI = idealI;
+ mIdealQ = idealQ;
}
/**
@@ -52,6 +66,33 @@ public enum Dibit
return mValue;
}
+ /**
+ * Ideal phase angle when this symbol is transmitted.
+ * @return ideal phase angle in radians.
+ */
+ public float getIdealPhase()
+ {
+ 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/edac/BPTC_17_12_3.java b/src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java
deleted file mode 100644
index c405a3c6..00000000
--- a/src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package io.github.dsheirer.edac;
-
-import io.github.dsheirer.bits.BinaryMessage;
-import io.github.dsheirer.bits.CorrectedBinaryMessage;
-
-/*
- Adopted from https://github.com/boatbod/op25/blob/e5e8fe457240fb358a8163bcf6fe7dda5e4d8392/op25/gr-op25_repeater/lib/hamming.cc
- */
-public class BPTC_17_12_3 {
-
- public static boolean decode17123(BinaryMessage d, int offset)
- {
- if(d==null) {
- return false;
- }
-
- // Calculate the checksum this column should have
- boolean c0 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(6 + offset) ^ d.get(7 + offset) ^ d.get(9 + offset);
- boolean c1 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(7 + offset) ^ d.get(8 + offset) ^ d.get(10 + offset);
- boolean c2 = d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(8 + offset) ^ d.get(9 + offset) ^ d.get(11 + offset);
- boolean c3 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(7 + offset) ^ d.get(10 + offset);
- boolean c4 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(5 + offset) ^ d.get(6 + offset) ^ d.get(8 + offset) ^ d.get(11 + offset);
-
- // Compare these with the actual bits
- byte n = 0x00;
- n |= (c0 != d.get(12 + offset)) ? 0x01 : 0x00;
- n |= (c1 != d.get(13 + offset)) ? 0x02 : 0x00;
- n |= (c2 != d.get(14 + offset)) ? 0x04 : 0x00;
- n |= (c3 != d.get(15 + offset)) ? 0x08 : 0x00;
- n |= (c4 != d.get(16 + offset)) ? 0x10 : 0x00;
-
- switch (n) {
- // Parity bit errors
- case 0x01: d.set(12 + offset, !d.get(12 + offset)); return true;
- case 0x02: d.set(13 + offset, !d.get(13 + offset)); return true;
- case 0x04: d.set(14 + offset, !d.get(14 + offset)); return true;
- case 0x08: d.set(15 + offset, !d.get(15 + offset)); return true;
- case 0x10: d.set(16 + offset, !d.get(16 + offset)); return true;
-
- // Data bit errors
- case 0x1B: d.set(0 + offset, !d.get(0 + offset)); return true;
- case 0x1F: d.set(1 + offset, !d.get(1 + offset)); return true;
- case 0x17: d.set(2 + offset, !d.get(2 + offset)); return true;
- case 0x07: d.set(3 + offset, !d.get(3 + offset)); return true;
- case 0x0E: d.set(4 + offset, !d.get(4 + offset)); return true;
- case 0x1C: d.set(5 + offset, !d.get(5 + offset)); return true;
- case 0x11: d.set(6 + offset, !d.get(6 + offset)); return true;
- case 0x0B: d.set(7 + offset, !d.get(7 + offset)); return true;
- case 0x16: d.set(8 + offset, !d.get(8 + offset)); return true;
- case 0x05: d.set(9 + offset, !d.get(9 + offset)); return true;
- case 0x0A: d.set(10 + offset,!d.get(10 + offset)); return true;
- case 0x14: d.set(11 + offset,!d.get(11 + offset)); return true;
-
- // No bit errors
- case 0x00: return true;
-
- // Unrecoverable errors
- default: return false;
- }
- }
-
-}
diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java b/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java
deleted file mode 100644
index 0d5ac81b..00000000
--- a/src/main/java/io/github/dsheirer/edac/BPTC_196_96.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-
-package io.github.dsheirer.edac;
-
-import io.github.dsheirer.bits.BinaryMessage;
-import io.github.dsheirer.bits.BitSetFullException;
-import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Block Product Turbo Code (BPTC) 196/96 Support
- */
-public class BPTC_196_96
-{
- public static final int BPTC_LENGTH = 196;
- public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99
- public static final int MAX_ORIGINAL_INDEX = 136;
- public static final int COLUMN_COUNT = 15;
- public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
- public static final int CHECKSUM_COLUMN_COUNT = 4;
- public static final int MESSAGE_START_INDEX = 4;
-
- /**
- * DMR De-interleaving indices
- */
- public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182,
- 167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3,
- 184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20,
- 5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37,
- 22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54,
- 39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71,
- 56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103,
- 88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120,
- 105, 90, 75, 60, 45, 30, 15};
-
- /**
- * DMR Column Indices
- */
- public static int[][] COLUMN_INDEXES = new int[][]{
- {1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181},
- {2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182},
- {3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183},
- {4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184},
- {5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185},
- {6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186},
- {7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187},
- {8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188},
- {9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189},
- {10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190},
- {11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191},
- {12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192},
- {13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193},
- {14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194},
- {15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}};
-
- /**
- * Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96)
- * protected message
- * @param original uncorrected BPTC(196,96) message
- * @return error corrected and extracted 96-bit message
- */
- public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original)
- {
- //De-Interleave
- CorrectedBinaryMessage message = deinterleave(original);
-
- //Perform error detection and correction
- correct(message);
-
- //Extract the 96-bit message from the corrected 196 bits
- CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH);
-
- //Transfer the corrected bit count
- extracted.setCorrectedBitCount(message.getCorrectedBitCount());
-
- int index = MESSAGE_START_INDEX;
-
- while(index < MAX_ORIGINAL_INDEX)
- {
- if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT)
- {
- try
- {
- extracted.add(message.get(index));
- }
- catch(BitSetFullException bsfe)
- {
- //Should never get to here
- bsfe.printStackTrace();
- return extracted;
- }
-
- index++;
- }
- else
- {
- index += CHECKSUM_COLUMN_COUNT;
- }
- }
-
- //Transfer bits R2, R1, and R0 to the end - RAS bits
- extracted.set(96, message.get(0));
- extracted.set(97, message.get(1));
- extracted.set(98, message.get(2));
-
- return extracted;
- }
-
- /**
- * De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message
- * @param message that contains interleaved (transmitted) message
- * @return de-interleaved message
- */
- private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message)
- {
- CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH);
- for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++)
- {
- deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x]));
- }
-
- return deinterleaved;
- }
-
- /**
- * Identifies the error indices from the row level Hamming(15,11,3) protected words
- * @param message containing protected words in rows
- * @return list of zero or more error indices
- */
- private static List getRowErrors(CorrectedBinaryMessage message)
- {
- List rowErrors = new ArrayList<>();
-
- int offset = 0;
- int index = -1;
-
- for(int row = 0; row < 13; row++)
- {
- //The first bit of the message is a pad/reserved bit and is ignored.
- offset = (row * 15) + 1;
- index = Hamming15.getErrorIndex(message, offset);
-
- if(index >= 0)
- {
- rowErrors.add(index);
- }
- }
-
- //Note: row errors should already be in order and don't need sorted
- return rowErrors;
- }
-
- /**
- * Identifies the error indices from the column level Hamming(13,9,3) protected words
- * @param message containing protected words in columns
- * @return list of zero or more error indices
- */
- private static List getColumnErrors(CorrectedBinaryMessage message)
- {
- List columnErrors = new ArrayList<>();
-
- int index = -1;
-
- for(int column = 0; column < 15; column++)
- {
- index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
-
- if(index >= 0)
- {
- columnErrors.add(index);
- }
- }
-
- Collections.sort(columnErrors);
- return columnErrors;
- }
-
- /**
- * Performs error detection and correction on the DMR BPTC(196,96) protected message
- *
- * @param message to process and extract
- */
- private static void correct(CorrectedBinaryMessage message)
- {
- List rowErrors = getRowErrors(message);
-
- if(!rowErrors.isEmpty())
- {
- List columnErrors = getColumnErrors(message);
-
- if(matches(rowErrors, columnErrors))
- {
- for(Integer index: rowErrors)
- {
- message.flip(index);
- }
-
- message.setCorrectedBitCount(rowErrors.size());
- }
- else
- {
- //Step 1: correct row errors and recalculate column errors (should be zero)
- for(Integer index: rowErrors)
- {
- message.flip(index);
- }
-
- List columnErrorsPart2 = getColumnErrors(message);
-
- if(columnErrorsPart2.isEmpty())
- {
- //Success
- message.setCorrectedBitCount(rowErrors.size());
- }
- else
- {
- //Reverse the row error correction
- for(Integer index: rowErrors)
- {
- message.flip(index);
- }
-
- //Step 2: correct column errors and recalculate row errors (should be zero)
- for(Integer index: columnErrors)
- {
- message.flip(index);
- }
-
- List rowErrorsPart2 = getRowErrors(message);
-
- if(rowErrorsPart2.isEmpty())
- {
- //Success
- message.setCorrectedBitCount(columnErrors.size());
- }
- else
- {
- //Reverse the column error correction
- for(Integer index: columnErrors)
- {
- message.flip(index);
- }
-
- //Set the error count greater than the maximum correctable column errors (16)
- message.setCorrectedBitCount(16);
- }
- }
- }
- }
- }
-
- /**
- * Indicates if the errors detected by the row protected words match the errors detected by the column protected words
- * @param rowErrors bit positions
- * @param columnErrors bit positions
- * @return true if they have the same quantity of bit position errors and the bit positions match
- */
- private static boolean matches(List rowErrors, List columnErrors)
- {
- if(rowErrors.size() == columnErrors.size())
- {
- for(int x = 0; x < rowErrors.size(); x++)
- {
- if(!rowErrors.get(x).equals(columnErrors.get(0)))
- {
- return false;
- }
- }
-
- return true;
- }
- else
- {
- return false;
- }
- }
-
- public static void main(String[] args)
- {
- String raw = "0000000001100010100101100011100100110100110101000100000011101001110001001000100001001010010100001000011111011000000011110000111000111111100011000011100001100000000101001010000100010111010100111001";
- System.out.println("RAW:" + raw);
-
- CorrectedBinaryMessage message = new CorrectedBinaryMessage(BinaryMessage.load(raw));
- System.out.println("LOD:" + message.toString());
- CorrectedBinaryMessage extracted = extract(message);
- System.out.println("EXT:" + extracted.toString());
- }
-}
diff --git a/src/main/java/io/github/dsheirer/edac/Golay24.java b/src/main/java/io/github/dsheirer/edac/Golay24.java
index b6735d96..0453a591 100644
--- a/src/main/java/io/github/dsheirer/edac/Golay24.java
+++ b/src/main/java/io/github/dsheirer/edac/Golay24.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/io/github/dsheirer/edac/Hamming13.java b/src/main/java/io/github/dsheirer/edac/Hamming13.java
index bb2a1f78..c0b4e1a6 100644
--- a/src/main/java/io/github/dsheirer/edac/Hamming13.java
+++ b/src/main/java/io/github/dsheirer/edac/Hamming13.java
@@ -1,6 +1,26 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Implements Hamming(13,9,3) Error Detection algorithm
@@ -8,8 +28,8 @@ import io.github.dsheirer.bits.BinaryMessage;
public class Hamming13
{
//DMR Checksums from generator matrix TS 102 361-1 Table B.14
- private static int[] CHECKSUMS = new int[]{0xF, 0xE, 0x7, 0xA, 0x5, 0XB, 0xC, 0x6, 0x3};
- private static int[] ERROR_INDEX = new int[]{-1, 12, 11, 8, 10, 4, 7, 2, 9, -1, 3, 5, 6, -1, 1, 0, -1};
+ private static int[] CHECKSUMS = new int[]{0xF, 0xE, 0x7, 0xA, 0x5, 0xB, 0xC, 0x6, 0x3, 0x8, 0x4, 0x2, 0x1};
+ private static int[] ERROR_INDEX = new int[]{-1, 12, 11, 8, 10, 4, 7, 2, 9, -1, 3, 5, 6, -1, 1, 0, -1, -1};
/**
* Calculates the bit error index of the Hamming(13,9,3) protected word that is contained in the binary message
@@ -28,7 +48,7 @@ public class Hamming13
return offset + ERROR_INDEX[syndrome];
}
- return -1;
+ return IHamming.NO_ERRORS;
}
/**
@@ -53,11 +73,11 @@ public class Hamming13
}
else
{
- return 10000 + errorIndex;
+ return 1000;
}
}
- return -1;
+ return IHamming.NO_ERRORS;
}
/**
@@ -95,7 +115,7 @@ public class Hamming13
return calculated;
}
- private static int getSyndrome(BinaryMessage message, int[] indices)
+ public static int getSyndrome(BinaryMessage message, int[] indices)
{
int calculated = calculateChecksum(message, indices);
@@ -126,4 +146,16 @@ public class Hamming13
int checksum = message.getInt(offset + 9, offset + 12);
return (checksum ^ calculated);
}
+
+ public static void main(String[] args)
+ {
+ String transmitted = "0000010000001";
+ String correct = "0000000000000";
+ String recommended = "0001000000001";
+// CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(transmitted));
+ CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(correct));
+// CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.load(recommended));
+ int syndrome = getSyndrome(cbm, 0);
+ System.out.println("Syndrome " + syndrome);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/edac/Hamming15.java b/src/main/java/io/github/dsheirer/edac/Hamming15.java
index f498bb72..e11e9acc 100644
--- a/src/main/java/io/github/dsheirer/edac/Hamming15.java
+++ b/src/main/java/io/github/dsheirer/edac/Hamming15.java
@@ -1,3 +1,22 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
@@ -8,7 +27,7 @@ import io.github.dsheirer.bits.BinaryMessage;
public class Hamming15
{
//DMR Checksums from generator matrix TS 102 361-1 Table B.15
- private static int[] CHECKSUMS = new int[]{0x9, 0xD, 0xF, 0xE, 0x7, 0XA, 0x5, 0xB, 0xC, 0x6, 0x3};
+ private static int[] CHECKSUMS = new int[]{0x9, 0xD, 0xF, 0xE, 0x7, 0xA, 0x5, 0xB, 0xC, 0x6, 0x3, 0x8, 0x4, 0x2, 0x1};
private static int[] ERROR_INDEX = new int[]{-1, 14, 13, 10, 12, 6, 9, 4, 11, 0, 5, 7, 8, 1, 3, 2};
/**
@@ -28,7 +47,7 @@ public class Hamming15
return offset + ERROR_INDEX[syndrome];
}
- return -1;
+ return IHamming.NO_ERRORS;
}
/**
@@ -58,7 +77,7 @@ public class Hamming15
* @param offset to bit 0 of the hamming protected word
* @return syndrome that can be used with the ERROR_INDEX error to find the index of the bit position error
*/
- private static int getSyndrome(BinaryMessage message, int offset)
+ public static int getSyndrome(BinaryMessage message, int offset)
{
int calculated = calculateChecksum(message, offset);
int checksum = message.getInt(offset + 11, offset + 14);
diff --git a/src/main/java/io/github/dsheirer/edac/Hamming16.java b/src/main/java/io/github/dsheirer/edac/Hamming16.java
index 951c4539..7436fb5c 100644
--- a/src/main/java/io/github/dsheirer/edac/Hamming16.java
+++ b/src/main/java/io/github/dsheirer/edac/Hamming16.java
@@ -1,13 +1,63 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
/**
- * Hamming(16,11,5) Error detection and correction utility.
+ * Hamming(16,11,4) Error detection and correction utility. This code is capable of correcting single-bit errors and
+ * detecting (but not correcting) double-bit errors.
*/
-public class Hamming16
+public class Hamming16 implements IHamming
{
- private static int[] CHECKSUMS = new int[]{0x13, 0x1A, 0x1F, 0x1C, 0x0E, 0x15, 0x0B, 0x16, 0x19, 0x0D, 0x07};
+ private static int[] CHECKSUMS = new int[]{0x13, 0x1A, 0x1F, 0x1C, 0x0E, 0x15, 0x0B, 0x16, 0x19, 0x0D, 0x07, 0x10,
+ 0x08, 0x04, 0x02, 0x01};
+
+ /**
+ * Calculates the bit error index of the Hamming(16,11,4) protected word that is contained in the binary message
+ * starting at the specified offset.
+ *
+ * @param message containing a Hamming protected word
+ * @param offset to the start of the protected word
+ * @return message index for an error bit or -1 if no errors are detected or 1000 if two bit errors are detected.
+ */
+ public int getErrorIndex(BinaryMessage message, int offset)
+ {
+ int syndrome = getSyndrome(message, offset);
+
+ if(syndrome == 0)
+ {
+ return NO_ERRORS;
+ }
+
+ for(int index = 0; index < CHECKSUMS.length; index++)
+ {
+ if(syndrome == CHECKSUMS[index])
+ {
+ return index + offset;
+ }
+ }
+
+ return MULTIPLE_ERRORS;
+ }
+
/**
* Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED)
@@ -22,78 +72,36 @@ public class Hamming16
{
int syndrome = getSyndrome(frame, startIndex);
- switch(syndrome)
+ if (syndrome == 0)
{
- case 0:
- return 0;
- case 1:
- frame.flip(startIndex + 15); //Parity 1
- return 1;
- case 2:
- frame.flip(startIndex + 14); //Parity 2
- return 1;
- case 3:
- frame.flip(startIndex + 10); //Data 1
- return 1;
- case 4:
- frame.flip(startIndex + 13); //Parity 4
- return 1;
- case 5:
- frame.flip(startIndex + 9); //Data 2
- return 1;
- case 6:
- frame.flip(startIndex + 8); //Data 3
- return 1;
- case 7:
- frame.flip(startIndex + 7); //Data 4
- return 1;
- case 8:
- frame.flip(startIndex + 12); //Parity 8
- return 1;
- case 9:
- frame.flip(startIndex + 6); //Data 5
- return 1;
- case 10:
- frame.flip(startIndex + 5); //Data 6
- return 1;
- case 11:
- frame.flip(startIndex + 4); //Data 7
- return 1;
- case 12:
- frame.flip(startIndex + 3); //Data 8
- return 1;
- case 13:
- frame.flip(startIndex + 2); //Data 9
- return 1;
- case 14:
- frame.flip(startIndex + 1); //Data 10
- return 1;
- case 15:
- frame.flip(startIndex); //Data 11
- return 1;
- case 16:
- frame.flip(startIndex + 11); //Parity 16
- return 1;
- default:
- return 2;
+ return 0;
}
+
+ for(int index = 0; index < CHECKSUMS.length; index++)
+ {
+ if(CHECKSUMS[index] == syndrome)
+ {
+ frame.flip(startIndex + index);
+ return 1;
+ }
+ }
+
+ return 2;
}
/**
- * Calculates the checksum (Parity 16,8,4,2,1) for data (11 <> 1 ) bits.
+ * Calculates the checksum from the transmitted data bits.
*
* @param frame - frame containing hamming protected word
* @param startIndex - start bit index of the hamming protected word
- * @return parity value, 0 - 16
+ * @return calculated checksum value
*/
private static int calculateChecksum(BinaryMessage frame, int startIndex)
{
int calculated = 0; //Starting value
/* Iterate the set bits and XOR running checksum with lookup value */
- for(int i = frame.nextSetBit(startIndex);
- i >= startIndex && i < startIndex + 11;
- i = frame.nextSetBit(i + 1))
+ for(int i = frame.nextSetBit(startIndex); i >= startIndex && i < startIndex + 11; i = frame.nextSetBit(i + 1))
{
calculated ^= CHECKSUMS[i - startIndex];
}
@@ -109,12 +117,10 @@ public class Hamming16
* @param startIndex - of bit 0 of the hamming protected word
* @return - 0 (no errors) or 1 (single bit error corrected)
*/
- private static int getSyndrome(BinaryMessage frame, int startIndex)
+ public static int getSyndrome(BinaryMessage frame, int startIndex)
{
int calculated = calculateChecksum(frame, startIndex);
-
int checksum = frame.getInt(startIndex + 11, startIndex + 15);
-
return (checksum ^ calculated);
}
}
diff --git a/src/main/java/io/github/dsheirer/edac/Hamming17.java b/src/main/java/io/github/dsheirer/edac/Hamming17.java
index 86b17997..3c94ff85 100644
--- a/src/main/java/io/github/dsheirer/edac/Hamming17.java
+++ b/src/main/java/io/github/dsheirer/edac/Hamming17.java
@@ -1,3 +1,22 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
@@ -5,81 +24,37 @@ import io.github.dsheirer.bits.BinaryMessage;
/**
* Hamming(17,12,3) Error detection and correction utility.
*/
-public class Hamming17
+public class Hamming17 implements IHamming
{
- private static int[] CHECKSUMS = new int[]{0x1B, 0x1F, 0x1D, 0x1C, 0x0E, 0x07, 0x11, 0x1A, 0x0D, 0x14, 0x0A, 0x05};
+ private static int[] CHECKSUMS = new int[]{0x1B, 0x1F, 0x1D, 0x1C, 0x0E, 0x07, 0x11, 0x1A, 0x0D, 0x14, 0x0A, 0x05,
+ 0x10, 0x08, 0x04, 0x02, 0x01};
/**
- * Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED)
+ * Calculates the bit error index of the Hamming(17,12,3) protected word that is contained in the binary message
+ * starting at the specified offset.
*
- * @param frame - binary frame containing a hamming(17,12,5) protected field
- * @param startIndex - offset to the first bit of the field
- * @return - 0 = no errors
- * 1 = a single-bit error was detected and corrected
- * 2 = two or more errors detected - no corrections made
+ * @param message containing a Hamming protected word
+ * @param offset to the start of the protected word
+ * @return message index for an error bit or -1 if no errors are detected or 1000 if more than one bit error detected.
*/
- public static int checkAndCorrect(BinaryMessage frame, int startIndex)
+ public int getErrorIndex(BinaryMessage message, int offset)
{
- int syndrome = getSyndrome(frame, startIndex);
+ int syndrome = getSyndrome(message, offset);
- switch(syndrome)
+ if(syndrome == 0)
{
- case 0:
- return 0;
- case 1:
- frame.flip(startIndex + 16); //Parity 1
- return 1;
- case 2:
- frame.flip(startIndex + 15); //Parity 2
- return 1;
- case 3:
- frame.flip(startIndex + 11); //Data 1
- return 1;
- case 4:
- frame.flip(startIndex + 14); //Parity 4
- return 1;
- case 5:
- frame.flip(startIndex + 10); //Data 2
- return 1;
- case 6:
- frame.flip(startIndex + 9); //Data 3
- return 1;
- case 7:
- frame.flip(startIndex + 8); //Data 4
- return 1;
- case 8:
- frame.flip(startIndex + 13); //Parity 8
- return 1;
- case 9:
- frame.flip(startIndex + 7); //Data 5
- return 1;
- case 10:
- frame.flip(startIndex + 6); //Data 6
- return 1;
- case 11:
- frame.flip(startIndex + 5); //Data 7
- return 1;
- case 12:
- frame.flip(startIndex + 4); //Data 8
- return 1;
- case 13:
- frame.flip(startIndex + 3); //Data 9
- return 1;
- case 14:
- frame.flip(startIndex + 2); //Data 10
- return 1;
- case 15:
- frame.flip(startIndex + 1); //Data 11
- return 1;
- case 16:
- frame.flip(startIndex + 12); //Parity 16
- return 1;
- case 17:
- frame.flip(startIndex); //Data 12
- return 1;
- default:
- return 2;
+ return NO_ERRORS;
}
+
+ for(int index = 0; index < CHECKSUMS.length; index++)
+ {
+ if(syndrome == CHECKSUMS[index])
+ {
+ return index + offset;
+ }
+ }
+
+ return MULTIPLE_ERRORS;
}
/**
@@ -87,16 +62,14 @@ public class Hamming17
*
* @param frame - frame containing hamming protected word
* @param startIndex - start bit index of the hamming protected word
- * @return parity value, 0 - 16
+ * @return checksum value
*/
private static int calculateChecksum(BinaryMessage frame, int startIndex)
{
int calculated = 0; //Starting value
/* Iterate the set bits and XOR running checksum with lookup value */
- for(int i = frame.nextSetBit(startIndex);
- i >= startIndex && i < startIndex + 12;
- i = frame.nextSetBit(i + 1))
+ for(int i = frame.nextSetBit(startIndex); i >= startIndex && i < startIndex + 12; i = frame.nextSetBit(i + 1))
{
calculated ^= CHECKSUMS[i - startIndex];
}
@@ -112,12 +85,10 @@ public class Hamming17
* @param startIndex - of bit 0 of the hamming protected word
* @return - 0 (no errors) or 1 (single bit error corrected)
*/
- private static int getSyndrome(BinaryMessage frame, int startIndex)
+ public static int getSyndrome(BinaryMessage frame, int startIndex)
{
int calculated = calculateChecksum(frame, startIndex);
-
int checksum = frame.getInt(startIndex + 12, startIndex + 16);
-
return (checksum ^ calculated);
}
}
diff --git a/src/main/java/io/github/dsheirer/edac/IHamming.java b/src/main/java/io/github/dsheirer/edac/IHamming.java
new file mode 100644
index 00000000..199cc3e2
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/edac/IHamming.java
@@ -0,0 +1,39 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.edac;
+
+import io.github.dsheirer.bits.BinaryMessage;
+
+/**
+ * Interface for Hamming error index calculator.
+ */
+public interface IHamming
+{
+ int NO_ERRORS = -1;
+ int MULTIPLE_ERRORS = 1000;
+
+ /**
+ * Calculate the error index for the Hamming protected binary sequence
+ * @param message containing the binary sequence
+ * @param offset to the start of the binary sequence
+ * @return -1 if no errors, single-bit error index, or 1000 if there is more than a single-bit error detected.
+ */
+ int getErrorIndex(BinaryMessage message, int offset);
+}
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java
deleted file mode 100644
index 6e704215..00000000
--- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DMRPane.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-package io.github.dsheirer.gui.instrument.decoder;
-
-import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
-import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
-import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
-import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
-import io.github.dsheirer.gui.instrument.chart.SymbolChart;
-import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented;
-import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
-import io.github.dsheirer.sample.Broadcaster;
-import io.github.dsheirer.sample.complex.ComplexSamples;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Priority;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Iterator;
-
-public class DMRPane extends ComplexDecoderPane
-{
- private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class);
-
- private HBox mSampleChartBox;
- private ComplexSampleLineChart mSampleLineChart;
- private EyeDiagramChart mEyeDiagramChart;
- private HBox mDecoderChartBox;
- private SymbolChart mSymbolChart;
- private PhaseLineChart mPLLPhaseErrorLineChart;
- private DoubleLineChart mPLLFrequencyLineChart;
- private DoubleLineChart mSamplesPerSymbolLineChart;
- private Broadcaster mFilteredBufferBroadcaster = new Broadcaster();
- private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR());
-
- public DMRPane()
- {
- super(DecoderType.P25_PHASE1);
- init();
- }
-
- private void init()
- {
- addListener(nativeBuffer ->
- {
- Iterator iterator = nativeBuffer.iterator();
-
- while(iterator.hasNext())
- {
- getDecoder().receive(iterator.next());
- }
- });
-
- getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
- getDecoder().setComplexSymbolListener(getSymbolChart());
- getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
- getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
- getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
- getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
- mFilteredBufferBroadcaster.addListener(getSampleLineChart());
-
- HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
- HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
- getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
-
- mDecoder.setMessageListener(message -> mLog.debug(message.toString()));
-
- }
-
- @Override
- public void setSampleRate(double sampleRate)
- {
- mLog.debug("Configuring for sample rate: " + sampleRate);
-
- mDecoder.setSampleRate(sampleRate);
- double samplesPerSymbol = sampleRate / 4800.0;
-
- getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
- }
-
- private DMRDecoderInstrumented getDecoder()
- {
- return mDecoder;
- }
-
- private SymbolChart getSymbolChart()
- {
- if(mSymbolChart == null)
- {
- mSymbolChart = new SymbolChart(10);
- }
-
- return mSymbolChart;
- }
-
- private HBox getDecoderChartBox()
- {
- if(mDecoderChartBox == null)
- {
- mDecoderChartBox = new HBox();
- mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
- getSymbolChart().setMaxWidth(Double.MAX_VALUE);
- getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
- getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
- HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
- HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
- HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
- mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
- getPLLFrequencyLineChart());
- }
-
- return mDecoderChartBox;
- }
-
- private HBox getSampleChartBox()
- {
- if(mSampleChartBox == null)
- {
- mSampleChartBox = new HBox();
- mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
- getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
- getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
- getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
- HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
- HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
- HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
- mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
- getSamplesPerSymbolLineChart());
- }
-
- return mSampleChartBox;
- }
-
- private ComplexSampleLineChart getSampleLineChart()
- {
- if(mSampleLineChart == null)
- {
- mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
- }
-
- return mSampleLineChart;
- }
-
- private EyeDiagramChart getEyeDiagramChart()
- {
- if(mEyeDiagramChart == null)
- {
- mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
- }
-
- return mEyeDiagramChart;
- }
-
- private PhaseLineChart getPLLPhaseErrorLineChart()
- {
- if(mPLLPhaseErrorLineChart == null)
- {
- mPLLPhaseErrorLineChart = new PhaseLineChart(40);
- }
-
- return mPLLPhaseErrorLineChart;
- }
-
- private DoubleLineChart getPLLFrequencyLineChart()
- {
- if(mPLLFrequencyLineChart == null)
- {
- mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
- }
-
- return mPLLFrequencyLineChart;
- }
-
- private DoubleLineChart getSamplesPerSymbolLineChart()
- {
- if(mSamplesPerSymbolLineChart == null)
- {
- mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
- }
-
- return mSamplesPerSymbolLineChart;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
index b9656872..151b8de7 100644
--- a/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
+++ b/src/main/java/io/github/dsheirer/gui/instrument/decoder/DecoderPaneFactory.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@ package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
-
import java.util.EnumSet;
public class DecoderPaneFactory
@@ -33,8 +32,7 @@ public class DecoderPaneFactory
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
- DecoderType.TAIT_1200,
- DecoderType.DMR);
+ DecoderType.TAIT_1200);
/**
* Creates a decoder pane for the decoder type
@@ -59,8 +57,6 @@ public class DecoderPaneFactory
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
case P25_PHASE2:
return new P25Phase2HDQPSKPane();
- case DMR:
- return new DMRPane();
}
return getDefaultPane();
diff --git a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java
index bfc49965..a3646217 100644
--- a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java
+++ b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java
@@ -20,6 +20,7 @@
package io.github.dsheirer.gui.viewer;
import io.github.dsheirer.message.IMessage;
+import io.github.dsheirer.module.decode.dmr.DMRHardSymbolProcessor;
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
@@ -168,7 +169,9 @@ public class DmrViewer extends VBox
public void run()
{
List messages = new ArrayList<>();
- DMRMessageFramer messageFramer = new DMRMessageFramer(null);
+ DMRMessageFramer messageFramer = new DMRMessageFramer();
+ messageFramer.start();
+ DMRHardSymbolProcessor symbolProcessor = new DMRHardSymbolProcessor(messageFramer);
DecodeConfigDMR config = new DecodeConfigDMR();
config.setUseCompressedTalkgroups(useCompressed);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
@@ -180,7 +183,7 @@ public class DmrViewer extends VBox
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
- messageFramer.receive(buffer);
+ symbolProcessor.receive(buffer);
}
}
catch(Exception ioe)
diff --git a/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java b/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java
new file mode 100644
index 00000000..23e7909b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/message/EmptyTimeslotPlaceholderMessage.java
@@ -0,0 +1,102 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+package io.github.dsheirer.message;
+
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.protocol.Protocol;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Empty message to convey that a number of bits were processed for an empty timeslot.
+ */
+public class EmptyTimeslotPlaceholderMessage extends Message
+{
+ private int mBitsProcessed;
+ private Protocol mProtocol;
+ private int mTimeslot = 0;
+
+ /**
+ * Constructs a sync loss message.
+ *
+ * @param timestamp of the message
+ * @param bitsProcessed without a sync pattern detection of message decode
+ * @param timeslot that lost the sync
+ */
+ public EmptyTimeslotPlaceholderMessage(long timestamp, int bitsProcessed, Protocol protocol, int timeslot)
+ {
+ super(timestamp);
+ mBitsProcessed = bitsProcessed;
+ mProtocol = protocol;
+ mTimeslot = timeslot;
+ }
+
+ /**
+ * Constructs a sync loss message.
+ *
+ * @param timestamp of the message
+ * @param bitsProcessed without a sync pattern detection of message decode
+ */
+ public EmptyTimeslotPlaceholderMessage(long timestamp, int bitsProcessed, Protocol protocol)
+ {
+ this(timestamp, bitsProcessed, protocol, 0);
+ }
+
+ /**
+ * Timeslot that lost the sync
+ * @return timeslot or a default of 0
+ */
+ @Override
+ public int getTimeslot()
+ {
+ return mTimeslot;
+ }
+
+ /**
+ * Indicates the number of bits that were processed without a sync detection or message decode
+ */
+ public int getBitsProcessed()
+ {
+ return mBitsProcessed;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "EMPTY TIMESLOT - BITS PROCESSED [" + getBitsProcessed() + "]";
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ return true;
+ }
+
+ @Override
+ public Protocol getProtocol()
+ {
+ return mProtocol;
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/message/TimeslotMessage.java b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java
index 8da4091d..64a93f2b 100644
--- a/src/main/java/io/github/dsheirer/message/TimeslotMessage.java
+++ b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java
@@ -24,7 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Timeslot message base class.
*/
-public abstract class TimeslotMessage extends AbstractMessage
+public abstract class TimeslotMessage extends AbstractMessage implements IMessage
{
public static final int TIMESLOT_0 = 0;
public static final int TIMESLOT_1 = 1;
diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
index 89a113b6..635ef6b9 100644
--- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java
@@ -479,7 +479,7 @@ public class DecoderFactory
AliasList aliasList, DecodeConfigDMR decodeConfig,
TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor)
{
- modules.add(new DMRDecoder(decodeConfig));
+ modules.add(new DMRDecoder(decodeConfig, channel.isTrafficChannel()));
DMRTrafficChannelManager dmrTrafficChannelManager = null;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java
deleted file mode 100644
index bd7e516c..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-
-package io.github.dsheirer.module.decode.dmr;
-
-import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
-import io.github.dsheirer.dsp.symbol.Dibit;
-import io.github.dsheirer.dsp.symbol.DibitDelayBuffer;
-import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
-import io.github.dsheirer.module.decode.dmr.message.CACH;
-import io.github.dsheirer.sample.Listener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Processes a stream of Dibit symbols and performs burst detection and timeslot framing. This framer
- * also detects abnormal PLL phase locks and issues PLL phase lock corrections.
- *
- * Timeslot Map:
- * 0: Common Association Channel (CACH) and Slow Link Control (SLC)
- * 1. Timeslot 1
- * 2. Timeslot 2
- */
-public class DMRBurstFramer implements Listener
-{
- private final static Logger mLog = LoggerFactory.getLogger(DMRBurstFramer.class);
-
- private static final int DMR_SYMBOL_RATE = 4800;
- private static final double PLL_PHASE_CORRECTION_90_DEGREES = (double)DMR_SYMBOL_RATE / 4.0;
- private static final double PLL_PHASE_CORRECTION_180_DEGREES = (double)DMR_SYMBOL_RATE / 2.0;
-
- private static final int BURST_DIBIT_START_TS1 = 0;
- private static final int BURST_DIBIT_START_TS2 = 144;
- private static final int BURST_DIBIT_LENGTH = 144;
- private static final int TWO_TIMESLOT_DIBIT_LENGTH = BURST_DIBIT_LENGTH * 2;
- private static final int SYNC_DIBIT_OFFSET_TS1 = 66;
- private static final int SYNC_DIBIT_OFFSET_TS2 = 210;
- private static final int SYNC_DIBIT_LENGTH = 24;
- private static final int MAX_STREAMING_SYNC_DETECT_BIT_ERRORS = 4;
- private static final int MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS = 6;
-
- /**
- * Threshold for issuing a sync loss message. This is set to trigger once the dibit count exceeds one second of
- * dibits (4800 baud/dibits) plus two bursts dibit length. The two burst length padding is so that we don't reset
- * the dibit counter below the burst sync check threshold.
- */
- private static final int SYNC_LOSS_MESSAGE_THRESHOLD = DMR_SYMBOL_RATE + TWO_TIMESLOT_DIBIT_LENGTH;
-
- /**
- * The message buffer is sized to hold two DMR bursts of 144 dibits (288 bits) each.
- */
- private DibitDelayBuffer mMessageBuffer = new DibitDelayBuffer(BURST_DIBIT_LENGTH * 2);
-
- /**
- * The sync delay buffer is sized to align sync detection with two bursts being fully loaded into the message and
- * timeslot 1 region of the message buffer is aligned with the sync delay buffer.
- *
- * This delay equals the number of dibits in the second half of the burst payload: 54 dibits (108 bits).
- */
- private DibitDelayBuffer mSyncDelayBuffer = new DibitDelayBuffer(54);
-
- /**
- * Synchronized indicates that either the primary or secondary sync tracker is currently synchronized to
- * one of the timeslot bursts. Once either sync tracker obtains a valid sync pattern, this flag is set
- * to true. The synchronized state will remain true as long as at least one of the two sync trackers
- * maintains sync state. Once both sync trackers lose sync, the framer will fallback into sync search/detection
- * mode to inspect each dibit until a sync match can be (re)discovered.
- */
- private boolean mSynchronized = false;
-
- /**
- * Tracks the number of dibits received to trigger burst or sync loss message processing
- */
- private int mDibitCounter = 0;
-
- /**
- * The Sync detection is used to gain initial lock to a sync pattern or to regain sync if lost. The sync detector
- * is used to align a DMR burst into the timeslot 1 region of the message dibit delay buffer so that it can be
- * inspected to ensure the timeslots are aligned.
- */
- private DMRSyncDetector mSyncDetectorTimeslot1 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
- MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
- private DMRSyncDetector mSyncDetectorTimeslot2 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
- MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
-
- /**
- * Timeslot sync trackers track the synchronization state for each of the two timeslots.
- */
- private SyncTracker mSyncTrackerTimeslot1 = new SyncTracker();
- private SyncTracker mSyncTrackerTimeslot2 = new SyncTracker();
-
- /**
- * Timeslot alignment tracker tracks that the timeslots in the delay buffer are aligned.
- */
- private TimeslotAlignmentTracker mTimeslotAlignmentTracker = new TimeslotAlignmentTracker();
-
- private IDMRBurstDetectListener mBurstDetectListener;
- private IPhaseLockedLoop mPhaseLockedLoop;
-
- /**
- * Constructs an instance
- * @param listener to be notified of framed burst detections and/or sync loss bits processed
- * @param phaseLockedLoop to receive symbol alignment corrections
- */
- public DMRBurstFramer(IDMRBurstDetectListener listener, IPhaseLockedLoop phaseLockedLoop)
- {
- mBurstDetectListener = listener;
- mPhaseLockedLoop = phaseLockedLoop;
- }
-
- /**
- * Primary dibit symbol input method
- * @param dibit to process
- */
- @Override
- public void receive(Dibit dibit)
- {
- mDibitCounter++;
-
- //Feed the message buffer first to ensure buffer contains two full bursts when a sync is detected
- mMessageBuffer.put(dibit);
-
- //Feed the sync delay buffer and reassign the delayed dibit to feed the sync detector if we're not synchronized
- dibit = mSyncDelayBuffer.getAndPut(dibit);
-
- if(mSynchronized)
- {
- if(mDibitCounter >= TWO_TIMESLOT_DIBIT_LENGTH)
- {
- if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
- {
- processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
- }
-
- dispatch();
- }
- }
- else
- {
- mSyncDetectorTimeslot2.add(dibit);
-
- if(mSyncDetectorTimeslot2.hasSync())
- {
- dispatch();
- }
- else if(mDibitCounter >= SYNC_LOSS_MESSAGE_THRESHOLD)
- {
- processSyncLossDibits(DMR_SYMBOL_RATE, 0);
- }
- }
- }
-
- /**
- * Dispatches burst 1 and/or burst 2 that is currently in the message buffer
- */
- private void dispatch()
- {
- if(mBurstDetectListener != null)
- {
- if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
- {
- processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
- }
-
- mSyncDetectorTimeslot1.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS1));
- mSyncDetectorTimeslot2.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS2));
-
- mSyncTrackerTimeslot1.update(mSyncDetectorTimeslot1.getSyncPattern(), mSyncDetectorTimeslot1.getCarrierLock());
- mSyncTrackerTimeslot2.update(mSyncDetectorTimeslot2.getSyncPattern(), mSyncDetectorTimeslot2.getCarrierLock());
-
- CorrectedBinaryMessage burst1 = null;
- CorrectedBinaryMessage burst2 = null;
-
- CACH cach1 = null;
- CACH cach2 = null;
-
- //If we have valid sync but PLL tracker misalignment, correct both timeslots before we extract messages
- if((mSyncTrackerTimeslot1.hasSync() && mSyncTrackerTimeslot1.hasCarrierMisAlignment()) ||
- (mSyncTrackerTimeslot2.hasSync() && mSyncTrackerTimeslot2.hasCarrierMisAlignment()))
- {
-// mLog.debug("Repairing Message Bits - PLL Mis-Alignment Detected");
- if(mSyncTrackerTimeslot1.hasCarrierMisAlignment())
- {
- repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot1.getCarrierLock());
- repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot1.getCarrierLock());
- }
- else
- {
- repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot2.getCarrierLock());
- repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot2.getCarrierLock());
- }
- }
-
- if(mSyncTrackerTimeslot1.hasSync())
- {
- burst1 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS1, BURST_DIBIT_LENGTH);
- burst1.incrementCorrectedBitCount(mSyncDetectorTimeslot1.getPatternMatchBitErrorCount());
-
- if(mSyncTrackerTimeslot1.getSyncPattern().hasCACH())
- {
- cach1 = CACH.getCACH(burst1);
- }
- }
-
- if(mSyncTrackerTimeslot2.hasSync())
- {
- burst2 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS2, BURST_DIBIT_LENGTH);
- burst2.incrementCorrectedBitCount(mSyncDetectorTimeslot2.getPatternMatchBitErrorCount());
-
- if(mSyncTrackerTimeslot2.getSyncPattern().hasCACH())
- {
- cach2 = CACH.getCACH(burst2);
- }
- }
-
- //Update the timeslot alignment tracker using the sync patterns and optional CACHes from each timeslot
- mTimeslotAlignmentTracker.update(cach1, cach2, mSyncDetectorTimeslot1.getSyncPattern(),
- mSyncDetectorTimeslot2.getSyncPattern());
-
- if(mTimeslotAlignmentTracker.hasSufficientData())
- {
- if(mTimeslotAlignmentTracker.isAligned())
- {
- if(burst1 != null)
- {
- mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 1);
- mDibitCounter -= BURST_DIBIT_LENGTH;
- }
- else
- {
- processSyncLossDibits(BURST_DIBIT_LENGTH, 1);
- }
-
- if(burst2 != null)
- {
- mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 2);
- mDibitCounter -= BURST_DIBIT_LENGTH;
- }
- else
- {
- processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
- }
-
- mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
- }
- else
- {
- if(burst1 != null)
- {
- mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 2);
- mDibitCounter -= BURST_DIBIT_LENGTH;
-
- //Transfer the sync pattern from tracker 1 to tracker 2 so we don't lose voice framing
- mSyncTrackerTimeslot2.setSyncPattern(mSyncTrackerTimeslot1.getSyncPattern());
- }
- else
- {
- processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
- mSyncTrackerTimeslot2.reset();
- }
-
- mTimeslotAlignmentTracker.reset();
- mSyncTrackerTimeslot1.reset();
- mDibitCounter = BURST_DIBIT_LENGTH;
- mSynchronized = true;
- }
- }
- else
- {
- //When we don't have sufficient timeslot tracking data, we either have misaligned timeslots from a
- //repeater or we have some form of mobile burst. If mobile, process to the correct timeslto, otherwise
- //dump the messages to timeslot 0 so that the burst doesn't corrupt the decoder states for either
- // timeslot, until we positively regain timeslot tracking
- if(burst1 != null)
- {
- DMRSyncPattern sync1 = mSyncTrackerTimeslot1.getSyncPattern();
-
- if(sync1.isMobileStationSyncPattern() || sync1.isDirectModeTS1())
- {
- mBurstDetectListener.burstDetected(burst1, sync1, 1);
- }
- else if(sync1.isDirectModeTS2())
- {
- mBurstDetectListener.burstDetected(burst1, sync1, 2);
- }
- else
- {
- mBurstDetectListener.burstDetected(burst1, sync1, 0);
- }
-
- mDibitCounter -= BURST_DIBIT_LENGTH;
- }
- else
- {
- processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
- }
-
- if(burst2 != null)
- {
- DMRSyncPattern sync2 = mSyncTrackerTimeslot2.getSyncPattern();
-
- if(sync2.isMobileStationSyncPattern() || sync2.isDirectModeTS1())
- {
- mBurstDetectListener.burstDetected(burst2, sync2, 1);
- }
- else if(sync2.isDirectModeTS2())
- {
- mBurstDetectListener.burstDetected(burst2, sync2, 2);
- }
- else
- {
- mBurstDetectListener.burstDetected(burst2, sync2, 0);
- }
-
- mDibitCounter -= BURST_DIBIT_LENGTH;
- }
- else
- {
- processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
- }
-
- mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
- }
-
- if(!mSynchronized)
- {
- mTimeslotAlignmentTracker.reset();
- mSyncTrackerTimeslot1.reset();
- mSyncTrackerTimeslot2.reset();
- }
-
- if(mDibitCounter < 0)
- {
- mDibitCounter = 0;
- }
- }
- }
-
- /**
- * Repairs the captured dibits in the message buffer when a PLL lock misalignment is detected.
- * @param offset to start of burst
- * @param carrierLock that indicates the mis-alignment
- */
- private void repairPLLMisalignment(int offset, QPSKCarrierLock carrierLock)
- {
- int end = offset + BURST_DIBIT_LENGTH;
-
- for(int x = offset; x < end; x++)
- {
- Dibit misalignedDibit = mMessageBuffer.get(x);
- mMessageBuffer.set(x, carrierLock.correct(misalignedDibit));
- }
-
- if(mPhaseLockedLoop != null)
- {
-// mLog.warn("Repairing PLL Phase Lock Mis-Alignment - Detected: " + carrierLock);
- switch(carrierLock)
- {
- case PLUS_90:
- mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_90_DEGREES);
- break;
- case MINUS_90:
- mPhaseLockedLoop.correctInversion(-PLL_PHASE_CORRECTION_90_DEGREES);
- break;
- case INVERTED:
- mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_180_DEGREES);
- break;
- }
- }
- }
-
- /**
- * Resets the message and sync delay buffers and resets the dibit counter to zero.
- */
- public void reset()
- {
- mDibitCounter = 0;
- mMessageBuffer.reset();
- mSyncDelayBuffer.reset();
- mTimeslotAlignmentTracker.reset();
- }
-
- /**
- * Calculates the value of the sync field currently in the message buffer as specified by the offset value.
- */
- private long getSyncValue(int dibitOffset)
- {
- Dibit[] syncDibits = mMessageBuffer.getBuffer(dibitOffset, SYNC_DIBIT_LENGTH);
-
- long value = 0;
-
- for(Dibit dibit: syncDibits)
- {
- value = Long.rotateLeft(value, 2);
- value += dibit.getValue();
- }
-
- return value;
- }
-
- /**
- * Processes dibits received without a sync condition
- * @param dibitCount with sync loss
- */
- private void processSyncLossDibits(int dibitCount, int timeslot)
- {
- mDibitCounter -= dibitCount;
-
- if(mBurstDetectListener != null)
- {
- mBurstDetectListener.syncLost(dibitCount * 2, timeslot);
- }
- }
-
- /**
- * Tracks the sync state for a timeslot to account for voice framing that does not transmit a sync pattern in each
- * frame.
- */
- public class SyncTracker
- {
- private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
- private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
-
- /**
- * Updates the sync pattern for the most recently detected burst to the specified pattern. When the previous
- * sync pattern is one of either base or mobile voice frame and the detected pattern is unknown, automatically
- * advances the sync pattern to the next voice frame in the super frame sequence to account for voice
- * frames B - F that do not have a sync pattern.
- */
- public void update(DMRSyncPattern pattern, QPSKCarrierLock carrierLock)
- {
- if(pattern == DMRSyncPattern.UNKNOWN)
- {
- switch(mSyncPattern)
- {
- case BASE_STATION_VOICE: //Voice frame A
- mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_B;
- break;
- case BS_VOICE_FRAME_B:
- mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_C;
- break;
- case BS_VOICE_FRAME_C:
- mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_D;
- break;
- case BS_VOICE_FRAME_D:
- mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_E;
- break;
- case BS_VOICE_FRAME_E:
- mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_F;
- break;
-
- case MOBILE_STATION_VOICE: //Voice frame A
- case DIRECT_MODE_VOICE_TIMESLOT_1:
- case DIRECT_MODE_VOICE_TIMESLOT_2:
- mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_B;
- break;
- case MS_VOICE_FRAME_B:
- mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_C;
- break;
- case MS_VOICE_FRAME_C:
- mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_D;
- break;
- case MS_VOICE_FRAME_D:
- mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_E;
- break;
- case MS_VOICE_FRAME_E:
- mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_F;
- break;
- default:
- mSyncPattern = pattern;
- mCarrierLock = carrierLock;
- break;
- }
- }
- else
- {
- mSyncPattern = pattern;
- mCarrierLock = carrierLock;
- }
- }
-
- /**
- * Most recently detected sync pattern
- */
- public DMRSyncPattern getSyncPattern()
- {
- return mSyncPattern;
- }
-
- /**
- * Explicitly sets the pattern for this tracker
- */
- public void setSyncPattern(DMRSyncPattern pattern)
- {
- mSyncPattern = pattern;
- }
-
- /**
- * Carrier lock as indicated by the sync detector.
- */
- public QPSKCarrierLock getCarrierLock()
- {
- return mCarrierLock;
- }
-
- /**
- * Indicates if the burst has a QPSK constellation mis-alignment that has to be corrected
- */
- public boolean hasCarrierMisAlignment()
- {
- return getCarrierLock() != QPSKCarrierLock.NORMAL;
- }
-
- /**
- * Checks the contents of the message buffer to determine if the message has a valid sync pattern, or if the
- * message is a continuation voice frame from a voice super frame. The identified sync pattern and number of
- * bit errors for that pattern are stored in the sync detector so that they can be accessed for dispatching
- * a message.
- */
- public boolean hasSync()
- {
- return getSyncPattern() != DMRSyncPattern.UNKNOWN;
- }
-
- /**
- * Resets the sync pattern to unknown
- */
- public void reset()
- {
- mSyncPattern = DMRSyncPattern.UNKNOWN;
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java
similarity index 97%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java
index 01e54dde..33c48536 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DmrCrcMaskManager.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRCrcMaskManager.java
@@ -21,18 +21,13 @@ package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,9 +44,9 @@ import org.slf4j.LoggerFactory;
* values against CSBK messages and some other function is involved in determining which CRC mask value to use when
* checking the CRC of the transmitted CSBK ... fun and games!
*/
-public class DmrCrcMaskManager
+public class DMRCrcMaskManager
{
- private Logger LOGGER = LoggerFactory.getLogger(DmrCrcMaskManager.class);
+ private Logger LOGGER = LoggerFactory.getLogger(DMRCrcMaskManager.class);
private Map mCsbkTrackerMapTS1 = new TreeMap<>();
private Map mCsbkTrackerMapTS2 = new TreeMap<>();
private Set mSortedTrackersTS1 = new TreeSet<>();
@@ -60,7 +55,7 @@ public class DmrCrcMaskManager
/**
* Constructs an instance
*/
- public DmrCrcMaskManager()
+ public DMRCrcMaskManager()
{
}
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 d2f093a7..7a57a3fe 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
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,22 +19,26 @@
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.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
-import io.github.dsheirer.dsp.gain.complex.ComplexGainFactory;
-import io.github.dsheirer.dsp.gain.complex.IComplexGainControl;
-import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
-import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
-import io.github.dsheirer.dsp.psk.pll.CostasLoop;
-import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor;
-import io.github.dsheirer.dsp.psk.pll.PLLBandwidth;
+import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulator;
+import io.github.dsheirer.dsp.psk.dqpsk.DQPSKDemodulatorFactory;
import io.github.dsheirer.dsp.squelch.PowerMonitor;
-import io.github.dsheirer.dsp.symbol.Dibit;
-import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
+import io.github.dsheirer.message.IMessage;
+import io.github.dsheirer.message.SyncLossMessage;
+import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
-import io.github.dsheirer.module.decode.FeedbackDecoder;
-import io.github.dsheirer.sample.Broadcaster;
+import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
+import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
+import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
+import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IByteBufferProvider;
import io.github.dsheirer.sample.complex.ComplexSamples;
@@ -44,7 +48,9 @@ import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.wave.ComplexWaveSource;
import java.io.File;
+import java.io.IOException;
import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -52,108 +58,82 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * DMR decoder module.
+ * DMR DQPSK decoder.
+ *
+ * Processes the sample stream using an optimal (scalar or vector) DQPSK demodulator. The demodulated sample stream
+ * is processed by the DMRSoftSymbolProcessor at each symbol time interval using a primary and two lagging soft sync
+ * detectors. The sync detectors use correlation of the demodulated soft symbols with all known sync patterns to
+ * detect the sync and align the symbol timing and symbol period to the optimal location and symbol period as
+ * calculated across each detected burst. Once the symbol processor has fingerprinted the channel type, it optimizes
+ * sync detection for one of three detected modes (BASE, MOBILE or DIRECT) to reduce sync correlation workload.
+ *
+ * The DMRSoftSymbolMessageFramer receives sync detections from the soft symbol processor and buffers symbols from the
+ * start of the burst through the sync so that the symbols can be resampled when the sync is detected, ensuring that
+ * the burst symbols are accurate using the symbol timing and period values calculated on the sync detection.
+ *
+ * For Direct Mode where one timeslot is empty, the symbol message framer uses two message burst buffers that alternate
+ * for each burst so that one buffer tracks the active timeslot and the other buffer tracks the empty timeslot to
+ * ensure accurate burst detection and tracking.
+ *
+ * The DMRMessageProcessor processes messages from the message framer to extract and reassemble link control and
+ * embedded link control parameters.
*/
-public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider,
- IComplexSamplesListener, Listener, IByteBufferProvider
+public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplexSamplesListener, ISourceEventListener,
+ ISourceEventProvider, Listener
{
- private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class);
- protected static final float SAMPLE_COUNTER_GAIN = 0.4f;
- private static final double SYMBOL_RATE = 4800.0;
- private double mSampleRate;
- private Broadcaster mDibitBroadcaster = new Broadcaster<>();
- private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
+ private static final Logger LOGGER = LoggerFactory.getLogger(DMRDecoder.class);
+ private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
+ private static final int SYMBOL_RATE = 4800;
+ private static final Map BASEBAND_FILTERS = new HashMap<>();
+ private DQPSKDemodulator mDemodulator;
+ private DMRMessageFramer mMessageFramer = new DMRMessageFramer();
+ private DMRSoftSymbolProcessor mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
private DMRMessageProcessor mMessageProcessor;
- protected IComplexGainControl mAGC = ComplexGainFactory.getComplexGainControl();
- private Map mBasebandFilters = new HashMap<>();
- protected IRealFilter mIBasebandFilter;
- protected IRealFilter mQBasebandFilter;
-
- protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
- protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
- protected CostasLoop mCostasLoop;
- protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor;
- protected DMRMessageFramer mMessageFramer;
- protected PowerMonitor mPowerMonitor = new PowerMonitor();
+ private IRealFilter mIBasebandFilter;
+ private IRealFilter mQBasebandFilter;
+ private PowerMonitor mPowerMonitor = new PowerMonitor();
/**
* Constructs an instance
+ * @param config for the DMR decoder
*/
- public DMRDecoder(DecodeConfigDMR config)
+ public DMRDecoder(DecodeConfigDMR config, boolean isTrafficChannel)
{
mMessageProcessor = new DMRMessageProcessor(config);
mMessageProcessor.setMessageListener(getMessageListener());
- getDibitBroadcaster().addListener(mByteBufferAssembler);
setSampleRate(25000.0);
+
+ if(isTrafficChannel)
+ {
+ mSymbolProcessor.setBaseStationMode(true);
+ }
}
- @Override
- public void start()
- {
- super.start();
- mQPSKDemodulator.start();
- }
-
- @Override
- public void stop()
- {
- super.stop();
- mQPSKDemodulator.stop();
- }
-
- /**
- * DMR decoder type.
- */
@Override
public DecoderType getDecoderType()
{
return DecoderType.DMR;
}
- /**
- * Message processor
- */
- protected DMRMessageProcessor getMessageProcessor()
- {
- return mMessageProcessor;
- }
-
/**
* Sets the sample rate and configures internal decoder components.
* @param sampleRate of the channel to decode
*/
public void setSampleRate(double sampleRate)
{
- if(sampleRate <= getSymbolRate() * 2)
+ if(sampleRate <= SYMBOL_RATE * 2)
{
throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be >9600 (2 * " +
- getSymbolRate() + " symbol rate)");
+ SYMBOL_RATE + " symbol rate)");
}
mPowerMonitor.setSampleRate((int)sampleRate);
- mSampleRate = sampleRate;
- mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter());
- mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter());
- mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
- mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300);
- mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this);
- mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
-
- mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
-
- if(mMessageFramer != null)
- {
- getDibitBroadcaster().removeListener(mMessageFramer);
- }
-
- //The Costas Loop receives symbol-inversion correction requests when detected.
- //The PLL gain monitor receives sync detect/loss signals from the message framer
- mMessageFramer = new DMRMessageFramer(mCostasLoop);
- mMessageFramer.setSyncDetectListener(mFrequencyCorrectionSyncMonitor);
- mMessageFramer.setListener(getMessageProcessor());
-
- mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
- getDibitBroadcaster().addListener(mMessageFramer);
+ mIBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
+ mQBasebandFilter = FilterFactory.getRealFilter(getBasebandFilter(sampleRate));
+ mDemodulator = DQPSKDemodulatorFactory.getDemodulator(sampleRate, SYMBOL_RATE);
+ mSymbolProcessor.setSamplesPerSymbol(mDemodulator.getSamplesPerSymbol());
+ mMessageFramer.setListener(mMessageProcessor);
+ mMessageProcessor.setMessageListener(getMessageListener());
}
/**
@@ -163,142 +143,86 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
@Override
public void receive(ComplexSamples samples)
{
- mMessageFramer.setCurrentTime(samples.timestamp());
-
+ mMessageFramer.setTimestamp(samples.timestamp());
float[] i = mIBasebandFilter.filter(samples.i());
float[] q = mQBasebandFilter.filter(samples.q());
//Process buffer for power measurements
mPowerMonitor.process(i, q);
- ComplexSamples amplified = mAGC.process(i, q, samples.timestamp());
- mQPSKDemodulator.receive(amplified);
+ float[] demodulated = mDemodulator.demodulate(i, q);
+ mSymbolProcessor.receive(demodulated);
}
/**
* Constructs a baseband filter for this decoder using the current sample rate
*/
- private float[] getBasebandFilter()
+ private float[] getBasebandFilter(double sampleRate)
{
- //Attempt to reuse a cached (ie already-designed) filter if available
- float[] filter = mBasebandFilters.get(getSampleRate());
-
- if(filter == null)
+ if(BASEBAND_FILTERS.containsKey(sampleRate))
{
- FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
- .sampleRate((int)getSampleRate())
+ return BASEBAND_FILTERS.get(sampleRate);
+ }
+
+ FIRFilterSpecification specification = FIRFilterSpecification
+ .lowPassBuilder()
+ .sampleRate(sampleRate)
.passBandCutoff(5100)
- .passBandAmplitude(1.0)
- .passBandRipple(0.01)
- .stopBandAmplitude(0.0)
- .stopBandStart(6500)
- .stopBandRipple(0.01)
- .build();
+ .passBandAmplitude(1.0).passBandRipple(0.01) //.01
+ .stopBandAmplitude(0.0).stopBandStart(6500) //6500
+ .stopBandRipple(0.01).build();
- try
- {
- filter = FilterFactory.getTaps(specification);//
- }
- catch(Exception fde) //FilterDesignException
- {
- mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
- }
+ float[] coefficients = null;
- if(filter != null)
- {
- mBasebandFilters.put(getSampleRate(), filter);
- }
- else
- {
- throw new IllegalStateException("Couldn't design a DMR baseband filter for sample rate: " + getSampleRate());
- }
- }
-
- return filter;
- }
-
- /**
- * Process source events
- */
- protected void process(SourceEvent sourceEvent)
- {
- switch(sourceEvent.getEvent())
+ try
{
- case NOTIFICATION_SAMPLE_RATE_CHANGE:
- mCostasLoop.reset();
- setSampleRate(sourceEvent.getValue().doubleValue());
- break;
- case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
- //Reset the PLL if/when the tuner PPM changes so that we can re-lock
- mCostasLoop.reset();
- break;
+ coefficients = FilterFactory.getTaps(specification);
+ BASEBAND_FILTERS.put(sampleRate, coefficients);
}
+ catch(Exception fde) //FilterDesignException
+ {
+ System.out.println("Error");
+ }
+
+ if(coefficients == null)
+ {
+ throw new IllegalStateException("Unable to design low pass filter for sample rate [" + sampleRate + "]");
+ }
+
+ return coefficients;
}
/**
- * Resets this decoder to prepare for processing a new channel
- */
- @Override
- public void reset()
- {
- mCostasLoop.reset();
- mFrequencyCorrectionSyncMonitor.reset();
- }
-
- /**
- * Assembler for packaging Dibit stream into reusable byte buffers.
- */
- protected Broadcaster getDibitBroadcaster()
- {
- return mDibitBroadcaster;
- }
-
- /**
- * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ * Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public void setBufferListener(Listener listener)
{
- mByteBufferAssembler.setBufferListener(listener);
+ mSymbolProcessor.setBufferListener(listener);
}
/**
- * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ * Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public void removeBufferListener(Listener listener)
{
- mByteBufferAssembler.removeBufferListener(listener);
+ mSymbolProcessor.setBufferListener(null);
}
/**
- * Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
+ * Implements the IByteBufferProvider interface - delegates to the symbol processor
*/
@Override
public boolean hasBufferListeners()
{
- return mByteBufferAssembler.hasBufferListeners();
+ return mSymbolProcessor.hasBufferListener();
}
- protected double getSymbolRate()
+ @Override
+ public Listener getSourceEventListener()
{
- return SYMBOL_RATE;
- }
-
- /**
- * Current sample rate for this decoder
- */
- protected double getSampleRate()
- {
- return mSampleRate;
- }
-
- /**
- * Samples per symbol based on current sample rate and symbol rate.
- */
- public float getSamplesPerSymbol()
- {
- return (float)(getSampleRate() / getSymbolRate());
+ return this::process;
}
/**
@@ -307,66 +231,194 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
@Override
public void setSourceEventListener(Listener listener)
{
- super.setSourceEventListener(listener);
mPowerMonitor.setSourceEventListener(listener);
}
- /**
- * Removes a registered source event listener from receiving source events from this decoder
- */
@Override
public void removeSourceEventListener()
{
- super.removeSourceEventListener();
mPowerMonitor.setSourceEventListener(null);
}
@Override
- public Listener getSourceEventListener()
+ public void start()
{
- return sourceEvent -> process(sourceEvent);
+ super.start();
+ mMessageFramer.start();
+ }
+
+ @Override
+ public void stop()
+ {
+ super.stop();
+ mMessageFramer.stop();
}
/**
- * Listener interface to receive reusable complex buffers
+ * Process source events
*/
+ private void process(SourceEvent sourceEvent)
+ {
+ switch(sourceEvent.getEvent())
+ {
+ case NOTIFICATION_SAMPLE_RATE_CHANGE:
+ setSampleRate(sourceEvent.getValue().doubleValue());
+ break;
+ }
+ }
+
@Override
public Listener getComplexSamplesListener()
{
- return DMRDecoder.this;
+ return this;
}
public static void main(String[] args)
{
- File file = new File("/home/denny/Downloads/TIII,HYT414.585000,.wav");
+ LOGGER.info("Starting ...");
- try
+ // 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_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_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";
+
+ boolean autoReplay = false;
+
+ DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR(), false);
+ decoder.start();
+
+ UserPreferences userPreferences = new UserPreferences();
+ DMRAudioModule audio1 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_1);
+ DMRAudioModule audio2 = new DMRAudioModule(userPreferences, new AliasList(""), DMRMessage.TIMESLOT_2);
+
+ decoder.setMessageListener(new Listener<>()
+ {
+ private long mBitCounter = 1;
+ private int mBitErrorCounter;
+ private int mValidMessageCounter;
+ private int mTotalMessageCounter;
+
+ @Override
+ public void receive(IMessage iMessage)
+ {
+ int errors = 0;
+
+ if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_1)
+ {
+ audio1.receive(iMessage);
+ }
+ if(iMessage.getTimeslot() == DMRMessage.TIMESLOT_2)
+ {
+ audio2.receive(iMessage);
+ }
+
+ if(iMessage instanceof DMRBurst burst)
+ {
+ mBitCounter += 288;
+ errors = burst.getMessage().getCorrectedBitCount();
+ mTotalMessageCounter++;
+
+ if(mTotalMessageCounter == 492)
+ {
+ int a = 0;
+ }
+ if(burst.isValid())
+ {
+ mBitErrorCounter += Math.max(errors, 0);
+ mValidMessageCounter++;
+ }
+ }
+ else if(iMessage instanceof LCMessage lcw)
+ {
+ mTotalMessageCounter++;
+ errors = lcw.getMessage().getCorrectedBitCount();
+ if(lcw.isValid())
+ {
+ mBitErrorCounter += errors;
+ mValidMessageCounter++;
+ }
+ }
+
+ double bitErrorRate = (double)mBitErrorCounter / (double)mBitCounter * 100.0;
+
+ boolean logFLC = false;
+ boolean logSLC = false;
+ boolean logCACH = false;
+ boolean logIdles = false;
+ boolean logEverything = true;
+
+ if(!logEverything && logFLC)
+ {
+ if(iMessage instanceof FullLCMessage)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+ else if(iMessage instanceof Terminator terminator)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+ else if(iMessage instanceof DataMessageWithLinkControl)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+ }
+
+ if(!logEverything && logCACH && iMessage instanceof DMRBurst burst && burst.hasCACH())
+ {
+ System.out.println("CACH:" + burst.getCACH());
+ }
+
+ if(!logEverything && logSLC && iMessage instanceof ShortLCMessage)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+
+ if(logEverything)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+
+ if(!logEverything && logIdles && iMessage instanceof SyncLossMessage)
+ {
+ System.out.println(">>MESSAGE: TS" + iMessage.getTimeslot() + " " + iMessage + " \t\t[" + errors + " | " + mBitErrorCounter + " | Valid:" + mValidMessageCounter + " Total:" + mTotalMessageCounter + " Msgs] Rate [" + DECIMAL_FORMAT.format(bitErrorRate) + " %]");
+ }
+ }
+ });
+
+ try(ComplexWaveSource source = new ComplexWaveSource(new File(file), autoReplay))
{
- ComplexWaveSource source = new ComplexWaveSource(file);
- source.open();
- System.out.println("Source Sample Rate: " + source.getSampleRate());
- DMRDecoder decoder = new DMRDecoder(new DecodeConfigDMR());
- decoder.setMessageListener(message -> System.out.println("TS:" + message.getTimeslot() + " " + message));
source.setListener(iNativeBuffer -> {
Iterator it = iNativeBuffer.iterator();
+
while(it.hasNext())
{
- ComplexSamples samples = it.next();
- decoder.receive(samples);
+ decoder.receive(it.next());
}
});
- decoder.setSampleRate(source.getSampleRate());
- decoder.start();
source.start();
+ decoder.setSampleRate(source.getSampleRate());
while(true)
{
- source.next(65535);
+ source.next(2048, true);
}
}
- catch(Exception e)
+ catch(IOException ioe)
{
- e.printStackTrace();
+ LOGGER.error("Error", ioe);
}
+
+ LOGGER.info("Finished");
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java
deleted file mode 100644
index cb078cea..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderInstrumented.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-package io.github.dsheirer.module.decode.dmr;
-
-import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented;
-import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
-import io.github.dsheirer.dsp.psk.SymbolDecisionData;
-import io.github.dsheirer.sample.Listener;
-import io.github.dsheirer.sample.complex.Complex;
-import io.github.dsheirer.sample.complex.ComplexSamples;
-
-/**
- * Instrumented version of DMR decoder
- */
-public class DMRDecoderInstrumented extends DMRDecoder
-{
- private Listener mPLLPhaseErrorListener;
- private Listener mPLLFrequencyListener;
- private Listener mSamplesPerSymbolListener;
- private Listener mComplexSymbolListener;
- private Listener mFilteredSymbolListener;
- private Listener mSymbolDecisionDataListener;
-
- /**
- * Instrumented version of the DMR decoder that supports registering listeners to provide access to data as
- * it is being processed by the decoder.
- */
- public DMRDecoderInstrumented(DecodeConfigDMR config)
- {
- super(config);
- }
-
- /**
- * Primary method for processing incoming complex sample buffers
- * @param complexSamples containing channelized complex samples
- */
- @Override
- public void receive(ComplexSamples samples)
- {
- mMessageFramer.setCurrentTime(samples.timestamp());
-
- float[] i = mIBasebandFilter.filter(samples.i());
- float[] q = mQBasebandFilter.filter(samples.q());
-
- if(mFilteredSymbolListener != null)
- {
- mFilteredSymbolListener.receive(new ComplexSamples(i, q, samples.timestamp()));
- }
-
- //Process buffer for power measurements
- mPowerMonitor.process(i, q);
-
- ComplexSamples amplified = mAGC.process(i, q, samples.timestamp());
- mQPSKDemodulator.receive(amplified);
- }
-
- /**
- * Overrides this method so we can correctly configure for instrumented operations
- */
- public void setSampleRate(double sampleRate)
- {
- super.setSampleRate(sampleRate);
-
- InterpolatingSampleBufferInstrumented instrumentedBuffer =
- new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
- mInterpolatingSampleBuffer = instrumentedBuffer;
-
- DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate());
- mQPSKDemodulator = instrumented;
-
- instrumented.setComplexSymbolListener(mComplexSymbolListener);
- instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
- instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
- instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
- instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
- instrumented.setSymbolListener(getDibitBroadcaster());
- getDibitBroadcaster().addListener(mMessageFramer);
- }
-
- public void setComplexSymbolListener(Listener listener)
- {
- mComplexSymbolListener = listener;
- ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener);
- }
-
- public void setPLLPhaseErrorListener(Listener listener)
- {
- mPLLPhaseErrorListener = listener;
- ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
- }
-
- public void setPLLFrequencyListener(Listener listener)
- {
- mPLLFrequencyListener = listener;
- ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
- }
-
- public void setFilteredBufferListener(Listener listener)
- {
- mFilteredSymbolListener = listener;
- }
-
- public void setSymbolDecisionDataListener(Listener listener)
- {
- mSymbolDecisionDataListener = listener;
- ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
- }
-
- public void setSamplesPerSymbolListener(Listener listener)
- {
- mSamplesPerSymbolListener = listener;
- ((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java
index e41e1fac..af5018b3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java
@@ -88,8 +88,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
-import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
@@ -546,7 +547,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
if(message.getSyncPattern().isMobileSyncPattern())
{
- if(message.getSyncPattern().isDirectMode())
+ if(message.getSyncPattern().isDirect())
{
updateCurrentCall(DecodeEventType.CALL, "DIRECT MODE", message.getTimestamp());
}
@@ -569,7 +570,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
- if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
+ if(embedded.getShortBurst() instanceof EmbeddedEncryptionParameters arc4)
{
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
}
@@ -1356,10 +1357,10 @@ public class DMRDecoderState extends TimeslotDecoderState
/**
* Updates the current call with encryption information.
- * @param encryptionParameters decoded from the Voice Frame F
+ * @param embeddedEncryptionParameters decoded from the Voice Frame F
* @param isGroup true for group or false for individual call.
*/
- private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
+ private void updateEncryptedCall(EmbeddedEncryptionParameters embeddedEncryptionParameters, boolean isGroup, long timestamp)
{
if(mCurrentCallEvent != null)
{
@@ -1367,11 +1368,11 @@ public class DMRDecoderState extends TimeslotDecoderState
if(details == null)
{
- details = encryptionParameters.toString();
+ details = embeddedEncryptionParameters.toString();
}
- else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
+ else if(!details.contains(embeddedEncryptionParameters.toString()) && !details.contains("ENCRYPTION"))
{
- details += " " + encryptionParameters;
+ details += " " + embeddedEncryptionParameters;
}
mCurrentCallEvent.setDetails(details);
@@ -1381,7 +1382,7 @@ public class DMRDecoderState extends TimeslotDecoderState
mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED :
DecodeEventType.CALL_ENCRYPTED, timestamp)
.channel(getCurrentChannel())
- .details(encryptionParameters.toString())
+ .details(embeddedEncryptionParameters.toString())
.identifiers(getIdentifierCollection().copyOf())
.timeslot(getTimeslot())
.build();
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
new file mode 100644
index 00000000..596ecff3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRHardSymbolProcessor.java
@@ -0,0 +1,78 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+import io.github.dsheirer.module.decode.dmr.sync.DMRHardSyncDetector;
+import java.nio.ByteBuffer;
+
+/**
+ * Processes a stream of dibit symbol decisions to detect DMR sync patterns and produce decoded DMR messages.
+ *
+ * Note: this symbol processor is primarily used for offline analysis of a captured bit stream recording.
+ */
+public class DMRHardSymbolProcessor
+{
+ private final DMRMessageFramer mMessageFramer;
+ private final DMRHardSyncDetector mSyncDetector = new DMRHardSyncDetector();
+ //Dibit delay line sizing: CACH(12) + MESSAGE_PREFIX(54) + SYNC(24)
+ private DibitDelayLine mDibitDelayLine = new DibitDelayLine(90);
+
+
+ /**
+ * Constructs an instance
+ * @param messageFramer to receive dibits and sync notifications.
+ */
+ public DMRHardSymbolProcessor(DMRMessageFramer messageFramer)
+ {
+ mMessageFramer = messageFramer;
+ }
+
+ /**
+ * Processes an individual dibit.
+ * @param dibit to process
+ */
+ public void process(Dibit dibit)
+ {
+ Dibit ejected = mDibitDelayLine.insert(dibit);
+ mMessageFramer.receive(ejected);
+
+ if(mSyncDetector.process(dibit))
+ {
+ mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
+ }
+ }
+
+ /**
+ * Primary method for streaming decoded symbol byte arrays.
+ *
+ * @param buffer to process into a stream of dibits for processing.
+ */
+ public void receive(ByteBuffer buffer)
+ {
+ for(byte value : buffer.array())
+ {
+ for(int x = 0; x <= 3; x++)
+ {
+ process(Dibit.parse(value, x));
+ }
+ }
+ }
+}
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 357e42de..16b20496 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
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,517 +16,375 @@
* along with this program. If not, see
* ****************************************************************************
*/
+
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
-import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
-import io.github.dsheirer.module.decode.dmr.audio.DMRCallSequenceRecorder;
import io.github.dsheirer.module.decode.dmr.message.CACH;
-import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
-import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
-import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
-import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Identity;
-import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Token;
-import io.github.dsheirer.preference.UserPreferences;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.protocol.Protocol;
-import io.github.dsheirer.record.binary.BinaryReader;
-import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.Arrays;
/**
- * DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
- * and issue phase corrections.
+ * DMR message framer. Processes a DMR symbol stream to produce DMR messages/bursts. Employs two symbol buffers to
+ * track and frame both DMR timeslots. Responds to external sync detections to alternately activate one of the two
+ * buffers and capture a DMR burst, convert it to the correct message format and send it to a message listener.
+ *
+ * This framer is able to detect DMR Direct mode and track one active and one inactive timeslot across each voice
+ * super frame in addition to framing base and mobile station data and voice bursts.
*/
-public class DMRMessageFramer implements Listener, IDMRBurstDetectListener
+public class DMRMessageFramer implements Listener
{
- private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFramer.class);
-
- private static final double DMR_BIT_RATE = 9600.0;
-
- /**
- * Provides sync detection and burst framing
- */
- private DMRBurstFramer mBurstFramer;
-
- /**
- * External listener for messages produced by this framer
- */
+ private static final int DIBIT_CACH_START = 0;
+ private static final int DIBIT_BURST_END = 144;
private Listener mMessageListener;
-
- /**
- * External listener for sync state information
- */
- private ISyncDetectListener mSyncDetectListener;
-
- /**
- * Tracks current time for use with framed bursts/messages. This value is updated externally from sample buffer
- * timestamps and internally as each framed message is received or sync loss is processed.
- */
- private long mCurrentTime = System.currentTimeMillis();
+ private Dibit[] mBufferA = new Dibit[144];
+ private Dibit[] mBufferB = new Dibit[144];
+ private int mBufferAPointer = 0;
+ private int mBufferBPointer = 0;
+ private DMRSyncPattern mBufferAPattern = DMRSyncPattern.UNKNOWN;
+ private DMRSyncPattern mBufferBPattern = DMRSyncPattern.UNKNOWN;
+ private int mBufferATimeslot;
+ private int mBufferBTimeslot;
+ private boolean mBufferAActive = false;
+ private boolean mAssemblingBurst = false;
+ private int mDibitCounter = 0;
+ private int mDibitSinceTimestampCounter = 0;
+ private long mReferenceTimestamp = 0;
+ private boolean mRunning = false;
/**
* Constructs an instance
- *
- * @param phaseLockedLoop to receive PLL phase lock error corrections that are identified by the DMRBurstFramer
- * when known phase misaligned sync patterns are detected.
- */
- public DMRMessageFramer(IPhaseLockedLoop phaseLockedLoop)
- {
- mBurstFramer = new DMRBurstFramer(this, phaseLockedLoop);
- }
-
- /**
- * Constructs an instance.
*/
public DMRMessageFramer()
{
- this(null);
+ Arrays.fill(mBufferA, Dibit.D00_PLUS_1);
+ Arrays.fill(mBufferB, Dibit.D00_PLUS_1);
}
/**
- * Registers a sync detect listener to be notified each time a sync pattern and NID are detected.
- */
- public void setSyncDetectListener(ISyncDetectListener syncDetectListener)
- {
- mSyncDetectListener = syncDetectListener;
- }
-
- /**
- * Current timestamp or timestamp of incoming message buffers that is continuously updated to as
- * close as possible to the bits processed for the expected baud rate.
- *
- * @return current time
- */
- private long getTimestamp()
- {
- return mCurrentTime;
- }
-
- /**
- * Sets the current time. This should be invoked by an incoming message buffer stream.
- *
- * @param currentTime
- */
- public void setCurrentTime(long currentTime)
- {
- mCurrentTime = currentTime;
- }
-
- /**
- * Updates the current timestamp based on the number of bits processed versus the bit rate per second
- * in order to keep an accurate running timestamp to use for timestamped message creation.
- *
- * @param bitsProcessed thus far
- */
- private void updateBitsProcessed(int bitsProcessed)
- {
- if(bitsProcessed > 0)
- {
- mCurrentTime += (long)((double)bitsProcessed / DMR_BIT_RATE * 1000.0);
- }
- }
-
- /**
- * Registers the listener for messages produced by this message framer
- *
- * @param messageListener to receive framed and decoded messages
- */
- public void setListener(Listener messageListener)
- {
- mMessageListener = messageListener;
- }
-
- /**
- * Primary method for streaming decoded symbol dibits for message framing.
- *
- * @param dibit to process
+ * Primary input method for receiving the stream of demodulated dibit symbols.
+ * @param dibit to process.
*/
@Override
public void receive(Dibit dibit)
{
- mBurstFramer.receive(dibit);
- }
+ mDibitCounter++;
+ mDibitSinceTimestampCounter++;
- /**
- * Primary method for streaming decoded symbol byte arrays.
- *
- * @param buffer to process into a stream of dibits for processing.
- */
- public void receive(ByteBuffer buffer)
- {
- for(byte value : buffer.array())
+ //Issue a sync loss for each full 1-second interval (ie 4800 dibits) of no sync.
+ if(!mAssemblingBurst && mDibitCounter >= 4800)
{
- for(int x = 0; x <= 3; x++)
+ dispatch(new SyncLossMessage(getTimestamp(), 9600, Protocol.DMR, DMRMessage.TIMESLOT_0));
+ mDibitCounter -= 4800;
+ mBufferAPattern = DMRSyncPattern.UNKNOWN;
+ mBufferBPattern = DMRSyncPattern.UNKNOWN;
+ }
+
+ if(mAssemblingBurst)
+ {
+ if(mBufferAActive)
{
- receive(Dibit.parse(value, x));
- }
- }
- }
+ mBufferA[mBufferAPointer++] = dibit;
- /**
- * DMR burst detection processing. Processes DMR burst/messages from external burst framer/producer.
- *
- * @param message bits for the burst/message and any bit errors detected in the sync pattern
- * @param syncPattern that was detected for the burst
- */
- @Override
- public void burstDetected(CorrectedBinaryMessage message, DMRSyncPattern syncPattern, int timeslot)
- {
- //Each burst is 288 bits
- updateBitsProcessed(288);
-
- //This allows the frequency error monitor to be notified when we're in sync
- if(mSyncDetectListener != null)
- {
- mSyncDetectListener.syncDetected(0);
- }
-
- CACH cach = null;
-
- if(syncPattern.hasCACH())
- {
- cach = CACH.getCACH(message);
- }
-
- if(mMessageListener != null)
- {
- DMRMessage dmrMessage = DMRMessageFactory.create(syncPattern, message, cach, getTimestamp(), timeslot);
-
- if(dmrMessage != null)
- {
- mMessageListener.receive(dmrMessage);
- }
- }
- }
-
- /**
- * Processes a sync loss to track how many bits were processed and update message listeners.
- *
- * @param bitsProcessed since the last sync detect
- */
- @Override
- public void syncLost(int bitsProcessed, int timeslot)
- {
- updateBitsProcessed(bitsProcessed);
-
- dispatchSyncLoss(bitsProcessed, timeslot);
-
- if(mSyncDetectListener != null)
- {
- mSyncDetectListener.syncLost(bitsProcessed);
- }
- }
-
- /**
- * Creates a sync loss message and dispatches it to the message listener
- *
- * @param bitsProcessed without a sync detection
- * @param timeslot that lost the sync
- */
- private void dispatchSyncLoss(int bitsProcessed, int timeslot)
- {
- if(bitsProcessed > 0 && mMessageListener != null)
- {
- mMessageListener.receive(new SyncLossMessage(getTimestamp(), bitsProcessed, Protocol.DMR, timeslot));
- }
- }
-
- public static class MessageListener implements Listener
- {
- private boolean mHasDMRData = false;
- public int mTS0Count = 0;
- public int mTS1Count = 0;
-
- @Override
- public void receive(IMessage message)
- {
- if(message instanceof DMRBurst && ((DMRBurst)message).hasCACH())
- {
- CACH cach = ((DMRBurst)message).getCACH();
- mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString());
+ if(mBufferAPointer >= mBufferA.length)
+ {
+ dispatchBufferA();
+ }
}
else
{
- mLog.info(" TS:" + message.getTimeslot() + " " + message.toString());
- }
+ mBufferB[mBufferBPointer++] = dibit;
- if(message instanceof DMRPacketMessage dpm)
- {
- if(dpm.getPacket() instanceof LRRPPacket lrrp)
+ if(mBufferBPointer >= mBufferB.length)
{
- mLog.info("LRRP: " + lrrp.toString());
- mLog.info("MESSAGE: " + lrrp.getMessage().toHexString());
+ dispatchBufferB();
}
}
-
-// if(message instanceof UDTHeader)
-// {
-// mLog.info("TS:" + message.getTimeslot() + " " + message.toString());
-// }
-
-// if(message instanceof UnknownCSBKMessage && message.isValid())
-// {
-// mLog.info("TS:" + ((DMRMessage)message).getTimeslot() + " " + message.toString());
-// }
- }
-
- public boolean hasData()
- {
- return mHasDMRData;
- }
-
- public void reset()
- {
- mHasDMRData = true;
}
}
- public static class LrrpProcessor implements Listener
+ private void dispatchBufferA()
{
- private Map> mLrrpMap = new HashMap<>();
+ mDibitCounter -= 144;
- public LrrpProcessor()
+ if(mDibitCounter > 0)
{
-
- }
-
- @Override
- public void receive(IMessage message)
- {
- if(message instanceof DMRPacketMessage dpm)
+ //If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0.
+ if(mDibitCounter == 144)
{
- if(dpm.getPacket() instanceof LRRPPacket lrrp)
+ //Note: since buffer b got skipped, we'll use buffer a timeslot and then it will get reassigned from
+ //the CACH to the correct timeslot.
+ dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferATimeslot));
+ }
+ else
+ {
+ dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0));
+
+ if(mDibitCounter > 1 && !mBufferAPattern.isDirect())
{
- process(lrrp);
+ mBufferATimeslot = 0;
+ mBufferBTimeslot = 0;
+ mBufferBPattern = DMRSyncPattern.UNKNOWN;
}
}
}
- public void process(LRRPPacket lrrpPacket)
+ mDibitCounter = 0;
+
+ CorrectedBinaryMessage message = getMessage(mBufferA, DIBIT_CACH_START, DIBIT_BURST_END);
+ CACH cach = CACH.getCACH(message);
+
+ if(mBufferAPattern.hasCACH() && cach.isValid() && mBufferATimeslot != cach.getTimeslot())
{
- Identity identity = getIdentity(lrrpPacket);
-
- if(identity != null)
- {
- List packets = mLrrpMap.get(identity.getID());
-
- if(packets == null)
- {
- packets = new ArrayList<>();
- }
-
- packets.add(lrrpPacket);
-
- mLrrpMap.put(identity.getID(), packets);
- }
+ mBufferATimeslot = cach.getTimeslot();
+ mBufferBTimeslot = (mBufferATimeslot == 1 ? 2 : 1);
}
- public void log()
+ dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
+
+ //Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
+ //across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
+ if(mBufferAPattern.isVoicePattern())
{
- mLog.info("*************** DUMPING LRRP PACKETS****************************");
- List ids = new ArrayList(mLrrpMap.keySet());
- Collections.sort(ids);
-
- for(Integer id: ids)
- {
- mLog.info("");
- mLog.info("###################### ID: " + id + " ###################################");
- List packets = mLrrpMap.get(id);
-
- for(LRRPPacket lrrp: packets)
- {
- mLog.info("PACKET: " + lrrp.toString());
- mLog.info("MESSAGE: " + lrrp.getMessage().toHexString());
- }
- }
+ mBufferAPattern = DMRSyncPattern.getNextVoice(mBufferAPattern);
}
- private static Identity getIdentity(LRRPPacket packet)
+ //Automatically trigger buffer B burst collection if it is collecting a voice super frame.
+ if(mBufferBPattern.isVoicePattern())
{
- for(Token token: packet.getTokens())
- {
- if(token instanceof Identity)
- {
- return (Identity)token;
- }
- }
-
- return null;
- }
- }
-
- public static void main(String[] args)
- {
- String path = "/media/denny/T7 Shield/Recordings/DMR/";
-
- //Con+ Traffic + Voice
-// String file = path + "20200513_143340_9600BPS_DMR_SaiaNet_Onondaga_Control.bits"; //Enh GPS Revert Window Annce
-// String file = path + "20200514_062135_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Neighbor only
-// String file = path + "20200514_063507_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 2579
-// String file = path + "20200514_064224_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 5056035
-// String file = path + "20200514_131623_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Grant: 5074193
-// String file = path + "SaiaNet/20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption
-// String file = path + "20200514_142249_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits";
-// String file = path + "20200514_144534_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Con+ Control w/GPS Window Announce
-
- //Con+ Multi-Site with Network Frequency Map - This file is really General Motors Con+ - Tarrant County site
-// String file = path + "20200716_235004_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
-
- //Cap-Max Tier III CC
-// String file = path + "20200710_053632_9600BPS_DMR_Niles_Radio_Coconino_Control.bits";
-
- //Cap+ Multi-Site 1 - Data Revert LSN1/2 with CSBKO 62 Site status
- String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
-// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210845_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
-// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_212309_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
-
- //Cap+ Multi-Site Enhanced Data Revert Channel (no CSBKO 62)
-// String file = path + "Texas_Hunt_Farmers_Electric_Capacity_Plus_Multi_Site/20200714_224018_9600BPS_DMR_Farmers_Electric_Cooperative_Hunt_LCN_3.bits"; //This may have PLL mis-align issues
-
- //Cap+ Multi-Site - Dallas Unknown
-// String file = path + "Texas_Dallas_Unknown_1_Capacity_Plus_Multi_Site/20200716_211233_9600BPS_DMR_Dallas_Unk_Dallas_Unk.bits";
-
- //Hytera Short Data = Proprietary (encrypted)
-// String file = path + "20200716_222839_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; //Hytera short data packets
-// String file = path + "20200716_223551_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits";
-
- //Motorola GPS Revert channel
-// String file = path + "20200730_085433_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
-
- //Hytera XPT (pseudo trunk) beaconing and voice
-// String file = path + "20200729_161450_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //PLL lock oscillating
-// String file = path + "20200729_162200_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits";
-// String file = path + "Hytera XPT/20200816_161126_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //analzing this <<<<<<<<<<<<<<<<<<<<<<<<<<
-
- //Hytera Tier III - CSBKO 40
-// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
-// String file = path + "20200812_061410_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
-// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; //MBC-channel grant
-
- //UDT Header And Blocks
-// String file = path + "20200730_090634_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
-
- //Cap+ BP Scrambling
-// String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits";
-
-// String file = "/home/denny/SDRTrunk/recordings/20200927_054837_9600BPS_DMR_Blair_Communications_(Capacity_Plus)_Dallas_Control_9.bits";
-
- DecodeConfigDMR config = new DecodeConfigDMR();
-
- Broadcaster messageBroadcaster = new Broadcaster<>();
- MessageListener messageListener = new MessageListener();
- messageBroadcaster.addListener(messageListener);
-
- LrrpProcessor lrrpProcessor = new LrrpProcessor();
- messageBroadcaster.addListener(lrrpProcessor);
-
- DMRCallSequenceRecorder ambeRecorder = new DMRCallSequenceRecorder(new UserPreferences(), 123456789l,
- "Denny System", "Denny Site");
- messageBroadcaster.addListener(ambeRecorder);
-
- boolean multi = false;
-
- if(multi)
- {
- try
- {
- DirectoryStream stream = Files.newDirectoryStream(Paths.get(path), "*.bits");
-
- stream.forEach(new Consumer()
- {
- @Override
- public void accept(Path path)
- {
- mLog.info("Processing: " + path.toString());
- DMRMessageFramer messageFramer = new DMRMessageFramer(null);
- DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
- messageFramer.setListener(messageProcessor);
- messageProcessor.setMessageListener(messageBroadcaster);
-
- try(BinaryReader reader = new BinaryReader(path, 200))
- {
- while(reader.hasNext())
- {
- ByteBuffer buffer = reader.next();
- messageFramer.receive(buffer);
- }
- }
- catch(Exception ioe)
- {
- ioe.printStackTrace();
- }
-
- if(!messageListener.hasData())
- {
-// mLog.info("Has Data: " + listener.hasData() + " File:" + path.toString());
-// try
-// {
-// Files.delete(path);
-// }
-// catch(IOException ioe)
-// {
-// ioe.printStackTrace();
-// }
- }
-
- messageListener.reset();
-
- System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count);
- messageListener.mTS0Count = 0;
- messageListener.mTS1Count = 0;
- }
- });
- }
- catch(IOException ioe)
- {
- ioe.printStackTrace();
- }
-
- lrrpProcessor.log();
+ mAssemblingBurst = true;
+ mBufferAActive = false;
+ mBufferBPointer = 0;
}
else
{
- DMRMessageFramer messageFramer = new DMRMessageFramer(null);
- DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
- messageFramer.setListener(messageProcessor);
- messageProcessor.setMessageListener(messageBroadcaster);
+ mAssemblingBurst = false;
+ }
+ }
- try(BinaryReader reader = new BinaryReader(Path.of(file), 200))
+ private void dispatchBufferB()
+ {
+ mDibitCounter -= 144;
+
+ if(mDibitCounter > 0)
+ {
+ //If there are 144 dibits then tag the sync loss to other timeslot, otherwise dump it to timeslot 0.
+ if(mDibitCounter == 144)
{
- while(reader.hasNext())
+ //Note: since buffer a got skipped, we'll use buffer b timeslot and then it will get reassigned from
+ //the CACH to the correct timeslot.
+ dispatch(new SyncLossMessage(getTimestamp(), 288, Protocol.DMR, mBufferBTimeslot));
+ }
+ else
+ {
+ dispatch(new SyncLossMessage(getTimestamp(), mDibitCounter * 2, Protocol.DMR, DMRMessage.TIMESLOT_0));
+
+ if(mDibitCounter > 1 && !mBufferBPattern.isDirect())
{
- ByteBuffer buffer = reader.next();
- messageFramer.receive(buffer);
+ mBufferATimeslot = 0;
+ mBufferBTimeslot = 0;
+ mBufferAPattern = DMRSyncPattern.UNKNOWN;
}
}
- catch(Exception ioe)
- {
- ioe.printStackTrace();
- }
+ }
- System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count);
- messageListener.mTS0Count = 0;
- messageListener.mTS1Count = 0;
+ mDibitCounter = 0;
+
+ CorrectedBinaryMessage burst = getMessage(mBufferB, DIBIT_CACH_START, DIBIT_BURST_END);
+ CACH cach = CACH.getCACH(burst);
+
+ if(mBufferBPattern.hasCACH() && cach.isValid() && mBufferBTimeslot != cach.getTimeslot())
+ {
+ mBufferBTimeslot = cach.getTimeslot();
+ mBufferATimeslot = (mBufferBTimeslot == 1 ? 2 : 1);
+ }
+
+ dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
+
+ //Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
+ //across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
+ if(mBufferBPattern.isVoicePattern())
+ {
+ mBufferBPattern = DMRSyncPattern.getNextVoice(mBufferBPattern);
+ }
+
+ //Automatically trigger buffer A burst collection if it is collecting a voice super frame.
+ if(mBufferAPattern.isVoicePattern())
+ {
+ mAssemblingBurst = true;
+ mBufferAActive = true;
+ mBufferAPointer = 0;
+ }
+ else
+ {
+ mAssemblingBurst = false;
+ }
+ }
+
+ /**
+ * Dispatches the message to the registered message listener.
+ * @param message
+ */
+ private void dispatch(IMessage message)
+ {
+ if(mRunning && mMessageListener != null)
+ {
+ mMessageListener.receive(message);
+ }
+ }
+
+ /**
+ * Starts this framer dispatching messages
+ */
+ public void start()
+ {
+ mRunning = true;
+ }
+
+ /**
+ * Stops this framer from dispatching messages
+ */
+ public void stop()
+ {
+ mRunning = false;
+ }
+
+ /**
+ * Sets the listener to receive framed DMR messages.
+ * @param listener for messages.
+ */
+ public void setListener(Listener listener)
+ {
+ mMessageListener = listener;
+ }
+
+ /**
+ * Creates a binary message from the dibit buffer.
+ * @param buffer containing dibits
+ * @param start index inclusive
+ * @param end index exclusive.
+ * @return binary message
+ */
+ private CorrectedBinaryMessage getMessage(Dibit[] buffer, int start, int end)
+ {
+ CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (end - start));
+
+ Dibit dibit = null;
+ for(int i = start; i < end; i++)
+ {
+ dibit = buffer[i];
+ message.add(dibit.getBit1(), dibit.getBit2());
+ }
+
+ return message;
+ }
+
+ /**
+ * Externally triggered sync detection to initiate DMR burst capture. Indicates a sync pattern is detected and the
+ * next received dibit symbol is the first dibit of the DMR burst.
+ * @param pattern detected
+ */
+ public void syncDetected(DMRSyncPattern pattern)
+ {
+ if(mAssemblingBurst)
+ {
+ if(mBufferAActive && mBufferAPointer < mBufferA.length)
+ {
+ mBufferAPointer = 144;
+ mBufferAPattern = DMRSyncPattern.UNKNOWN;
+ }
+ else if(!mBufferAActive && mBufferBPointer < mBufferB.length)
+ {
+ mBufferBPointer = 144;
+ mBufferBPattern = DMRSyncPattern.UNKNOWN;
+ }
+ }
+
+ mAssemblingBurst = true;
+
+ if(mBufferAActive)
+ {
+ mBufferAActive = false;
+ mBufferBPointer = 0;
+ mBufferBPattern = pattern;
+
+ if(pattern.isDirect())
+ {
+ if(pattern.isDirectTS1())
+ {
+ mBufferATimeslot = 2;
+ mBufferBTimeslot = 1;
+ }
+ else if(pattern.isDirectTS2())
+ {
+ mBufferATimeslot = 1;
+ mBufferBTimeslot = 2;
+ }
+
+ if(mBufferAPattern == DMRSyncPattern.UNKNOWN)
+ {
+ mBufferAPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT;
+ }
+ }
+ }
+ else
+ {
+ mBufferAActive = true;
+ mBufferAPointer = 0;
+ mBufferAPattern = pattern;
+
+ if(pattern.isDirect())
+ {
+ if(pattern.isDirectTS1())
+ {
+ mBufferATimeslot = 1;
+ mBufferBTimeslot = 2;
+ }
+ else if(pattern.isDirectTS2())
+ {
+ mBufferATimeslot = 2;
+ mBufferBTimeslot = 1;
+ }
+
+ if(mBufferBPattern == DMRSyncPattern.UNKNOWN)
+ {
+ mBufferBPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets or updates the current dibit stream time from an incoming sample buffer.
+ * @param time to use as a reference timestamp.
+ */
+ public void setTimestamp(long time)
+ {
+ mReferenceTimestamp = time;
+ mDibitSinceTimestampCounter = 0;
+ }
+
+ /**
+ * Calculates the timestamp accurate to the currently received dibit.
+ * @return timestamp in milliseconds.
+ */
+ private long getTimestamp()
+ {
+ if(mReferenceTimestamp > 0)
+ {
+ return mReferenceTimestamp + (long)(1000.0 * mDibitSinceTimestampCounter / 4800);
+ }
+ else
+ {
+ mDibitSinceTimestampCounter = 0;
+ return System.currentTimeMillis();
}
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
index fc19560e..f0d9cbdc 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
@@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
+import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
@@ -73,7 +74,7 @@ public class DMRMessageProcessor implements Listener
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
private Listener mMessageListener;
private Map mTimeslotFrequencyMap = new TreeMap<>();
- private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager();
+ private DMRCrcMaskManager mCrcMaskManager = new DMRCrcMaskManager();
private boolean mIgnoreCrcChecksums;
/**
@@ -115,6 +116,13 @@ public class DMRMessageProcessor implements Listener
return;
}
+ if(message instanceof SyncLossMessage)
+ {
+ mSLCAssembler.reset();
+ mFLCAssemblerTimeslot1.reset();
+ mFLCAssemblerTimeslot2.reset();
+ }
+
//Detect and correct messages employing an alternate CRC mask pattern.
if(!message.isValid() && message instanceof CSBKMessage csbk)
{
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
new file mode 100644
index 00000000..c8b063f7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSoftSymbolProcessor.java
@@ -0,0 +1,458 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr;
+
+import io.github.dsheirer.dsp.filter.interpolator.PhaseAwareLinearInterpolator;
+import io.github.dsheirer.dsp.symbol.Dibit;
+import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorFactory;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncDetectMode;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncModeMonitor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
+import io.github.dsheirer.sample.Listener;
+import java.nio.ByteBuffer;
+
+/**
+ * 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.
+ *
+ * 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
+ * of the channel.
+ *
+ * This processor uses a symbol delay buffer (dibit delay line) to retain a partial DMR burst from CACH up through the
+ * sync field of the burst. This allows the optimize method to resample the burst symbols once the sync is detected and
+ * optimal symbol timing and symbol period are calculated. This ensures the best symbol decisions are then passed to
+ * 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]
+ */
+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 SYMBOL_QUADRANT_BOUNDARY = (float)(Math.PI / 2.0);
+ private static final float SYNC_DETECTION_THRESHOLD = 60;
+ private DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor();
+ private DMRSoftSyncDetector mSyncDetector = DMRSoftSyncDetectorFactory.getDetector();
+ private DMRSoftSyncDetector mSyncDetectorLag1 = DMRSoftSyncDetectorFactory.getDetector();
+ private DMRSoftSyncDetector mSyncDetectorLag2 = 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 mSamplePoint;
+ private float[] mBuffer;
+ private int mBufferLoadPointer;
+ private int mBufferPointer;
+ private int mBufferWorkspaceLength;
+ private int mSymbolsSinceLastSync = 0;
+
+ /**
+ * Constructs an instance
+ * @param messageFramer to receive demodulated symbols/dibits and sync detect notifications
+ */
+ public DMRSoftSymbolProcessor(DMRMessageFramer messageFramer)
+ {
+ if(messageFramer == null)
+ {
+ throw new IllegalArgumentException("Message framer cannot be null");
+ }
+
+ mMessageFramer = messageFramer;
+ mSyncModeMonitor.add(mSyncDetector);
+ mSyncModeMonitor.add(mSyncDetectorLag1);
+ mSyncModeMonitor.add(mSyncDetectorLag2);
+ }
+
+ /**
+ * 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.
+ */
+ public void setBaseStationMode(boolean baseStationMode)
+ {
+ if(baseStationMode)
+ {
+ mSyncModeMonitor.setMode(DMRSyncDetectMode.BASE_ONLY);
+ }
+ }
+
+ /**
+ * Registers the listener to receive demodulated bit stream buffers.
+ * @param listener to register
+ */
+ public void setBufferListener(Listener listener)
+ {
+ mDibitAssembler.setBufferListener(listener);
+ }
+
+ /**
+ * Indicates if there is a registered buffer listener
+ */
+ public boolean hasBufferListener()
+ {
+ return mDibitAssembler.hasBufferListeners();
+ }
+
+ /**
+ * Sets the reference timestamp from the incoming sample stream to use for assigning a timestamp to framed messages.
+ * @param timestamp to set for reference
+ */
+ public void setTimestamp(long timestamp)
+ {
+ mMessageFramer.setTimestamp(timestamp);
+ }
+
+ /**
+ * Primary input method for receiving a stream of demodulated samples to process into symbols.
+ * @param samples to process
+ */
+ public void receive(float[] samples)
+ {
+ int samplesPointer = 0;
+
+ while(samplesPointer < samples.length)
+ {
+ if(mBufferLoadPointer == mBuffer.length)
+ {
+ System.arraycopy(mBuffer, mBufferWorkspaceLength, mBuffer, 0, mBuffer.length - mBufferWorkspaceLength);
+ mBufferLoadPointer -= mBufferWorkspaceLength;
+ mBufferPointer -= mBufferWorkspaceLength;
+ }
+
+ int copyLength = Math.min(mBuffer.length - mBufferLoadPointer, samples.length - samplesPointer);
+ System.arraycopy(samples, samplesPointer, mBuffer, mBufferLoadPointer, copyLength);
+ samplesPointer += copyLength;
+ mBufferLoadPointer += copyLength;
+
+ while(mBufferPointer < (mBufferLoadPointer - 7)) //Interpolator needs 1 and optimizer needs 6 pad spaces
+ {
+ mBufferPointer++;
+ mSamplePoint--;
+
+ if(mSamplePoint < 1)
+ {
+ mSymbolsSinceLastSync++;
+
+ if(mSymbolsSinceLastSync > 144)
+ {
+ mSyncLock = false;
+ }
+
+ float softSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[mBufferPointer],
+ mBuffer[mBufferPointer + 1], mSamplePoint);
+
+ Dibit symbol = toSymbol(softSymbol);
+
+ //Store the symbol in the delay line and broadcast the delayed ejected symbol to the message framer
+ //and the bitstream assembler.
+ Dibit ejected = mDibitDelayLine.insert(symbol);
+ mMessageFramer.receive(ejected);
+ mDibitAssembler.receive(ejected);
+
+ float scorePrimary = mSyncDetector.process(softSymbol);
+
+ if(mSyncLock && scorePrimary > SYNC_DETECTION_THRESHOLD &&
+ optimize(mSyncDetector.getDetectedPattern(), 0.0f))
+ {
+ mSymbolsSinceLastSync = 0;
+ mSyncModeMonitor.detected(mSyncDetector.getDetectedPattern());
+ mMessageFramer.syncDetected(mSyncDetector.getDetectedPattern());
+ }
+ 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);
+
+ if(mSymbolsSinceLastSync > 1 && scoreLag1 > scorePrimary && scoreLag1 > scoreLag2 &&
+ scoreLag1 > SYNC_DETECTION_THRESHOLD &&
+ optimize(mSyncDetectorLag1.getDetectedPattern(), -mLaggingSyncOffset1))
+ {
+ 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());
+ }
+ }
+
+ //Add another symbol's worth of samples to the counter
+ mSamplePoint += mObservedSamplesPerSymbol;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * @param pattern that was detected
+ * @param additionalOffset from current mBufferPointer and mSamplePoint. This can be zero offset for the primary
+ * 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)
+ {
+ //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);
+
+ //Find the optimal symbol timing
+ float stepSize = mObservedSamplesPerSymbol / 10.0f;
+ float stepSizeMin = 0.03f;
+ float adjustment = 0.0f;
+ float adjustmentMax = mObservedSamplesPerSymbol / 2.0f;
+ float candidate = offset;
+
+ int candidateIntegral = (int)Math.floor(candidate);
+ float candidateFractional = candidate - candidateIntegral;
+ float scoreCenter = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+
+ candidate = offset - stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ float scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+
+ candidate = offset + stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ float scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+
+ while(stepSize > stepSizeMin && Math.abs(adjustment) <= adjustmentMax)
+ {
+ if(scoreLeft > scoreRight && scoreLeft > scoreCenter)
+ {
+ adjustment -= stepSize;
+ scoreRight = scoreCenter;
+ scoreCenter = scoreLeft;
+
+ candidate = offset + adjustment - stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+ }
+ else if(scoreRight > scoreLeft && scoreRight > scoreCenter)
+ {
+ adjustment += stepSize;
+ scoreLeft = scoreCenter;
+ scoreCenter = scoreRight;
+
+ candidate = offset + adjustment + stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+ }
+ else
+ {
+ stepSize *= 0.5f;
+
+ if(stepSize > stepSizeMin)
+ {
+ candidate = offset + adjustment - stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ scoreLeft = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+
+ candidate = offset + adjustment + stepSize;
+ candidateIntegral = (int)Math.floor(candidate);
+ candidateFractional = candidate - candidateIntegral;
+ scoreRight = score(candidateIntegral, candidateFractional, mObservedSamplesPerSymbol, pattern);
+ }
+ }
+ }
+
+ //If we didn't find an optimal correlation score above the 95 threshold, return a false sync.
+ if(scoreCenter < 95)
+ {
+ return false;
+ }
+
+ if(additionalOffset != 0.0)
+ {
+ adjustment += additionalOffset;
+ }
+
+ mSamplePoint += adjustment;
+
+ while(mSamplePoint < 0)
+ {
+ mSamplePoint++;
+ mBufferPointer--;
+ }
+
+ while(mSamplePoint > 1)
+ {
+ mSamplePoint--;
+ 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 && adjustment < 0.5 && mSymbolsSinceLastSync > 143 && mSymbolsSinceLastSync < 289)
+ {
+ mObservedSamplesPerSymbol += (float)((double)adjustment / (double)mSymbolsSinceLastSync * 0.2);
+ }
+
+ //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)
+ {
+ float resampleStart = mBufferPointer + mSamplePoint;
+ resampleStart -= (89 * mObservedSamplesPerSymbol); //Start at 89 (+ 1 current = 90) symbols
+ int resampleStartIntegral;
+
+ for(int x = 0; x < 66; x++)
+ {
+ resampleStartIntegral = (int)Math.floor(resampleStart);
+
+ if(resampleStartIntegral >= 0)
+ {
+ float resampledSoftSymbol = PhaseAwareLinearInterpolator.calculate(mBuffer[resampleStartIntegral],
+ mBuffer[resampleStartIntegral + 1], resampleStart - resampleStartIntegral);
+ mDibitDelayLine.insert(toSymbol(resampledSoftSymbol));
+ }
+ else
+ {
+ //This shouldn't happen since there's 2x dibits of padding on the front side, but just in case.
+ mDibitDelayLine.insert(Dibit.D01_PLUS_3);
+ }
+
+ resampleStart += mObservedSamplesPerSymbol;
+ }
+
+ //We don't need to resample the sync region ... just use the actual sync dibit values.
+ for(Dibit dibit: pattern.toDibits())
+ {
+ mDibitDelayLine.insert(dibit);
+ }
+ }
+ else
+ {
+ //Overwrite the most recent 24 dibits with the detected sync so there's no sync bit errors
+ mDibitDelayLine.update(pattern.toDibits());
+ }
+
+ return true;
+ }
+
+ /**
+ * 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.
+ * @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)
+ {
+ float score = 0;
+ float[] symbols = pattern.toSymbols();
+ float[] softSymbols = new float[24];
+ int integral;
+
+ for(int x = 0; x < 24; x++)
+ {
+ float 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);
+ softSymbols[x] = softSymbol;
+ score += softSymbol * symbols[x];
+ fractional += samplesPerSymbol;
+ integral = (int)Math.floor(fractional);
+ bufferPointer += integral;
+ fractional -= integral;
+ }
+
+ return score;
+ }
+
+ /**
+ * Sets or updates the samples per symbol
+ * @param samplesPerSymbol to apply.
+ */
+ public void setSamplesPerSymbol(float samplesPerSymbol)
+ {
+ mSamplesPerSymbol = samplesPerSymbol;
+ mObservedSamplesPerSymbol = mSamplesPerSymbol;
+ mSamplePoint = mSamplesPerSymbol;
+ mLaggingSyncOffset1 = mSamplesPerSymbol / 3;
+ mLaggingSyncOffset2 = mLaggingSyncOffset1 * 2;
+ mBufferWorkspaceLength = (int)Math.ceil(BUFFER_WORKSPACE_LENGTH_DIBITS * mSamplesPerSymbol);
+ int bufferLength = (int)(Math.ceil(BUFFER_LENGTH_DIBITS * mSamplesPerSymbol));
+ mBuffer = new float[bufferLength];
+ mBufferLoadPointer = (int)Math.ceil(BUFFER_PROTECTED_REGION_DIBITS * mSamplesPerSymbol);
+ mBufferPointer = mBufferLoadPointer;
+ }
+
+ /**
+ * Decodes the sample value to determine the correct QPSK quadrant and maps the value to a Dibit symbol.
+ * @param sample in radians.
+ * @return symbol decision.
+ */
+ private static Dibit toSymbol(float sample)
+ {
+ if(sample > 0)
+ {
+ return sample > SYMBOL_QUADRANT_BOUNDARY ? Dibit.D01_PLUS_3 : Dibit.D00_PLUS_1;
+ }
+ else
+ {
+ return sample < -SYMBOL_QUADRANT_BOUNDARY ? Dibit.D11_MINUS_3 : Dibit.D10_MINUS_1;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java
deleted file mode 100644
index 4bccc8f5..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncDetector.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package io.github.dsheirer.module.decode.dmr;
-
-import io.github.dsheirer.dsp.symbol.Dibit;
-import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
-import org.apache.commons.lang3.Validate;
-
-/**
- * Detector for DMR sync patterns that also includes support for detecting PLL phase lock errors. There are
- * two primary use cases for this class:
- *
- * The first is to detect a sync pattern from a stream of dibits that are processed via the add(dibit) method. After
- * each dibit is added, the sync pattern can be inspected by either the hasSync() method or the getSyncPattern()
- * method. The maxStreamBitErrors construction parameter specifies how many bit errors can be present when matching
- * patterns to the dibit stream.
- *
- * The second use case is to set a candidate sync pattern value using the setCurrentSyncValue() method. You can then
- * use the hasSync() and getSyncPatter() methods to determine if the set value matches a valid sync pattern. The
- * maxExplicitBitErrors specifies how many bit errors can be present when matching patterns to the explicitly set
- * sync value.
- *
- * Note: sync pattern matching maximum bit errors uses a hard-coded value for PLL misalignment sync detection since
- * this threshold should be relatively high to avoid false triggering and once detected, a PLL should be immediately
- * commanded to correct the issue.
- */
-public class DMRSyncDetector
-{
- private static long SYNC_MASK = 0xFFFFFFFFFFFFl;
- private static final int MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT = 1;
- private int mMaxStreamBitErrors;
- private int mMaxExplicitBitErrors;
- private long mCurrentSyncValue;
- private long mErrorPattern;
- private int mPatternMatchBitErrorCount;
- private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
- private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
-
- /**
- * Constructs an instance
- * @param maxStreamBitErrors allowed for matching sync patterns when using the dibit streaming use case.
- * @param maxExplicitBitErrors allows for matching sync patterns when using the explicitly set sync value use case.
- */
- public DMRSyncDetector(int maxStreamBitErrors, int maxExplicitBitErrors)
- {
- Validate.inclusiveBetween(0, 24, maxStreamBitErrors,
- "Max (allowable) stream bit errors for sync match must be between 0 and 24");
- Validate.inclusiveBetween(0, 24, maxExplicitBitErrors,
- "Max (allowable) explicit bit errors for sync match must be between 0 and 24");
- mMaxStreamBitErrors = maxStreamBitErrors;
- mMaxExplicitBitErrors = maxExplicitBitErrors;
- }
-
- /**
- * Sync pattern for the current sync value.
- */
- public DMRSyncPattern getSyncPattern()
- {
- return mSyncPattern;
- }
-
- /**
- * Indicates if there is a sync pattern in the currently set sync value that was either explicitly set or was
- * aggregated from a stream of dibits. When this method returns true, the sync pattern can be accessed via the
- * getSyncPattern() method.
- */
- public boolean hasSync()
- {
- return getSyncPattern() != DMRSyncPattern.UNKNOWN;
- }
-
- /**
- * Carrier lock for the QPSK PLL to detect when the PLL has locked to the signal at +/- 90 degrees or 180 degrees.
- * @return carrier lock value.
- */
- public QPSKCarrierLock getCarrierLock()
- {
- return mCarrierLock;
- }
-
- /**
- * Indicates if the detected sync pattern has normal QPSK carrier lock.
- */
- public boolean hasNormalCarrierLock()
- {
- return getCarrierLock() == QPSKCarrierLock.NORMAL;
- }
-
- /**
- * Number of bit errors that were present when matching the current sync value to the current sync pattern.
- */
- public int getPatternMatchBitErrorCount()
- {
- return mPatternMatchBitErrorCount;
- }
-
- /**
- * Processes the streaming dibit by left shifting the current sync value and adding the dibit onto the end and then
- * test for sync pattern match.
- * @param dibit to process and test for sync pattern.
- */
- public void add(Dibit dibit)
- {
- mCurrentSyncValue = Long.rotateLeft(mCurrentSyncValue, 2);
- mCurrentSyncValue &= SYNC_MASK;
- mCurrentSyncValue += dibit.getValue();
- checkSync(mMaxStreamBitErrors);
- }
-
- /**
- * Explicitly sets the argument as the current sync value and tests for sync pattern match.
- * @param value to load as the current sync value and test
- */
- public void setCurrentSyncValue(long value)
- {
- mCurrentSyncValue = value;
- checkSync(mMaxExplicitBitErrors);
- }
-
- /**
- * Checks the current sync value against each of the sync patterns to determine if the value matches a pattern.
- */
- private void checkSync(int maxBitErrors)
- {
- for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS)
- {
- mErrorPattern = mCurrentSyncValue ^ pattern.getPattern();
-
- if(mErrorPattern == 0)
- {
- mSyncPattern = pattern;
- mCarrierLock = QPSKCarrierLock.NORMAL;
- mPatternMatchBitErrorCount = 0;
- return;
- }
-
- mPatternMatchBitErrorCount = Long.bitCount(mErrorPattern);
-
- if(mPatternMatchBitErrorCount <= maxBitErrors)
- {
- mSyncPattern = pattern;
- mCarrierLock = QPSKCarrierLock.NORMAL;
- return;
- }
-
-
- //For PLL mis-aligned lock patterns, reduce the pattern bit error match threshold
- mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getPlus90Pattern());
-
- if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
- {
- mSyncPattern = pattern;
- mCarrierLock = QPSKCarrierLock.PLUS_90;
- return;
- }
-
- mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getMinus90Pattern());
-
- if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
- {
- mSyncPattern = pattern;
- mCarrierLock = QPSKCarrierLock.MINUS_90;
- return;
- }
-
- mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getInvertedPattern());
-
- if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
- {
- mSyncPattern = pattern;
- mCarrierLock = QPSKCarrierLock.INVERTED;
- return;
- }
- }
-
- mSyncPattern = DMRSyncPattern.UNKNOWN;
- mCarrierLock = QPSKCarrierLock.NORMAL;
- mPatternMatchBitErrorCount = 0;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java
deleted file mode 100644
index cc7d4e6b..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-package io.github.dsheirer.module.decode.dmr;
-
-import java.util.EnumSet;
-
-/**
- * DMR burst sync patterns enumeration. In addition to the ETSI defined sync patterns, each enumeration entry also
- * contains derived sync patterns that match decoded bit streams where a QPSK Phase-Locked Loop has locked to the signal
- * misaligned by +/- 90 degrees, or 180 degrees. These derived patterns can be used to detect PLL misalignment for
- * PLL correction purposes.
- */
-public enum DMRSyncPattern
-{
- BASE_STATION_DATA(0xDFF57D75DF5Dl, 0xBAAFEBEFBAFBl, 0x455014104504l, 0x200A828A20A2l, "BS DATA"),
- BASE_STATION_VOICE(0x755FD7DF75F7l, 0xEFFABEBAEFAEl, 0x100541451051l, 0x8AA028208A08l, "BS VOICE A"),
-
- MOBILE_STATION_DATA(0xD5D7F77FD757l, 0xBFBEAEEABEFEl, 0x404151154101l, 0x2A28088028A8l, "MS DATA"),
- MOBILE_STATION_VOICE(0x7F7D5DD57DFDl, 0xEAEBFBBFEBABl, 0x151404401454l, 0x8082A22A8202l, "MS VOICE A"),
- DIRECT_MODE_DATA_TIMESLOT_1(0xF7FDD5DDFD55l, 0xAEABBFBBABFFl, 0x515440445400l, 0x08022A2202AAl, "MS DATA - DUAL CAPACITY DIRECT MODE"),
- DIRECT_MODE_DATA_TIMESLOT_2(0xD7557F5FF7F5l, 0xBEFFEAFAAEAFl, 0x410015055150l, 0x28AA80A0080Al, "MS DATA - DUAL CAPACITY DIRECT MODE"),
- DIRECT_MODE_VOICE_TIMESLOT_1(0x5D577F7757FFl, 0xFBFEEAEEFEAAl, 0x040115110155l, 0xA2A88088A800l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"),
- DIRECT_MODE_VOICE_TIMESLOT_2(0x7DFFD5F55D5Fl, 0xEBAABFAFFBFAl, 0x145540500405l, 0x82002A0AA2A0l, "MS VOICE A - DUAL CAPACITY DIRECT MODE"),
- MOBILE_STATION_REVERSE_CHANNEL(0x77D55F7DFD77l, 0xEEBFFAEBABEEl, 0x114005145411l, 0x882AA0820288l, "MS RVRS"),
-
- RESERVED(0xDD7FF5D757DDl, 0xBBEAAFBEFEBBl, 0x441550410144l, 0x22800A28A822l, "RESERVED"),
-
- //These are used to identify the sync-less sub frames of a voice super frame
- BS_VOICE_FRAME_B("BS VOICE B"),
- BS_VOICE_FRAME_C("BS VOICE C"),
- BS_VOICE_FRAME_D("BS VOICE D"),
- BS_VOICE_FRAME_E("BS VOICE E"),
- BS_VOICE_FRAME_F("BS VOICE F"),
-
- MS_VOICE_FRAME_B("MS VOICE B"),
- MS_VOICE_FRAME_C("MS VOICE C"),
- MS_VOICE_FRAME_D("MS VOICE D"),
- MS_VOICE_FRAME_E("MS VOICE E"),
- MS_VOICE_FRAME_F("MS VOICE F"),
-
- UNKNOWN("UNKNOWN");
-
- private long mPattern;
- private long mPlus90Pattern;
- private long mMinus90Pattern;
- private long mInvertedPattern;
- private String mLabel;
-
- /**
- * DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1
- */
- DMRSyncPattern(long pattern, long plus90Pattern, long minus90Pattern, long invertedPattern, String label)
- {
- mPattern = pattern;
- mPlus90Pattern = plus90Pattern;
- mMinus90Pattern = minus90Pattern;
- mInvertedPattern = invertedPattern;
- mLabel = label;
- }
-
- /**
- * Alternate constructor for syncs that don't have defined patterns.
- */
- DMRSyncPattern(String label)
- {
- mLabel = label;
- }
-
- //Valid sync patterns (excluding the sync-less voice patterns)
- public static final EnumSet SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
- MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_MODE_DATA_TIMESLOT_1, DIRECT_MODE_DATA_TIMESLOT_2,
- DIRECT_MODE_VOICE_TIMESLOT_1, DIRECT_MODE_VOICE_TIMESLOT_2, MOBILE_STATION_REVERSE_CHANNEL);
-
- //Sync patterns containing a Common Announcement Channel (CACH)
- private static final EnumSet CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
- BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F);
-
- //Direct Mode TS 1 Sync Patterns
- private static final EnumSet DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_1,
- DIRECT_MODE_DATA_TIMESLOT_1);
-
- //Direct Mode TS 2 Sync Patterns
- private static final EnumSet DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_2,
- DIRECT_MODE_DATA_TIMESLOT_2);
-
- //Mobile Station Sync Patterns
- private static final EnumSet MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA,
- MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F);
-
- /**
- * Pattern that represents the enum entry
- */
- public long getPattern()
- {
- return mPattern;
- }
-
- /**
- * Pattern that represents the PLL locking to the carrier misaligned by plus 90 degrees.
- */
- public long getPlus90Pattern()
- {
- return mPlus90Pattern;
- }
-
- /**
- * Pattern that represents the PLL locking to the carrier misaligned by minus 90 degrees.
- */
- public long getMinus90Pattern()
- {
- return mMinus90Pattern;
- }
-
- /**
- * Pattern that represents the PLL locking to the carrier misaligned by 180 degrees (inverted).
- */
- public long getInvertedPattern()
- {
- return mInvertedPattern;
- }
-
- /**
- * Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH).
- */
- public boolean hasCACH()
- {
- return CACH_PATTERNS.contains(this);
- }
-
- /**
- * Indicates if this is a Direct Mode sync pattern
- */
- public boolean isDirectMode()
- {
- return isDirectModeTS1() || isDirectModeTS2();
- }
-
- /**
- * Indicates if this is Direct Mode sync pattern for timestot 1
- * @return true if DMTS1
- */
- public boolean isDirectModeTS1()
- {
- return DIRECT_MODE_TS1_PATTERNS.contains(this);
- }
-
- /**
- * Indicates if this is Direct Mode sync pattern for timestot 2
- * @return true if DMTS2
- */
- public boolean isDirectModeTS2()
- {
- return DIRECT_MODE_TS2_PATTERNS.contains(this);
- }
-
- /**
- * Indicates if this is a mobile station sync pattern
- * @return true if a mobile station sync pattern.
- */
- public boolean isMobileStationSyncPattern()
- {
- return MOBILE_STATION_PATTERNS.contains(this);
- }
-
- /**
- * Indicates if this is a mobile sync pattern, either mobile station or direct mode.
- * @return true if a mobile sync pattern.
- */
- public boolean isMobileSyncPattern()
- {
- return isMobileStationSyncPattern() || isDirectMode();
- }
-
- /**
- * String representation of the sync pattern
- */
- public String toString()
- {
- return mLabel;
- }
-}
\ No newline at end of file
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
new file mode 100644
index 00000000..5cc072c7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DibitDelayLine.java
@@ -0,0 +1,78 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+import java.util.Arrays;
+
+/**
+ * Implements a circular buffer delay line for dibits.
+ */
+public class DibitDelayLine
+{
+ private Dibit[] mDelayLine;
+ public int mPointer;
+ private int mLength;
+
+ /**
+ * Constructs an instance
+ * @param length of the delay line in dibits.
+ */
+ public DibitDelayLine(int length)
+ {
+ mLength = length;
+ mDelayLine = new Dibit[length];
+ Arrays.fill(mDelayLine, Dibit.D00_PLUS_1);
+ }
+
+ /**
+ * Inserts the dibit into the delay line and returns the oldest dibit from the delay line.
+ * @param dibit to insert
+ * @return oldest dibit
+ */
+ public Dibit insert(Dibit dibit)
+ {
+ Dibit ejected = mDelayLine[mPointer];
+ mDelayLine[mPointer++] = dibit;
+ mPointer %= mLength;
+ return ejected;
+ }
+
+ /**
+ * Overwrites the most recently added dibits in the delay line with the provided sequence. This can be used when
+ * a sync pattern is detected to overwrite the calculated symbols with the known sync pattern.
+ * @param dibits to overwrite
+ */
+ public void update(Dibit[] dibits)
+ {
+ mPointer = mPointer - dibits.length;
+
+ if(mPointer < 0)
+ {
+ mPointer += mDelayLine.length;
+ }
+
+ for(Dibit dibit: dibits)
+ {
+ mDelayLine[mPointer++] = dibit;
+ mPointer %= mLength;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java b/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java
deleted file mode 100644
index c9d8d17b..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/IDMRBurstDetectListener.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * ******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2019 Dennis Sheirer, Zhenyu Mao
- *
- * 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;
-
-
-import io.github.dsheirer.bits.CorrectedBinaryMessage;
-
-/**
- * Listener interface to be notified each time a DMR burst has been detected or when sync is or continues to be lost.
- */
-public interface IDMRBurstDetectListener
-{
- /**
- * Indicates that a DMR sync has been detected and binary message with the burst contents are ready for processing
- * @param binaryMessage burst Binary Message and any bit errors detected in the message
- * @param pattern SyncPattern
- */
- void burstDetected(CorrectedBinaryMessage binaryMessage, DMRSyncPattern pattern, int timeslot);
-
- /**
- * Indicates that sync has been lost on the dibit stream
- *
- * @param bitsProcessed since the last sync detect
- * @param timeslot that was being tracked when the sync loss was detected
- */
- void syncLost(int bitsProcessed, int timeslot);
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java b/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java
deleted file mode 100644
index 87094fdf..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/TimeslotAlignmentTracker.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- * ****************************************************************************
- */
-
-package io.github.dsheirer.module.decode.dmr;
-
-import io.github.dsheirer.module.decode.dmr.message.CACH;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Tracks proper alignment of timeslots into the two bursts of a burst framer.
- */
-public class TimeslotAlignmentTracker
-{
- private final static Logger mLog = LoggerFactory.getLogger(TimeslotAlignmentTracker.class);
-
- private int mBurst1Ts1 = 0;
- private int mBurst1Ts2 = 0;
- private int mBurst2Ts1 = 0;
- private int mBurst2Ts2 = 0;
-
- /**
- * Indicates if the track has accumulated enough cach data to accurately declare timeslot alignment. The test
- * checks that both timeslots each have different counts for timeslot 0 and 1 and that both timeslots each indicate
- * a different dominant timeslot than the other.
- */
- public boolean hasSufficientData()
- {
- return ((mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
- (mBurst1Ts1 < mBurst1Ts2 && mBurst2Ts2 < mBurst2Ts1) ||
- (mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts1 != mBurst2Ts2) ||
- (mBurst1Ts1 != mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst1Ts2 == 0));
- }
-
- /**
- * Indicates if the timeslots are aligned by inspection of the CACH value for frame 0 and frame 1
- */
- public boolean isAligned()
- {
- return (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
- (mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts2 > mBurst2Ts1) ||
- (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst2Ts2 == 0);
- }
-
- /**
- * Updates the tracker with the cach fields or sync patterns from the current TDMA burst frames
- * @param cach1 optional cach that is currently aligned as timeslot 1
- * @param cach2 optional cach that is currently aligned as timeslot 2
- * @param pattern1 sync pattern for the burst currently aligned as timeslot 1
- * @param pattern2 sync pattern for the burst currently aligned as timeslot 2
- */
- public void update(CACH cach1, CACH cach2, DMRSyncPattern pattern1, DMRSyncPattern pattern2)
- {
- if(cach1 != null && cach1.isValid())
- {
- if(cach1.isTimeslot0())
- {
- mBurst1Ts1++;
- }
- else
- {
- mBurst1Ts2++;
- }
- }
-
- if(cach2 != null && cach2.isValid())
- {
- if(cach2.isTimeslot0())
- {
- mBurst2Ts1++;
- }
- else
- {
- mBurst2Ts2++;
- }
- }
-
- if(pattern1.isDirectMode())
- {
- switch((pattern1))
- {
- case DIRECT_MODE_DATA_TIMESLOT_1:
- case DIRECT_MODE_VOICE_TIMESLOT_1:
- mBurst1Ts1++;
- break;
- case DIRECT_MODE_DATA_TIMESLOT_2:
- case DIRECT_MODE_VOICE_TIMESLOT_2:
- mBurst1Ts2++;
- break;
- }
- }
-
- if(pattern2.isDirectMode())
- {
- switch((pattern2))
- {
- case DIRECT_MODE_DATA_TIMESLOT_1:
- case DIRECT_MODE_VOICE_TIMESLOT_1:
- mBurst2Ts1++;
- break;
- case DIRECT_MODE_DATA_TIMESLOT_2:
- case DIRECT_MODE_VOICE_TIMESLOT_2:
- mBurst2Ts2++;
- break;
- }
- }
- }
-
- /**
- * Resets the tracker values.
- */
- public void reset()
- {
- mBurst1Ts1 = 0;
- mBurst1Ts2 = 0;
- mBurst2Ts1 = 0;
- mBurst2Ts2 = 0;
- }
-
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
- sb.append("BURST 1: ").append(mBurst1Ts1).append("/").append(mBurst1Ts2);
- sb.append(" BURST 2:").append(mBurst2Ts1).append("/").append(mBurst2Ts2);
- if(hasSufficientData())
- {
- if(isAligned())
- {
- sb.append(" **ALIGNED**");
- }
- else
- {
- sb.append(" >>MIS-ALIGNED<<");
- }
- }
- return sb.toString();
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java
index 69177173..eb503656 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,13 +33,14 @@ import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.module.decode.dmr.identifier.DMRToneIdentifier;
+import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
-import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
-import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
@@ -98,7 +99,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
}
/**
- * Processes DMR AMBE audio frame
+ * Processes DMR AMBE audio frames and signalling messages.
*/
public void receive(IMessage message)
{
@@ -108,49 +109,69 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
if(!mEncryptedCallStateEstablished)
{
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
- if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirectMode())
+ if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirect())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = false;
}
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
//in the EMB field.
- else if(message instanceof VoiceEMBMessage voiceMessage && voiceMessage.getEMB().isValid())
+ else if(message instanceof VoiceEMBMessage voice)
+ {
+ if(voice.hasEmbeddedParameters() &&
+ voice.getEmbeddedParameters().getShortBurst() instanceof EmbeddedEncryptionParameters)
+ {
+ mEncryptedCallStateEstablished = true;
+ mEncryptedCall = true;
+ }
+ else if(voice.getEMB().isValid())
+ {
+ mEncryptedCallStateEstablished = true;
+ mEncryptedCall = voice.getEMB().isEncrypted();
+ }
+ }
+ else if(message instanceof VoiceHeader vh && vh.getLCMessage() instanceof AbstractVoiceChannelUser vcu &&
+ vcu.isValid())
{
mEncryptedCallStateEstablished = true;
- mEncryptedCall = voiceMessage.getEMB().isEncrypted();
+ mEncryptedCall = vcu.getServiceOptions().isEncrypted();
}
- else if(message instanceof VoiceHeader voiceHeader)
+ else if(message instanceof PiHeader pi && pi.getLCMessage() instanceof EncryptionParameters ep &&
+ ep.isValid())
{
- LCMessage lc = voiceHeader.getLCMessage();
-
- if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
- {
- if(flc.isValid())
- {
- mEncryptedCallStateEstablished = true;
- mEncryptedCall = flc.isEncrypted();
- }
- }
+ mEncryptedCallStateEstablished = true;
+ mEncryptedCall = true;
}
//Note: the DMRMessageProcessor extracts Full Link Control messages from Voice Frames B-C and sends them
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
- else if(message instanceof AbstractVoiceChannelUser avcu)
+ else if(message instanceof AbstractVoiceChannelUser avcu && avcu.isValid())
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = avcu.getServiceOptions().isEncrypted();
}
+
+ if(mEncryptedCall)
+ {
+ mQueuedAmbeFrames.clear();
+ }
}
//Queue or process audio frames. Note: audio frames are held/queued until encrypted state is established
//before any audio is generated for the audio segment.
if(message instanceof VoiceMessage voiceMessage)
{
- List frames = voiceMessage.getAMBEFrames();
- for(byte[] frame: frames)
+ if(mEncryptedCallStateEstablished && mEncryptedCall)
{
- processAudio(frame, message.getTimestamp());
+ mQueuedAmbeFrames.clear();
+ }
+ else
+ {
+ List frames = voiceMessage.getAMBEFrames();
+ for(byte[] frame: frames)
+ {
+ processAudio(frame, message.getTimestamp());
+ }
}
}
else if(message instanceof Terminator)
@@ -168,23 +189,30 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
{
if(mEncryptedCallStateEstablished)
{
- //Process any ambe frames that were queued awaiting encryption state determination
- if(!mQueuedAmbeFrames.isEmpty())
+ if(mEncryptedCall)
{
- if(!mEncryptedCall)
+ mQueuedAmbeFrames.clear();
+ }
+ else
+ {
+ //Process any ambe frames that were queued awaiting encryption state determination
+ if(!mQueuedAmbeFrames.isEmpty())
{
- for(byte[] queuedFrame: mQueuedAmbeFrames)
+ if(!mEncryptedCall)
{
- produceAudio(queuedFrame, timestamp);
+ for(byte[] queuedFrame: mQueuedAmbeFrames)
+ {
+ produceAudio(queuedFrame, timestamp);
+ }
+
+ mQueuedAmbeFrames.clear();
}
mQueuedAmbeFrames.clear();
}
- mQueuedAmbeFrames.clear();
+ produceAudio(frame, timestamp);
}
-
- produceAudio(frame, timestamp);
}
else
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java
new file mode 100644
index 00000000..fd111a7d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java
@@ -0,0 +1,360 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.bptc;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.IHamming;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Block Product Turbo Code (BPTC) base implementation for BPTC codes using simple even parity column checks.
+ */
+public abstract class BPTCBase
+{
+ public static int ERRORS_NOT_CORRECTED = -2;
+ private IHamming mHamming;
+ private int mColumnCount;
+ private int mRowCount;
+
+ /**
+ * Constructs an instance
+ * @param hamming to provide row checking using the correct Hamming error detection and correction algorithm.
+ * @param column count
+ * @param row count
+ */
+ public BPTCBase(IHamming hamming, int columnCount, int rowCount)
+ {
+ mHamming = hamming;
+ mColumnCount = columnCount;
+ mRowCount = rowCount;
+ }
+
+ /**
+ * Performs error detection and correction.
+ * @param message to correct
+ * @return true if the message was corrected or false if there are uncorrectable errors.
+ */
+ public boolean correct(CorrectedBinaryMessage message)
+ {
+ BinaryMessage columns = getColumnErrors(message);
+ BinaryMessage rows = getRowErrors(message);
+
+ if(columns.isEmpty() && rows.isEmpty())
+ {
+ return true;
+ }
+
+ List correctedIndexes = new ArrayList<>();
+
+ //1: fix non-shadowed single bit errors in each row
+ for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1))
+ {
+ int index = correct1BitErrors(row, message, rows);
+
+ if(index >= 0)
+ {
+ correctedIndexes.add(index);
+ }
+ }
+
+ //Refresh the column errors
+ columns = getColumnErrors(message);
+
+ if(columns.isEmpty() && rows.isEmpty())
+ {
+ message.incrementCorrectedBitCount(correctedIndexes.size());
+ return true;
+ }
+
+ //2: fix non-shadowed multi bit errors (up to 2) in each row
+ if(columns.cardinality() <= 2)
+ {
+ for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1))
+ {
+ List solution = correctMultiBitErrors(row, message, columns, rows);
+
+ if(solution.size() > 0)
+ {
+ correctedIndexes.addAll(solution);
+ }
+ }
+ }
+
+ if(columns.isEmpty() && rows.isEmpty())
+ {
+ message.incrementCorrectedBitCount(correctedIndexes.size());
+ return true;
+ }
+
+ message.setCorrectedBitCount(ERRORS_NOT_CORRECTED);
+ return false;
+ }
+
+ /**
+ * Indicates if the column passes an even parity check.
+ * @param column to test
+ * @return true if the parity check passes
+ */
+ public boolean isColumnCorrect(int column, BinaryMessage message)
+ {
+ boolean correct = true;
+
+ for(int row = 0; row < mRowCount; row++)
+ {
+ correct ^= message.get(row * mColumnCount + column);
+ }
+
+ return correct;
+ }
+
+ /**
+ * Calculates the column from the bit index/offset
+ * @param index for a bit
+ * @return column that contains the index
+ */
+ public int getColumn(int index)
+ {
+ return index % mColumnCount;
+ }
+
+ /**
+ * Calculates the index for a bit that exists at the row and column.
+ * @param column for the bit.
+ * @param row for the bit
+ * @return index for the bit.
+ */
+ public int getIndex(int column, int row)
+ {
+ return row * mColumnCount + column;
+ }
+
+ /**
+ * Checks the row for bit errors.
+ * @param row to check
+ * @param message containing bits to check
+ * @return true if the row passes the Hamming check or false if not.
+ */
+ private boolean isRowCorrect(int row, CorrectedBinaryMessage message)
+ {
+ return mHamming.getErrorIndex(message, row * mColumnCount) == IHamming.NO_ERRORS;
+ }
+
+ /**
+ * Calculates the index for the bit error in the message
+ * @param row to check
+ * @param message with bits
+ * @return error index in the range 0-195, or -1 if the message row has no bit errors.
+ */
+ private int getRowErrorIndex(int row, CorrectedBinaryMessage message)
+ {
+ return mHamming.getErrorIndex(message, row * mColumnCount);
+ }
+
+ /**
+ * Returns a bit map indicating the row errors.
+ * @param message containing rows.
+ * @return row error map
+ */
+ public BinaryMessage getRowErrors(CorrectedBinaryMessage message)
+ {
+ BinaryMessage rows = new BinaryMessage(mRowCount);
+
+ for(int row = 0; row < mRowCount; row++)
+ {
+ rows.set(row, !isRowCorrect(row, message));
+ }
+
+ return rows;
+ }
+
+ /**
+ * Returns a bit map indicating the column errors.
+ * @param message containing rows.
+ * @return column error map
+ */
+ public BinaryMessage getColumnErrors(CorrectedBinaryMessage message)
+ {
+ BinaryMessage columns = new BinaryMessage(mColumnCount);
+
+ for(int column = 0; column < mColumnCount; column++)
+ {
+ columns.set(column, !isColumnCorrect(column, message));
+ }
+
+ return columns;
+ }
+
+ /**
+ * Corrects single bit errors in each row using only the Hamming error index.
+ * @param row to correct
+ * @param message with bits
+ * @param rows error flag set
+ * @return bit index that was corrected or -2 if the error was not corrected
+ */
+ public int correct1BitErrors(int row, CorrectedBinaryMessage message, BinaryMessage rows)
+ {
+ int index = getRowErrorIndex(row, message);
+
+ if(index >= 0 && index < IHamming.MULTIPLE_ERRORS)
+ {
+ message.flip(index);
+
+ if(isRowCorrect(row, message))
+ {
+ rows.clear(row);
+ return index;
+ }
+
+ message.flip(index);
+ }
+
+ return ERRORS_NOT_CORRECTED;
+ }
+
+ /**
+ * Corrects multiple bit errors in a single row when all other rows are corrected first, by flipping the
+ * bits in the row that show errors in the columns and testing the solution.
+ * @param row to correct
+ * @param message with bits
+ * @param columns bit error matrix
+ * @param rows bit error matrix
+ * @return solution or an empty list if no solution.
+ */
+ public List correctMultiBitErrors(int row, CorrectedBinaryMessage message, BinaryMessage columns,
+ BinaryMessage rows)
+ {
+ //Test flipping bits in the row everywhere that we have a column error
+ List testFlips = new ArrayList<>();
+
+ for(int column = columns.nextSetBit(0); column >= 0 && column < mColumnCount;
+ column = columns.nextSetBit(column + 1))
+ {
+ int toFlip = getIndex(column, row);
+ message.flip(toFlip);
+ testFlips.add(toFlip);
+ }
+
+ if(!isRowCorrect(row, message))
+ {
+ int index = getRowErrorIndex(row, message);
+
+ if(index >= 0 && index < IHamming.MULTIPLE_ERRORS)
+ {
+ message.flip(index);
+
+ if(testFlips.contains(index))
+ {
+ testFlips.removeIf(value -> value == index);
+ }
+ else
+ {
+ testFlips.add(index);
+ }
+ }
+ }
+
+ if(isRowCorrect(row, message))
+ {
+ //Check the columns also
+
+ boolean columnsCorrect = true;
+
+ for(Integer flip: testFlips)
+ {
+ columnsCorrect &= isColumnCorrect(getColumn(flip), message);
+ }
+
+ if(columnsCorrect)
+ {
+ rows.clear(row);
+
+ for(Integer flip: testFlips)
+ {
+ columns.clear(getColumn(flip));
+ }
+
+ return testFlips;
+ }
+ }
+
+ for(Integer flip: testFlips)
+ {
+ message.flip(flip);
+ }
+
+ return Collections.emptyList();
+ }
+
+ public void logErrorMap(CorrectedBinaryMessage message)
+ {
+ StringBuilder sb = new StringBuilder();
+ for(int row = 0; row < mRowCount; row++)
+ {
+ int offset = row * mColumnCount;
+ sb.append("Row ").append(row).append(": ");
+ sb.append(message.getSubMessage(offset, offset + mColumnCount));
+ sb.append(" ").append(offset).append(":").append(offset + mColumnCount).append("\n");
+ }
+
+ System.out.println(sb);
+ }
+
+ public void logDiagnostic(CorrectedBinaryMessage message)
+ {
+ StringBuilder sb = new StringBuilder();
+ for(int row = 0; row < mRowCount; row++)
+ {
+ int offset = row * mColumnCount;
+ sb.append(" Row ").append(row).append(": ");
+ sb.append(message.getSubMessage(offset, offset + mColumnCount));
+ int index = getRowErrorIndex(row, message);
+ if(index != IHamming.NO_ERRORS)
+ {
+ sb.append(" < Index:").append(index);
+
+ if(index == IHamming.MULTIPLE_ERRORS)
+ {
+ sb.append(" (2+ bit errors)");
+ }
+ }
+ sb.append("\n");
+ }
+
+ sb.append("Errors: ");
+
+ BinaryMessage columnErrors = getColumnErrors(message);
+ for(int column = 0; column < mColumnCount; column++)
+ {
+ if(columnErrors.get(column))
+ {
+ sb.append("^");
+ }
+ else
+ {
+ sb.append(".");
+ }
+ }
+
+ System.out.println("Diagnostic:\n" + sb + "\n");
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java
new file mode 100644
index 00000000..274a70c0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java
@@ -0,0 +1,132 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.bptc;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.Hamming16;
+
+/**
+ * Block product turbo code (BPTC) for decoding Full Link Control messages reassembled from the 32-bit payload fragments
+ * in each of the Voice B-E messages.
+ */
+public class BPTC_128_77 extends BPTCBase
+{
+ public BPTC_128_77()
+ {
+ super(new Hamming16(), 16, 8);
+ }
+
+ /**
+ * Performs error detection and correction and extracts the payload from the BPTC encoded message.
+ * @param message with BPTC encoding.
+ * @return decoded message.
+ */
+ public CorrectedBinaryMessage extract(BinaryMessage message)
+ {
+ CorrectedBinaryMessage deinterleaved = deinterleave(message);
+ correct(deinterleaved);
+
+ CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
+ int pointer = 0;
+
+ //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
+ for(int row = 0; row < 2; row++)
+ {
+ for(int column = 0; column < 11; column++)
+ {
+ extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column));
+ }
+ }
+
+ for(int row = 2; row < 7; row++)
+ {
+ for(int column = 0; column < 10; column++)
+ {
+ extractedMessage.set(pointer++, deinterleaved.get(row * 16 + column));
+ }
+
+ //Transfer the checksum parity bit from this row
+ extractedMessage.set(70 + row, deinterleaved.get(row * 16 + 10));
+ }
+
+ extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount());
+ return extractedMessage;
+ }
+
+ /**
+ * Deinterleaves the transmitted message.
+ * @param interleaved message
+ * @return deinterleaved message.
+ */
+ public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved)
+ {
+ CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(128);
+ int index;
+ deinterleaved.set(127, interleaved.get(127));
+ for(int i = 0; i < 127; i++)
+ {
+ index = (i * 8) % 127;
+ deinterleaved.set(i, interleaved.get(index));
+ }
+
+ return deinterleaved;
+ }
+
+ public static void main(String[] args)
+ {
+ BPTC_128_77 bptc = new BPTC_128_77();
+ String deinterleavedRaw = "00000000000000000000000000000000001000000010010101011001001000010000000000000000000000000110101000010110111100010000111110011111";
+
+ String deinterleavedReference = "00000000000000000000000000000000000000000010011100011001001000110000000000000000000000000110101000010110111100010000111110011111"; //No errors
+ CorrectedBinaryMessage deinterleavedReferenceMessage = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedReference));
+
+ String interleaved = "00000000000000000000000000010010000100010000001000000010000100010000001100000110001101100000001100000101001000010010010100110011"; //Under test
+ CorrectedBinaryMessage interleavedMessage = new CorrectedBinaryMessage(BinaryMessage.load(interleaved));
+// CorrectedBinaryMessage deinterleaved = deinterleave(interleavedMessage);
+ CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedRaw));
+
+
+ String deinterleavedUncorrected = deinterleaved.toString();
+ bptc.logDiagnostic(deinterleaved);
+ bptc.correct(deinterleaved);
+
+ System.out.println("DEINTERLEAVED: " + deinterleavedUncorrected);
+ System.out.println(" CORRECTED: " + deinterleaved);
+ System.out.println(" REFERENCE: " + deinterleavedReferenceMessage);
+
+ deinterleaved.xor(deinterleavedReferenceMessage);
+ System.out.println(" DELTA: " + deinterleaved);
+
+ if(deinterleaved.cardinality() > 0)
+ {
+ System.out.println("--------------------------------");
+ System.out.println("Residual Error Map:");
+ bptc.logErrorMap(deinterleaved);
+ System.out.println("--------------------------------");
+ }
+ else
+ {
+ System.out.println("--------------------------------");
+ System.out.println("Message Was Fully Corrected");
+ System.out.println("--------------------------------");
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java
similarity index 96%
rename from src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java
index 9840e1c6..3bae9c2d 100644
--- a/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_16_2.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,10 +17,11 @@
* ****************************************************************************
*/
-package io.github.dsheirer.edac;
+package io.github.dsheirer.module.decode.dmr.bptc;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.Hamming16;
/**
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java
new file mode 100644
index 00000000..3fcc011a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_196_96.java
@@ -0,0 +1,670 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.bptc;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.BitSetFullException;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.Hamming13;
+import io.github.dsheirer.edac.Hamming15;
+import io.github.dsheirer.edac.IHamming;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Block Product Turbo Code (BPTC) 196/96 Support
+ */
+public class BPTC_196_96
+{
+ public static final int BPTC_LENGTH = 196;
+ public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99
+ public static final int MAX_ORIGINAL_INDEX = 136;
+ public static final int COLUMN_COUNT = 15;
+ public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
+ public static final int CHECKSUM_COLUMN_COUNT = 4;
+ public static final int MESSAGE_START_INDEX = 4;
+ public static final int MAXIMUM_RECURSION_TURBO_DEPTH = 20;
+
+ /**
+ * DMR De-interleaving indices
+ */
+ public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182,
+ 167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3,
+ 184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20,
+ 5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37,
+ 22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54,
+ 39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71,
+ 56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103,
+ 88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120,
+ 105, 90, 75, 60, 45, 30, 15};
+
+ /**
+ * DMR Column Indices
+ */
+ public static int[][] COLUMN_INDEXES = new int[][]{
+ {1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181},
+ {2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182},
+ {3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183},
+ {4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184},
+ {5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185},
+ {6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186},
+ {7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187},
+ {8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188},
+ {9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189},
+ {10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190},
+ {11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191},
+ {12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192},
+ {13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193},
+ {14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194},
+ {15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}};
+
+ /**
+ * Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96)
+ * protected message
+ * @param original uncorrected BPTC(196,96) message
+ * @return error corrected and extracted 96-bit message
+ */
+ public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original)
+ {
+ //De-Interleave
+ CorrectedBinaryMessage message = deinterleave(original);
+
+ //Perform error detection and correction
+ boolean pursueShadows = true;
+ correct(message, pursueShadows);
+
+ //Extract the 96-bit message from the corrected 196 bits
+ CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH);
+
+ //Transfer the corrected bit count
+ extracted.setCorrectedBitCount(message.getCorrectedBitCount());
+
+ int index = MESSAGE_START_INDEX;
+
+ while(index < MAX_ORIGINAL_INDEX)
+ {
+ if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT)
+ {
+ try
+ {
+ extracted.add(message.get(index));
+ }
+ catch(BitSetFullException bsfe)
+ {
+ //Should never get to here
+ bsfe.printStackTrace();
+ return extracted;
+ }
+
+ index++;
+ }
+ else
+ {
+ index += CHECKSUM_COLUMN_COUNT;
+ }
+ }
+
+ //Transfer bits R2, R1, and R0 to the end - RAS bits
+ extracted.set(96, message.get(0));
+ extracted.set(97, message.get(1));
+ extracted.set(98, message.get(2));
+
+ return extracted;
+ }
+
+ /**
+ * De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message
+ * @param message that contains interleaved (transmitted) message
+ * @return de-interleaved message
+ */
+ private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message)
+ {
+ CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH);
+ for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++)
+ {
+ deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x]));
+ }
+
+ return deinterleaved;
+ }
+
+ /**
+ * Identifies the error indices from the row level Hamming(15,11,3) protected words
+ * @param message containing protected words in rows
+ * @return list of zero or more error indices
+ */
+ private static List getRowErrors(CorrectedBinaryMessage message)
+ {
+ List rowErrors = new ArrayList<>();
+
+ int offset = 0;
+ int index = -1;
+
+ for(int row = 0; row < 13; row++)
+ {
+ //The first bit of the message is a pad/reserved bit and is ignored.
+ offset = (row * 15) + 1;
+ index = Hamming15.getErrorIndex(message, offset);
+
+ if(index >= 0 && index < 1000)
+ {
+ rowErrors.add(index);
+ }
+ }
+
+ //Note: row errors should already be in order and don't need sorted
+ return rowErrors;
+ }
+
+ /**
+ * Identifies the error indices from the column level Hamming(13,9,3) protected words
+ * @param message containing protected words in columns
+ * @return list of zero or more error indices
+ */
+ private static List getColumnErrors(CorrectedBinaryMessage message)
+ {
+ List columnErrors = new ArrayList<>();
+
+ int index = -1;
+
+ for(int column = 0; column < 15; column++)
+ {
+ index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
+
+ if(index >= 0 && index < 1000)
+ {
+ columnErrors.add(index);
+ }
+ }
+
+ Collections.sort(columnErrors);
+ return columnErrors;
+ }
+
+ /**
+ * Creates a list of the values that intersect between list 1 and list 2.
+ * @param list1 to compare
+ * @param list2 to compare
+ * @return intersection
+ */
+ private static List intersection(List list1, List list2)
+ {
+ List intersection = new ArrayList<>();
+
+ for(Integer i : list1)
+ {
+ if(list2.contains(i))
+ {
+ intersection.add(i);
+ }
+ }
+
+ return intersection;
+ }
+
+ private static void logDiagnostic(CorrectedBinaryMessage message)
+ {
+ StringBuilder sb = new StringBuilder();
+ for(int row = 0; row < 13; row++)
+ {
+ int offset = (row * 15) + 1;
+ sb.append("Row ");
+ if(row < 10)
+ {
+ sb.append(" ").append(row);
+ }
+ else
+ {
+ sb.append(row);
+ }
+ sb.append(": ");
+ sb.append(message.getSubMessage(offset, offset + 16));
+ int index = getRowErrorIndex(row, message);
+ sb.append(" Index:").append(index == IHamming.NO_ERRORS ? "-": index);
+ sb.append("\n");
+ }
+
+ sb.append("Column Error Indices: ");
+ for(int column = 0; column < 15; column++)
+ {
+ int index = getColumnErrorIndex(column, message);
+ String label = index == IHamming.NO_ERRORS ? "-" : (index == 1000 ? "(bad)" : String.valueOf(index));
+ sb.append(column).append(":").append(label).append(" ");
+ }
+
+ System.out.println("Diagnostic:\n" + sb + "\n");
+ }
+
+ private static void logErrorMap(CorrectedBinaryMessage message)
+ {
+ StringBuilder sb = new StringBuilder();
+ for(int row = 0; row < 13; row++)
+ {
+ int offset = (row * 15) + 1;
+ sb.append("Row ");
+ if(row < 10)
+ {
+ sb.append(" ").append(row);
+ }
+ else
+ {
+ sb.append(row);
+ }
+ sb.append(": ");
+ sb.append(message.getSubMessage(offset, offset + 15));
+
+ sb.append(" ").append(offset).append(":").append(offset + 14).append(" =");
+
+ for(int index = offset; index < offset + 15; index++)
+ {
+ if(message.get(index))
+ {
+ sb.append(" " + index);
+ }
+ }
+ sb.append("\n");
+ }
+
+ System.out.println("Error Map:\n" + sb + "\n");
+ }
+
+ /**
+ * Calculates the indices that make up the intersections of the row and column errors.
+ * @param rows bitmap indicating rows with errors
+ * @param columns bitmap indicating columns with errors
+ * @return set of intersection indices.
+ */
+ public static List getIntersectionIndices(BinaryMessage columns, BinaryMessage rows)
+ {
+ List intersections = new ArrayList<>();
+
+ for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
+ {
+ for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
+ {
+ intersections.add(getIndex(column, row));
+ }
+ }
+
+ return intersections;
+ }
+
+ /**
+ * Performs turbo decode of the message by testing multiple paths across rows and columns to identify paths
+ * that resolve errors validated by the Hamming checks. Can also detect and often correct shadowing where the bit
+ * errors occur in a 2x2, 2x3, 3x2 or 3x3 column/row grid and can't normally be corrected by the Hamming code.
+ * @param message to correct.
+ * @param pursueShadows to test intersection flipping for double/triple shadows.
+ * @return true if the message was corrected, false otherwise.
+ */
+ public static boolean correct(CorrectedBinaryMessage message, boolean pursueShadows)
+ {
+ List columnErrors = getColumnErrors(message);
+ List rowErrors = getRowErrors(message);
+
+ if(columnErrors.isEmpty() && rowErrors.isEmpty())
+ {
+ return true;
+ }
+
+ //Fix the easy bits first.
+ List flippedBits = new ArrayList<>();
+ List intersection = intersection(columnErrors, rowErrors);
+
+ if(!intersection.isEmpty())
+ {
+ for(Integer i : intersection)
+ {
+ message.flip(i);
+ flippedBits.add(i);
+ }
+
+ message.incrementCorrectedBitCount(intersection.size());
+ }
+
+ //Set of flags for columns with errors
+ BinaryMessage columns = new BinaryMessage(15);
+ for(int column = 0; column < 15; column++)
+ {
+ columns.set(column, !isColumnCorrect(column, message));
+ }
+
+ //Set of flags for rows with errors
+ BinaryMessage rows = new BinaryMessage(13);
+ for(int row = 0; row < 13; row++)
+ {
+ rows.set(row, !isRowCorrect(row, message));
+ }
+
+ if(columns.isEmpty() && rows.isEmpty())
+ {
+ return true; //No (more) errors
+ }
+
+ //1: fix up to 2-bit errors per column or row without pursuing multi-column or multi-row paths
+ boolean pursue = false;
+
+ for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
+ {
+ List solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>());
+ flippedBits.addAll(solution);
+ }
+
+ if(!columns.isEmpty() || !rows.isEmpty())
+ {
+ for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
+ {
+ List solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>());
+ flippedBits.addAll(solution);
+ }
+ }
+
+ if(!columns.isEmpty() || !rows.isEmpty())
+ {
+ pursue = true;
+
+ for(int column = columns.nextSetBit(0); column >= 0 && column < 15; column = columns.nextSetBit(column + 1))
+ {
+ List solution = correctColumn(column, message, columns, rows, 0, pursue, new ArrayList<>());
+ flippedBits.addAll(solution);
+ }
+
+ if(!columns.isEmpty() || !rows.isEmpty())
+ {
+ for(int row = rows.nextSetBit(0); row >= 0 && row < 13; row = rows.nextSetBit(row + 1))
+ {
+ List solution = correctRow(row, message, columns, rows, 0, pursue, new ArrayList<>());
+ flippedBits.addAll(solution);
+ }
+ }
+ }
+
+ //Detect double/triple row/column bit errors that are shadowing each other and preventing the
+ // Hamming codes from giving accurate bit error locations. Flip all of their intersections and
+ // then attempt to correct the message again. We use a flag 'pursueShadows' to ensure that we
+ // only chase the shadow once, not recursively, since this parent method can be called recursively.
+ if(pursueShadows && ((columns.cardinality() == 2 || columns.cardinality() == 3) &&
+ (rows.cardinality() == 2 || rows.cardinality() == 3)))
+ {
+ List intersectionIndices = getIntersectionIndices(columns, rows);
+ for(Integer intersectionIndex: intersectionIndices)
+ {
+ message.flip(intersectionIndex);
+ }
+
+ //Set pursue shadows to false so we don't recursively chase shadows
+ pursueShadows = false;
+
+ if(correct(message, pursueShadows))
+ {
+ columns.clear();
+ rows.clear();
+ int correctedBits = intersectionIndices.size() - message.getCorrectedBitCount();
+ message.incrementCorrectedBitCount(correctedBits);
+ }
+ else
+ {
+ for(Integer intersectionIndex: intersectionIndices)
+ {
+ message.flip(intersectionIndex);
+ }
+ }
+ }
+
+ if(columns.isEmpty() && rows.isEmpty())
+ {
+ Collections.sort(flippedBits);
+ message.incrementCorrectedBitCount(flippedBits.size());
+ }
+ else
+ {
+ message.setCorrectedBitCount(BPTCBase.ERRORS_NOT_CORRECTED);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Recursive method ...
+ * @param column
+ * @param message
+ * @param columns
+ * @param rows
+ * @return
+ */
+ public static List correctColumn(int column, CorrectedBinaryMessage message, BinaryMessage columns,
+ BinaryMessage rows, int depth, boolean pursue, List path)
+ {
+ //Don't let the recursive call exceed threshold
+ if(depth > MAXIMUM_RECURSION_TURBO_DEPTH)
+ {
+ return Collections.emptyList();
+ }
+
+ int index = getColumnErrorIndex(column, message);
+ int row = getRow(index);
+
+ //If the candidate index is in range and the row for the candidate has an error, proceed
+ if(0 <= index && index <= 195 && rows.get(row) && !path.contains(index))
+ {
+ message.flip(index);
+
+ if(isColumnCorrect(column, message))
+ {
+ path.add(index);
+
+ //If the row is now correct, stop here.
+ if(isRowCorrect(row, message))
+ {
+ columns.clear(column);
+ rows.clear(row);
+ return path;
+ }
+
+ //Recursively test bit flipping in the row. A non-empty list is the solution set.
+ List solution = correctRow(row, message, columns, rows, depth + 1, pursue, path);
+
+ //If our candidate index is part of the discovered solution, fail, otherwise return it as part of
+ // the solution.
+ if(solution.size() > 0)
+ {
+ columns.clear(column);
+ return solution;
+ }
+ else if(pursue) //Pursue additional column flips
+ {
+ for(int column2 = columns.nextSetBit(column + 1); column2 >= 0 && column2 < 15; column2 = columns.nextSetBit(column2 + 1))
+ {
+ List solution2 = correctColumn(column2, message, columns, rows, depth + 1, pursue, path);
+
+ if(solution2.size() > 0)
+ {
+ columns.clear(column);
+ rows.clear(row);
+ return solution2;
+ }
+ }
+ }
+
+ path.removeLast();
+ }
+
+ //Revert the change
+ message.flip(index);
+ }
+
+ return Collections.emptyList();
+ }
+
+ public static List correctRow(int row, CorrectedBinaryMessage message, BinaryMessage columns,
+ BinaryMessage rows, int depth, boolean pursue, List path)
+ {
+ //Don't let the recursive call exceed threshold
+ if(depth > MAXIMUM_RECURSION_TURBO_DEPTH)
+ {
+ return Collections.emptyList();
+ }
+
+ int index = getRowErrorIndex(row, message);
+ int column = getColumn(index);
+
+ //If the candidate index is in range and the column for the candidate has an error, proceed
+ if(0 <= index && index <= 195 && columns.get(column) && !path.contains(index))
+ {
+ message.flip(index);
+
+ if(isRowCorrect(row, message))
+ {
+ path.add(index);
+
+ //If the column is now correct, stop here and return the path as the solution
+ if(isColumnCorrect(column, message))
+ {
+ columns.clear(column);
+ rows.clear(row);
+ return path;
+ }
+
+ //Recursively test bit flipping in the column. A non-empty list is the solution set.
+ List solution = correctColumn(column, message, columns, rows, depth + 1, pursue, path);
+
+ //If our candidate index is part of the discovered solution, fail, otherwise return it as part of
+ // the solution.
+ if(solution.size() > 0)
+ {
+ rows.clear(row);
+ return solution;
+ }
+ else if(pursue) //Pursue additional row flips
+ {
+ for(int row2 = rows.nextSetBit(row + 1); row2 >= 0 && row2 < 13; row2 = rows.nextSetBit(row2 + 1))
+ {
+ List solution2 = correctRow(row2, message, columns, rows, depth + 1, pursue, path);
+
+ if(solution2.size() > 0 && !solution2.contains(index))
+ {
+ columns.clear(column);
+ rows.clear(row);
+ return solution2;
+ }
+ }
+ }
+
+ path.removeLast();
+ }
+
+ //Revert the change
+ message.flip(index);
+ }
+
+ return Collections.emptyList();
+ }
+
+ public static int getIndex(int column, int row)
+ {
+ return row * 15 + column + 1;
+ }
+
+ /**
+ * Checks the row for bit errors.
+ * @param row to check
+ * @param message containing bits to check
+ * @return true if the row passes the Hamming check or false if not.
+ */
+ private static boolean isRowCorrect(int row, CorrectedBinaryMessage message)
+ {
+ return Hamming15.getSyndrome(message, (row * 15) + 1) == 0;
+ }
+
+ /**
+ * Calculates the index for the bit error in the message
+ * @param row to check
+ * @param message with bits
+ * @return error index in the range 0-195, or -1 if the message row has no bit errors.
+ */
+ private static int getRowErrorIndex(int row, CorrectedBinaryMessage message)
+ {
+ return Hamming15.getErrorIndex(message, (row * 15) + 1);
+ }
+
+ /**
+ * Checks the column for bit errors.
+ * @param column to check
+ * @param message containing bits to check
+ * @return true if the column passes the Hamming check or false if not.
+ */
+ private static boolean isColumnCorrect(int column, CorrectedBinaryMessage message)
+ {
+ return Hamming13.getSyndrome(message, COLUMN_INDEXES[column]) == 0;
+ }
+
+ /**
+ * Calculates the index for the bit error in the message.
+ * @param column to check
+ * @param message with bits.
+ * @return error index in the range 0-195, or -1 if message has no bit errors, or 1000 if message has more than 1 bit error.
+ */
+ private static int getColumnErrorIndex(int column, CorrectedBinaryMessage message)
+ {
+ return Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
+ }
+
+ public static int getColumn(int index)
+ {
+ return (index - 1) % 15;
+ }
+
+ public static int getRow(int index)
+ {
+ return (index - 1) / 15;
+ }
+
+ public static void main(String[] args)
+ {
+ String raw = "0000101111101000000100001010100001010000001000000011100000000000100000000000001000000000001000000000000010000000001111001010000100001000000001100000100001000111110101011111011111101001111011100010";
+ String ref = "0000101111100000000100001100100001010000001000000000000000000000000000000000000000000000000000000000000000000000001111001010000100001000000001110000100001011111110101011011011111101011111011100000";
+ CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
+ CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref));
+
+// correct2(refMessage);
+
+ boolean pursueShadows = true;
+ correct(rawMessage, pursueShadows);
+ System.out.println("RAW: " + raw);
+ System.out.println("COR: " + rawMessage);
+ System.out.println("REF: " + ref);
+
+ rawMessage.xor(refMessage);
+ System.out.println("RES: " + rawMessage);
+
+ if(rawMessage.cardinality() > 0)
+ {
+ System.out.println("Residual Error Map:");
+ logErrorMap(rawMessage);
+ }
+
+ rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
+ rawMessage.xor(refMessage);
+ System.out.println("\n\nXOR: " + rawMessage);
+ logErrorMap(rawMessage);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java
new file mode 100644
index 00000000..b04ac3cf
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_68_36.java
@@ -0,0 +1,121 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.bptc;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.Hamming17;
+
+/**
+ * Block product turbo code (BPTC) for decoding Short Link Control reassembled from CACH fragments.
+ */
+public class BPTC_68_36 extends BPTCBase
+{
+ public BPTC_68_36()
+ {
+ super(new Hamming17(), 17, 4);
+ }
+
+ /**
+ * Performs error detection and correction and extracts the payload from the BPTC encoded message.
+ * @param message with BPTC encoding.
+ * @return decoded message.
+ */
+ public CorrectedBinaryMessage extract(BinaryMessage message)
+ {
+ CorrectedBinaryMessage deinterleaved = deinterleave(message);
+ correct(deinterleaved);
+
+ //Extract the message
+ CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(36);
+
+ for(int row = 0; row < 3; row++)
+ {
+ for(int column = 0; column < 12; column++)
+ {
+ extractedMessage.set(row * 12 + column, deinterleaved.get(row * 17 + column));
+ }
+ }
+
+ extractedMessage.setCorrectedBitCount(deinterleaved.getCorrectedBitCount());
+ return extractedMessage;
+ }
+
+
+ /**
+ * Deinterleaves the transmitted message.
+ * @param interleaved message
+ * @return deinterleaved message.
+ */
+ public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved)
+ {
+ CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(68);
+
+ for(int index = 0; index < 67; index++)
+ {
+ if(interleaved.get(index))
+ {
+ deinterleaved.set((index * 17 % 67));
+ }
+ }
+
+ if(interleaved.get(67))
+ {
+ deinterleaved.set(67);
+ }
+
+ return deinterleaved;
+ }
+
+ public static void main(String[] args)
+ {
+ BPTC_68_36 bptc = new BPTC_68_36();
+
+ String ref = "10101001100001000000001101101111000000110011001110011111101011001100"; //No errors
+ String raw = "10101001100001000000001101101111000000110011001110011111101011001100"; //Under test
+ CorrectedBinaryMessage rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
+ CorrectedBinaryMessage refMessage = new CorrectedBinaryMessage(BinaryMessage.load(ref));
+
+ bptc.logDiagnostic(rawMessage);
+
+ bptc.correct(rawMessage);
+
+ System.out.println("RAW: " + raw);
+ System.out.println("COR: " + rawMessage);
+ System.out.println("REF: " + ref);
+
+ rawMessage.xor(refMessage);
+ System.out.println("RES: " + rawMessage);
+
+ if(rawMessage.cardinality() > 0)
+ {
+ System.out.println("--------------------------------");
+ System.out.println("Residual Error Map:");
+ bptc.logErrorMap(rawMessage);
+ System.out.println("--------------------------------");
+ }
+
+ System.out.println("\n\n*********** TRUTH DATA ********************");
+ rawMessage = new CorrectedBinaryMessage(BinaryMessage.load(raw));
+ rawMessage.xor(refMessage);
+ System.out.println("XOR: " + rawMessage);
+ bptc.logErrorMap(rawMessage);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java
index e8dbe5b2..613fe456 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/CACH.java
@@ -1,14 +1,49 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
+import java.util.HashMap;
+import java.util.Map;
/**
* Common Announcement Channel is a 24-bit sequence that precedes an outbound DMR frame transmitted by a repeater.
*/
public class CACH
{
+ //Generated by method: createHamming7_4BitErrorMap(). The value of the uncorrected 7-bit CACH is used as an index
+ //to lookup the bit error position where:
+ // 0-6: bit error index location where bits are ordered as transmitted: 0, 1 ..., 6
+ // -1: valid/correct message
+ // -2: invalid message with 2 or more bit errors
+ public static final int[] BIT_ERROR_INDEXES = new int[]{-2, -2, -2, -2, -2, -2, -2, -2, -2, 5, 6, -1, 1, 2, 0, 4,
+ -2, 1, 4, 0, 5, 3, -1, 6, 0, 4, 1, 2, 6, -1, 3, 5, -2, -2, -2, -2, -2, -2, -2, -2, 4, 0, 2, 1, -1, 6, 5, -2,
+ 6, -1, 3, 5, 0, 4, 1, -2, 5, 3, -1, 6, 2, 1, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2, 2, 1, 4, 0, 5, -2, -1, 6,
+ 3, 5, 6, -1, 1, -2, 0, 4, -1, 6, 5, 3, 4, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2, 6, -1, -2, 5, 0, 4, 1, 2,
+ 4, 0, -2, 1, -1, 6, 5, 3, 1, 2, 0, 4, 3, 5, 6, -1};
+
+ private static final IntField VALUE_FIELD = IntField.range(0, 6);
+
private static final int CACH_MESSAGE_LENGTH = 24;
private static final int[] INTERLEAVE_MATRIX = new int[]{0, 4, 8, 12, 14, 18, 22, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
15, 16, 17, 19, 20, 21, 23};
@@ -38,6 +73,23 @@ public class CACH
mValid = valid;
}
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ if(!isValid())
+ {
+ sb.append("[CRC-ERROR] ");
+ }
+ sb.append(getInboundChannelAccessType().name()).append(" ");
+ sb.append(getLCSS());
+ sb.append(" TS:").append(getTimeslot());
+ sb.append(" CRC:").append(getCrcChecksum(getMessage()));
+ sb.append(" PAYLOAD:").append(getPayload().toHexString());
+ sb.append(" MSG:").append(getMessage().toHexString()).append(" ");
+ return sb.toString();
+ }
+
/**
* Descrambled message bits for this cach
*/
@@ -64,18 +116,6 @@ public class CACH
return false;
}
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
-// sb.append(getMessage().toHexString()).append(" ");
-// sb.append(getInboundChannelAccessType().name()).append(" ");
- sb.append(getLCSS());
-// sb.append(" (").append(getTimeslot()).append("|").append(getCrcChecksum(getMessage())).append(")");
-// sb.append(" ").append(getPayload().toHexString());
- return sb.toString();
- }
-
/**
* Indicates if this cach passes the FEC error correction and indicates that the inbound channel Access Type,
* Timeslot and LCSS information is valid.
@@ -109,17 +149,17 @@ public class CACH
}
/**
- * Indicates the outbound timeslot for the next frame that follows this CACH, 0 or 1
+ * Indicates the outbound timeslot for the next frame that follows this CACH, 1 or 2
*/
public int getTimeslot()
{
- return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 1 : 0;
+ return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 2 : 1;
}
/**
* Indicates if the timeslot is 0
*/
- public boolean isTimeslot0()
+ public boolean isTimeslot1()
{
return !mMessage.get(OUTBOUND_BURST_TIMESLOT);
}
@@ -142,7 +182,6 @@ public class CACH
{
CorrectedBinaryMessage decodedMessage = new CorrectedBinaryMessage(CACH_MESSAGE_LENGTH);
-
//Deinterleave the transmitted message to create the decoded message
for(int x = 0; x < CACH_MESSAGE_LENGTH; x++)
{
@@ -152,14 +191,15 @@ public class CACH
}
}
- int checksum = getCrcChecksum(decodedMessage);
+ int codeword = decodedMessage.getInt(VALUE_FIELD);
+ int bitError = BIT_ERROR_INDEXES[codeword];
-// //debug - investigating corruption of CACH, SLC and EMB during encrypted Hytera/MotoTRBO calls.
-// BinaryMessage test = message.getSubMessage(0, CACH_MESSAGE_LENGTH);
-// System.out.println("--> CACH:" + test.toString() + " DE:" + decodedMessage.toString() + " CHK:" + checksum);
-
- boolean valid = (checksum == 0);
+ if(bitError >= 0)
+ {
+ decodedMessage.flip(bitError);
+ }
+ boolean valid = bitError != -2;
return new CACH(decodedMessage, valid);
}
@@ -184,4 +224,101 @@ public class CACH
return checksum;
}
+
+ public static void createHamming7_4BitErrorMap()
+ {
+ IntField valueField = IntField.range(0, 6);
+ Map map = new HashMap<>();
+
+ //Link Control Start/Stop value 00 is not valid for CACH, so we'll mark these as invalid codewords (-2).
+ int invalidLCSSMask = 0x18; //0011000b
+
+ BinaryMessage parity0 = new BinaryMessage(7);
+ parity0.load(4, 3, 5);
+ BinaryMessage parity1 = new BinaryMessage(7);
+ parity1.load(4, 3, 7);
+ BinaryMessage parity2 = new BinaryMessage(7);
+ parity2.load(4, 3, 6);
+ BinaryMessage parity3 = new BinaryMessage(7);
+ parity3.load(4, 3, 3);
+
+ //There are 2^4 = 16 potential valid codewords
+ for(int x = 0; x < 16; x++)
+ {
+ BinaryMessage bm = new BinaryMessage(7);
+ bm.load(0, 4, x);
+
+ if(bm.get(0))
+ {
+ bm.xor(parity0);
+ }
+ if(bm.get(1))
+ {
+ bm.xor(parity1);
+ }
+ if(bm.get(2))
+ {
+ bm.xor(parity2);
+ }
+ if(bm.get(3))
+ {
+ bm.xor(parity3);
+ }
+
+ int value = bm.getInt(valueField);
+
+ //Ignore any legal codeword that has a value of 00 in the LCSS field.
+ if(((value & invalidLCSSMask) ^ invalidLCSSMask) == invalidLCSSMask)
+ {
+ continue;
+ }
+
+ //Use -1 to indicate no errors and we'll reserve -2 for invalid/non-correctable 2-bit errors.
+ map.put(value, -1);
+
+ //Iteratively induce single bit errors and record them into the map
+ for(int bitError = 0; bitError < 7; bitError++)
+ {
+ bm.flip(bitError);
+ value = bm.getInt(valueField);
+
+ //Ignore any bit combination with a 00 in the LCSS field.
+ if(((value & invalidLCSSMask) ^ invalidLCSSMask) != invalidLCSSMask)
+ {
+ map.put(value, bitError);
+ }
+
+ bm.flip(bitError);
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("public static final int[] BIT_ERROR_INDEXES = new int[]{");
+
+ for(int z = 0; z < 128; z++)
+ {
+ if(map.containsKey(z))
+ {
+ sb.append(map.get(z));
+ }
+ else
+ {
+ sb.append("-2");
+ }
+
+ if(z != 127)
+ {
+ sb.append(",");
+ }
+ }
+
+ sb.append("};");
+
+ System.out.println(sb);
+ }
+
+ public static void main(String[] args)
+ {
+ createHamming7_4BitErrorMap();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
index 06db716d..d496419c 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Base DMR Burst Message class
*/
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java
index a9d19cba..2dc30dc6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java
@@ -20,63 +20,21 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.message.Message;
+import io.github.dsheirer.message.TimeslotMessage;
import io.github.dsheirer.protocol.Protocol;
/**
* Base DMR Message
*/
-public abstract class DMRMessage extends Message
+public abstract class DMRMessage extends TimeslotMessage
{
- public static final int TIMESLOT_1 = 1;
- public static final int TIMESLOT_2 = 2;
-
- private CorrectedBinaryMessage mCorrectedBinaryMessage;
- private boolean mValid = true;
- private int mTimeslot;
-
/**
* Constructs an instance
* @param timestamp for the message
*/
public DMRMessage(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
- super(timestamp);
- mCorrectedBinaryMessage = message;
- mTimeslot = timeslot;
- }
-
- /**
- * Message bits
- */
- public CorrectedBinaryMessage getMessage()
- {
- return mCorrectedBinaryMessage;
- }
-
- /**
- * Indicates if this message is valid
- */
- public boolean isValid()
- {
- return mValid;
- }
-
- /**
- * Sets the valid flag for this message
- */
- public void setValid(boolean valid)
- {
- mValid = valid;
- }
-
- /**
- * Timeslot for this message
- * @return 0 or 1
- */
- public int getTimeslot()
- {
- return mTimeslot;
+ super(message, timeslot, timestamp);
}
@Override
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java
index 96d04b0e..cc898e11 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessageFactory.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,11 +20,14 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage;
+import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.DMRDataMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceAMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
+import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,39 +43,33 @@ public class DMRMessageFactory
* @param timeslot for the burst
* @return DMRMessage instance or null for UNKNOWN sync pattern
*/
- public static DMRBurst create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
+ public static IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
long timestamp, int timeslot)
{
switch(syncPattern)
{
- case BASE_STATION_VOICE:
- case MOBILE_STATION_VOICE:
- case BS_VOICE_FRAME_B:
- case BS_VOICE_FRAME_C:
- case BS_VOICE_FRAME_D:
- case BS_VOICE_FRAME_E:
- case BS_VOICE_FRAME_F:
-
- case MS_VOICE_FRAME_B:
- case MS_VOICE_FRAME_C:
- case MS_VOICE_FRAME_D:
- case MS_VOICE_FRAME_E:
- case MS_VOICE_FRAME_F:
+ case BASE_STATION_VOICE, MOBILE_STATION_VOICE:
+ case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F:
+ case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F:
+ case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E,
+ DIRECT_VOICE_FRAME_F:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
- case DIRECT_MODE_VOICE_TIMESLOT_1:
+ case DIRECT_VOICE_TIMESLOT_1:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 1);
- case DIRECT_MODE_VOICE_TIMESLOT_2:
+ case DIRECT_VOICE_TIMESLOT_2:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2);
case BASE_STATION_DATA:
case MOBILE_STATION_DATA:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
- case DIRECT_MODE_DATA_TIMESLOT_1:
+ case DIRECT_DATA_TIMESLOT_1:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
- case DIRECT_MODE_DATA_TIMESLOT_2:
+ case DIRECT_DATA_TIMESLOT_2:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
+ case DIRECT_EMPTY_TIMESLOT:
+ return new EmptyTimeslotPlaceholderMessage(timestamp, 288, Protocol.DMR, timeslot);
case UNKNOWN:
return null;
- case MOBILE_STATION_REVERSE_CHANNEL:
+ case REVERSE_CHANNEL:
case RESERVED:
default:
return new UnknownDMRMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
@@ -95,14 +92,13 @@ public class DMRMessageFactory
{
case BASE_STATION_VOICE:
case MOBILE_STATION_VOICE:
- case DIRECT_MODE_VOICE_TIMESLOT_1:
- case DIRECT_MODE_VOICE_TIMESLOT_2:
+ case DIRECT_VOICE_TIMESLOT_1:
+ case DIRECT_VOICE_TIMESLOT_2:
return new VoiceAMessage(syncPattern, message, cach, timestamp, timeslot);
- case BS_VOICE_FRAME_B:
- case BS_VOICE_FRAME_C:
- case BS_VOICE_FRAME_D:
- case BS_VOICE_FRAME_E:
- case BS_VOICE_FRAME_F:
+ case BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F:
+ case MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F:
+ case DIRECT_VOICE_FRAME_B, DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E,
+ DIRECT_VOICE_FRAME_F:
default:
return new VoiceEMBMessage(syncPattern, message, cach, timestamp, timeslot);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java
index 81afa0ea..4b13ffbb 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/UnknownDMRMessage.java
@@ -21,8 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
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 f21c6a04..e3da332a 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
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.edac.BPTC_196_96;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.bptc.BPTC_196_96;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1_2Rate;
@@ -52,6 +51,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,33 +79,73 @@ public class DMRDataMessageFactory
if(slotType.isValid())
{
+ switch (slotType.getDataType())
+ {
+ case RATE_3_OF_4_DATA:
+ return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot);
+ case RATE_1_DATA:
+ return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot);
+ }
+
+ //Get the BPTC(196,96) protected payload. If the corrected bit count is -1, the payload/message is invalid
+ CorrectedBinaryMessage payload = getPayload(message);
+
switch(slotType.getDataType())
{
case SLOT_IDLE:
- return new IDLEMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage idle = new IDLEMessage(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ idle.setValid(false);
+ }
+ return idle;
case CSBK:
- return CSBKMessageFactory.create(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage csbk = CSBKMessageFactory.create(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ csbk.setValid(false);
+ }
+ return csbk;
case USB_DATA:
- CorrectedBinaryMessage usbdPayload = getPayload(message);
+ CorrectedBinaryMessage usbdPayload = payload;
switch(USBData.getServiceType(usbdPayload))
{
case LIP_SHORT_LOCATION_REQUEST:
default:
- return new USBData(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage usb =new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ usb.setValid(false);
+ }
+ return usb;
}
case MBC_ENC_HEADER:
case MBC_HEADER:
- return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage mbc = new MBCHeader(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ mbc.setValid(false);
+ }
+ return mbc;
case CHANNEL_CONTROL_ENC_HEADER:
case PI_HEADER:
- return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ pi.setValid(false);
+ }
+ return pi;
case VOICE_HEADER:
- return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ voice.setValid(false);
+ }
+ return voice;
case DATA_ENC_HEADER:
case DATA_HEADER:
- CorrectedBinaryMessage payload = getPayload(message);
int crcBitCount = CRCDMR.correctCCITT80(payload, 0, 80, 0xCCCC);
- boolean valid = crcBitCount < 2;
+ boolean valid = crcBitCount < 2 && payload.getCorrectedBitCount() != -1;
DataPacketFormat dpf = DataHeader.getDataPacketFormat(payload);
switch(dpf)
@@ -191,22 +231,44 @@ public class DMRDataMessageFactory
return dh;
}
case TLC:
- return new Terminator(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ tlc.setValid(false);
+ }
+ return tlc;
case RATE_1_OF_2_DATA:
- return new DataBlock1_2Rate(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
- case RATE_3_OF_4_DATA:
- return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot);
- case RATE_1_DATA:
- return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot);
+ DataMessage data = new DataBlock1_2Rate(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ data.setValid(false);
+ }
+ return data;
case MBC_BLOCK:
- return new MBCContinuationBlock(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage mbcBlock = new MBCContinuationBlock(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ mbcBlock.setValid(false);
+ }
+ return mbcBlock;
case RESERVED_15:
case UNKNOWN:
- return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ unk.setValid(false);
+ }
+ return unk;
}
}
- return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
+ CorrectedBinaryMessage payload = getPayload(message);
+ DataMessage unk = new UnknownDataMessage(pattern, payload, cach, slotType, timestamp, timeslot);
+ if(payload.getCorrectedBitCount() < 0)
+ {
+ unk.setValid(false);
+ }
+ return unk;
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java
index 7d3127b5..48d1b806 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessage.java
@@ -19,9 +19,9 @@
package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Base DMR Data Message
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java
index 5a20131b..702f437f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java
@@ -21,11 +21,11 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java
index 60a6924b..cfc667c6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/IDLEMessage.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,8 +20,8 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
@@ -54,6 +54,11 @@ public class IDLEMessage extends DataMessage
sb.append(" RAS:").append(getBPTCReservedBits());
}
+ if(!isValid())
+ {
+ sb.append(" [CRC-ERROR]");
+ }
+
sb.append(" IDLE");
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java
index ad5e9b35..39fe902b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/UnknownDataMessage.java
@@ -21,8 +21,8 @@ package io.github.dsheirer.module.decode.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java
index 4026b5e6..912c0f79 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java
index e7194f38..a6fb56a5 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 1/1 Data Block
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java
index a575083a..b09bd837 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 1/2 BPTC 196 Data Block
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java
index 145cd497..59fbed93 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Rate 3/4 Trellis Coded Data Block
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java
index 6c956fe6..55711ab0 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/UnknownDataBlock.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Unknown DMR data block
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java
index 7329e39d..7430d133 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessage.java
@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRCDMR;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Control Signalling Block (CSBK) Message
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java
index 9575de80..65e40796 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/CSBKMessageFactory.java
@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera08Acknowledge;
@@ -88,6 +87,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.List;
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java
index 99c5610f..37af0675 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/UnknownCSBKMessage.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java
index 17ad6c59..d3f3c222 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera08Acknowledge.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java
index c8a4b625..ae440275 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/Hytera68Acknowledge.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java
index 10da342a..8853c6d3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAdjacentSiteInformation.java
@@ -20,11 +20,11 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Hytera Neighbor/Adjacent Site Information
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java
index 6412c40a..a6fcabf7 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAloha.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java
index a5c0edbf..8ac3e359 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraAnnouncement.java
@@ -22,12 +22,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java
index d6879f7a..ef341593 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraCsbko44.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java
index ca3b5205..3379cb70 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraSmsAvailableNotification.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java
index fc5726c1..4eec6c34 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraTrafficChannelTalkerStatus.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java
index 4db18381..58c66017 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTAdjacentSites.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java
index 81753420..c32da89f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTPreamble.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java
index dc96bf4c..33e36dcc 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/hytera/HyteraXPTSiteState.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java
index f0b10fc9..f2adb5cc 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAdvantageModeVoiceChannelUpdate.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java
index 32fc3fd2..33e58538 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxAloha.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java
index 0e1df375..745d5d38 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,7 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java
index 446cd86b..d965e4a4 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusCSBKO_60.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java
index b663f5c6..1a3ef86a 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowAnnouncement.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java
index 9518a49e..6e4a2beb 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusDataRevertWindowGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
index c228ae9b..08fa4e2e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java
index 0483d579..5770dbf0 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusPreamble.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
index bef140fd..48318c01 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
@@ -24,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -34,6 +33,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java
index ded57642..daa98445 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusCSBKO_16.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
index dedd64f9..ba9d82d8 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java
index 6213c4ae..5485cd94 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowAnnouncement.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java
index 73bc3c4a..d9b7d9aa 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataRevertWindowGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java
index 76965fe7..a811dac8 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusNeighborReport.java
@@ -23,11 +23,11 @@ import com.google.common.base.Joiner;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.site.SiteIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
index 1da6ac4a..0fe6d6db 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPMessageType;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java
index d37c5fe6..68f0843e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationRequest.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java
index 552b1a12..0c2cc603 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusRegistrationResponse.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java
index 1acd93ed..70187273 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTalkgroupAffiliation.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java
index 4719fcb2..56d3a8ae 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusTerminateChannelGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
index e09ca5d3..e8e4113c 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java
index 63c320ba..16b84246 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Aloha.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
index 640c11f0..c346dbbe 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -36,6 +35,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java
index d3579412..49dfe3e6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/MoveTSCC.java
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
@@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java
index 387e6bc2..92c2d8ed 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Preamble.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java
index aa476e29..855d729f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Protect.java
@@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ProtectKind;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java
index 2cc099d5..6a439cac 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/Acknowledge.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java
index 8ac0c323..25fb5fb2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/AcknowledgeStatus.java
@@ -2,11 +2,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowle
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java
index 6ebe196a..0413a1c4 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/acknowledge/RegistrationAccepted.java
@@ -1,9 +1,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Registration Accepted
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java
index 2ae9e021..2d1e6327 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/Ahoy.java
@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* DMR Tier III - Ahoy!
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java
index 3cdcc7e9..4299af8d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/AuthenticateRegisterRadioCheck.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java
index 25226f5c..a8b45f2a 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/CancelCall.java
@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java
index ba886f7a..5b555a8c 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/ServiceRadioCheck.java
@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java
index 29757be8..47d85b51 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/StunReviveKill.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java
index af9d57c2..ee52fae1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/ahoy/UnknownAhoy.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java
index bb3a0c35..48cc5fc9 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java
@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -30,6 +29,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java
index 3213f402..dfcf7c8d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceChannelFrequency.java
@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java
index 99a09bb3..c37b3a46 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java
@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -29,6 +28,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java
index 4e6d2ab6..9afdb3d2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/Announcement.java
@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java
index 979228b1..7e2f2521 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/CallTimerParameters.java
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java
index 4f6d95ae..43d9dbff 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/LocalTime.java
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java
index 5b751b34..ac0d19b7 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/MassRegistration.java
@@ -22,10 +22,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java
index 5850895b..e22ac7c3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announce
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java
index dff50189..f3b49efd 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/BroadcastTalkgroupVoiceChannelGrant.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java
index 55578103..02fbddc8 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java
@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
@@ -31,6 +30,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.List;
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java
index 6e42736a..a90c3bd6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateDataChannelGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java
index c9fdcca7..641f325d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/DuplexPrivateVoiceChannelGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java
index f643f66e..168dcce5 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateDataChannelGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java
index d6888909..5eec1333 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/PrivateVoiceChannelGrant.java
@@ -22,11 +22,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java
index e44f46a8..836ae8fd 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupDataChannelGrant.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java
index a8220da1..631dd169 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/TalkgroupVoiceChannelGrant.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java
index 83714ce5..fda3ce9b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ConfirmedDataHeader.java
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java
index fbac812f..e029b28e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DataHeader.java
@@ -20,10 +20,10 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Data Header message.
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java
index 893b5fd5..2bba75a2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java
@@ -21,10 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.DefinedDataFormat;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java
index 8558f340..ce38dd41 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/HeaderMessage.java
@@ -20,10 +20,10 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Header data message.
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java
index 0923ce63..11d9ab62 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/MBCHeader.java
@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java
index 04c4c907..ca7b0c08 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/OctetDataHeader.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Packet sequence header that contains octet (byte) data
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java
index 70b932cf..bc3e2181 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PacketSequenceHeader.java
@@ -22,12 +22,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Packet Sequence Data Header
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java
index 0a46442f..764d42d6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java
@@ -20,11 +20,11 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* PI/Encryption Header with Link Control
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java
index 33b8eef1..14561199 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java
@@ -21,11 +21,11 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Proprietary Data Header
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java
index 0cc0ee1a..390553cd 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/RawShortDataHeader.java
@@ -20,9 +20,9 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Raw Short Data Header
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java
index f919f789..7c397905 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ResponseDataHeader.java
@@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ResponseStatus;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java
index 7fec9358..323f11c5 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ShortDataHeader.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java
index 314519f4..d345c81d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/StatusDataHeader.java
@@ -23,13 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus;
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java
index ee5f96c4..d126ef5e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UDTHeader.java
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -32,6 +31,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.UnifiedDataTransportFormat;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java
index 11c89810..c2f5aa6e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/UnconfirmedDataHeader.java
@@ -21,10 +21,9 @@ package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
-
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java
index b4435000..810ebbf9 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/VoiceHeader.java
@@ -20,7 +20,7 @@
package io.github.dsheirer.module.decode.dmr.message.data.header;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java
index 2f579d78..549916a3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java
@@ -20,11 +20,11 @@
package io.github.dsheirer.module.decode.dmr.message.data.header.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Hytera Proprietary Data Header for SAP Short Data
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
index b3346d51..eafc19c0 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
@@ -21,13 +21,13 @@ package io.github.dsheirer.module.decode.dmr.message.data.header.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Motorola MOTOTRBO Network Interface Service (MNIS) Data Header for SAP:1
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java
index c81de344..6c72bf47 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java
@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.data.header.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Motorola Proprietary Data Header
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
index a65dd2c6..2e9c03d1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
@@ -116,7 +116,7 @@ public class LCMessageFactory
//Some Hytera Tier-3 systems use a zero-valued mask when the FLC is carried in the voice header or terminator
//and in the same transmission it uses the standard mask when carried across the voice frames. It doesn't make
//sense, but maybe it's a bug in their software implementation?
- if(!valid && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
+ if(!valid && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
{
//Retry the RS(12,9,4) with a mask value of zero
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message, 0);
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java
index 2e1439ae..2705248d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.edac.Hamming16;
+import io.github.dsheirer.module.decode.dmr.bptc.BPTC_128_77;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import org.slf4j.Logger;
@@ -38,6 +38,7 @@ public class FLCAssembler
private int mFragmentCount = 0;
private long mTimestamp;
private int mTimeslot;
+ private BPTC_128_77 mBPTC = new BPTC_128_77();
/**
* Constructs an instance
@@ -47,6 +48,15 @@ public class FLCAssembler
mTimeslot = timeslot;
}
+ /**
+ * Resets this assembler when a sync loss is detected.
+ */
+ public void reset()
+ {
+ mAssemblingMessage = null;
+ mFragmentCount = 0;
+ }
+
/**
* Processes the full link control fragment
* @return a full link control message or null if the message is not yet fully assembled
@@ -101,7 +111,8 @@ public class FLCAssembler
if(mFragmentCount == 4)
{
- message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
+// message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
+ message = decode2(mAssemblingMessage, mTimestamp, mTimeslot);
}
mAssemblingMessage = null;
@@ -112,88 +123,124 @@ public class FLCAssembler
/**
* Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
*/
- private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
+ private FullLCMessage decode2(BinaryMessage message, long timestamp, int timeslot)
{
if(message != null)
{
- boolean valid = true;
-
- CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128);
- int src;
- descrambled.set(127, message.get(127));
- for(int i = 0; i < 127; i++)
- {
- src = (i * 8) % 127;
- boolean x = message.get(src);
- descrambled.set(i, message.get(src));
- }
-
- int errorCount = 0;
-
- for(int row = 0; row < 8; row++)
- {
- int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
-
- errorCount += rowErrorCount;
-
- if(errorCount > 1)
- {
- valid = false;
- }
- }
-
- //If valid, check the column parity values
- if(valid)
- {
- //Check the column parity values
- for(int column = 0; column < 16; column++)
- {
- boolean parity = descrambled.get(column);
-
- for(int y = 1; y < 8; y++)
- {
- parity ^= descrambled.get(column + (y * 16));
- }
-
- if(parity)
- {
- valid = false;
- }
- }
- }
-
- //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
- CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
- int pointer = 0;
-
- for(int row = 0; row < 2; row++)
- {
- for(int column = 0; column < 11; column++)
- {
- extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
- }
- }
-
- for(int row = 2; row < 7; row++)
- {
- for(int column = 0; column < 10; column++)
- {
- extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
- }
-
- //Transfer the checksum parity bit from this row
- extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
- }
-
- extractedMessage.setCorrectedBitCount(errorCount);
+ CorrectedBinaryMessage extractedMessage = mBPTC.extract(message);
FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
- flco.setValid(valid);
+ flco.setValid(extractedMessage.getCorrectedBitCount() >= 0);
return flco;
}
return null;
}
+
+// /**
+// * Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
+// */
+// private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
+// {
+// if(message != null)
+// {
+// System.out.println("FLC Raw:" + message);
+// boolean valid = true;
+//
+// CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128);
+// int src;
+// descrambled.set(127, message.get(127));
+// for(int i = 0; i < 127; i++)
+// {
+// src = (i * 8) % 127;
+// boolean x = message.get(src);
+// descrambled.set(i, message.get(src));
+// }
+//
+// System.out.println("FLC Des:" + descrambled);
+// int errorCount = 0;
+//
+// for(int row = 0; row < 8; row++)
+// {
+// int errorIndex = Hamming16.getErrorIndex(descrambled, row * 16);
+// BinaryMessage rowErrors = BPTC_128_77.getRowErrors(descrambled);
+// BinaryMessage columnErrors = BPTC_128_77.getColumnErrors(descrambled);
+//
+// int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
+//
+// if(rowErrorCount == 0)
+// {
+// System.out.println("Row [" + row + "]");
+// }
+// else
+// {
+// System.out.println("Row [" + row + "] Error Count: " + rowErrorCount + " Index:" + errorIndex +
+// " Value: " + message.get(errorIndex) +
+// " Row Errors: " + rowErrors + " Column Errors: " + columnErrors );
+// }
+//
+// errorCount += rowErrorCount;
+//
+// if(errorCount > 1)
+// {
+// valid = false;
+// }
+// }
+//
+// //If valid, check the column parity values
+// if(valid)
+// {
+// //Check the column parity values
+// for(int column = 0; column < 16; column++)
+// {
+// boolean parity = descrambled.get(column);
+//
+// for(int y = 1; y < 8; y++)
+// {
+// parity ^= descrambled.get(column + (y * 16));
+// }
+//
+// if(parity)
+// {
+// valid = false;
+// }
+// }
+// }
+//
+// //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
+// CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
+// int pointer = 0;
+//
+// for(int row = 0; row < 2; row++)
+// {
+// for(int column = 0; column < 11; column++)
+// {
+// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
+// }
+// }
+//
+// for(int row = 2; row < 7; row++)
+// {
+// for(int column = 0; column < 10; column++)
+// {
+// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
+// }
+//
+// //Transfer the checksum parity bit from this row
+// extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
+// }
+//
+// extractedMessage.setCorrectedBitCount(errorCount);
+//
+//
+// FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
+// flco.setValid(valid);
+// return flco;
+// }
+//
+// return null;
+// }
+
/**
* Adds the cach payload to the currently assembling message
*/
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java
index 80d3fba9..fb4e15e9 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/GroupVoiceChannelUser.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -68,7 +68,6 @@ public class GroupVoiceChannelUser extends AbstractVoiceChannelUser
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getServiceOptions());
- sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java
index 32eda818..ec477fdc 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/TalkerAliasHeader.java
@@ -100,6 +100,12 @@ public class TalkerAliasHeader extends FullLCMessage
public String toString()
{
StringBuilder sb = new StringBuilder();
+
+ if(!isValid())
+ {
+ sb.append("[CRC-ERROR] ");
+ }
+
sb.append("FLC TALKER ALIAS HEADER");
sb.append(" FORMAT:").append(getFormat());
sb.append(" CHARACTERS:").append(getCharacterLength());
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java
index 3794f389..2c2f404d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ConnectPlusControlChannel.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRNetwork;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
-
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +58,7 @@ public class ConnectPlusControlChannel extends ShortLCMessage
}
sb.append("SLC MOTOROLA CON+ CONTROL CHANNEL NETWORK:").append(getNetwork());
sb.append(" SITE:").append(getSite());
+ sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java
index 0bb70579..1cd84a42 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/SLCAssembler.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.edac.Hamming17;
+import io.github.dsheirer.module.decode.dmr.bptc.BPTC_68_36;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import org.slf4j.Logger;
@@ -37,6 +37,7 @@ public class SLCAssembler
private CorrectedBinaryMessage mAssemblingMessage;
private int mFragmentCount = 0;
private long mTimestamp;
+ private BPTC_68_36 mBPTC = new BPTC_68_36();
/**
* Constructs an instance
@@ -45,6 +46,16 @@ public class SLCAssembler
{
}
+ /**
+ * Resets this assembler when a sync loss is detected to prevent malformed SLC messages.
+ */
+ public void reset()
+ {
+ mAssemblingMessage = null;
+ mFragmentCount = 0;
+ mTimestamp = System.currentTimeMillis();
+ }
+
/**
* Processes the short link control fragment
* @return a short link control message or null if the message is not yet fully assembled
@@ -121,80 +132,20 @@ public class SLCAssembler
return message;
}
- private static ShortLCMessage decode(BinaryMessage interleaved, long timestamp)
+ /**
+ * Performs error detection and correction and extracts the message from the BPTC protected interleaved message.
+ * @param interleaved message
+ * @param timestamp for the decoded message.
+ * @return decoded message.
+ */
+ private ShortLCMessage decode(BinaryMessage interleaved, long timestamp)
{
if(interleaved != null)
{
- boolean valid = true;
-
- CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(68);
- int i, src;
-
- for(int index = 0; index < 67; index++)
- {
- if(interleaved.get(index))
- {
- deinterleaved.set((index * 17 % 67));
- }
- }
-
- if(interleaved.get(67))
- {
- deinterleaved.set(67);
- }
-
- int errorCount = 0;
-
- for(int row = 0; row < 4; row++)
- {
- int rowErrorCount = Hamming17.checkAndCorrect(deinterleaved, row * 17);
- errorCount += rowErrorCount;
-
- if(rowErrorCount > 1)
- {
- valid = false;
- }
- }
-
- //If valid, check the column parity values
- if(valid)
- {
- //Check the column parity values
- for(int column = 0; column < 17; column++)
- {
- boolean parity = deinterleaved.get(column);
-
- for(int y = 1; y < 4; y++)
- {
- parity ^= deinterleaved.get(column + (y * 17));
- }
-
- if(parity)
- {
- valid = false;
- }
- }
- }
-
- //Extract the message
- CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(51);
-
- for(int row = 0; row < 3; row++)
- {
- for(int column = 0; column < 12; column++)
- {
- extractedMessage.set(row * 12 + column, deinterleaved.get(row * 17 + column));
- }
- }
-
- extractedMessage.setCorrectedBitCount(errorCount);
- extractedMessage.setSize(36);
- ShortLCMessage slco = LCMessageFactory.createShort(extractedMessage, timestamp, 0);
-
-
+ CorrectedBinaryMessage corrected = mBPTC.extract(interleaved);
//Note: slco is timeslot-agnostic, so we use timeslot 0 every time
-// ShortLCMessage slco = LCMessageFactory.createShort(decoded, timestamp, 0);
- slco.setValid(valid);
+ ShortLCMessage slco = LCMessageFactory.createShort(corrected, timestamp, 0);
+ slco.setValid(corrected.getCorrectedBitCount() >= 0);
return slco;
}
@@ -231,18 +182,4 @@ public class SLCAssembler
dispatch();
}
}
-
- public static void main(String[] args)
- {
- String raw = "A0000C0000A0000C0000";
- System.out.println(" RAW:" + raw);
- String bits = "10100000000000000110000000000000001010000000000000011000000000000000";
-
- BinaryMessage rawBin= BinaryMessage.load(bits);
- System.out.println("ORIG:" + rawBin.toHexString());
-
- ShortLCMessage slc = decode(rawBin, System.currentTimeMillis());
-
- System.out.println(slc.toString());
- }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java
index 4b865312..689991d1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/mbc/MBCContinuationBlock.java
@@ -20,10 +20,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.mbc;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java
index d8013377..a8aa0333 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/terminator/Terminator.java
@@ -20,10 +20,10 @@
package io.github.dsheirer.module.decode.dmr.message.data.terminator;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
/**
* Terminator data message with link control
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java
index 199b40ed..94b8fae5 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/usb/USBData.java
@@ -23,12 +23,12 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceType;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java
index 8eab4622..b3cb07b6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/filter/VoiceMessageFilter.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import java.util.function.Function;
@@ -51,8 +51,8 @@ public class VoiceMessageFilter extends Filter
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_D));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_E));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_F));
- add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_1));
- add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_2));
+ add(new FilterElement<>(DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1));
+ add(new FilterElement<>(DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2));
}
@Override
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java
index 9300db1f..c96f5868 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java
@@ -21,8 +21,8 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
index fb026978..4214ab14 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
index a092ab65..3054c213 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import java.util.ArrayList;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
index c4bd455d..38d8b087 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,12 +21,12 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.edac.BPTC_16_2;
import io.github.dsheirer.edac.Golay24;
+import io.github.dsheirer.module.decode.dmr.bptc.BPTC_16_2;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedEncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
-import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NonStandardShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
@@ -183,7 +183,7 @@ public class VoiceSuperFrameProcessor
return switch(opcode)
{
case NULL -> new NullShortBurst(decoded);
- case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EncryptionParameters(decoded);
+ case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EmbeddedEncryptionParameters(decoded);
case TXI_DELAY -> new TransmitInterrupt(decoded);
default -> new UnknownShortBurst(decoded);
};
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java
similarity index 93%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java
index 987bc00c..12b2be93 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedEncryptionParameters.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
/**
* Encryption parameters short burst payload.
*/
-public class EncryptionParameters extends ShortBurst
+public class EmbeddedEncryptionParameters extends ShortBurst
{
private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] ALGORITHM = new int[]{8, 9, 10};
@@ -35,7 +35,7 @@ public class EncryptionParameters extends ShortBurst
*
* @param message containing the de-interleaved and error-corrected short burst payload.
*/
- public EncryptionParameters(CorrectedBinaryMessage message)
+ public EmbeddedEncryptionParameters(CorrectedBinaryMessage message)
{
super(message);
}
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
new file mode 100644
index 00000000..3293e5f7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRHardSyncDetector.java
@@ -0,0 +1,113 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+
+/**
+ * DMR sync detector that processes hard symbol (ie dibit) decisions.
+ */
+public class DMRHardSyncDetector extends DMRSyncDetector
+{
+ private static final long SYNC_MASK = 0xFFFFFFFFFFFFL;
+ private static final int MAXIMUM_BIT_ERROR = 5;
+ private long mValue;
+ private DMRSyncPattern mDetectedPattern = DMRSyncPattern.UNKNOWN;
+
+ /**
+ * Constructs an instance
+ */
+ public DMRHardSyncDetector()
+ {
+ }
+
+ public DMRSyncPattern getDetectedPattern()
+ {
+ return mDetectedPattern;
+ }
+
+ /**
+ * Processes the dibit.
+ * @param dibit to process
+ * @return true if a sync pattern is detected.
+ */
+ public boolean process(Dibit dibit)
+ {
+ mValue = (Long.rotateLeft(mValue, 2) & SYNC_MASK) + dibit.getValue();
+
+ int bitCount = Long.bitCount(mValue ^ DMRSyncPattern.BASE_STATION_DATA.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.BASE_STATION_VOICE.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.MOBILE_STATION_DATA.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.MOBILE_STATION_VOICE.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_DATA_TIMESLOT_1.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_DATA_TIMESLOT_2.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ return true;
+ }
+
+ bitCount = Long.bitCount(mValue ^ DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2.getPattern());
+ if(bitCount <= MAXIMUM_BIT_ERROR)
+ {
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ return true;
+ }
+
+ 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
new file mode 100644
index 00000000..00120cea
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetector.java
@@ -0,0 +1,93 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+import java.util.Arrays;
+
+/**
+ * DMR Sync Detector. Uses soft demodulated symbols to detect sync patterns in the demodulated symbol stream.
+ */
+public abstract class DMRSoftSyncDetector extends DMRSyncDetector
+{
+ protected static final float MAX_POSITIVE = Dibit.D01_PLUS_3.getIdealPhase();
+ protected static final float MAX_NEGATIVE = Dibit.D11_MINUS_3.getIdealPhase();
+ protected static final float[] BASE_DATA = DMRSyncPattern.BASE_STATION_DATA.toSymbols();
+ protected static final float[] BASE_VOICE = DMRSyncPattern.BASE_STATION_VOICE.toSymbols();
+ protected static final float[] MOBILE_DATA = DMRSyncPattern.MOBILE_STATION_DATA.toSymbols();
+ protected static final float[] MOBILE_VOICE = DMRSyncPattern.MOBILE_STATION_VOICE.toSymbols();
+ protected static final float[] DIRECT_DATA_1 = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1.toSymbols();
+ protected static final float[] DIRECT_DATA_2 = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2.toSymbols();
+ protected static final float[] DIRECT_VOICE_1 = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1.toSymbols();
+ protected static final float[] DIRECT_VOICE_2 = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2.toSymbols();
+
+ protected float[] mSymbols = new float[48]; //2x longer than 48-bit/24-dibit DMR sync patterns.
+ protected int mSymbolPointer = 0;
+
+ public DMRSoftSyncDetector()
+ {
+ }
+
+ /**
+ * Resets this sync detector and flushes any stored soft symbols from the buffer.
+ */
+ public void reset()
+ {
+ Arrays.fill(mSymbols, 0.0f);
+ mSymbolPointer = 0;
+ }
+
+ /**
+ * Detected DMR sync pattern that produced the highest correlation score from the most recent call to process().
+ * @return detected sync pattern.
+ */
+ public DMRSyncPattern getDetectedPattern()
+ {
+ return mDetectedPattern;
+ }
+
+ /**
+ * 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.
+ */
+ protected 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)
+ {
+ //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;
+
+ return calculate();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java
new file mode 100644
index 00000000..8e29d8a0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorFactory.java
@@ -0,0 +1,59 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import io.github.dsheirer.vector.calibrate.CalibrationManager;
+import io.github.dsheirer.vector.calibrate.CalibrationType;
+import io.github.dsheirer.vector.calibrate.Implementation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for creating DMR Soft Sync Detectors
+ */
+public class DMRSoftSyncDetectorFactory
+{
+ private static final Logger mLog = LoggerFactory.getLogger(DMRSoftSyncDetectorFactory.class);
+
+ /**
+ * Creates the implementation using calibration data to select the optimal
+ * implementation from scalar and vector options.
+ * @return demodulator instance
+ */
+ public static DMRSoftSyncDetector getDetector()
+ {
+ Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.DMR_SOFT_SYNC_DETECTOR);
+
+ switch(implementation)
+ {
+ case VECTOR_SIMD_64:
+ return new DMRSoftSyncDetectorVector64();
+ case VECTOR_SIMD_128:
+ return new DMRSoftSyncDetectorVector128();
+ case VECTOR_SIMD_256:
+ return new DMRSoftSyncDetectorVector256();
+ case VECTOR_SIMD_512:
+ return new DMRSoftSyncDetectorVector512();
+ case SCALAR:
+ default:
+ return new DMRSoftSyncDetectorScalar();
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java
new file mode 100644
index 00000000..49353f11
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorScalar.java
@@ -0,0 +1,192 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+/**
+ * Scalar implementation of DMR Soft Sync Detector.
+ */
+public class DMRSoftSyncDetectorScalar extends DMRSoftSyncDetector
+{
+ /**
+ * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
+ * symbols that include this recent soft symbol.
+ *
+ * @return correlation score.
+ */
+ public float calculate()
+ {
+ float symbol;
+
+ switch(mMode)
+ {
+ case AUTOMATIC:
+ float allBaseData = 0;
+ float allBaseVoice = 0;
+ float allMobileData = 0;
+ float allMobileVoice = 0;
+ float allDirectData1 = 0;
+ float allDirectData2 = 0;
+ float allDirectVoice1 = 0;
+ float allDirectVoice2 = 0;
+
+ for(int x = 0; x < 24; x++)
+ {
+ symbol = mSymbols[mSymbolPointer + x];
+
+ allBaseData += BASE_DATA[x] * symbol;
+ allBaseVoice += BASE_VOICE[x] * symbol;
+ allMobileData += MOBILE_DATA[x] * symbol;
+ allMobileVoice += MOBILE_VOICE[x] * symbol;
+ allDirectData1 += DIRECT_DATA_1[x] * symbol;
+ allDirectData2 += DIRECT_DATA_2[x] * symbol;
+ allDirectVoice1 += DIRECT_VOICE_1[x] * symbol;
+ allDirectVoice2 += DIRECT_VOICE_2[x] * symbol;
+ }
+
+ float allBestScore = allBaseData;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ if(allBaseVoice > allBestScore)
+ {
+ allBestScore = allBaseVoice;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ if(allMobileData > allBestScore)
+ {
+ allBestScore = allMobileData;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ }
+
+ if(allMobileVoice > allBestScore)
+ {
+ allBestScore = allMobileVoice;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ if(allDirectData1 > allBestScore)
+ {
+ allBestScore = allDirectData1;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ }
+
+ if(allDirectData2 > allBestScore)
+ {
+ allBestScore = allDirectData2;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ if(allDirectVoice1 > allBestScore)
+ {
+ allBestScore = allDirectVoice1;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ if(allDirectVoice2 > allBestScore)
+ {
+ allBestScore = allDirectVoice2;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return allBestScore;
+ case BASE_ONLY:
+ float baseData = 0;
+ float baseVoice = 0;
+
+ for(int x = 0; x < 24; x++)
+ {
+ symbol = mSymbols[mSymbolPointer + x];
+ baseData += BASE_DATA[x] * symbol;
+ baseVoice += BASE_VOICE[x] * symbol;
+ }
+
+ if(baseData > baseVoice)
+ {
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+ return baseData;
+ }
+ else
+ {
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ return baseVoice;
+ }
+ case MOBILE_ONLY:
+ float mobileData = 0;
+ float mobileVoice = 0;
+
+ for(int x = 0; x < 24; x++)
+ {
+ symbol = mSymbols[mSymbolPointer + x];
+ mobileData += MOBILE_DATA[x] * symbol;
+ mobileVoice += MOBILE_VOICE[x] * symbol;
+ }
+
+ float mobileBestScore = mobileVoice;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+
+ if(mobileData > mobileBestScore)
+ {
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ mobileBestScore = mobileData;
+ }
+
+ return mobileBestScore;
+ case DIRECT_ONLY:
+ float directData1 = 0;
+ float directData2 = 0;
+ float directVoice1 = 0;
+ float directVoice2 = 0;
+
+ for(int x = 0; x < 24; x++)
+ {
+ symbol = mSymbols[mSymbolPointer + x];
+ directData1 += DIRECT_DATA_1[x] * symbol;
+ directData2 += DIRECT_DATA_2[x] * symbol;
+ directVoice1 += DIRECT_VOICE_1[x] * symbol;
+ directVoice2 += DIRECT_VOICE_2[x] * symbol;
+ }
+
+ float directBestScore = directData1;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+
+ if(directData2 > directBestScore)
+ {
+ directBestScore = directData2;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ if(directVoice1 > directBestScore)
+ {
+ directBestScore = directVoice1;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ if(directVoice2 > directBestScore)
+ {
+ directBestScore = directVoice2;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return directBestScore;
+ default:
+ throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java
new file mode 100644
index 00000000..b1f7fc06
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector128.java
@@ -0,0 +1,395 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * SIMD Vector 128 implementation of DMR Soft Sync Detector.
+ */
+public class DMRSoftSyncDetectorVector128 extends DMRSoftSyncDetector
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_128;
+
+ /**
+ * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
+ * symbols that include this recent soft symbol.
+ *
+ * @return correlation score.
+ */
+ public float calculate()
+ {
+ FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer);
+ FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 4);
+ FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8);
+ FloatVector symbols4 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 12);
+ FloatVector symbols5 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16);
+ FloatVector symbols6 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 20);
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector sync1, sync2, sync3, sync4, sync5, sync6;
+ float bestScore, candidate;
+
+ switch(mMode)
+ {
+ case AUTOMATIC:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ case BASE_ONLY:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case MOBILE_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case DIRECT_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ default:
+ throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode);
+ }
+ }
+}
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
new file mode 100644
index 00000000..b01f5edd
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector256.java
@@ -0,0 +1,296 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * SIMD Vector 256 implementation of DMR Soft Sync Detector.
+ */
+public class DMRSoftSyncDetectorVector256 extends DMRSoftSyncDetector
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_256;
+
+ /**
+ * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
+ * symbols that include this recent soft symbol.
+ *
+ * @return correlation score.
+ */
+ public float calculate()
+ {
+ FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer);
+ FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8);
+ FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16);
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector sync1, sync2, sync3;
+ float bestScore, candidate;
+
+ switch(mMode)
+ {
+ case AUTOMATIC:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ case BASE_ONLY:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case MOBILE_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case DIRECT_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ default:
+ throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java
new file mode 100644
index 00000000..e7be109b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector512.java
@@ -0,0 +1,270 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * SIMD Vector 512 implementation of DMR Soft Sync Detector.
+ */
+public class DMRSoftSyncDetectorVector512 extends DMRSoftSyncDetector
+{
+ private static final boolean[] VECTOR_MASK = new boolean[]{false, false, false, false, false, false, false, false,
+ true, true, true, true, true, true, true, true};
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_512;
+
+ /**
+ * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
+ * symbols that include this recent soft symbol.
+ *
+ * @return correlation score.
+ */
+ public float calculate()
+ {
+ //There are 24 symbols that we load into 2x vectors of 16. We load the first 16 symbols into vector 1 and the
+ //remaining 8 symbols into vector 2 using a vector mask that ignores the first 8 symbols and loads the second 8
+ // symbols. This sets the first 8 symbols to zero and those values are ignored during calculations.
+ VectorMask mask = VectorMask.fromArray(VECTOR_SPECIES, VECTOR_MASK, 0);
+ FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer);
+ FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8, mask);
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector sync1, sync2;
+ float bestScore, candidate;
+
+ switch(mMode)
+ {
+ case AUTOMATIC:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ case BASE_ONLY:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case MOBILE_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case DIRECT_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ default:
+ throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java
new file mode 100644
index 00000000..9689ec5f
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSoftSyncDetectorVector64.java
@@ -0,0 +1,593 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorOperators;
+import jdk.incubator.vector.VectorSpecies;
+
+/**
+ * SIMD Vector 64 implementation of DMR Soft Sync Detector.
+ */
+public class DMRSoftSyncDetectorVector64 extends DMRSoftSyncDetector
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_64;
+
+ /**
+ * Processes the demodulated soft symbol value and returns a correlation value against the preceding 24 soft
+ * symbols that include this recent soft symbol.
+ *
+ * @return correlation score.
+ */
+ public float calculate()
+ {
+ FloatVector symbols1 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer);
+ FloatVector symbols2 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 2);
+ FloatVector symbols3 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 4);
+ FloatVector symbols4 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 6);
+ FloatVector symbols5 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 8);
+ FloatVector symbols6 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 10);
+ FloatVector symbols7 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 12);
+ FloatVector symbols8 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 14);
+ FloatVector symbols9 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 16);
+ FloatVector symbols10 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 18);
+ FloatVector symbols11 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 20);
+ FloatVector symbols12 = FloatVector.fromArray(VECTOR_SPECIES, mSymbols, mSymbolPointer + 22);
+ FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
+ FloatVector sync1, sync2, sync3, sync4, sync5, sync6, sync7, sync8, sync9, sync10, sync11, sync12;
+ float bestScore, candidate;
+
+ switch(mMode)
+ {
+ case AUTOMATIC:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ case BASE_ONLY:
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_DATA, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, BASE_VOICE, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.BASE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case MOBILE_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_DATA, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_DATA;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, MOBILE_VOICE, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.MOBILE_STATION_VOICE;
+ }
+
+ return bestScore;
+ case DIRECT_ONLY:
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_1, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ bestScore = accumulator.reduceLanes(VectorOperators.ADD);
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_1;
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_DATA_2, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_DATA_TIMESLOT_2;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_1, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_1;
+ }
+
+ accumulator = FloatVector.zero(VECTOR_SPECIES);
+ sync1 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 0);
+ sync2 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 2);
+ sync3 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 4);
+ sync4 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 6);
+ sync5 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 8);
+ sync6 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 10);
+ sync7 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 12);
+ sync8 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 14);
+ sync9 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 16);
+ sync10 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 18);
+ sync11 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 20);
+ sync12 = FloatVector.fromArray(VECTOR_SPECIES, DIRECT_VOICE_2, 22);
+
+ accumulator = symbols1.fma(sync1, accumulator);
+ accumulator = symbols2.fma(sync2, accumulator);
+ accumulator = symbols3.fma(sync3, accumulator);
+ accumulator = symbols4.fma(sync4, accumulator);
+ accumulator = symbols5.fma(sync5, accumulator);
+ accumulator = symbols6.fma(sync6, accumulator);
+ accumulator = symbols7.fma(sync7, accumulator);
+ accumulator = symbols8.fma(sync8, accumulator);
+ accumulator = symbols9.fma(sync9, accumulator);
+ accumulator = symbols10.fma(sync10, accumulator);
+ accumulator = symbols11.fma(sync11, accumulator);
+ accumulator = symbols12.fma(sync12, accumulator);
+ candidate = accumulator.reduceLanes(VectorOperators.ADD);
+
+ if(candidate > bestScore)
+ {
+ bestScore = candidate;
+ mDetectedPattern = DMRSyncPattern.DIRECT_VOICE_TIMESLOT_2;
+ }
+
+ return bestScore;
+ default:
+ throw new IllegalStateException("Unrecognized DMR Sync Detection Mode: " + mMode);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java
new file mode 100644
index 00000000..96f58e96
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetectMode.java
@@ -0,0 +1,28 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+/**
+ * DMR sync detection modes enumeration.
+ */
+public enum DMRSyncDetectMode
+{
+ AUTOMATIC, BASE_ONLY, MOBILE_ONLY, DIRECT_ONLY;
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java
new file mode 100644
index 00000000..71515244
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncDetector.java
@@ -0,0 +1,48 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+/**
+ * Base implementation of DMR sync detector that uses sync detection modes to activate or deactivate groups of DMR
+ * sync patterns. The default mode is AUTOMATIC to monitor for all sync patterns. Additional modes can be used to
+ * limit sync monitoring for smaller groups of sync patterns for base station, mobile stations and direct mode.
+ */
+public abstract class DMRSyncDetector
+{
+ protected DMRSyncDetectMode mMode = DMRSyncDetectMode.AUTOMATIC;
+ protected DMRSyncPattern mDetectedPattern = DMRSyncPattern.BASE_STATION_DATA;
+
+ /**
+ * Sets the operating mode for this detector.
+ * @param mode of sync detection.
+ */
+ public void setMode(DMRSyncDetectMode mode)
+ {
+ mMode = mode;
+ }
+
+ /**
+ * Current mode of operation
+ */
+ public DMRSyncDetectMode getMode()
+ {
+ return mMode;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java
new file mode 100644
index 00000000..ab76373a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncModeMonitor.java
@@ -0,0 +1,115 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Monitors sync pattern detections to automatically detect the prevailing or dominant sync mode group used on a channel
+ * and optimizes the DMR sync detectors to reduce the quantity of false detects of unused sync patterns and the overall
+ * sync detection processing workload.
+ */
+public class DMRSyncModeMonitor
+{
+ private static final int DOMINANT_THRESHOLD = 10;
+ private final List mSyncDetectors = new ArrayList<>();
+ private int mBaseCount;
+ private int mMobileCount;
+ private int mDirectCount;
+ private DMRSyncDetectMode mMode = DMRSyncDetectMode.AUTOMATIC;
+
+ /**
+ * Constructs an instance
+ */
+ public DMRSyncModeMonitor()
+ {
+ }
+
+ /**
+ * Sets (and locks) the sync detection mode. This can be directly invoked when the operating mode is known
+ * in advance, such as for a repeated traffic channel where the expected mode is BASE_ONLY, otherwise this method
+ * is automatically invoked by this monitor when operating in ALL mode, once the dominant mode is established.
+ * @param mode to set
+ */
+ public void setMode(DMRSyncDetectMode mode)
+ {
+ mMode = mode;
+
+ for(DMRSyncDetector detector : mSyncDetectors)
+ {
+ detector.setMode(mode);
+ }
+ }
+
+ /**
+ * Indicates a sync pattern is positively detected and updates the current sync detection mode for all monitored
+ * sync detectors once the dominant mode is established.
+ * @param pattern that was detected.
+ */
+ public void detected(DMRSyncPattern pattern)
+ {
+ if(mMode != DMRSyncDetectMode.AUTOMATIC)
+ {
+ return;
+ }
+
+ switch(pattern)
+ {
+ case BASE_STATION_DATA:
+ case BASE_STATION_VOICE:
+ mBaseCount++;
+ break;
+ case MOBILE_STATION_DATA:
+ case MOBILE_STATION_VOICE:
+ case REVERSE_CHANNEL:
+ mMobileCount++;
+ break;
+ case DIRECT_DATA_TIMESLOT_1:
+ case DIRECT_DATA_TIMESLOT_2:
+ case DIRECT_VOICE_TIMESLOT_1:
+ case DIRECT_VOICE_TIMESLOT_2:
+ mDirectCount++;
+ break;
+ }
+
+ if(mBaseCount - mMobileCount > DOMINANT_THRESHOLD && mBaseCount - mDirectCount > DOMINANT_THRESHOLD)
+ {
+ setMode(DMRSyncDetectMode.BASE_ONLY);
+ }
+ else if(mMobileCount - mBaseCount > DOMINANT_THRESHOLD && mMobileCount - mDirectCount > DOMINANT_THRESHOLD)
+ {
+ setMode(DMRSyncDetectMode.MOBILE_ONLY);
+ }
+ else if(mDirectCount - mBaseCount > DOMINANT_THRESHOLD && mDirectCount - mMobileCount > DOMINANT_THRESHOLD)
+ {
+ setMode(DMRSyncDetectMode.DIRECT_ONLY);
+ }
+ }
+
+ /**
+ * Adds a sync detector to be managed by this instance.
+ * @param syncDetector to add
+ */
+ public void add(DMRSyncDetector syncDetector)
+ {
+ mSyncDetectors.add(syncDetector);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java
new file mode 100644
index 00000000..b6b90f3d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/sync/DMRSyncPattern.java
@@ -0,0 +1,291 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+package io.github.dsheirer.module.decode.dmr.sync;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+import java.util.EnumSet;
+
+/**
+ * DMR burst sync patterns enumeration.
+ *
+ * Note: in addition to the ETSI-defined patterns, there are additional entries that are used as placeholder sync
+ * patterns for tracking bursts across voice super frames and for tracking empty timeslots for direct mode.
+ */
+public enum DMRSyncPattern
+{
+ BASE_STATION_DATA(0xDFF57D75DF5DL, "BS DATA"),
+ BASE_STATION_VOICE(0x755FD7DF75F7L, "BS VOICE A"),
+
+ //These are used to identify the sync-less sub frames of a voice super frame
+ BS_VOICE_FRAME_B("BS VOICE B"),
+ BS_VOICE_FRAME_C("BS VOICE C"),
+ BS_VOICE_FRAME_D("BS VOICE D"),
+ BS_VOICE_FRAME_E("BS VOICE E"),
+ BS_VOICE_FRAME_F("BS VOICE F"),
+
+ MOBILE_STATION_DATA(0xD5D7F77FD757L, "MS DATA"),
+ MOBILE_STATION_VOICE(0x7F7D5DD57DFDL, "MS VOICE A"),
+ REVERSE_CHANNEL(0x77D55F7DFD77L, "MS REVERSE"),
+
+ //These are used to identify the sync-less sub frames of a voice super frame
+ MS_VOICE_FRAME_B("MS VOICE B"),
+ MS_VOICE_FRAME_C("MS VOICE C"),
+ MS_VOICE_FRAME_D("MS VOICE D"),
+ MS_VOICE_FRAME_E("MS VOICE E"),
+ MS_VOICE_FRAME_F("MS VOICE F"),
+
+ DIRECT_EMPTY_TIMESLOT("DIRECT EMPTY TIMESLOT"),
+
+ DIRECT_DATA_TIMESLOT_1(0xF7FDD5DDFD55L, "DIRECT DATA TS 1"),
+ DIRECT_DATA_TIMESLOT_2(0xD7557F5FF7F5L, "DIRECT DATA TS 2"),
+ DIRECT_VOICE_TIMESLOT_1(0x5D577F7757FFL, "DIRECT VOICE A TS 1"),
+ DIRECT_VOICE_TIMESLOT_2(0x7DFFD5F55D5FL, "DIRECT VOICE A TS 2"),
+
+ //These are used to identify the sync-less sub frames of a voice super frame
+ DIRECT_VOICE_FRAME_B("DIRECT VOICE B"),
+ DIRECT_VOICE_FRAME_C("DIRECT VOICE C"),
+ DIRECT_VOICE_FRAME_D("DIRECT VOICE D"),
+ DIRECT_VOICE_FRAME_E("DIRECT VOICE E"),
+ DIRECT_VOICE_FRAME_F("DIRECT VOICE F"),
+
+ RESERVED(0xDD7FF5D757DDl, "RESERVED"),
+ UNKNOWN("UNKNOWN");
+
+ private long mPattern;
+ private String mLabel;
+
+ /**
+ * DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1
+ */
+ DMRSyncPattern(long pattern, String label)
+ {
+ mPattern = pattern;
+ mLabel = label;
+ }
+
+ /**
+ * Alternate constructor for syncs that don't have defined patterns.
+ */
+ DMRSyncPattern(String label)
+ {
+ mLabel = label;
+ }
+
+ //Valid sync patterns (excluding the sync-less voice patterns)
+ public static final EnumSet SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
+ MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_DATA_TIMESLOT_1, DIRECT_DATA_TIMESLOT_2,
+ DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2);
+
+ //Sync patterns containing a Common Announcement Channel (CACH)
+ private static final EnumSet CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
+ BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F);
+
+ //Direct Mode TS 1 Sync Patterns
+ private static final EnumSet DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_VOICE_TIMESLOT_1,
+ DIRECT_DATA_TIMESLOT_1);
+
+ //Direct Mode TS 2 Sync Patterns
+ private static final EnumSet DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_VOICE_TIMESLOT_2,
+ DIRECT_DATA_TIMESLOT_2);
+
+ //Direct Mode Pseudo Voice Frame Patterns
+ private static final EnumSet DIRECT_MODE_VOICE_PATTERNS = EnumSet.range(DIRECT_VOICE_FRAME_B,
+ DIRECT_VOICE_FRAME_F);
+
+ //Mobile Station Sync Patterns
+ private static final EnumSet MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA,
+ MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E,
+ MS_VOICE_FRAME_F);
+
+ //Voice frame patterns
+ private static final EnumSet VOICE_PATTERNS = EnumSet.of(BASE_STATION_VOICE, BS_VOICE_FRAME_B,
+ BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F, MOBILE_STATION_VOICE,
+ MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F,
+ DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2, DIRECT_EMPTY_TIMESLOT, DIRECT_VOICE_FRAME_B,
+ DIRECT_VOICE_FRAME_C, DIRECT_VOICE_FRAME_D, DIRECT_VOICE_FRAME_E, DIRECT_VOICE_FRAME_F);
+
+ /**
+ * Pattern that represents the enum entry
+ */
+ public long getPattern()
+ {
+ return mPattern;
+ }
+
+ /**
+ * Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH).
+ */
+ public boolean hasCACH()
+ {
+ return CACH_PATTERNS.contains(this);
+ }
+
+ /**
+ * Indicates if this is a Direct mode sync pattern, either timeslot 1 or 2.
+ */
+ public boolean isDirect()
+ {
+ return isDirectTS1() || isDirectTS2() || isDirectVoice();
+ }
+
+ /**
+ * Indicates if this is Direct Mode sync pattern for timeslot 1
+ * @return true if Direct Mode TS1
+ */
+ public boolean isDirectTS1()
+ {
+ return DIRECT_MODE_TS1_PATTERNS.contains(this);
+ }
+
+ /**
+ * Indicates if this is Direct Mode sync pattern for timeslot 2
+ * @return true if Direct Mode TS2
+ */
+ public boolean isDirectTS2()
+ {
+ return DIRECT_MODE_TS2_PATTERNS.contains(this);
+ }
+
+ /**
+ * Indicates if this is Direct Mode voice sync pattern
+ * @return true if Direct Mode Voice
+ */
+ public boolean isDirectVoice()
+ {
+ return DIRECT_MODE_VOICE_PATTERNS.contains(this);
+ }
+
+ /**
+ * Indicates if this is a mobile station sync pattern
+ * @return true if a mobile station sync pattern.
+ */
+ public boolean isMobileStationSyncPattern()
+ {
+ return MOBILE_STATION_PATTERNS.contains(this);
+ }
+
+ /**
+ * Indicates if this is a mobile sync pattern, either mobile station or direct mode.
+ * @return true if a mobile sync pattern.
+ */
+ public boolean isMobileSyncPattern()
+ {
+ return isMobileStationSyncPattern() || isDirect();
+ }
+
+ /**
+ * Indicates if the current sync pattern is a VOICE pattern.
+ * @return true if a voice sync pattern.
+ */
+ public boolean isVoicePattern()
+ {
+ return VOICE_PATTERNS.contains(this);
+ }
+
+ /**
+ * Determines the next voice sync pattern that follows the argument pattern.
+ * @param pattern for the current burst
+ * @return pattern for the next burst, or UNKNOWN
+ */
+ public static DMRSyncPattern getNextVoice(DMRSyncPattern pattern)
+ {
+ return switch (pattern)
+ {
+ case BASE_STATION_VOICE -> BS_VOICE_FRAME_B;
+ case BS_VOICE_FRAME_B -> BS_VOICE_FRAME_C;
+ case BS_VOICE_FRAME_C -> BS_VOICE_FRAME_D;
+ case BS_VOICE_FRAME_D -> BS_VOICE_FRAME_E;
+ case BS_VOICE_FRAME_E -> BS_VOICE_FRAME_F;
+
+ case MOBILE_STATION_VOICE -> MS_VOICE_FRAME_B;
+ case MS_VOICE_FRAME_B -> MS_VOICE_FRAME_C;
+ case MS_VOICE_FRAME_C -> MS_VOICE_FRAME_D;
+ case MS_VOICE_FRAME_D -> MS_VOICE_FRAME_E;
+ case MS_VOICE_FRAME_E -> MS_VOICE_FRAME_F;
+
+ case DIRECT_VOICE_TIMESLOT_1, DIRECT_VOICE_TIMESLOT_2 -> DIRECT_VOICE_FRAME_B;
+ case DIRECT_VOICE_FRAME_B -> DIRECT_VOICE_FRAME_C;
+ case DIRECT_VOICE_FRAME_C -> DIRECT_VOICE_FRAME_D;
+ case DIRECT_VOICE_FRAME_D -> DIRECT_VOICE_FRAME_E;
+ case DIRECT_VOICE_FRAME_E -> DIRECT_VOICE_FRAME_F;
+
+ //Empty slot pattern remains the same to continue accounting for the empty timeslot.
+ case DIRECT_EMPTY_TIMESLOT -> DIRECT_EMPTY_TIMESLOT;
+
+ default -> UNKNOWN;
+ };
+ }
+
+ /**
+ * String representation of the sync pattern
+ */
+ public String toString()
+ {
+ return mLabel;
+ }
+
+ /**
+ * Converts the DMR sync pattern to a float array of ideal phase values for each Dibit to use for correlation against
+ * a stream of transmitted symbol phases.
+ * @return symbols array representing the sync pattern.
+ */
+ public float[] toSymbols()
+ {
+ float[] symbols = new float[24];
+ Dibit[] dibits = toDibits();
+
+ for(int x = 0; x < 24; x++)
+ {
+ symbols[x] = dibits[x].getIdealPhase();
+ }
+
+ return symbols;
+ }
+
+ /**
+ * Converts the DMR sync pattern to an array of dibit symbols. Note: only the +3 and -3 dibit symbols are used in
+ * sync patterns.
+ * @return dibit symbols array representing the sync pattern.
+ */
+ public Dibit[] toDibits()
+ {
+ Dibit[] dibits = new Dibit[24];
+ long value = getPattern();
+ long mask = 3;
+ long dibitValue;
+
+ for(int x = 0; x < 24; x++)
+ {
+ dibitValue = (value & mask) >> (2 * x);
+
+ if(dibitValue == 1)
+ {
+ dibits[23 - x] = Dibit.D01_PLUS_3;
+ }
+ else
+ {
+ dibits[23 - x] = Dibit.D11_MINUS_3;
+ }
+
+ mask = mask << 2;
+ }
+
+ return dibits;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java
index 72a62b6e..2771474f 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/Calibration.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,12 +19,11 @@
package io.github.dsheirer.vector.calibrate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.text.DecimalFormat;
import java.util.Random;
import java.util.prefs.Preferences;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Abstract calibration plugin base class
@@ -123,6 +122,24 @@ public abstract class Calibration
return samples;
}
+ /**
+ * Generates an array of floating point samples in the range 0.0 - 1.0
+ * @param size of array
+ * @return generated samples
+ */
+ protected float[] getPositiveFloatSamples(int size)
+ {
+ Random random = new Random();
+
+ float[] samples = new float[size];
+ for(int x = 0; x < samples.length; x++)
+ {
+ samples[x] = random.nextFloat();
+ }
+
+ return samples;
+ }
+
/**
* Generates an array of floating point samples in the range -1.0 - 1.0
* @param size of array
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 fea304b1..fc8b35f6 100644
--- a/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/CalibrationManager.java
@@ -25,6 +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.FmDemodulatorCalibration;
import io.github.dsheirer.vector.calibrate.filter.FirFilterCalibration;
import io.github.dsheirer.vector.calibrate.filter.RealDcRemovalCalibration;
@@ -35,10 +36,12 @@ import io.github.dsheirer.vector.calibrate.filter.RealHalfBand63TapFilterCalibra
import io.github.dsheirer.vector.calibrate.filter.RealHalfBandDefaultFilterCalibration;
import io.github.dsheirer.vector.calibrate.gain.ComplexGainCalibration;
import io.github.dsheirer.vector.calibrate.gain.ComplexGainControlCalibration;
+import io.github.dsheirer.vector.calibrate.interpolator.InterpolatorCalibration;
import io.github.dsheirer.vector.calibrate.magnitude.MagnitudeCalibration;
import io.github.dsheirer.vector.calibrate.mixer.ComplexMixerCalibration;
import io.github.dsheirer.vector.calibrate.oscillator.ComplexOscillatorCalibration;
import io.github.dsheirer.vector.calibrate.oscillator.RealOscillatorCalibration;
+import io.github.dsheirer.vector.calibrate.sync.DMRSoftSyncCalibration;
import io.github.dsheirer.vector.calibrate.window.WindowCalibration;
import java.util.ArrayList;
import java.util.Collections;
@@ -104,8 +107,11 @@ public class CalibrationManager
sInstance.add(new ComplexGainControlCalibration());
sInstance.add(new ComplexOscillatorCalibration());
sInstance.add(new ComplexMixerCalibration());
+ sInstance.add(new DMRSoftSyncCalibration());
+ sInstance.add(new DqpskDemodulatorCalibration());
sInstance.add(new FirFilterCalibration());
sInstance.add(new FmDemodulatorCalibration());
+ sInstance.add(new InterpolatorCalibration());
sInstance.add(new MagnitudeCalibration());
sInstance.add(new RealDcRemovalCalibration());
sInstance.add(new RealHalfBand11TapFilterCalibration());
@@ -114,7 +120,6 @@ public class CalibrationManager
sInstance.add(new RealHalfBand63TapFilterCalibration());
sInstance.add(new RealHalfBandDefaultFilterCalibration());
sInstance.add(new RealOscillatorCalibration());
-
// sInstance.add(new HilbertCalibration()); //Not currently used
sInstance.add(new WindowCalibration()); //Not currently used
}
@@ -275,20 +280,8 @@ public class CalibrationManager
public static void main(String[] args)
{
CalibrationManager manager = getInstance();
-
- StringBuilder sb = new StringBuilder();
- sb.append("Current Calibration Settings:\n");
- for(CalibrationType type: CalibrationType.values())
- {
- Calibration calibration = manager.getCalibration(type);
- if(calibration != null)
- {
- sb.append("\t").append(type.name()).append("\t").append(manager.getCalibration(type).getImplementation()).append("\n");
- }
- }
- System.out.println(sb);
-
- manager.reset();
+// manager.reset();
+ manager.reset(CalibrationType.DQPSK_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 edab2d4d..a08fff71 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-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,6 +37,8 @@ public enum CalibrationType
AIRSPY_UNPACKED_ITERATOR("Airspy Unpacked Iterator", 1),
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),
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),
@@ -46,6 +48,7 @@ public enum CalibrationType
FM_DEMODULATOR("FM Demodulator", 2),
GAIN_CONTROL_COMPLEX("Complex Gain Control", 1),
HILBERT_TRANSFORM("Hilbert Transform", 1),
+ INTERPOLATOR("Interpolator", 1),
MAGNITUDE("Magnitude", 1),
MIXER_COMPLEX("Complex Mixer", 1),
OSCILLATOR_REAL("Real Oscillator", 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/DqpskDemodulatorCalibration.java
new file mode 100644
index 00000000..faa973ee
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/demodulator/DqpskDemodulatorCalibration.java
@@ -0,0 +1,287 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+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.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;
+
+/**
+ * Calibrates DQPSK demodulator options
+ */
+public class DqpskDemodulatorCalibration 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);
+
+ /**
+ * Constructs an instance
+ */
+ public DqpskDemodulatorCalibration()
+ {
+ super(CalibrationType.DQPSK_DEMODULATOR);
+ }
+
+ @Override public void calibrate() throws CalibrationException
+ {
+ float[] i = getFloatSamples(BUFFER_SIZE);
+ float[] q = getFloatSamples(BUFFER_SIZE);
+
+ Mean scalarMean = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testScalar(i, q);
+ scalarMean.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+
+ Mean vectorMean64 = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector64(i, q);
+ vectorMean64.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult()));
+
+ Mean vectorMean128 = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector128(i, q);
+ vectorMean128.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult()));
+
+ Mean vectorMean256 = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector256(i, q);
+ vectorMean256.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult()));
+
+ Mean vectorMean512 = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector512(i, q);
+ vectorMean512.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vectorMean512.getResult()));
+
+ //Start tests
+ scalarMean.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testScalar(i, q);
+ scalarMean.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+
+ vectorMean64.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector64(i, q);
+ vectorMean64.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vectorMean64.getResult()));
+
+ vectorMean128.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector128(i, q);
+ vectorMean128.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vectorMean128.getResult()));
+
+ vectorMean256.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector256(i, q);
+ vectorMean256.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vectorMean256.getResult()));
+
+ vectorMean512.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector512(i, q);
+ vectorMean512.increment(score);
+ }
+
+ mLog.info("DQPSK DEMODULATOR - 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("DQPSK DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation());
+ }
+
+ private long testScalar(float[] i, float[] q)
+ {
+ long count = 0;
+ float accumulator = 0;
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ float[] demodulated = mScalarDemodulator.demodulate(i, q);
+ accumulator += demodulated[1];
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector64(float[] i, float[] q)
+ {
+ long count = 0;
+ float accumulator = 0;
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ float[] demodulated = mVectorDemodulator64.demodulate(i, q);
+ accumulator += demodulated[1];
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector128(float[] i, float[] q)
+ {
+ long count = 0;
+ float accumulator = 0;
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ float[] demodulated = mVectorDemodulator128.demodulate(i, q);
+ accumulator += demodulated[1];
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector256(float[] i, float[] q)
+ {
+ long count = 0;
+ float accumulator = 0;
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ float[] demodulated = mVectorDemodulator256.demodulate(i, q);
+ accumulator += demodulated[1];
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector512(float[] i, float[] q)
+ {
+ long count = 0;
+ float accumulator = 0;
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ float[] demodulated = mVectorDemodulator512.demodulate(i, q);
+ accumulator += demodulated[1];
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ public static void main(String[] args)
+ {
+ DqpskDemodulatorCalibration calibration = new DqpskDemodulatorCalibration();
+
+ try
+ {
+ calibration.calibrate();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Error during calibration", e);
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java
new file mode 100644
index 00000000..2a6f944c
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/interpolator/InterpolatorCalibration.java
@@ -0,0 +1,268 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.vector.calibrate.interpolator;
+
+import io.github.dsheirer.dsp.filter.interpolator.Interpolator;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorScalar;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector128;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector256;
+import io.github.dsheirer.dsp.filter.interpolator.InterpolatorVector64;
+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 jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+
+/**
+ * Calibration plugin for FIR filters
+ */
+public class InterpolatorCalibration extends Calibration
+{
+ private static final VectorSpecies VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
+ private static final int BUFFER_SIZE = 8;
+ private static final int ITERATION_DURATION_MS = 1000;
+ private static final int WARMUP_ITERATIONS = 5;
+ private static final int TEST_ITERATIONS = 5;
+
+ private Interpolator mScalar = new InterpolatorScalar();
+ private Interpolator mVector256 = new InterpolatorVector256();
+ private Interpolator mVector128 = new InterpolatorVector128();
+ private Interpolator mVector64 = new InterpolatorVector64();
+
+ /**
+ * Constructs an instance
+ */
+ public InterpolatorCalibration()
+ {
+ super(CalibrationType.INTERPOLATOR);
+ }
+
+ /**
+ * Performs calibration to determine optimal (Scalar vs Vector) operation type.
+ * @throws CalibrationException
+ */
+ @Override public void calibrate() throws CalibrationException
+ {
+ float[] samples = getFloatSamples(BUFFER_SIZE);
+ float[] interpolationPoints = getPositiveFloatSamples(2048);
+
+ Mean scalarMean = new Mean();
+
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testScalar(samples, interpolationPoints);
+ scalarMean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+
+ Mean vector256Mean = new Mean();
+
+ if(VECTOR_SPECIES.length() >= 8)
+ {
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector256(samples, interpolationPoints);
+ vector256Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
+ }
+
+ Mean vector128Mean = new Mean();
+
+ if(VECTOR_SPECIES.length() >= 4)
+ {
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector128(samples, interpolationPoints);
+ vector128Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
+ }
+
+ Mean vector64Mean = new Mean();
+
+ if(VECTOR_SPECIES.length() >= 2)
+ {
+ for(int x = 0; x < WARMUP_ITERATIONS; x++)
+ {
+ long score = testVector64(samples, interpolationPoints);
+ vector64Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
+ }
+
+ //Test starts ...
+ scalarMean.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testScalar(samples, interpolationPoints);
+ scalarMean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
+
+ double bestScore = scalarMean.getResult();
+ setImplementation(Implementation.SCALAR);
+
+ if(VECTOR_SPECIES.length() >= 8)
+ {
+ vector256Mean.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector256(samples, interpolationPoints);
+ vector256Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
+
+ if(vector256Mean.getResult() > bestScore)
+ {
+ bestScore = vector256Mean.getResult();
+ setImplementation(Implementation.VECTOR_SIMD_256);
+ }
+ }
+
+ if(VECTOR_SPECIES.length() >= 4)
+ {
+ vector128Mean.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector128(samples, interpolationPoints);
+ vector128Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
+
+ if(vector128Mean.getResult() > bestScore)
+ {
+ bestScore = vector128Mean.getResult();
+ setImplementation(Implementation.VECTOR_SIMD_128);
+ }
+ }
+
+ if(VECTOR_SPECIES.length() >= 2)
+ {
+ vector64Mean.clear();
+
+ for(int x = 0; x < TEST_ITERATIONS; x++)
+ {
+ long score = testVector64(samples, interpolationPoints);
+ vector64Mean.increment(score);
+ }
+
+ mLog.info("INTERPOLATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
+
+ if(vector64Mean.getResult() > bestScore)
+ {
+ setImplementation(Implementation.VECTOR_SIMD_64);
+ }
+ }
+
+ mLog.info("INTERPOLATOR - SET OPTIMAL IMPLEMENTATION TO: " + getImplementation());
+ }
+
+ private long testScalar(float[] samples, float[] interpolationPoints)
+ {
+ double accumulator = 0.0f;
+ long count = 0;
+
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ for(int x = 0; x < interpolationPoints.length; x++)
+ {
+ accumulator += mScalar.filter(samples, 0, interpolationPoints[x]);
+ }
+
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector64(float[] samples, float[] interpolationPoints)
+ {
+ double accumulator = 0.0f;
+
+ long start = System.currentTimeMillis();
+ long count = 0;
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ for(int x = 0; x < interpolationPoints.length; x++)
+ {
+ accumulator += mVector64.filter(samples, 0, interpolationPoints[x]);
+ }
+
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector128(float[] samples, float[] interpolationPoints)
+ {
+ double accumulator = 0.0f;
+ long count = 0;
+
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ for(int x = 0; x < interpolationPoints.length; x++)
+ {
+ accumulator += mVector128.filter(samples, 0, interpolationPoints[x]);
+ }
+
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+
+ private long testVector256(float[] samples, float[] interpolationPoints)
+ {
+ double accumulator = 0.0f;
+ long count = 0;
+
+ long start = System.currentTimeMillis();
+
+ while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
+ {
+ for(int x = 0; x < interpolationPoints.length; x++)
+ {
+ accumulator += mVector256.filter(samples, 0, interpolationPoints[x]);
+ }
+ count++;
+ }
+
+ return count + (long)(accumulator * 0);
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java b/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java
new file mode 100644
index 00000000..bed91710
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/vector/calibrate/sync/DMRSoftSyncCalibration.java
@@ -0,0 +1,292 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.vector.calibrate.sync;
+
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorScalar;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector128;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector256;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector512;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector64;
+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;
+
+/**
+ * DMR Soft Sync Detector calibration
+ */
+public class DMRSoftSyncCalibration 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 DMRSoftSyncDetector mScalarDetector = new DMRSoftSyncDetectorScalar();
+ private final DMRSoftSyncDetector mVectorDetector64 = new DMRSoftSyncDetectorVector64();
+ private final DMRSoftSyncDetector mVectorDetector128 = new DMRSoftSyncDetectorVector128();
+ private final DMRSoftSyncDetector mVectorDetector256 = new DMRSoftSyncDetectorVector256();
+ private final DMRSoftSyncDetector mVectorDetector512 = new DMRSoftSyncDetectorVector512();
+
+ /**
+ * Constructs an instance
+ */
+ public DMRSoftSyncCalibration()
+ {
+ super(CalibrationType.DMR_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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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("DMR 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;
+ }
+
+ public static void main(String[] args)
+ {
+ DMRSoftSyncCalibration calibration = new DMRSoftSyncCalibration();
+
+ try
+ {
+ calibration.calibrate();
+ }
+ catch(Exception e)
+ {
+ mLog.error("Error during calibration", e);
+ }
+ }
+}
diff --git a/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java b/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java
new file mode 100644
index 00000000..5ad1fb5c
--- /dev/null
+++ b/src/test/java/io/github/dsheirer/dsp/psk/dqpsk/DmrSoftSyncDetectorTest.java
@@ -0,0 +1,87 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.dsp.psk.dqpsk;
+
+import io.github.dsheirer.dsp.symbol.Dibit;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetector;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorScalar;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector128;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector256;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector512;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSoftSyncDetectorVector64;
+import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * JUnit tests for the DMR Soft Sync Detector.
+ */
+public class DmrSoftSyncDetectorTest
+{
+ private static final float SYMBOL_VALUE = (float)(3.0 * Math.PI / 4.0);
+
+ /**
+ * Test: feed each sync pattern into each detector implementation and verify that each detector selected the
+ * correct sync pattern.
+ *
+ * Success: all detector implementations correctly detect all sync patterns.
+ */
+ @Test
+ void testAllSyncPatterns()
+ {
+ DMRSoftSyncDetector scalar = new DMRSoftSyncDetectorScalar();
+ DMRSoftSyncDetector vector64 = new DMRSoftSyncDetectorVector64();
+ DMRSoftSyncDetector vector128 = new DMRSoftSyncDetectorVector128();
+ DMRSoftSyncDetector vector256 = new DMRSoftSyncDetectorVector256();
+ DMRSoftSyncDetector vector512 = new DMRSoftSyncDetectorVector512();
+
+ float symbol;
+
+ for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS)
+ {
+ for(Dibit dibit: pattern.toDibits())
+ {
+ symbol = toSymbol(dibit);
+ scalar.process(symbol);
+ vector64.process(symbol);
+ vector128.process(symbol);
+ vector256.process(symbol);
+ vector512.process(symbol);
+ }
+
+ assertSame(scalar.getDetectedPattern(), pattern, "Scalar Detector Did Not Correctly Detect Pattern: " + pattern);
+ assertSame(vector64.getDetectedPattern(), pattern, "Vector 64 Detector Did Not Correctly Detect Pattern: " + pattern);
+ assertSame(vector128.getDetectedPattern(), pattern, "Vector 128 Detector Did Not Correctly Detect Pattern: " + pattern);
+ assertSame(vector256.getDetectedPattern(), pattern, "Vector 256 Detector Did Not Correctly Detect Pattern: " + pattern);
+ assertSame(vector512.getDetectedPattern(), pattern, "Vector 512 Detector Did Not Correctly Detect Pattern: " + pattern);
+ }
+ }
+
+ /**
+ * Converts the dibit to the optimal symbol value
+ * @param dibit to convert
+ * @return dibit's symbol value in radians.
+ */
+ private static float toSymbol(Dibit dibit)
+ {
+ return dibit == Dibit.D01_PLUS_3 ? SYMBOL_VALUE : -SYMBOL_VALUE;
+ }
+}